Skip to content

Commit bde86fd

Browse files
authored
Merge pull request #242 from dnd-side-project/dev
feat(Follow): νŒ”λ‘œμ›Œ/νŒ”λ‘œμž‰ λͺ©λ‘ 쑰회
2 parents 6b8c304 + 362600f commit bde86fd

File tree

16 files changed

+359
-12
lines changed

16 files changed

+359
-12
lines changed

β€Žmain-server/src/main/java/com/example/demo/domain/browse/controller/BrowsePlaylistController.javaβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import com.example.demo.domain.browse.dto.PlaylistViewCountDto;
66
import com.example.demo.domain.browse.service.BrowsePlaylistService;
77
import com.example.demo.domain.browse.service.BrowseViewCountService;
8-
import com.example.demo.domain.playlist.dto.page.CursorPageResponse;
8+
import com.example.demo.global.paging.CursorPageResponse;
99
import com.example.demo.global.security.filter.CustomUserDetails;
1010
import io.swagger.v3.oas.annotations.Operation;
1111
import io.swagger.v3.oas.annotations.Parameter;

β€Žmain-server/src/main/java/com/example/demo/domain/browse/service/BrowsePlaylistService.javaβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import com.example.demo.domain.browse.dto.BrowsePlaylistDto;
99
import com.example.demo.domain.browse.dto.CreatorDto;
1010
import com.example.demo.domain.browse.repository.BrowsePlaylistRepository;
11-
import com.example.demo.domain.playlist.dto.page.CursorPageResponse;
11+
import com.example.demo.global.paging.CursorPageResponse;
1212
import com.example.demo.domain.playlist.entity.Playlist;
1313
import com.example.demo.domain.playlist.repository.PlaylistRepository;
1414
import com.example.demo.domain.playlist.service.PlaylistService;

β€Žmain-server/src/main/java/com/example/demo/domain/follow/controller/FollowController.javaβ€Ž

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
package com.example.demo.domain.follow.controller;
22

3+
import com.example.demo.domain.follow.dto.response.FollowCountResponse;
4+
import com.example.demo.domain.follow.dto.response.FollowListItem;
5+
import com.example.demo.domain.follow.dto.response.FollowListResponse;
36
import com.example.demo.domain.follow.dto.response.IsUserFollowingResponse;
47
import com.example.demo.domain.follow.service.FollowService;
8+
import com.example.demo.global.paging.CursorPageResponse;
59
import com.example.demo.global.security.filter.CustomUserDetails;
610
import io.swagger.v3.oas.annotations.Operation;
711
import io.swagger.v3.oas.annotations.Parameter;
12+
import io.swagger.v3.oas.annotations.media.Content;
13+
import io.swagger.v3.oas.annotations.media.Schema;
14+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
815
import io.swagger.v3.oas.annotations.tags.Tag;
916
import lombok.RequiredArgsConstructor;
1017
import org.springframework.http.ResponseEntity;
@@ -19,6 +26,69 @@ public class FollowController {
1926

2027
private final FollowService followService;
2128

29+
@GetMapping("/follower/{userId}")
30+
@Operation(
31+
summary = "ν•΄λ‹Ή μœ μ €μ˜ νŒ”λ‘œμ›Œ λͺ©λ‘",
32+
description = "ν•΄λ‹Ή μœ μ €μ˜ νŒ”λ‘œμ›Œ λͺ©λ‘μ„ κ°€μ Έμ˜΅λ‹ˆλ‹€. 각 ν•­λͺ©μ— ν˜„μž¬ λ‘œκ·ΈμΈν•œ μ‚¬μš©μžκ°€ ν•΄λ‹Ή μœ μ €λ₯Ό νŒ”λ‘œμš° 쀑인지λ₯Ό ν¬ν•¨ν•©λ‹ˆλ‹€.",
33+
responses = {
34+
@ApiResponse(
35+
responseCode = "200",
36+
content = @Content(
37+
schema = @Schema(implementation = FollowListResponse.class)
38+
)
39+
)
40+
}
41+
)
42+
public ResponseEntity<CursorPageResponse<FollowListItem, Long>> getFollowerList(
43+
@Parameter(hidden = true)
44+
@AuthenticationPrincipal CustomUserDetails me,
45+
@PathVariable String userId,
46+
@RequestParam(required = false) Long cursor,
47+
@RequestParam(required = false, defaultValue = "20") int limit
48+
) {
49+
CursorPageResponse<FollowListItem, Long> response =
50+
followService.getFollowerList(userId, me.getId(), cursor, limit);
51+
52+
return ResponseEntity.ok(response);
53+
}
54+
55+
@GetMapping("/following/{userId}")
56+
@Operation(
57+
summary = "ν•΄λ‹Ή μœ μ €μ˜ νŒ”λ‘œμž‰ λͺ©λ‘",
58+
description = "ν•΄λ‹Ή μœ μ €μ˜ νŒ”λ‘œμž‰ λͺ©λ‘μ„ κ°€μ Έμ˜΅λ‹ˆλ‹€. 각 ν•­λͺ©μ— ν˜„μž¬ λ‘œκ·ΈμΈν•œ μ‚¬μš©μžκ°€ ν•΄λ‹Ή μœ μ €λ₯Ό νŒ”λ‘œμš° 쀑인지λ₯Ό ν¬ν•¨ν•©λ‹ˆλ‹€.",
59+
responses = {
60+
@ApiResponse(
61+
responseCode = "200",
62+
content = @Content(
63+
schema = @Schema(implementation = FollowListResponse.class)
64+
)
65+
)
66+
}
67+
)
68+
public ResponseEntity<CursorPageResponse<FollowListItem, Long>> getFolloweeList(
69+
@Parameter(hidden = true)
70+
@AuthenticationPrincipal CustomUserDetails me,
71+
@PathVariable String userId,
72+
@RequestParam(required = false) Long cursor,
73+
@RequestParam(required = false, defaultValue = "20") int limit
74+
) {
75+
CursorPageResponse<FollowListItem, Long> response =
76+
followService.getFollowingList(userId, me.getId(), cursor, limit);
77+
78+
return ResponseEntity.ok(response);
79+
}
80+
81+
@GetMapping("/count/{userId}")
82+
@Operation(
83+
summary = "νŒ”λ‘œμ›Œ/νŒ”λ‘œμž‰ 숫자",
84+
description = "ν•΄λ‹Ή μœ μ €μ˜ νŒ”λ‘œμš°/νŒ”λ‘œμž‰ 숫자λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€."
85+
)
86+
public ResponseEntity<FollowCountResponse> getFollowCount(
87+
@PathVariable String userId
88+
) {
89+
return ResponseEntity.ok(followService.getFollowCount(userId));
90+
}
91+
2292
@GetMapping("/{followeeId}")
2393
@Operation(
2494
summary = "νŒ”λ‘œμš° μ—¬λΆ€ 확인 True/False",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.example.demo.domain.follow.dto.response;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
5+
@Schema(description = "νŒ”λ‘œμš°/νŒ”λ‘œμž‰ 수")
6+
public record FollowCountResponse(long followerCount, long followingCount) {
7+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.example.demo.domain.follow.dto.response;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import lombok.Getter;
5+
6+
@Getter
7+
@Schema(description = "νŒ”λ‘œμ›Œ λͺ©λ‘ 응닡 μ•„μ΄ν…œ")
8+
public class FollowListItem {
9+
10+
@Schema(description = "νŒ”λ‘œμš° ID", example = "1")
11+
private Long followId;
12+
13+
@Schema(description = "μœ μ € ID", example = "hong123")
14+
private String userId;
15+
16+
@Schema(description = "μœ μ € λ‹‰λ„€μž„", example = "홍길동")
17+
private String username;
18+
19+
@Schema(description = "곡유 μ½”λ“œ", example = "ABCD1234")
20+
private String shareCode;
21+
22+
@Schema(description = "ν”„λ‘œν•„ 이미지 URL", example = "https://cdn.example.com/profile.jpg")
23+
private String profileUrl;
24+
25+
@Schema(description = "ν˜„μž¬ λ‘œκ·ΈμΈν•œ μ‚¬μš©μžκ°€ 이 μœ μ €λ₯Ό νŒ”λ‘œμš° 쀑인지 μ—¬λΆ€", example = "true")
26+
private boolean followedByMe;
27+
28+
public void changeFollowedByMe(boolean followedByMe) {
29+
this.followedByMe = followedByMe;
30+
}
31+
32+
public FollowListItem(
33+
Long followId,
34+
String userId,
35+
String username,
36+
String shareCode,
37+
String profileUrl
38+
) {
39+
this.followId = followId;
40+
this.userId = userId;
41+
this.username = username;
42+
this.shareCode = shareCode;
43+
this.profileUrl = profileUrl;
44+
}
45+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.example.demo.domain.follow.dto.response;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
5+
import java.util.List;
6+
7+
@Schema(name = "FollowListResponse", description = "νŒ”λ‘œμš° λͺ©λ‘ μ»€μ„œ νŽ˜μ΄μ§€ 응닡 (μŠ€μ›¨κ±° 쑰회용)")
8+
public record FollowListResponse(
9+
@Schema(description = "쑰회된 μ½˜ν…μΈ  λͺ©λ‘")
10+
List<FollowListItem> content,
11+
12+
@Schema(description = "λ‹€μŒ νŽ˜μ΄μ§€ μš”μ²­μ— μ‚¬μš©ν•  μ»€μ„œ", nullable = true)
13+
Long nextCursor,
14+
15+
@Schema(description = "ν˜„μž¬ νŽ˜μ΄μ§€ ν•­λͺ© 수", example = "20")
16+
int size,
17+
18+
@Schema(description = "λ‹€μŒ νŽ˜μ΄μ§€ 쑴재 μ—¬λΆ€", example = "true")
19+
boolean hasNext,
20+
21+
@Schema(description = "총 검색 κ²°κ³Ό 수", example = "22")
22+
long totalCount
23+
) {}

β€Žmain-server/src/main/java/com/example/demo/domain/follow/repository/FollowRepository.javaβ€Ž

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,10 @@ void insertIfNotExists(@Param("followerId") String followerId,
2727

2828
@Modifying(clearAutomatically = true, flushAutomatically = true)
2929
void deleteByFollower_IdAndFollowee_Id(String followerId, String followeeId);
30+
31+
@Query("select count(f) from Follow f where f.followee.id = :userId")
32+
long countFollowerByUsers_Id(@Param("userId") String userId);
33+
34+
@Query("select count(f) from Follow f where f.follower.id = :userId")
35+
long countFolloweeByUsers_Id(@Param("userId") String userId);
3036
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.example.demo.domain.follow.repository;
22

3+
import com.example.demo.domain.follow.dto.response.FollowListItem;
34
import com.example.demo.domain.follow.dto.response.FollowedPlaylist;
45
import com.example.demo.domain.playlist.dto.common.PlaylistSortOption;
56
import java.util.List;
@@ -8,5 +9,7 @@ public interface FollowRepositoryCustom {
89

910
List<FollowedPlaylist> findFolloweePlaylistsWithMeta(String userId, PlaylistSortOption sort, int limit);
1011

11-
12+
List<FollowListItem> findFollowerListByUserId(String userId, Long cursor, int limit);
13+
List<FollowListItem> findFolloweeListByUserId(String userId, Long cursor, int limit);
14+
List<String> findFolloweeIdsIn(String userId, List<String> followeeIds);
1215
}

β€Žmain-server/src/main/java/com/example/demo/domain/follow/repository/FollowRepositoryCustomImpl.javaβ€Ž

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.example.demo.domain.follow.repository;
22

3+
import com.example.demo.domain.follow.dto.response.FollowListItem;
34
import com.example.demo.domain.follow.dto.response.FollowedPlaylist;
45
import com.example.demo.domain.follow.entity.QFollow;
56
import com.example.demo.domain.playlist.dto.common.PlaylistSortOption;
@@ -48,4 +49,72 @@ public List<FollowedPlaylist> findFolloweePlaylistsWithMeta(String followerId,
4849
.limit(limit)
4950
.fetch();
5051
}
52+
53+
// ν•΄λ‹Ή μœ μ €λ₯Ό νŒ”λ‘œμš°ν•˜λŠ” μ‚¬λžŒ λͺ©λ‘
54+
@Override
55+
public List<FollowListItem> findFollowerListByUserId(String userId, Long cursor, int limit) {
56+
QFollow f = QFollow.follow;
57+
QUsers u = QUsers.users;
58+
59+
return queryFactory
60+
.select(Projections.constructor(
61+
FollowListItem.class,
62+
f.id,
63+
u.id.stringValue(),
64+
u.username,
65+
u.shareCode,
66+
u.profileUrl
67+
))
68+
.from(f)
69+
.join(f.follower, u)
70+
.where(
71+
f.followee.id.eq(userId),
72+
cursor != null ? f.id.lt(cursor) : null
73+
)
74+
.orderBy(f.id.desc())
75+
.limit(limit+1)
76+
.fetch();
77+
}
78+
79+
@Override
80+
public List<FollowListItem> findFolloweeListByUserId(String userId, Long cursor, int limit) {
81+
QFollow f = QFollow.follow;
82+
QUsers u = QUsers.users;
83+
84+
return queryFactory
85+
.select(Projections.constructor(
86+
FollowListItem.class,
87+
f.id,
88+
u.id.stringValue(),
89+
u.username,
90+
u.shareCode,
91+
u.profileUrl
92+
))
93+
.from(f)
94+
.join(f.followee, u)
95+
.where(
96+
f.follower.id.eq(userId),
97+
cursor != null ? f.id.lt(cursor) : null
98+
)
99+
.orderBy(f.id.desc())
100+
.limit(limit + 1)
101+
.fetch();
102+
}
103+
104+
// λͺ©λ‘ 쀑에 λ‚΄κ°€ νŒ”λ‘œμš° 쀑인 μ•„μ΄λ””λ§Œ κ°€μ Έμ˜΄
105+
@Override
106+
public List<String> findFolloweeIdsIn(String userId, List<String> followeeIds) {
107+
if (followeeIds == null || followeeIds.isEmpty()) return List.of();
108+
109+
QFollow f = QFollow.follow;
110+
111+
return queryFactory
112+
.select(f.followee.id)
113+
.from(f)
114+
.where(
115+
f.follower.id.eq(userId),
116+
f.followee.id.in(followeeIds)
117+
)
118+
.fetch();
119+
}
51120
}

0 commit comments

Comments
Β (0)