-
Notifications
You must be signed in to change notification settings - Fork 0
Feat: 동아리 소속 목록 조회 api, 동아리 목록 조회 api, 동아리 상세 정보 조회 api 구현 #347
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
e1d43dc
[feat]: 동아리 소속 목록 조회 api 구현
jiyun921 4c42e72
[test]: 동아리 소속 목록 조회 테스트 구현
jiyun921 7090b05
[fix]: 동아리 소속 목록 조회 응답 구조를 명세에 맞게 수정
jiyun921 d4d83cb
[feat]: 동아리 목록 조회 api 구현
jiyun921 a2be583
[fix]: cursor 타입 수정
jiyun921 9c44e7d
[test]: 동아리 목록 조회 테스트 구현
jiyun921 d818cac
[feat]: 동아리 상세 조회 api 구현
jiyun921 840644f
[feat]: 동아리 목록 조회 API에 로그인 사용자 기반 구독 정보 조회 기능 추가
jiyun921 26f1195
[test]: 동아리 상세 조회 테스트 구현
jiyun921 2b65645
[fix]: 동아리 목록/상세 조회 api - JWT 기반 email 인증 방식으로 수정
jiyun921 8cec711
[refactor]: club 상세 조회 api - 위치 정보가 없을 경우 location을 null로 반환하도록 수정
jiyun921 c67ff24
[fix]: name/recruitEndDate 정렬 시 복합 커서 적용
jiyun921 e4e7276
[refactor]: ClubDetail 조회 시 불필요한 userToken 제거
jiyun921 9d613f4
[refactor]: club 목록 조회 시 구독 정보 관련 N+1 쿼리 제거
jiyun921 e0a0241
[fix]: recruitEndDate 정렬을 위한 복합 커서(group|date|id) 구조 도입
jiyun921 2c6280e
[refactor]: Persistence에서 findSubscribedClubIds 가공 로직 Service로 책임 이동
jiyun921 3bbe835
[refactor] ClubDetailCommand 도입 및 email을 Command에 포함하도록 수정
jiyun921 018f9b9
[refactor] 미로그인 시 loginUserId 변수 생성 제거
jiyun921 0be4f6b
[refactor] hasLocation() 로직을 Service에서 ClubDetailDto로 이동
jiyun921 18dbfc3
[refactor] ClubDetailResponse Location 매핑 구조 정리 및 from() 추가
jiyun921 64f8efe
[refactor] 메서드명 구체화 및 메서드 순서 정리
jiyun921 ab20703
[comment] 머지 전 주석 저장
jiyun921 cd4eb4b
[merge] develop 브랜치 머지
jiyun921 34a716b
[refactor] 동아리 목록 조회 커서 페이징 제거
jiyun921 0403b77
[refactor] ClubDivisionListResponse에서 Result 리스트 직접 사용하도록 수정
jiyun921 59782a7
[refactor] totalCount 필드 관련 메서드 제거
jiyun921 31bc32e
[refactor] 구독 조회 로직을 RootUser 기준으로 통일
jiyun921 2df4cc3
[refactor] ClubQueryService 구독 로직 메서드 추출 및 DTO from 패턴 적용
jiyun921 6a7d5f8
[refactor] 모집 상태 계산 로직을 Domain으로 이동하고 Repository 책임 분리
jiyun921 28e7dd7
[refactor] Club 조회 시 Enum 변환 책임을 Service로 이동
jiyun921 0d1c6c2
[refactor] 구독자 수 Long 타입 통일 및 구독 관련 조회 로직 ClubSubscriptionPort로 이동
jiyun921 b550602
[test] ClubPersistenceAdapter 테스트 추가
jiyun921 ba06ea0
Merge branch 'develop' into feat/#336-club-read
jiyun921 8978c10
[fix] 존재하지 않는 동아리 구독 테스트 기댓값 NOT_FOUND로 수정
jiyun921 9cad760
[refactor] DTO from 제거 및 Service로 변환 책임 이동
jiyun921 4cfc7f0
[refactor] findSubscribedClubIds를 id 직접 조회 방식으로 수정
jiyun921 3289297
[chore] vectorstore.json 깃 추적 제거
jiyun921 ffced92
[refactor] countSubscribersByClubIds 테스트 map 검증을 containsEntry 방식으로 개선
jiyun921 cab52e4
[refactor] countSubscribersByClubIds_empty_input 테스트 변수명 수정
jiyun921 b2636b9
[refactor] ClubQueryServiceTest 불필요한 Mockito eq() 제거
jiyun921 8615b63
[test] Club 인수 테스트 구현
jiyun921 d9e0b9f
[refactor] ClubAcceptanceTest public 제거
jiyun921 aafe35b
[refactor] Club 인수 테스트 primitive 타입 null 비교 로직 수정
jiyun921 5e61ccd
[refactor] Club 인수 테스트 primitive 타입 null 비교 로직 수정
jiyun921 889e3c5
[refactor] 불필요한 개행 제거
jiyun921 255a70f
[refactor] ClubAcceptanceTest 문자열 직접 비교로 수정
jiyun921 780dedc
[refactor] ClubListCommand division을 생성 시점에 List로 변환하도록 수정
jiyun921 f31d6f4
[refactor] Rootuser 불필요한 Optional 형식 수정
jiyun921 c5f562e
[refactor] ClubDetailDto -> ClubDetailReadModel로 이름 수정
jiyun921 29b9708
[refactor] 구독자 수 조회 로직Object[] -> Projection으로 수정
jiyun921 35ce612
[fix] 동아리 조회 시 이미지 S3 Presigned URL 적용
jiyun921 a3256b1
[refactor] 동아리 목록 조회 카테고리 필터 검증 추가
jiyun921 161192e
[refactor] 빈 clubIds 입력 시 조기 반환 처리 추가
jiyun921 78d5250
[refactor] SQL 개행 원복
jiyun921 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
113 changes: 113 additions & 0 deletions
113
src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| package com.kustacks.kuring.club.adapter.in.web; | ||
|
|
||
| import com.kustacks.kuring.auth.authentication.AuthorizationExtractor; | ||
| import com.kustacks.kuring.auth.authentication.AuthorizationType; | ||
| import com.kustacks.kuring.auth.token.JwtTokenProvider; | ||
| import com.kustacks.kuring.club.adapter.in.web.dto.ClubDetailResponse; | ||
| import com.kustacks.kuring.club.adapter.in.web.dto.ClubDivisionListResponse; | ||
| import com.kustacks.kuring.club.adapter.in.web.dto.ClubListResponse; | ||
| import com.kustacks.kuring.club.application.port.in.ClubQueryUseCase; | ||
| import com.kustacks.kuring.club.application.port.in.dto.ClubDetailCommand; | ||
| import com.kustacks.kuring.club.application.port.in.dto.ClubDetailResult; | ||
| import com.kustacks.kuring.club.application.port.in.dto.ClubDivisionResult; | ||
| import com.kustacks.kuring.club.application.port.in.dto.ClubListCommand; | ||
| import com.kustacks.kuring.club.application.port.in.dto.ClubListResult; | ||
| import com.kustacks.kuring.common.annotation.RestWebAdapter; | ||
| import com.kustacks.kuring.common.dto.BaseResponse; | ||
| import com.kustacks.kuring.common.exception.InvalidStateException; | ||
| import com.kustacks.kuring.common.exception.code.ErrorCode; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.security.SecurityRequirement; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.validation.annotation.Validated; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.RequestHeader; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Optional; | ||
|
|
||
| import static com.kustacks.kuring.auth.authentication.AuthorizationExtractor.extractAuthorizationValue; | ||
| import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.CLUB_DETAIL_SEARCH_SUCCESS; | ||
| import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.CLUB_DIVISION_SEARCH_SUCCESS; | ||
| import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.CLUB_LIST_SEARCH_SUCCESS; | ||
|
|
||
| @Tag(name = "Club-Query", description = "동아리 정보 조회") | ||
| @Validated | ||
| @RequiredArgsConstructor | ||
| @RestWebAdapter(path = "/api/v2/clubs") | ||
| public class ClubQueryApiV2 { | ||
|
|
||
| private static final String FCM_TOKEN_HEADER_KEY = "User-Token"; | ||
| private static final String JWT_TOKEN_HEADER_KEY = "JWT"; | ||
|
|
||
| private final JwtTokenProvider jwtTokenProvider; | ||
| private final ClubQueryUseCase clubQueryUseCase; | ||
|
|
||
| @Operation(summary = "동아리 소속 목록 조회", description = "서버가 지원하는 동아리 소속 목록을 조회합니다") | ||
| @GetMapping("/divisions") | ||
| public ResponseEntity<BaseResponse<ClubDivisionListResponse>> getSupportedClubDivisions() { | ||
|
|
||
| List<ClubDivisionResult> results = clubQueryUseCase.getClubDivisions(); | ||
|
|
||
| ClubDivisionListResponse response = ClubDivisionListResponse.from(results); | ||
|
|
||
| return ResponseEntity.ok().body(new BaseResponse<>(CLUB_DIVISION_SEARCH_SUCCESS, response)); | ||
| } | ||
|
|
||
| @Operation(summary = "동아리 목록 조회", description = "필터 조건에 맞는 동아리 목록을 조회합니다") | ||
| @SecurityRequirement(name = JWT_TOKEN_HEADER_KEY) | ||
| @GetMapping | ||
| public ResponseEntity<BaseResponse<ClubListResponse>> getClubs( | ||
| @RequestParam(required = false) String category, | ||
| @RequestParam(required = false) String division, | ||
| @RequestHeader(value = AuthorizationExtractor.AUTHORIZATION, required = false) String bearerToken | ||
| ) { | ||
| String email = resolveLoginEmail(bearerToken); | ||
|
|
||
| ClubListCommand command = new ClubListCommand(category, division, email); | ||
|
|
||
| ClubListResult result = clubQueryUseCase.getClubs(command); | ||
|
|
||
| ClubListResponse response = ClubListResponse.from(result); | ||
|
|
||
| return ResponseEntity.ok().body(new BaseResponse<>(CLUB_LIST_SEARCH_SUCCESS, response)); | ||
| } | ||
|
|
||
| @Operation(summary = "동아리 상세 조회", description = "특정 동아리의 상세 정보를 조회합니다.") | ||
| @SecurityRequirement(name = FCM_TOKEN_HEADER_KEY) | ||
| @SecurityRequirement(name = JWT_TOKEN_HEADER_KEY) | ||
| @GetMapping("/{id}") | ||
| public ResponseEntity<BaseResponse<ClubDetailResponse>> getClubDetail( | ||
| @PathVariable Long id, | ||
| @RequestHeader(value = FCM_TOKEN_HEADER_KEY, required = false) String userToken, | ||
| @RequestHeader(value = AuthorizationExtractor.AUTHORIZATION, required = false) String bearerToken | ||
| ) { | ||
| String email = resolveLoginEmail(bearerToken); | ||
|
|
||
| ClubDetailCommand command = new ClubDetailCommand(id, email); | ||
|
|
||
| ClubDetailResult result = clubQueryUseCase.getClubDetail(command); | ||
|
|
||
| ClubDetailResponse response = ClubDetailResponse.from(result); | ||
|
|
||
| return ResponseEntity.ok().body(new BaseResponse<>(CLUB_DETAIL_SEARCH_SUCCESS, response)); | ||
| } | ||
|
|
||
| private String resolveLoginEmail(String bearerToken) { | ||
| return Optional.ofNullable(bearerToken) | ||
| .map(token -> extractAuthorizationValue(token, AuthorizationType.BEARER)) | ||
| .map(this::validateJwtAndGetEmail) | ||
| .orElse(null); | ||
| } | ||
|
|
||
| private String validateJwtAndGetEmail(String jwtToken) { | ||
| if (!jwtTokenProvider.validateToken(jwtToken)) { | ||
| throw new InvalidStateException(ErrorCode.JWT_INVALID_TOKEN); | ||
| } | ||
| return jwtTokenProvider.getPrincipal(jwtToken); | ||
| } | ||
| } |
68 changes: 68 additions & 0 deletions
68
src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| package com.kustacks.kuring.club.adapter.in.web.dto; | ||
|
|
||
| import com.kustacks.kuring.club.application.port.in.dto.ClubDetailResult; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| public record ClubDetailResponse( | ||
| Long id, | ||
| String name, | ||
| String summary, | ||
| String category, | ||
| String division, | ||
| Long subscriberCount, | ||
| boolean isSubscribed, | ||
| String instagramUrl, | ||
| String youtubeUrl, | ||
| String etcUrl, | ||
| String description, | ||
| String qualifications, | ||
| String recruitmentStatus, | ||
| LocalDateTime recruitStartAt, | ||
| LocalDateTime recruitEndAt, | ||
| String applyUrl, | ||
| String posterImageUrl, | ||
| Location location | ||
| ) { | ||
|
|
||
| public static ClubDetailResponse from(ClubDetailResult result) { | ||
| return new ClubDetailResponse( | ||
| result.id(), | ||
| result.name(), | ||
| result.summary(), | ||
| result.category().getName(), | ||
| result.division().getName(), | ||
| result.subscriberCount(), | ||
| result.isSubscribed(), | ||
| result.instagramUrl(), | ||
| result.youtubeUrl(), | ||
| result.etcUrl(), | ||
| result.description(), | ||
| result.qualifications(), | ||
| result.recruitmentStatus().getValue(), | ||
| result.recruitStartAt(), | ||
| result.recruitEndAt(), | ||
| result.applyUrl(), | ||
| result.posterImageUrl(), | ||
| Location.from(result.location()) | ||
| ); | ||
| } | ||
|
|
||
| public record Location( | ||
| String building, | ||
| String room, | ||
| Double lon, | ||
| Double lat | ||
| ) { | ||
| public static Location from(ClubDetailResult.Location location) { | ||
| if (location == null) return null; | ||
|
|
||
| return new Location( | ||
| location.building(), | ||
| location.room(), | ||
| location.lon(), | ||
| location.lat() | ||
| ); | ||
| } | ||
| } | ||
| } | ||
|
rlagkswn00 marked this conversation as resolved.
|
||
13 changes: 13 additions & 0 deletions
13
src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionListResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.kustacks.kuring.club.adapter.in.web.dto; | ||
|
|
||
| import com.kustacks.kuring.club.application.port.in.dto.ClubDivisionResult; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record ClubDivisionListResponse( | ||
| List<ClubDivisionResult> divisions | ||
| ) { | ||
| public static ClubDivisionListResponse from(List<ClubDivisionResult> results) { | ||
| return new ClubDivisionListResponse(results); | ||
| } | ||
| } |
15 changes: 15 additions & 0 deletions
15
src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubListResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.kustacks.kuring.club.adapter.in.web.dto; | ||
|
|
||
| import com.kustacks.kuring.club.application.port.in.dto.ClubItemResult; | ||
| import com.kustacks.kuring.club.application.port.in.dto.ClubListResult; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record ClubListResponse( | ||
| List<ClubItemResult> clubs | ||
| ) { | ||
|
|
||
| public static ClubListResponse from(ClubListResult result) { | ||
| return new ClubListResponse(result.clubs()); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,20 @@ | ||
| package com.kustacks.kuring.club.adapter.out.persistence; | ||
|
|
||
| import com.kustacks.kuring.club.application.port.out.dto.ClubDetailReadModel; | ||
| import com.kustacks.kuring.club.application.port.out.dto.ClubReadModel; | ||
| import com.kustacks.kuring.club.domain.Club; | ||
| import com.kustacks.kuring.club.domain.ClubCategory; | ||
| import com.kustacks.kuring.club.domain.ClubDivision; | ||
|
|
||
| import java.time.LocalDateTime; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
|
|
||
| public interface ClubQueryRepository { | ||
|
|
||
| List<ClubReadModel> searchClubs(ClubCategory category, List<ClubDivision> divisions); | ||
|
|
||
| Optional<ClubDetailReadModel> findClubDetailById(Long id); | ||
|
|
||
| List<Club> findClubsBetweenDates(LocalDateTime start, LocalDateTime end); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.