-
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
base: develop
Are you sure you want to change the base?
Changes from all commits
e1d43dc
4c42e72
7090b05
d4d83cb
a2be583
9c44e7d
d818cac
840644f
26f1195
2b65645
8cec711
c67ff24
e4e7276
9d613f4
e0a0241
2c6280e
3bbe835
018f9b9
0be4f6b
18dbfc3
64f8efe
ab20703
cd4eb4b
34a716b
0403b77
59782a7
31bc32e
2df4cc3
6a7d5f8
28e7dd7
0d1c6c2
b550602
ba06ea0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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); | ||
| } | ||
| } |
| 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.
Show resolved
Hide resolved
|
||
| 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); | ||
| } | ||
| } |
| 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()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,11 @@ | |
| import com.kustacks.kuring.club.application.port.out.ClubQueryPort; | ||
| import com.kustacks.kuring.club.application.port.out.ClubSubscriptionCommandPort; | ||
| import com.kustacks.kuring.club.application.port.out.ClubSubscriptionQueryPort; | ||
| import com.kustacks.kuring.club.application.port.out.dto.ClubDetailDto; | ||
| 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 com.kustacks.kuring.club.domain.ClubSubscribe; | ||
| import com.kustacks.kuring.common.annotation.PersistenceAdapter; | ||
| import com.kustacks.kuring.user.domain.RootUser; | ||
|
|
@@ -12,7 +16,9 @@ | |
| import java.time.LocalDate; | ||
| import java.time.LocalDateTime; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Optional; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| @PersistenceAdapter | ||
| @RequiredArgsConstructor | ||
|
|
@@ -21,11 +27,58 @@ public class ClubPersistenceAdapter implements ClubQueryPort, ClubSubscriptionCo | |
| private final ClubRepository clubRepository; | ||
| private final ClubSubscribeRepository clubSubscribeRepository; | ||
|
|
||
| @Override | ||
| public List<ClubReadModel> searchClubs( | ||
| ClubCategory category, | ||
| List<ClubDivision> divisions | ||
| ) { | ||
| return clubRepository.searchClubs(category, divisions); | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<ClubDetailDto> findClubDetailById(Long id) { | ||
| return clubRepository.findClubDetailById(id); | ||
| } | ||
|
|
||
| @Override | ||
| public Optional<Club> findClubById(Long id) { | ||
| return clubRepository.findById(id); | ||
| } | ||
|
|
||
| @Override | ||
| public List<Long> findSubscribedClubIds( | ||
| List<Long> clubIds, | ||
| Long rootUserId | ||
| ) { | ||
| return clubSubscribeRepository | ||
| .findByClubIdInAndRootUserId(clubIds, rootUserId) | ||
| .stream() | ||
| .map(sub -> sub.getClub().getId()) | ||
| .toList(); | ||
| } | ||
|
Comment on lines
+48
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -name "ClubSubscribeRepository*" -type fRepository: ku-ring/ku-ring-backend-web Length of output: 164 🏁 Script executed: find . -name "ClubSubscribe.java" -type fRepository: ku-ring/ku-ring-backend-web Length of output: 137 🏁 Script executed: rg -n "findByClubIdInAndRootUserId" --type java -A 15Repository: ku-ring/ku-ring-backend-web Length of output: 3999 🏁 Script executed: cat -n ./src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.javaRepository: ku-ring/ku-ring-backend-web Length of output: 1280 🏁 Script executed: cat -n ./src/main/java/com/kustacks/kuring/club/domain/ClubSubscribe.javaRepository: ku-ring/ku-ring-backend-web Length of output: 1609
repository 메서드를 다음 중 하나로 개선하세요:
🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| @Override | ||
| public Long countSubscribers(Long clubId) { | ||
| return clubSubscribeRepository.countByClubId(clubId); | ||
| } | ||
|
|
||
| @Override | ||
| public Map<Long, Long> countSubscribersByClubIds(List<Long> clubIds) { | ||
|
|
||
| if (clubIds == null || clubIds.isEmpty()) { | ||
| return Map.of(); | ||
| } | ||
|
|
||
| List<Object[]> subscriptions = clubSubscribeRepository.countSubscribersByClubIds(clubIds); | ||
|
|
||
| return subscriptions.stream() | ||
| .collect(Collectors.toMap( | ||
| row -> (Long) row[0], | ||
| row -> (Long) row[1] | ||
| )); | ||
| } | ||
jiyun921 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @Override | ||
| public List<Club> findClubsBetweenDates(LocalDateTime start, LocalDateTime end) { | ||
| return clubRepository.findClubsBetweenDates(start, end); | ||
|
|
@@ -55,7 +108,7 @@ public void deleteSubscription(RootUser rootUser, Club club) { | |
| } | ||
|
|
||
| @Override | ||
| public long countSubscriptions(Long rootUserId) { | ||
| public Long countSubscriptions(Long rootUserId) { | ||
| return clubSubscribeRepository.countByRootUserId(rootUserId); | ||
| } | ||
jiyun921 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| 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.ClubDetailDto; | ||
| 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<ClubDetailDto> findClubDetailById(Long id); | ||
|
|
||
| List<Club> findClubsBetweenDates(LocalDateTime start, LocalDateTime end); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.