Skip to content

Commit ad4a83c

Browse files
authored
Merge pull request #57 from ApptiveDev/feat/46-user-block
feat: 차단 기능 구현
2 parents 13b55ab + ad6624d commit ad4a83c

19 files changed

Lines changed: 632 additions & 25 deletions

src/main/java/apptive/team5/diary/repository/DiaryRepository.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ public interface DiaryRepository extends JpaRepository<DiaryEntity, Long> {
3232
@Query("select max(d.id) from DiaryEntity d")
3333
Long findMaxId();
3434

35-
@Query("select d from DiaryEntity d where d.user.id != :userId and d.id >= :diaryId and d.scope in :scopes")
36-
List<DiaryEntity> findDiaryNotMineGreaterThanIdAndScopeIn(Long userId, Long diaryId, List<DiaryScope> scopes, Pageable pageable);
35+
@Query("select d from DiaryEntity d where d.user.id not in :blockedUserIds and d.id >= :diaryId and d.scope in :scopes")
36+
List<DiaryEntity> findDiaryNotMineGreaterThanIdAndScopeIn(Set<Long> blockedUserIds, Long diaryId, List<DiaryScope> scopes, Pageable pageable);
3737

38-
@Query("select d from DiaryEntity d where d.user.id != :userId and d.id <= :diaryId and d.scope in :scopes")
39-
List<DiaryEntity> findDiaryByNotMineAndLessThanIdAndScopeIn(Long userId, Long diaryId, List<DiaryScope> scopes, Pageable pageable);
38+
@Query("select d from DiaryEntity d where d.user.id not in :blockedUserIds and d.id <= :diaryId and d.scope in :scopes")
39+
List<DiaryEntity> findDiaryByNotMineAndLessThanIdAndScopeIn(Set<Long> blockedUserIds, Long diaryId, List<DiaryScope> scopes, Pageable pageable);
4040

4141
@Query("delete from DiaryEntity d where d.user.id = :userId")
4242
@Modifying(clearAutomatically = true, flushAutomatically = true)

src/main/java/apptive/team5/diary/repository/QDiaryLikeRepository.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.springframework.transaction.annotation.Transactional;
1313

1414
import java.util.List;
15+
import java.util.Set;
1516

1617
import static apptive.team5.diary.domain.QDiaryLikeEntity.*;
1718
import static apptive.team5.user.domain.QUserEntity.userEntity;
@@ -26,13 +27,14 @@ public QDiaryLikeRepository(EntityManager entityManager) {
2627
this.queryFactory = new JPAQueryFactory(entityManager);
2728
}
2829

29-
public Page<DiaryLikeEntity> findByDiaryIdLikeSearchCond(Long diaryId, String searchCond, Pageable pageable) {
30+
public Page<DiaryLikeEntity> findByDiaryIdLikeSearchCondExcludedBlockedUsers(Long diaryId, String searchCond, Set<Long> blockedUserIds, Pageable pageable) {
3031

3132
List<DiaryLikeEntity> content = queryFactory
3233
.selectFrom(diaryLikeEntity)
3334
.join(diaryLikeEntity.user, userEntity).fetchJoin()
3435
.where(
3536
diaryLikeEntity.diary.id.eq(diaryId),
37+
notInBlockedUserIds(blockedUserIds),
3638
searchTagOrUserName(searchCond)
3739
)
3840
.offset(pageable.getOffset())
@@ -44,13 +46,21 @@ public Page<DiaryLikeEntity> findByDiaryIdLikeSearchCond(Long diaryId, String se
4446
.join(diaryLikeEntity.user, userEntity)
4547
.where(
4648
diaryLikeEntity.diary.id.eq(diaryId),
49+
notInBlockedUserIds(blockedUserIds),
4750
searchTagOrUserName(searchCond)
4851
)
4952
.from(diaryLikeEntity);
5053

5154
return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne);
5255
}
5356

57+
private BooleanExpression notInBlockedUserIds(Set<Long> blockedUserIds) {
58+
if (blockedUserIds == null || blockedUserIds.isEmpty()) {
59+
return null;
60+
}
61+
return diaryLikeEntity.user.id.notIn(blockedUserIds);
62+
}
63+
5464
private BooleanExpression searchTagOrUserName(String searchCond) {
5565

5666
if (searchCond == null) {

src/main/java/apptive/team5/diary/service/DiaryLikeLowService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ public DiaryLikeEntity saveDiaryLike(DiaryLikeEntity diaryLike) {
3131
}
3232

3333
@Transactional(readOnly = true)
34-
public Page<DiaryLikeEntity> findByDiaryIdLikeSearchCond(Long diaryId, String searchCond, Pageable pageable) {
35-
return qDiaryLikeRepository.findByDiaryIdLikeSearchCond(diaryId, searchCond, pageable);
34+
public Page<DiaryLikeEntity> findByDiaryIdLikeSearchCondExcludedBlockedUsers(Long diaryId, String searchCond, Set<Long> blockedUserIds, Pageable pageable) {
35+
return qDiaryLikeRepository.findByDiaryIdLikeSearchCondExcludedBlockedUsers(diaryId, searchCond, blockedUserIds, pageable);
3636
}
3737

3838
@Transactional(readOnly = true)

src/main/java/apptive/team5/diary/service/DiaryLikeService.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import apptive.team5.user.domain.UserEntity;
1010
import apptive.team5.user.dto.UserResponse;
1111
import apptive.team5.user.dto.UserSearchResponse;
12+
import apptive.team5.user.service.UserBlockLowService;
1213
import apptive.team5.user.service.UserLowService;
1314
import lombok.RequiredArgsConstructor;
1415
import org.springframework.data.domain.Page;
@@ -28,6 +29,7 @@ public class DiaryLikeService {
2829
private final UserLowService userLowService;
2930
private final DiaryLowService diaryLowService;
3031
private final SubscribeLowService subscribeLowService;
32+
private final UserBlockLowService userBlockLowService;
3133

3234
public DiaryLikeResponseDto toggleDiaryLike(Long userId, Long diaryId) {
3335
UserEntity user = userLowService.getReferenceById(userId);
@@ -49,8 +51,10 @@ public Page<UserSearchResponse> getDiaryLikeUsers(Long diaryId, Long userId, Str
4951

5052
DiaryEntity findDiary = diaryLowService.findDiaryById(diaryId);
5153

52-
// 다이어리 좋야요를 누른 사용자
53-
Page<UserEntity> likeUsers = diaryLikeLowService.findByDiaryIdLikeSearchCond(diaryId, searchCond, pageable)
54+
Set<Long> blockedUserIds = userBlockLowService.getBlockedUserIds(userId);
55+
56+
// 다이어리 좋야요를 누른 사용자 중 차단되지 않은 유저
57+
Page<UserEntity> likeUsers = diaryLikeLowService.findByDiaryIdLikeSearchCondExcludedBlockedUsers(diaryId, searchCond, blockedUserIds, pageable)
5458
.map(DiaryLikeEntity::getUser);
5559

5660
List<Long> diaryLikeUserIds = likeUsers.stream().map(UserEntity::getId).toList();

src/main/java/apptive/team5/diary/service/DiaryLowService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,17 @@ public List<DiaryEntity> findByUserIdAndPeriod(Long userId, LocalDateTime start,
6262
}
6363

6464
@Transactional(readOnly = true)
65-
public List<DiaryEntity> findRandomDiary(Long userId) {
65+
public List<DiaryEntity> findRandomDiary(Set<Long> blockedUserIds) {
6666

6767
Long maxId = diaryRepository.findMaxId();
6868

6969
long randomId = ThreadLocalRandom.current().nextLong(1, maxId + 1);
7070

7171
List<DiaryScope> scopes = List.of(DiaryScope.PUBLIC, DiaryScope.KILLING_PART);
7272

73-
List<DiaryEntity> randomDiaries = diaryRepository.findDiaryNotMineGreaterThanIdAndScopeIn(userId, randomId, scopes, PageRequest.of(0, 5));
73+
List<DiaryEntity> randomDiaries = diaryRepository.findDiaryNotMineGreaterThanIdAndScopeIn(blockedUserIds, randomId, scopes, PageRequest.of(0, 5));
7474

75-
if (randomDiaries.isEmpty()) return diaryRepository.findDiaryByNotMineAndLessThanIdAndScopeIn(userId, randomId, scopes, PageRequest.of(0, 5));
75+
if (randomDiaries.isEmpty()) return diaryRepository.findDiaryByNotMineAndLessThanIdAndScopeIn(blockedUserIds, randomId, scopes, PageRequest.of(0, 5));
7676

7777
return randomDiaries;
7878
}

src/main/java/apptive/team5/diary/service/DiaryService.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import apptive.team5.diary.mapper.DiaryResponseMapper;
88
import apptive.team5.subscribe.service.SubscribeLowService;
99
import apptive.team5.user.domain.UserEntity;
10+
import apptive.team5.user.service.UserBlockLowService;
1011
import apptive.team5.user.service.UserLowService;
1112
import lombok.RequiredArgsConstructor;
1213
import org.springframework.data.domain.Page;
@@ -21,6 +22,7 @@
2122
import java.util.*;
2223
import java.util.function.Function;
2324
import java.util.stream.Collectors;
25+
import java.util.stream.Stream;
2426

2527
@Transactional
2628
@Service
@@ -35,6 +37,7 @@ public class DiaryService {
3537
private final SubscribeLowService subscribeLowService;
3638
private final DiaryReportLowService diaryReportLowService;
3739
private final DiaryStoreLowService diaryStoreLowService;
40+
private final UserBlockLowService userBlockLowService;
3841

3942
@Transactional(readOnly = true)
4043
public Page<MyDiaryResponseDto> getMyDiaries(Long userId, Pageable pageable) {
@@ -90,7 +93,13 @@ public List<CalendarDiaryResponseDto> getMyDiariesByPeriod(Long userId, LocalDat
9093

9194
@Transactional(readOnly = true)
9295
public RandomDiaryResponseDto getRandomDiaries(Long userId) {
93-
List<DiaryEntity> randomDiary = diaryLowService.findRandomDiary(userId);
96+
97+
Set<Long> blockedUserIds = Stream.concat(
98+
userBlockLowService.getBlockedUserIds(userId).stream(),
99+
Stream.of(userId) // 랜덤에서는 본인 id도 제외
100+
).collect(Collectors.toSet());
101+
102+
List<DiaryEntity> randomDiary = diaryLowService.findRandomDiary(blockedUserIds);
94103

95104
Collections.shuffle(randomDiary);
96105

src/main/java/apptive/team5/global/exception/ExceptionCode.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ public enum ExceptionCode {
2222
DUPLICATE_SUBSCRIBE_REQUEST("이미 구독한 회원입니다."),
2323
INVALID_DIARY_LIST("유효하지 않은 다이어리 ID가 포함되어 있습니다."),
2424
DUPLICATE_DIARY_REPORT("이미 신고한 게시글입니다."),
25-
REQUIRED_POLICY_NOT_AGREED("필수 약관에 동의해야 합니다.");
25+
REQUIRED_POLICY_NOT_AGREED("필수 약관에 동의해야 합니다."),
26+
DUPLICATE_BLOCKED_USER("이미 차단한 회원입니다."),
27+
BAD_BLOCK_REQUEST("자신을 차단할 수는 없습니다.");
2628

2729
private final String description;
2830

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package apptive.team5.user.controller;
2+
3+
import apptive.team5.user.domain.UserEntity;
4+
import apptive.team5.user.dto.UserResponse;
5+
import apptive.team5.user.service.UserBlockService;
6+
import lombok.RequiredArgsConstructor;
7+
import org.springframework.data.domain.Page;
8+
import org.springframework.data.domain.PageRequest;
9+
import org.springframework.http.HttpStatus;
10+
import org.springframework.http.ResponseEntity;
11+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
12+
import org.springframework.web.bind.annotation.*;
13+
14+
@RestController
15+
@RequestMapping("/api/users")
16+
@RequiredArgsConstructor
17+
public class UserBlockController {
18+
19+
private final UserBlockService userBlockService;
20+
21+
@PostMapping("/{blockedId}/blocks")
22+
public ResponseEntity<Void> addBlockedUser(
23+
@PathVariable Long blockedId,
24+
@AuthenticationPrincipal Long userId) {
25+
26+
userBlockService.addBlockedUser(blockedId, userId);
27+
28+
return ResponseEntity.status(HttpStatus.CREATED).build();
29+
}
30+
31+
@DeleteMapping("/{blockedId}/blocks")
32+
public ResponseEntity<Void> removeBlockedUser(
33+
@PathVariable Long blockedId,
34+
@AuthenticationPrincipal Long userId){
35+
36+
userBlockService.removeBlockedUser(userId, blockedId);
37+
38+
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
39+
}
40+
41+
@GetMapping("/blocks")
42+
public ResponseEntity<Page<UserResponse>> getBlockedUser(
43+
@AuthenticationPrincipal Long userId,
44+
@RequestParam(defaultValue = "0") int page,
45+
@RequestParam(defaultValue = "5") int size
46+
) {
47+
48+
49+
Page<UserResponse> response = userBlockService.getBlockedUser(userId, PageRequest.of(page, size));
50+
51+
return ResponseEntity.status(HttpStatus.OK).body(response);
52+
53+
}
54+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package apptive.team5.user.domain;
2+
3+
import jakarta.persistence.*;
4+
import lombok.AccessLevel;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
10+
@Entity
11+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
12+
@AllArgsConstructor
13+
@Getter
14+
@Table(uniqueConstraints = {
15+
@UniqueConstraint(
16+
name = "unique_user_block",
17+
columnNames = {
18+
"blocker_id",
19+
"blocked_id"
20+
}
21+
),
22+
}
23+
)
24+
public class UserBlock {
25+
26+
@Id
27+
@GeneratedValue(strategy = GenerationType.IDENTITY)
28+
private Long id;
29+
30+
@ManyToOne(fetch = FetchType.LAZY)
31+
@JoinColumn(name = "blocker_id", nullable = false)
32+
private UserEntity blocker;
33+
34+
@ManyToOne(fetch = FetchType.LAZY)
35+
@JoinColumn(name = "blocked_id", nullable = false)
36+
private UserEntity blockedUser;
37+
38+
public UserBlock(UserEntity blocker, UserEntity blockedUser) {
39+
this.blocker = blocker;
40+
this.blockedUser = blockedUser;
41+
}
42+
}

src/main/java/apptive/team5/user/repository/QUserRepository.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import apptive.team5.user.domain.UserEntity;
44
import com.querydsl.core.types.dsl.BooleanExpression;
5+
import com.querydsl.jpa.JPAExpressions;
56
import com.querydsl.jpa.impl.JPAQuery;
67
import com.querydsl.jpa.impl.JPAQueryFactory;
78
import jakarta.persistence.EntityManager;
@@ -12,7 +13,9 @@
1213
import org.springframework.transaction.annotation.Transactional;
1314

1415
import java.util.List;
16+
import java.util.Set;
1517

18+
import static apptive.team5.diary.domain.QDiaryLikeEntity.diaryLikeEntity;
1619
import static apptive.team5.user.domain.QUserEntity.userEntity;
1720

1821
@Transactional
@@ -25,27 +28,37 @@ public QUserRepository(EntityManager entityManager) {
2528
this.queryFactory = new JPAQueryFactory(entityManager);
2629
}
2730

28-
public Page<UserEntity> findByTagOrUsername(String searchCond, Pageable pageable) {
31+
public Page<UserEntity> findByTagOrUsernameExcludingBlocked(Set<Long> blockedUserIds, String searchCond, Pageable pageable) {
32+
BooleanExpression searchCondition = tagLike(searchCond).or(usernameLike(searchCond));
33+
BooleanExpression notBlockedCondition = notInBlockedUserIds(blockedUserIds);
2934

3035
List<UserEntity> content = queryFactory
3136
.selectFrom(userEntity)
32-
.where(tagLike(searchCond).or(usernameLike(searchCond)))
37+
.where(searchCondition, notBlockedCondition)
3338
.offset(pageable.getOffset())
3439
.limit(pageable.getPageSize())
3540
.fetch();
3641

37-
JPAQuery<Long> countQuery = queryFactory.select(userEntity.count())
38-
.where(tagLike(searchCond).or(usernameLike(searchCond)))
39-
.from(userEntity);
42+
JPAQuery<Long> countQuery = queryFactory
43+
.select(userEntity.count())
44+
.from(userEntity)
45+
.where(searchCondition, notBlockedCondition);
4046

4147
return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne);
4248
}
4349

50+
private BooleanExpression notInBlockedUserIds(Set<Long> blockedUserIds) {
51+
if (blockedUserIds == null || blockedUserIds.isEmpty()) {
52+
return null;
53+
}
54+
return userEntity.id.notIn(blockedUserIds);
55+
}
56+
4457
private BooleanExpression tagLike(String tag) {
45-
return tag != null ? userEntity.tag.like("%"+tag+"%") : null;
58+
return tag != null ? userEntity.tag.like("%" + tag + "%") : null;
4659
}
4760

4861
private BooleanExpression usernameLike(String username) {
49-
return username != null ? userEntity.username.like("%"+username+"%") : null;
62+
return username != null ? userEntity.username.like("%" + username + "%") : null;
5063
}
5164
}

0 commit comments

Comments
 (0)