Skip to content

Commit 4cb72ea

Browse files
안훈기안훈기
authored andcommitted
✨Feat: 모든 동호회 조회 API + 동호회 상세 조회 API
1 parent d6e333a commit 4cb72ea

File tree

9 files changed

+270
-73
lines changed

9 files changed

+270
-73
lines changed

src/main/java/com/be/sportizebe/domain/club/controller/ClubController.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import com.be.sportizebe.domain.club.dto.request.ClubCreateRequest;
44
import com.be.sportizebe.domain.club.dto.request.ClubUpdateRequest;
5+
import com.be.sportizebe.domain.club.dto.response.ClubDetailResponse;
56
import com.be.sportizebe.domain.club.dto.response.ClubImageResponse;
67
import com.be.sportizebe.domain.club.dto.response.ClubResponse;
8+
import com.be.sportizebe.domain.club.dto.response.ClubScrollResponse;
79
import com.be.sportizebe.domain.club.service.ClubServiceImpl;
810
import com.be.sportizebe.domain.user.entity.User;
911
import com.be.sportizebe.global.response.BaseResponse;
@@ -37,7 +39,27 @@ public ResponseEntity<BaseResponse<ClubResponse>> createClub(
3739
return ResponseEntity.status(HttpStatus.CREATED)
3840
.body(BaseResponse.success("동호회 생성 성공", response));
3941
}
42+
@GetMapping
43+
@Operation(summary = "모든 동호회 조회 (무한스크롤)",
44+
description = "커서 기반 무한스크롤 방식으로 동호회 목록을 조회합니다.")
45+
public ResponseEntity<BaseResponse<ClubScrollResponse>> getClubs(
46+
@RequestParam(required = false) Long cursor,
47+
@RequestParam(defaultValue = "20") int size
48+
) {
49+
ClubScrollResponse response = clubService.getClubsByScroll(cursor, size);
50+
return ResponseEntity.ok(
51+
BaseResponse.success("동호회 목록 조회 성공", response)
52+
);
53+
}
4054

55+
@GetMapping("/{clubId}")
56+
@Operation(summary = "동호회 상세 조회", description = "동호회 단건 상세 정보를 조회합니다.")
57+
public ResponseEntity<BaseResponse<ClubDetailResponse>> getClub(
58+
@Parameter(description = "동호회 ID") @PathVariable Long clubId
59+
) {
60+
ClubDetailResponse response = clubService.getClub(clubId);
61+
return ResponseEntity.ok(BaseResponse.success("동호회 상세 조회 성공", response));
62+
}
4163
@PutMapping("/{clubId}")
4264
@Operation(summary = "동호회 수정", description = "동호회 정보를 수정합니다. 동호회장만 수정할 수 있습니다.")
4365
public ResponseEntity<BaseResponse<ClubResponse>> updateClub(
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.be.sportizebe.domain.club.dto.response;
2+
3+
import com.be.sportizebe.domain.club.entity.Club;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import java.time.LocalDateTime;
6+
7+
@Schema(title = "ClubDetailResponse", description = "동호회 상세 조회 응답")
8+
public record ClubDetailResponse(
9+
10+
@Schema(description = "동호회 ID", example = "1")
11+
Long clubId,
12+
13+
@Schema(description = "동호회 이름", example = "수원 FC 풋살 동호회")
14+
String name,
15+
16+
@Schema(description = "동호회 소개글 (전체)", example = "매주 화, 토 저녁에 풋살을 즐기는 동호회입니다.")
17+
String introduce,
18+
19+
@Schema(description = "동호회 종목", example = "SOCCER")
20+
String clubType,
21+
22+
@Schema(description = "최대 정원", example = "20")
23+
int maxMembers,
24+
25+
@Schema(description = "현재 동호회 인원 수", example = "12")
26+
int currentMembers,
27+
28+
@Schema(description = "동호회 대표 이미지 URL", example = "https://bucket.s3.ap-northeast-2.amazonaws.com/club/uuid.jpg")
29+
String clubImageUrl,
30+
31+
@Schema(description = "동호회 생성 일시", example = "2026-02-01T12:30:00")
32+
LocalDateTime createdAt
33+
) {
34+
public static ClubDetailResponse from(Club club, int memberCount) {
35+
return new ClubDetailResponse(
36+
club.getId(),
37+
club.getName(),
38+
club.getIntroduce(),
39+
club.getClubType().name(),
40+
club.getMaxMembers(),
41+
memberCount,
42+
club.getClubImage(),
43+
club.getCreatedAt()
44+
);
45+
}
46+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.be.sportizebe.domain.club.dto.response;
2+
3+
import com.be.sportizebe.domain.club.entity.Club;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import java.time.LocalDateTime;
6+
7+
@Schema(title = "ClubListItemResponse", description = "동호회 목록(무한스크롤) 카드 단위 응답")
8+
public record ClubListItemResponse(
9+
10+
@Schema(description = "동호회 ID", example = "1")
11+
Long clubId,
12+
13+
@Schema(description = "동호회 이름", example = "수원 FC 풋살 동호회")
14+
String name,
15+
16+
@Schema(description = "동호회 종목", example = "SOCCER")
17+
String sportType,
18+
19+
@Schema(description = "동호회 소개글 (목록용 요약)", example = "주 2회 풋살을 즐기는 동호회입니다.")
20+
String description,
21+
22+
@Schema(description = "동호회 대표 이미지 URL", example = "https://bucket.s3.ap-northeast-2.amazonaws.com/club/uuid.jpg")
23+
String imageUrl,
24+
25+
@Schema(description = "현재 동호회 인원 수", example = "12")
26+
int memberCount,
27+
28+
@Schema(description = "동호회 생성 일시", example = "2026-02-01T12:30:00")
29+
LocalDateTime createdAt
30+
) {
31+
public static ClubListItemResponse from(Club club, int memberCount) {
32+
return new ClubListItemResponse(
33+
club.getId(),
34+
club.getName(),
35+
club.getClubType().name(),
36+
club.getIntroduce(),
37+
club.getClubImage(),
38+
memberCount,
39+
club.getCreatedAt()
40+
);
41+
}
42+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.be.sportizebe.domain.club.dto.response;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import java.util.List;
5+
6+
@Schema(title = "ClubScrollResponse", description = "동호회 목록 무한스크롤 조회 응답")
7+
public record ClubScrollResponse(
8+
9+
@Schema(description = "동호회 목록")
10+
List<ClubListItemResponse> items,
11+
12+
@Schema(description = "다음 조회를 위한 커서 값 (마지막 clubId)", example = "15")
13+
Long nextCursor,
14+
15+
@Schema(description = "다음 페이지 존재 여부", example = "true")
16+
boolean hasNext
17+
) {
18+
}

src/main/java/com/be/sportizebe/domain/club/repository/ClubMemberRepository.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
import org.springframework.data.jpa.repository.JpaRepository;
55

66
public interface ClubMemberRepository extends JpaRepository<ClubMember, Long> {
7+
int countByClubId(Long clubId);
78
}

src/main/java/com/be/sportizebe/domain/club/repository/ClubRepository.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,24 @@
33
import com.be.sportizebe.domain.club.entity.Club;
44
import org.springframework.data.jpa.repository.JpaRepository;
55

6+
import com.be.sportizebe.domain.club.entity.Club;
7+
import org.springframework.data.domain.Pageable;
8+
import org.springframework.data.jpa.repository.Query;
9+
import org.springframework.data.repository.query.Param;
10+
import org.springframework.data.jpa.repository.JpaRepository;
11+
12+
import java.util.List;
13+
614
public interface ClubRepository extends JpaRepository<Club, Long> {
715
boolean existsByName(String name);
16+
17+
@Query("""
18+
SELECT c FROM Club c
19+
WHERE (:cursor IS NULL OR c.id < :cursor)
20+
ORDER BY c.id DESC
21+
""")
22+
List<Club> findClubsByCursor(
23+
@Param("cursor") Long cursor,
24+
Pageable pageable
25+
);
826
}

src/main/java/com/be/sportizebe/domain/club/service/ClubService.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@
22

33
import com.be.sportizebe.domain.club.dto.request.ClubCreateRequest;
44
import com.be.sportizebe.domain.club.dto.request.ClubUpdateRequest;
5+
import com.be.sportizebe.domain.club.dto.response.ClubDetailResponse;
56
import com.be.sportizebe.domain.club.dto.response.ClubImageResponse;
67
import com.be.sportizebe.domain.club.dto.response.ClubResponse;
8+
import com.be.sportizebe.domain.club.dto.response.ClubScrollResponse;
79
import com.be.sportizebe.domain.user.entity.User;
810
import org.springframework.web.multipart.MultipartFile;
911

1012
public interface ClubService {
11-
ClubResponse createClub(ClubCreateRequest request, MultipartFile image, User user); // 동호회 생성
13+
ClubResponse createClub(ClubCreateRequest request, MultipartFile image, User user); // 동호회 생성
1214

13-
ClubResponse updateClub(Long clubId, ClubUpdateRequest request, User user); // 동호회 수정
15+
ClubResponse updateClub(Long clubId, ClubUpdateRequest request, User user); // 동호회 수정
1416

15-
ClubImageResponse updateClubImage(Long clubId, MultipartFile image, User user); // 동호회 사진 수정
17+
ClubImageResponse updateClubImage(Long clubId, MultipartFile image, User user); // 동호회 사진 수정
18+
19+
ClubDetailResponse getClub(Long clubId); // 동호회 개별 조회
20+
21+
ClubScrollResponse getClubsByScroll(Long cursor, int size); // 동호회 전체 조회 (무한 스크롤)
1622
}

0 commit comments

Comments
 (0)