diff --git a/src/main/java/cloneproject/Instagram/controller/HashtagController.java b/src/main/java/cloneproject/Instagram/controller/HashtagController.java index 800af994..cd82950a 100644 --- a/src/main/java/cloneproject/Instagram/controller/HashtagController.java +++ b/src/main/java/cloneproject/Instagram/controller/HashtagController.java @@ -1,6 +1,5 @@ package cloneproject.Instagram.controller; -import cloneproject.Instagram.dto.hashtag.HashtagDTO; import cloneproject.Instagram.dto.post.PostDTO; import cloneproject.Instagram.dto.result.ResultResponse; import cloneproject.Instagram.service.HashtagService; @@ -13,7 +12,9 @@ import org.springframework.data.domain.Page; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -47,19 +48,20 @@ public ResponseEntity getHashtagPosts( return ResponseEntity.ok(ResultResponse.of(GET_HASHTAG_POSTS_SUCCESS, response)); } - @ApiOperation(value = "해시태그 연관 검색어 목록 페이징 조회") - @ApiImplicitParams({ - @ApiImplicitParam(name = "page", value = "page", example = "1", required = true), - @ApiImplicitParam(name = "size", value = "size", example = "10", required = true), - @ApiImplicitParam(name = "hashtag", value = "hashtag", example = "만두", required = true) - }) - @GetMapping("/hashtags") - public ResponseEntity getHashtags( - @NotNull(message = "page는 필수입니다.") @RequestParam int page, - @NotNull(message = "size는 필수입니다.") @RequestParam int size, - @NotBlank(message = "hashtag는 필수입니다.") @RequestParam String hashtag) { - final Page response = hashtagService.getHashTagsLikeName(page, size, hashtag.substring(1)); + @ApiOperation(value = "해시태그 팔로우") + @ApiImplicitParam(name = "hashtag", value = "hashtag", example = "만두", required = true) + @PostMapping("/hashtags/follow") + public ResponseEntity followHashtag(@NotBlank(message = "hashtag는 필수입니다.") @RequestParam String hashtag) { + hashtagService.followHashtag(hashtag); + return ResponseEntity.ok(ResultResponse.of(FOLLOW_HASHTAG_SUCCESS, null)); + } - return ResponseEntity.ok(ResultResponse.of(GET_HASHTAGS_SUCCESS, response)); + @ApiOperation(value = "해시태그 언팔로우") + @ApiImplicitParam(name = "hashtag", value = "hashtag", example = "만두", required = true) + @DeleteMapping("/hashtags/follow") + public ResponseEntity unfollowHashtag(@NotBlank(message = "hashtag는 필수입니다.") @RequestParam String hashtag) { + hashtagService.unfollowHashtag(hashtag); + return ResponseEntity.ok(ResultResponse.of(UNFOLLOW_HASHTAG_SUCCESS, null)); } + } diff --git a/src/main/java/cloneproject/Instagram/controller/MemberController.java b/src/main/java/cloneproject/Instagram/controller/MemberController.java index b9c98d23..2bec35ae 100644 --- a/src/main/java/cloneproject/Instagram/controller/MemberController.java +++ b/src/main/java/cloneproject/Instagram/controller/MemberController.java @@ -7,11 +7,6 @@ import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; -import java.util.List; - -import javax.validation.constraints.NotBlank; - -import org.hibernate.validator.constraints.Length; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -22,7 +17,6 @@ import cloneproject.Instagram.dto.member.EditProfileResponse; import cloneproject.Instagram.dto.member.MenuMemberDTO; import cloneproject.Instagram.dto.member.MiniProfileResponse; -import cloneproject.Instagram.dto.member.SearchedMemberDTO; import cloneproject.Instagram.dto.member.UserProfileResponse; import cloneproject.Instagram.dto.result.ResultCode; import cloneproject.Instagram.dto.result.ResultResponse; @@ -117,14 +111,4 @@ public ResponseEntity editProfile(@Validated @RequestBody EditPr return new ResponseEntity<>(result, HttpStatus.valueOf(result.getStatus())); } - @ApiOperation(value = "멤버 검색") - @ApiImplicitParam(name = "text", value = "검색내용", required = true, example = "dlwl") - @GetMapping(value = "/search") - public ResponseEntity searchMember(@RequestParam String text) { - List memberInfos = memberService.searchMember(text); - - ResultResponse result = ResultResponse.of(ResultCode.SEARCH_MEMBER_SUCCESS, memberInfos); - return new ResponseEntity<>(result, HttpStatus.valueOf(result.getStatus())); - } - } diff --git a/src/main/java/cloneproject/Instagram/controller/SearchController.java b/src/main/java/cloneproject/Instagram/controller/SearchController.java new file mode 100644 index 00000000..764cbf29 --- /dev/null +++ b/src/main/java/cloneproject/Instagram/controller/SearchController.java @@ -0,0 +1,56 @@ +package cloneproject.Instagram.controller; + +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import cloneproject.Instagram.dto.result.ResultCode; +import cloneproject.Instagram.dto.result.ResultResponse; +import cloneproject.Instagram.dto.search.SearchDTO; +import cloneproject.Instagram.service.SearchService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Validated +@Api(tags = "검색 API") +@RestController +@RequiredArgsConstructor +public class SearchController { + + private final SearchService searchService; + + @ApiOperation(value = "검색") + @ApiImplicitParam(name = "text", value = "검색내용", required = true, example = "dlwl") + @GetMapping(value = "/topsearch") + public ResponseEntity searchText(@RequestParam String text) { + List searchDTOs = searchService.searchByText(text); + + ResultResponse result = ResultResponse.of(ResultCode.SEARCH_SUCCESS, searchDTOs); + return new ResponseEntity<>(result, HttpStatus.valueOf(result.getStatus())); + } + + @ApiOperation(value = "검색 조회수 증가") + @ApiImplicitParams({ + @ApiImplicitParam(name = "entityName", value = "조회수 증가시킬 식별 name", required = true, example = "dlwlrma"), + @ApiImplicitParam(name = "entityType", value = "조회수 증가시킬 type", required = true, example = "MEMBER") + }) + @PostMapping(value = "/topsearch/upcount") + public ResponseEntity increaseSearchMemberCount(@RequestParam String entityName, @RequestParam String entityType) { + searchService.increaseSearchCount(entityName, entityType); + + ResultResponse result = ResultResponse.of(ResultCode.INCREASE_SEARCH_COUNT_SUCCESS, null); + return new ResponseEntity<>(result, HttpStatus.valueOf(result.getStatus())); + } + +} diff --git a/src/main/java/cloneproject/Instagram/dto/error/ErrorCode.java b/src/main/java/cloneproject/Instagram/dto/error/ErrorCode.java index 2deea0f2..ac3398d0 100644 --- a/src/main/java/cloneproject/Instagram/dto/error/ErrorCode.java +++ b/src/main/java/cloneproject/Instagram/dto/error/ErrorCode.java @@ -79,9 +79,14 @@ public enum ErrorCode { // Email CANT_SEND_EMAIL(500, "E001", "이메일 전송 중 오류가 발생했습니다."), + // HashTag + HASHTAG_NOT_FOUND(400, "H001", "존재하지 않는 해시태그 입니다"), + CANT_FOLLOW_HASHTAG(400, "H002", "해시태그 팔로우 실패"), + CANT_UNFOLLOW_HASHTAG(400, "H003", "해시태그 언팔로우 실패"), + // Story INVALID_STORY_IMAGE(400, "S001", "스토리 이미지는 필수입니다."), - INVALID_STORY_IMAGE_INDEX(400, "S002", "스토리 이미지 인덱스가 올바르지 않습니다."), + INVALID_STORY_IMAGE_INDEX(400, "S002", "스토리 이미지 인덱스가 올바르지 않습니다.") ; private int status; diff --git a/src/main/java/cloneproject/Instagram/dto/hashtag/HashtagDTO.java b/src/main/java/cloneproject/Instagram/dto/hashtag/HashtagDTO.java index 2b48c303..40624a4b 100644 --- a/src/main/java/cloneproject/Instagram/dto/hashtag/HashtagDTO.java +++ b/src/main/java/cloneproject/Instagram/dto/hashtag/HashtagDTO.java @@ -1,5 +1,7 @@ package cloneproject.Instagram.dto.hashtag; +import com.querydsl.core.annotations.QueryProjection; + import cloneproject.Instagram.entity.hashtag.Hashtag; import lombok.Getter; import lombok.NoArgsConstructor; @@ -11,6 +13,7 @@ public class HashtagDTO { private String name; private Integer count; + @QueryProjection public HashtagDTO(Hashtag hashtag) { this.name = hashtag.getName(); this.count = hashtag.getCount(); diff --git a/src/main/java/cloneproject/Instagram/dto/member/SearchedMemberDTO.java b/src/main/java/cloneproject/Instagram/dto/member/SearchedMemberDTO.java deleted file mode 100644 index 0ca6203a..00000000 --- a/src/main/java/cloneproject/Instagram/dto/member/SearchedMemberDTO.java +++ /dev/null @@ -1,37 +0,0 @@ -package cloneproject.Instagram.dto.member; - -import java.util.List; - -import com.querydsl.core.annotations.QueryProjection; - -import cloneproject.Instagram.vo.Image; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@NoArgsConstructor -public class SearchedMemberDTO { - - private String username; - private String name; - private Image image; - private boolean isFollowing; - private boolean isFollower; - private boolean hasStory; - private List followingMemberFollow; // 내 팔로잉 중 해당 멤버를 팔로우 하고있는 사람 - - @QueryProjection - public SearchedMemberDTO(String username, String name, Image image, boolean isFollowing, boolean isFollower) { - this.username = username; - this.name = name; - this.image = image; - this.isFollowing = isFollowing; - this.isFollower = isFollower; - this.hasStory = false; - } - - public void setFollowingMemberFollow(List followingMemberFollow) { - this.followingMemberFollow = followingMemberFollow; - } -} - diff --git a/src/main/java/cloneproject/Instagram/dto/result/ResultCode.java b/src/main/java/cloneproject/Instagram/dto/result/ResultCode.java index 043e23d8..ae806b29 100644 --- a/src/main/java/cloneproject/Instagram/dto/result/ResultCode.java +++ b/src/main/java/cloneproject/Instagram/dto/result/ResultCode.java @@ -89,9 +89,15 @@ public enum ResultCode { // Hashtag GET_HASHTAG_POSTS_SUCCESS(200, "H001", "해시태그 게시물 목록 페이징 조회 성공"), GET_HASHTAGS_SUCCESS(200, "H002", "해시태그 목록 페이징 조회 성공"), + FOLLOW_HASHTAG_SUCCESS(200, "H003", "해시태그 팔로우 성공"), + UNFOLLOW_HASHTAG_SUCCESS(200, "H004", "해시태그 언팔로우 성공"), // Story CREATE_STORY_SUCCESS(200, "S001", "스토리 업로드 성공"), + + // Search + SEARCH_SUCCESS(200, "SE001", "검색 성공"), + INCREASE_SEARCH_COUNT_SUCCESS(200, "SE002", "검색 조회수 증가 성공"), ; private int status; diff --git a/src/main/java/cloneproject/Instagram/dto/search/SearchDTO.java b/src/main/java/cloneproject/Instagram/dto/search/SearchDTO.java new file mode 100644 index 00000000..55d7217c --- /dev/null +++ b/src/main/java/cloneproject/Instagram/dto/search/SearchDTO.java @@ -0,0 +1,22 @@ +package cloneproject.Instagram.dto.search; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class SearchDTO { + + private String dtype; + + @JsonIgnore + private Long count; + +} diff --git a/src/main/java/cloneproject/Instagram/dto/search/SearchHashtagDTO.java b/src/main/java/cloneproject/Instagram/dto/search/SearchHashtagDTO.java new file mode 100644 index 00000000..8eef22c3 --- /dev/null +++ b/src/main/java/cloneproject/Instagram/dto/search/SearchHashtagDTO.java @@ -0,0 +1,26 @@ +package cloneproject.Instagram.dto.search; + +import com.querydsl.core.annotations.QueryProjection; + +import cloneproject.Instagram.entity.hashtag.Hashtag; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class SearchHashtagDTO extends SearchDTO{ + + private String name; + private Integer postCount; + + @QueryProjection + public SearchHashtagDTO(String dtype, Long count, Hashtag hashtag){ + super(dtype, count); + this.name = hashtag.getName(); + this.postCount = hashtag.getCount(); + } + +} \ No newline at end of file diff --git a/src/main/java/cloneproject/Instagram/dto/search/SearchMemberDTO.java b/src/main/java/cloneproject/Instagram/dto/search/SearchMemberDTO.java new file mode 100644 index 00000000..09639b61 --- /dev/null +++ b/src/main/java/cloneproject/Instagram/dto/search/SearchMemberDTO.java @@ -0,0 +1,37 @@ +package cloneproject.Instagram.dto.search; + +import java.util.List; + +import com.querydsl.core.annotations.QueryProjection; + +import cloneproject.Instagram.dto.member.FollowDTO; +import cloneproject.Instagram.dto.member.MemberDTO; +import cloneproject.Instagram.entity.member.Member; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class SearchMemberDTO extends SearchDTO{ + + private MemberDTO memberDTO; + private boolean isFollowing; + private boolean isFollower; + private List followingMemberFollow; + + @QueryProjection + public SearchMemberDTO(String dtype, Long count, Member member, boolean isFollowing, boolean isFollower){ + super(dtype, count); + this.memberDTO = new MemberDTO(member); + this.isFollowing = isFollowing; + this.isFollower = isFollower; + // this.followingMemberFollow = followingMemberFollow; + } + public void setFollowingMemberFollow(List followingMemberFollow) { + this.followingMemberFollow = followingMemberFollow; + } + +} \ No newline at end of file diff --git a/src/main/java/cloneproject/Instagram/entity/member/HashtagFollow.java b/src/main/java/cloneproject/Instagram/entity/member/HashtagFollow.java new file mode 100644 index 00000000..3eebb981 --- /dev/null +++ b/src/main/java/cloneproject/Instagram/entity/member/HashtagFollow.java @@ -0,0 +1,44 @@ +package cloneproject.Instagram.entity.member; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import cloneproject.Instagram.entity.hashtag.Hashtag; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "hashtag_follows") +public class HashtagFollow { + + @Id + @Column(name = "hashtag_follow_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "hashtag_id") + private Hashtag hashtag; + + @Builder + public HashtagFollow(Member member, Hashtag hashtag) { + this.member = member; + this.hashtag = hashtag; + } + +} diff --git a/src/main/java/cloneproject/Instagram/entity/search/Search.java b/src/main/java/cloneproject/Instagram/entity/search/Search.java new file mode 100644 index 00000000..d210ad5a --- /dev/null +++ b/src/main/java/cloneproject/Instagram/entity/search/Search.java @@ -0,0 +1,38 @@ +package cloneproject.Instagram.entity.search; + +import lombok.Getter; + +import javax.persistence.*; + +@Getter +@Entity +@Inheritance(strategy = InheritanceType.JOINED) +@DiscriminatorColumn +@Table(name = "searches") +public class Search { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "search_id") + private Long id; + + @Column(name = "search_count") + private Long count; + + @Column(insertable = false, updatable = false) + private String dtype; + + protected Search(){ + this.count = 0L; + } + + public void upCount(){ + this.count++; + } + + @Transient + public void setDtype() { + this.dtype = getClass().getAnnotation(DiscriminatorValue.class).value(); + } + +} diff --git a/src/main/java/cloneproject/Instagram/entity/search/SearchHashtag.java b/src/main/java/cloneproject/Instagram/entity/search/SearchHashtag.java new file mode 100644 index 00000000..f3c0af20 --- /dev/null +++ b/src/main/java/cloneproject/Instagram/entity/search/SearchHashtag.java @@ -0,0 +1,31 @@ +package cloneproject.Instagram.entity.search; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import cloneproject.Instagram.entity.hashtag.Hashtag; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DiscriminatorValue("HASHTAG") +@Table(name = "search_hashtags") +public class SearchHashtag extends Search{ + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "hashtag_id") + private Hashtag hashtag; + + public SearchHashtag(Hashtag hashtag) { + super(); + this.hashtag = hashtag; + } + +} diff --git a/src/main/java/cloneproject/Instagram/entity/search/SearchMember.java b/src/main/java/cloneproject/Instagram/entity/search/SearchMember.java new file mode 100644 index 00000000..07ebed8d --- /dev/null +++ b/src/main/java/cloneproject/Instagram/entity/search/SearchMember.java @@ -0,0 +1,31 @@ +package cloneproject.Instagram.entity.search; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import cloneproject.Instagram.entity.member.Member; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@DiscriminatorValue("MEMBER") +@Table(name = "search_members") +public class SearchMember extends Search{ + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + public SearchMember(Member member) { + super(); + this.member = member; + } + +} diff --git a/src/main/java/cloneproject/Instagram/exception/CantFollowHashtagException.java b/src/main/java/cloneproject/Instagram/exception/CantFollowHashtagException.java new file mode 100644 index 00000000..e71395e4 --- /dev/null +++ b/src/main/java/cloneproject/Instagram/exception/CantFollowHashtagException.java @@ -0,0 +1,9 @@ +package cloneproject.Instagram.exception; + +import cloneproject.Instagram.dto.error.ErrorCode; + +public class CantFollowHashtagException extends BusinessException{ + public CantFollowHashtagException(){ + super(ErrorCode.CANT_FOLLOW_HASHTAG); + } +} \ No newline at end of file diff --git a/src/main/java/cloneproject/Instagram/exception/CantUnfollowHashtagException.java b/src/main/java/cloneproject/Instagram/exception/CantUnfollowHashtagException.java new file mode 100644 index 00000000..238e0047 --- /dev/null +++ b/src/main/java/cloneproject/Instagram/exception/CantUnfollowHashtagException.java @@ -0,0 +1,9 @@ +package cloneproject.Instagram.exception; + +import cloneproject.Instagram.dto.error.ErrorCode; + +public class CantUnfollowHashtagException extends BusinessException{ + public CantUnfollowHashtagException(){ + super(ErrorCode.CANT_UNFOLLOW_HASHTAG); + } +} diff --git a/src/main/java/cloneproject/Instagram/exception/HashtagNotFoundException.java b/src/main/java/cloneproject/Instagram/exception/HashtagNotFoundException.java new file mode 100644 index 00000000..efa24d09 --- /dev/null +++ b/src/main/java/cloneproject/Instagram/exception/HashtagNotFoundException.java @@ -0,0 +1,9 @@ +package cloneproject.Instagram.exception; + +import cloneproject.Instagram.dto.error.ErrorCode; + +public class HashtagNotFoundException extends BusinessException{ + public HashtagNotFoundException(){ + super(ErrorCode.HASHTAG_NOT_FOUND); + } +} \ No newline at end of file diff --git a/src/main/java/cloneproject/Instagram/repository/HashTagRepositoryQuerydsl.java b/src/main/java/cloneproject/Instagram/repository/HashTagRepositoryQuerydsl.java deleted file mode 100644 index 457cf8b8..00000000 --- a/src/main/java/cloneproject/Instagram/repository/HashTagRepositoryQuerydsl.java +++ /dev/null @@ -1,10 +0,0 @@ -package cloneproject.Instagram.repository; - -import cloneproject.Instagram.dto.hashtag.HashtagDTO; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; - -public interface HashTagRepositoryQuerydsl { - - Page findHashtagDtoPageLikeName(Pageable pageable, String name); -} diff --git a/src/main/java/cloneproject/Instagram/repository/HashTagRepositoryQuerydslImpl.java b/src/main/java/cloneproject/Instagram/repository/HashTagRepositoryQuerydslImpl.java deleted file mode 100644 index 44b55e04..00000000 --- a/src/main/java/cloneproject/Instagram/repository/HashTagRepositoryQuerydslImpl.java +++ /dev/null @@ -1,42 +0,0 @@ -package cloneproject.Instagram.repository; - -import cloneproject.Instagram.dto.hashtag.HashtagDTO; -import cloneproject.Instagram.entity.hashtag.Hashtag; -import com.querydsl.jpa.impl.JPAQueryFactory; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; - -import java.util.List; -import java.util.stream.Collectors; - -import static cloneproject.Instagram.entity.hashtag.QHashtag.hashtag; - -@RequiredArgsConstructor -public class HashTagRepositoryQuerydslImpl implements HashTagRepositoryQuerydsl { - - private final JPAQueryFactory queryFactory; - - @Override - public Page findHashtagDtoPageLikeName(Pageable pageable, String name) { - final List hashtags = queryFactory - .selectFrom(hashtag) - .where(hashtag.name.like(name + "%")) - .orderBy(hashtag.count.desc()) - .limit(pageable.getPageSize()) - .offset(pageable.getOffset()) - .fetch(); - - final long total = queryFactory - .selectFrom(hashtag) - .where(hashtag.name.like(name + "%")) - .fetchCount(); - - final List hashtagDTOs = hashtags.stream() - .map(HashtagDTO::new) - .collect(Collectors.toList()); - - return new PageImpl<>(hashtagDTOs, pageable, total); - } -} diff --git a/src/main/java/cloneproject/Instagram/repository/HashtagFollowRepository.java b/src/main/java/cloneproject/Instagram/repository/HashtagFollowRepository.java new file mode 100644 index 00000000..3901a8f9 --- /dev/null +++ b/src/main/java/cloneproject/Instagram/repository/HashtagFollowRepository.java @@ -0,0 +1,14 @@ +package cloneproject.Instagram.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import cloneproject.Instagram.entity.member.HashtagFollow; + +public interface HashtagFollowRepository extends JpaRepository{ + + boolean existsByMemberIdAndHashtagId(Long memberId, Long hashtagId); + Optional findByMemberIdAndHashtagId(Long memberId, Long hashtagId); + +} diff --git a/src/main/java/cloneproject/Instagram/repository/HashtagRepository.java b/src/main/java/cloneproject/Instagram/repository/HashtagRepository.java index e9e34270..c1de0a30 100644 --- a/src/main/java/cloneproject/Instagram/repository/HashtagRepository.java +++ b/src/main/java/cloneproject/Instagram/repository/HashtagRepository.java @@ -7,7 +7,7 @@ import java.util.Optional; import java.util.Set; -public interface HashtagRepository extends JpaRepository, HashTagRepositoryQuerydsl { +public interface HashtagRepository extends JpaRepository { List findAllByNameIn(Set names); diff --git a/src/main/java/cloneproject/Instagram/repository/MemberRepositoryQuerydsl.java b/src/main/java/cloneproject/Instagram/repository/MemberRepositoryQuerydsl.java index 1e29351b..710dc976 100644 --- a/src/main/java/cloneproject/Instagram/repository/MemberRepositoryQuerydsl.java +++ b/src/main/java/cloneproject/Instagram/repository/MemberRepositoryQuerydsl.java @@ -4,7 +4,6 @@ import java.util.List; import cloneproject.Instagram.dto.member.MiniProfileResponse; -import cloneproject.Instagram.dto.member.SearchedMemberDTO; import cloneproject.Instagram.dto.member.UserProfileResponse; import cloneproject.Instagram.entity.member.Member; @@ -12,7 +11,6 @@ public interface MemberRepositoryQuerydsl { UserProfileResponse getUserProfile(Long loginedUserId, String username); MiniProfileResponse getMiniProfile(Long loginedUserId, String username); - List searchMember(Long loginedUserId, String text); List findAllByUsernames(List usernames); } diff --git a/src/main/java/cloneproject/Instagram/repository/MemberRepositoryQuerydslImpl.java b/src/main/java/cloneproject/Instagram/repository/MemberRepositoryQuerydslImpl.java index 0e040cd5..b41ec9ba 100644 --- a/src/main/java/cloneproject/Instagram/repository/MemberRepositoryQuerydslImpl.java +++ b/src/main/java/cloneproject/Instagram/repository/MemberRepositoryQuerydslImpl.java @@ -178,70 +178,6 @@ public MiniProfileResponse getMiniProfile(Long loginedUserId, String username) { } - @Override - public List searchMember(Long loginedUserId, String text) { - final List result = queryFactory - .select(new QSearchedMemberDTO( - member.username, - member.name, - member.image, - JPAExpressions - .selectFrom(follow) - .where(follow.member.id.eq(loginedUserId).and(follow.followMember.username.eq(member.username))) - .exists(), - JPAExpressions - .selectFrom(follow) - .where(follow.member.username.eq(member.username).and(follow.followMember.id.eq(loginedUserId))) - .exists() - )) - .from(member) - .where(member.username.contains(text).or(member.name.contains(text)) - .and(member.id.notIn(JPAExpressions - .select(block.blockMember.id) - .from(block) - .where(block.member.id.eq(loginedUserId))) - .and(member.id.notIn(JPAExpressions - .select(block.member.id) - .from(block) - .where(block.blockMember.id.eq(loginedUserId)))))) - .fetch(); - - final List resultUsernames = result.stream().map(SearchedMemberDTO::getUsername) - .collect(Collectors.toList()); - - final Map memberIdMap = queryFactory - .from(member) - .where(member.username.in(resultUsernames)) - .transform(groupBy(member.username).as(member.id)); - result.forEach(member -> { - member.setHasStory(memberStoryRedisRepository.findById(memberIdMap.get(member.getUsername())).isPresent()); - }); - - final List follows = queryFactory - .select(new QFollowDTO( - follow.member.username, - follow.followMember.username - )) - .from(follow) - .where(follow.followMember.username.in(resultUsernames) - .and(follow.member.id.in( - JPAExpressions - .select(follow.followMember.id) - .from(follow) - .where(follow.member.id.eq(loginedUserId)) - ))) - .fetch(); - - if (follows.isEmpty()) - return result; - - final Map> followsMap = follows.stream() - .collect(Collectors.groupingBy(FollowDTO::getFollowMemberUsername)); - result.forEach(r -> r.setFollowingMemberFollow(followsMap.get(r.getUsername()))); - - return result; - } - @Override public List findAllByUsernames(List usernames) { return queryFactory diff --git a/src/main/java/cloneproject/Instagram/repository/search/SearchHashtagRepository.java b/src/main/java/cloneproject/Instagram/repository/search/SearchHashtagRepository.java new file mode 100644 index 00000000..b3833861 --- /dev/null +++ b/src/main/java/cloneproject/Instagram/repository/search/SearchHashtagRepository.java @@ -0,0 +1,16 @@ +package cloneproject.Instagram.repository.search; + +import java.util.List; +import java.util.Optional; + +import cloneproject.Instagram.entity.hashtag.Hashtag; +import org.springframework.data.jpa.repository.JpaRepository; + +import cloneproject.Instagram.entity.search.SearchHashtag; + +public interface SearchHashtagRepository extends JpaRepository { + + Optional findByHashtagName(String name); + + List findAllByHashtagIn(List hashtags); +} diff --git a/src/main/java/cloneproject/Instagram/repository/search/SearchMemberRepository.java b/src/main/java/cloneproject/Instagram/repository/search/SearchMemberRepository.java new file mode 100644 index 00000000..f0c9ba75 --- /dev/null +++ b/src/main/java/cloneproject/Instagram/repository/search/SearchMemberRepository.java @@ -0,0 +1,13 @@ +package cloneproject.Instagram.repository.search; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import cloneproject.Instagram.entity.search.SearchMember; + +public interface SearchMemberRepository extends JpaRepository{ + + Optional findByMemberUsername(String username); + +} diff --git a/src/main/java/cloneproject/Instagram/repository/search/SearchRepository.java b/src/main/java/cloneproject/Instagram/repository/search/SearchRepository.java new file mode 100644 index 00000000..d1aff165 --- /dev/null +++ b/src/main/java/cloneproject/Instagram/repository/search/SearchRepository.java @@ -0,0 +1,9 @@ +package cloneproject.Instagram.repository.search; + +import org.springframework.data.jpa.repository.JpaRepository; + +import cloneproject.Instagram.entity.search.Search; + +public interface SearchRepository extends JpaRepository, SearchRepositoryQuerydsl{ + +} diff --git a/src/main/java/cloneproject/Instagram/repository/search/SearchRepositoryQuerydsl.java b/src/main/java/cloneproject/Instagram/repository/search/SearchRepositoryQuerydsl.java new file mode 100644 index 00000000..8fb214a1 --- /dev/null +++ b/src/main/java/cloneproject/Instagram/repository/search/SearchRepositoryQuerydsl.java @@ -0,0 +1,12 @@ +package cloneproject.Instagram.repository.search; + +import java.util.List; + +import cloneproject.Instagram.dto.search.SearchDTO; + +public interface SearchRepositoryQuerydsl { + + List searchByText(Long loginedId, String text); + List searchByTextOnlyHashtag(Long loginedId, String text); + +} diff --git a/src/main/java/cloneproject/Instagram/repository/search/SearchRepositoryQuerydslImpl.java b/src/main/java/cloneproject/Instagram/repository/search/SearchRepositoryQuerydslImpl.java new file mode 100644 index 00000000..0a15014d --- /dev/null +++ b/src/main/java/cloneproject/Instagram/repository/search/SearchRepositoryQuerydslImpl.java @@ -0,0 +1,258 @@ +package cloneproject.Instagram.repository.search; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.querydsl.core.group.GroupBy; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import cloneproject.Instagram.dto.member.FollowDTO; +import cloneproject.Instagram.dto.member.QFollowDTO; +import cloneproject.Instagram.dto.search.QSearchHashtagDTO; +import cloneproject.Instagram.dto.search.QSearchMemberDTO; +import cloneproject.Instagram.dto.search.SearchDTO; +import cloneproject.Instagram.dto.search.SearchHashtagDTO; +import cloneproject.Instagram.dto.search.SearchMemberDTO; +import cloneproject.Instagram.entity.search.Search; +import cloneproject.Instagram.repository.story.MemberStoryRedisRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import static cloneproject.Instagram.entity.member.QMember.member; +import static cloneproject.Instagram.entity.hashtag.QHashtag.hashtag; +import static cloneproject.Instagram.entity.search.QSearch.search; +import static cloneproject.Instagram.entity.search.QSearchMember.searchMember; +import static cloneproject.Instagram.entity.search.QSearchHashtag.searchHashtag; +import static cloneproject.Instagram.entity.member.QFollow.follow; + +@Slf4j +@RequiredArgsConstructor +public class SearchRepositoryQuerydslImpl implements SearchRepositoryQuerydsl{ + + private final JPAQueryFactory queryFactory; + private final MemberStoryRedisRepository memberStoryRedisRepository; + + @Override + public List searchByText(Long loginedId, String text){ + + String keyword = text + "%"; + + List searches = queryFactory + .select(search) + .from(search) + .where(search.id.in( + JPAExpressions + .select(searchMember.id) + .from(searchMember) + .innerJoin(searchMember.member, member) + .where(searchMember.member.username.like(keyword) + .or(searchMember.member.name.like(keyword))) + ).or(search.id.in( + JPAExpressions + .select(searchHashtag.id) + .from(searchHashtag) + .innerJoin(searchHashtag.hashtag, hashtag) + .where(searchHashtag.hashtag.name.like(keyword)) + ))) + .orderBy(search.count.desc()) + .limit(50) + .distinct() + .fetch(); + + final List searchIds = searches.stream() + .map(Search::getId) + .collect(Collectors.toList()); + + Map memberMap = queryFactory + .from(searchMember) + .innerJoin(searchMember.member, member) + .where(searchMember.id.in(searchIds)) + .transform(GroupBy.groupBy(searchMember.id).as(new QSearchMemberDTO( + searchMember._super.dtype, + searchMember._super.count, + searchMember.member, + JPAExpressions + .selectFrom(follow) + .where(follow.member.id.eq(loginedId).and(follow.followMember.id.eq(searchMember.member.id))) + .exists(), + JPAExpressions + .selectFrom(follow) + .where(follow.member.id.eq(searchMember.member.id).and(follow.followMember.id.eq(loginedId))) + .exists() + ) + ) + ); + + // follow 주입 + final List resultUsernames = memberMap.values().stream().map(s -> s.getMemberDTO().getUsername()) + .collect(Collectors.toList()); + + // TODO 우선 이전의 search에 있던 코드 그대로 넣었는데 개선 필요해보임 ! + memberMap.forEach((id, member) -> { + member.getMemberDTO().setHasStory(memberStoryRedisRepository.findById(id).isPresent()); + }); + + final List follows = queryFactory + .select(new QFollowDTO( + follow.member.username, + follow.followMember.username + )) + .from(follow) + .where(follow.followMember.username.in(resultUsernames) + .and(follow.member.id.in( + JPAExpressions + .select(follow.followMember.id) + .from(follow) + .where(follow.member.id.eq(loginedId)) + ))) + .fetch(); + + final Map> followsMap = follows.stream() + .collect(Collectors.groupingBy(FollowDTO::getFollowMemberUsername)); + memberMap.forEach((id, member) -> member.setFollowingMemberFollow(followsMap.get(member.getMemberDTO().getUsername()))); + + // 해시태그 + Map hashtagMap = queryFactory + .from(searchHashtag) + .innerJoin(searchHashtag.hashtag, hashtag) + .where(searchHashtag.id.in(searchIds)) + .transform(GroupBy.groupBy(searchHashtag.id).as(new QSearchHashtagDTO( + searchHashtag.dtype, + searchHashtag.count, + searchHashtag.hashtag + )) + ); + + final List resultHashtagNames = hashtagMap.values().stream().map(h -> h.getName()) + .collect(Collectors.toList()); + + boolean matchingMemberExists = resultUsernames.contains(text); + boolean matchingHashtagExists = resultHashtagNames.contains(text); + + final List searchDTOs = searches.stream() + .map(search -> { + switch (search.getDtype()) { + case "MEMBER": + return memberMap.get(search.getId()); + case "HASHTAG": + return hashtagMap.get(search.getId()); + default: + return null; + } + }) + .collect(Collectors.toList()); + + if(!matchingHashtagExists){ + SearchHashtagDTO searchHashtagDTO = queryFactory + .select(new QSearchHashtagDTO( + searchHashtag.dtype, + searchHashtag.count, + searchHashtag.hashtag + )) + .from(searchHashtag) + .innerJoin(searchHashtag.hashtag, hashtag) + .where(searchHashtag.hashtag.name.eq(text)) + .fetchFirst(); + if(searchHashtagDTO != null) { + searchDTOs.add(0, searchHashtagDTO); + searchDTOs.remove(searchDTOs.size()-1); + } + } + if(!matchingMemberExists){ + SearchMemberDTO searchMemberDTO = queryFactory + .select(new QSearchMemberDTO( + searchMember._super.dtype, + searchMember._super.count, + searchMember.member, + JPAExpressions + .selectFrom(follow) + .where(follow.member.id.eq(loginedId).and(follow.followMember.id.eq(searchMember.member.id))) + .exists(), + JPAExpressions + .selectFrom(follow) + .where(follow.member.id.eq(searchMember.member.id).and(follow.followMember.id.eq(loginedId))) + .exists() + )) + .from(searchMember) + .innerJoin(searchMember.member, member) + .where(searchMember.member.username.eq(text)) + .fetchFirst(); + if(searchMemberDTO != null) { + searchDTOs.add(0, searchMemberDTO); + searchDTOs.remove(searchDTOs.size()-1); + } + } + + return searchDTOs; + } + + + @Override + public List searchByTextOnlyHashtag(Long loginedId, String text){ + String keyword = text + "%"; + + List searches = queryFactory + .select(search) + .from(search) + .where(search.id.in( + JPAExpressions + .select(searchHashtag.id) + .from(searchHashtag) + .innerJoin(searchHashtag.hashtag, hashtag) + .where(searchHashtag.hashtag.name.like(keyword)) + )) + .orderBy(search.count.desc()) + .limit(50) + .distinct() + .fetch(); + + final List searchIds = searches.stream() + .map(Search::getId) + .collect(Collectors.toList()); + + Map hashtagMap = queryFactory + .from(searchHashtag) + .innerJoin(searchHashtag.hashtag, hashtag) + .where(searchHashtag.id.in(searchIds)) + .transform(GroupBy.groupBy(searchHashtag.id).as(new QSearchHashtagDTO( + searchHashtag.dtype, + searchHashtag.count, + searchHashtag.hashtag + )) + ); + + boolean matchingHashtagExists = queryFactory + .from(searchHashtag) + .innerJoin(searchHashtag.hashtag, hashtag).fetchJoin() + .where(searchHashtag.id.in(searchIds).and(searchHashtag.hashtag.name.eq(text))) + .fetchFirst() != null; + + final List searchDTOs = searches.stream() + .map(search -> { + return hashtagMap.get(search.getId()); + + }) + .collect(Collectors.toList()); + + if(!matchingHashtagExists){ + SearchHashtagDTO searchHashtagDTO = queryFactory + .select(new QSearchHashtagDTO( + searchHashtag.dtype, + searchHashtag.count, + searchHashtag.hashtag + )) + .from(searchHashtag) + .innerJoin(searchHashtag.hashtag, hashtag) + .where(searchHashtag.hashtag.name.eq(text)) + .fetchFirst(); + if(searchHashtagDTO != null) { + searchDTOs.add(0, searchHashtagDTO); + searchDTOs.remove(searchDTOs.size()-1); + } + } + + return searchDTOs; + } +} diff --git a/src/main/java/cloneproject/Instagram/service/HashtagService.java b/src/main/java/cloneproject/Instagram/service/HashtagService.java index 705de184..08006512 100644 --- a/src/main/java/cloneproject/Instagram/service/HashtagService.java +++ b/src/main/java/cloneproject/Instagram/service/HashtagService.java @@ -1,10 +1,14 @@ package cloneproject.Instagram.service; -import cloneproject.Instagram.dto.hashtag.HashtagDTO; import cloneproject.Instagram.dto.post.PostDTO; import cloneproject.Instagram.entity.hashtag.Hashtag; +import cloneproject.Instagram.entity.member.HashtagFollow; import cloneproject.Instagram.entity.member.Member; +import cloneproject.Instagram.exception.CantFollowHashtagException; +import cloneproject.Instagram.exception.CantUnfollowHashtagException; +import cloneproject.Instagram.exception.HashtagNotFoundException; import cloneproject.Instagram.exception.MemberDoesNotExistException; +import cloneproject.Instagram.repository.HashtagFollowRepository; import cloneproject.Instagram.repository.HashtagRepository; import cloneproject.Instagram.repository.MemberRepository; import cloneproject.Instagram.repository.post.PostRepository; @@ -28,6 +32,7 @@ public class HashtagService { private final PostRepository postRepository; private final MemberRepository memberRepository; private final HashtagRepository hashtagRepository; + private final HashtagFollowRepository hashtagFollowRepository; public Page getHashTagPosts(int page, int size, String name) { page = (page == 0 ? 0 : page - 1); @@ -41,9 +46,38 @@ public Page getHashTagPosts(int page, int size, String name) { return postRepository.findPostDtoPageByHashtag(pageable, member, findHashtag.get()); } - public Page getHashTagsLikeName(int page, int size, String name) { - page = (page == 0 ? 0 : page - 1); - final Pageable pageable = PageRequest.of(page, size); - return hashtagRepository.findHashtagDtoPageLikeName(pageable, name); + @Transactional + public void followHashtag(String hashtagName){ + String memberId = SecurityContextHolder.getContext().getAuthentication().getName(); + Hashtag hashtag = hashtagRepository.findByName(hashtagName) + .orElseThrow(HashtagNotFoundException::new); + Member member = memberRepository.findById(Long.valueOf(memberId)) + .orElseThrow(MemberDoesNotExistException::new); + + if(hashtagFollowRepository.existsByMemberIdAndHashtagId(member.getId(), hashtag.getId())){ + throw new CantFollowHashtagException(); + } + + HashtagFollow hashtagFollow = HashtagFollow.builder() + .member(member) + .hashtag(hashtag) + .build(); + + hashtagFollowRepository.save(hashtagFollow); + } + + @Transactional + public void unfollowHashtag(String hashtagName){ + String memberId = SecurityContextHolder.getContext().getAuthentication().getName(); + Hashtag hashtag = hashtagRepository.findByName(hashtagName) + .orElseThrow(HashtagNotFoundException::new); + Member member = memberRepository.findById(Long.valueOf(memberId)) + .orElseThrow(MemberDoesNotExistException::new); + + HashtagFollow hashtagFollow = hashtagFollowRepository.findByMemberIdAndHashtagId(member.getId(), hashtag.getId()) + .orElseThrow(CantUnfollowHashtagException::new); + + hashtagFollowRepository.delete(hashtagFollow); } + } diff --git a/src/main/java/cloneproject/Instagram/service/MemberAuthService.java b/src/main/java/cloneproject/Instagram/service/MemberAuthService.java index d14f9ff8..48ab0a74 100644 --- a/src/main/java/cloneproject/Instagram/service/MemberAuthService.java +++ b/src/main/java/cloneproject/Instagram/service/MemberAuthService.java @@ -21,12 +21,14 @@ import cloneproject.Instagram.dto.member.UpdatePasswordRequest; import cloneproject.Instagram.entity.member.Member; import cloneproject.Instagram.entity.redis.RefreshToken; +import cloneproject.Instagram.entity.search.SearchMember; import cloneproject.Instagram.exception.AccountDoesNotMatchException; import cloneproject.Instagram.exception.CantResetPasswordException; import cloneproject.Instagram.exception.InvalidJwtException; import cloneproject.Instagram.exception.MemberDoesNotExistException; import cloneproject.Instagram.exception.UseridAlreadyExistException; import cloneproject.Instagram.repository.MemberRepository; +import cloneproject.Instagram.repository.search.SearchMemberRepository; import cloneproject.Instagram.util.JwtUtil; import cloneproject.Instagram.vo.GeoIP; import io.jsonwebtoken.JwtException; @@ -44,6 +46,7 @@ public class MemberAuthService { private final MemberRepository memberRepository; private final RefreshTokenService refreshTokenService; private final GeoIPLocationService geoIPLocationService; + private final SearchMemberRepository searchMemberRepository; private final BCryptPasswordEncoder bCryptPasswordEncoder; @@ -71,6 +74,10 @@ public boolean register(RegisterRequest registerRequest){ String encryptedPassword = bCryptPasswordEncoder.encode(member.getPassword()); member.setEncryptedPassword(encryptedPassword); memberRepository.save(member); + + SearchMember searchMember = new SearchMember(member); + searchMemberRepository.save(searchMember); + return true; } diff --git a/src/main/java/cloneproject/Instagram/service/MemberService.java b/src/main/java/cloneproject/Instagram/service/MemberService.java index 4459639d..58fbcb83 100644 --- a/src/main/java/cloneproject/Instagram/service/MemberService.java +++ b/src/main/java/cloneproject/Instagram/service/MemberService.java @@ -1,7 +1,5 @@ package cloneproject.Instagram.service; -import java.util.List; - import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -132,10 +130,4 @@ public void editProfile(EditProfileRequest editProfileRequest){ memberRepository.save(member); } - public List searchMember(String text){ - final String memberId = SecurityContextHolder.getContext().getAuthentication().getName(); - List result = memberRepository.searchMember(Long.valueOf(memberId), text); - return result; - } - } diff --git a/src/main/java/cloneproject/Instagram/service/PostService.java b/src/main/java/cloneproject/Instagram/service/PostService.java index 0df4a492..e6190536 100644 --- a/src/main/java/cloneproject/Instagram/service/PostService.java +++ b/src/main/java/cloneproject/Instagram/service/PostService.java @@ -76,6 +76,7 @@ public class PostService { private final JoinRoomRepository joinRoomRepository; private final HashtagRepository hashtagRepository; private final HashtagPostRepository hashtagPostRepository; + private final SearchService searchService; public Page getPostDtoPage(int size, int page) { page = (page == 0 ? 0 : page - 1) + 10; @@ -403,13 +404,14 @@ private void registerHashtags(Post post, String content) { .collect(Collectors.toMap(Hashtag::getName, h -> h)); final List newHashtagPost = new ArrayList<>(); names.forEach(name -> { - Hashtag hashtag; + final Hashtag hashtag; if (hashtagMap.containsKey(name)) { hashtag = hashtagMap.get(name); hashtag.upCount(); - } - else + } else { hashtag = hashtagRepository.save(new Hashtag(name)); + searchService.createSearchHashtag(hashtag); + } newHashtagPost.add(new HashtagPost(hashtag, post)); }); hashtagPostRepository.saveAllBatch(newHashtagPost); @@ -429,6 +431,7 @@ private void unregisterHashtags(Post post) { final List hashtagPosts = hashtagPostRepository.findAllByHashtagIn(deleteHashtags); hashtagPostRepository.deleteAllInBatch(hashtagPosts); hashtagRepository.deleteAllInBatch(deleteHashtags); + searchService.deleteSearchHashtags(deleteHashtags); } public Page getCommentDtoPage(Long postId, int page) { diff --git a/src/main/java/cloneproject/Instagram/service/SearchService.java b/src/main/java/cloneproject/Instagram/service/SearchService.java new file mode 100644 index 00000000..42ba8c8e --- /dev/null +++ b/src/main/java/cloneproject/Instagram/service/SearchService.java @@ -0,0 +1,69 @@ +package cloneproject.Instagram.service; + +import java.util.List; +import java.util.stream.Collectors; + +import cloneproject.Instagram.entity.hashtag.Hashtag; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import cloneproject.Instagram.dto.search.SearchDTO; +import cloneproject.Instagram.entity.search.SearchHashtag; +import cloneproject.Instagram.entity.search.SearchMember; +import cloneproject.Instagram.exception.HashtagNotFoundException; +import cloneproject.Instagram.exception.MemberDoesNotExistException; +import cloneproject.Instagram.repository.search.SearchHashtagRepository; +import cloneproject.Instagram.repository.search.SearchMemberRepository; +import cloneproject.Instagram.repository.search.SearchRepository; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class SearchService { + + private final SearchRepository searchRepository; + private final SearchMemberRepository searchMemberRepository; + private final SearchHashtagRepository searchHashtagRepository; + + @Transactional(readOnly = true) + public List searchByText(String text) { + final String memberId = SecurityContextHolder.getContext().getAuthentication().getName(); + final List result; + if (text.charAt(0) == '#') { + result = searchRepository.searchByTextOnlyHashtag(Long.valueOf(memberId), text.substring(1)); + } else { + result = searchRepository.searchByText(Long.valueOf(memberId), text); + } + return result; + } + + @Transactional + public void increaseSearchCount(String entityName, String entityType) { + switch(entityType){ + case "MEMBER": + SearchMember searchMember = searchMemberRepository.findByMemberUsername(entityName) + .orElseThrow(MemberDoesNotExistException::new); + searchMember.upCount(); + searchMemberRepository.save(searchMember); + break; + case "HASHTAG": + SearchHashtag searchHashtag = searchHashtagRepository.findByHashtagName(entityName) + .orElseThrow(HashtagNotFoundException::new); + searchHashtag.upCount(); + searchHashtagRepository.save(searchHashtag); + break; + } + } + + @Transactional + public void createSearchHashtag(Hashtag hashtag) { + searchHashtagRepository.save(new SearchHashtag(hashtag)); + } + + @Transactional + public void deleteSearchHashtags(List hashtags) { + final List searchHashtags = searchHashtagRepository.findAllByHashtagIn(hashtags); + searchHashtagRepository.deleteAllInBatch(searchHashtags); + } +}