From e1d43dc434cb231bc87c1e00bb40ee3480200644 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sun, 15 Feb 2026 21:21:01 +0900 Subject: [PATCH 01/34] =?UTF-8?q?[feat]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=86=8C=EC=86=8D=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/adapter/in/web/ClubQueryApiV2.java | 38 +++++++++++++++++++ .../in/web/dto/ClubDivisionResponse.java | 15 ++++++++ .../application/port/in/ClubQueryUseCase.java | 9 +++++ .../port/in/dto/ClubDivisionResult.java | 15 ++++++++ .../application/service/ClubQueryService.java | 22 +++++++++++ .../common/dto/ResponseCodeAndMessages.java | 3 ++ 6 files changed, 102 insertions(+) create mode 100644 src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java create mode 100644 src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionResponse.java create mode 100644 src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java create mode 100644 src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDivisionResult.java create mode 100644 src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java new file mode 100644 index 000000000..6b6d9cc77 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java @@ -0,0 +1,38 @@ +package com.kustacks.kuring.club.adapter.in.web; + +import com.kustacks.kuring.club.adapter.in.web.dto.ClubDivisionResponse; +import com.kustacks.kuring.club.application.port.in.ClubQueryUseCase; +import com.kustacks.kuring.common.annotation.RestWebAdapter; +import com.kustacks.kuring.common.dto.BaseResponse; +import io.swagger.v3.oas.annotations.Operation; +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 java.util.List; + +import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.CLUB_DIVISION_SEARCH_SUCCESS; + +@Tag(name = "Club Query", description = "동아리 정보 조회") +@Validated +@RequiredArgsConstructor +@RestWebAdapter(path = "/api/v2/clubs") +public class ClubQueryApiV2 { + + private final ClubQueryUseCase clubQueryUseCase; + + @Operation(summary = "동아리 소속 목록 조회", description = "서버가 지원하는 동아리 소속 목록을 조회합니다") + @GetMapping("/divisions") + public ResponseEntity>> getSupportedClubDivisions() { + + List divisions = clubQueryUseCase.getClubDivisions() + .stream() + .map(ClubDivisionResponse::from) + .toList(); + + return ResponseEntity.ok().body(new BaseResponse<>(CLUB_DIVISION_SEARCH_SUCCESS, divisions)); + } + +} \ No newline at end of file diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionResponse.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionResponse.java new file mode 100644 index 000000000..fea4e2ca2 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionResponse.java @@ -0,0 +1,15 @@ +package com.kustacks.kuring.club.adapter.in.web.dto; + +import com.kustacks.kuring.club.application.port.in.dto.ClubDivisionResult; + +public record ClubDivisionResponse( + String code, + String koreanName +) { + public static ClubDivisionResponse from(ClubDivisionResult result) { + return new ClubDivisionResponse( + result.code(), + result.koreanName() + ); + } +} diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java new file mode 100644 index 000000000..6e23b81ca --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java @@ -0,0 +1,9 @@ +package com.kustacks.kuring.club.application.port.in; + +import com.kustacks.kuring.club.application.port.in.dto.ClubDivisionResult; + +import java.util.List; + +public interface ClubQueryUseCase { + List getClubDivisions(); +} diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDivisionResult.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDivisionResult.java new file mode 100644 index 000000000..3a701c47b --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDivisionResult.java @@ -0,0 +1,15 @@ +package com.kustacks.kuring.club.application.port.in.dto; + +import com.kustacks.kuring.club.domain.ClubDivision; + +public record ClubDivisionResult( + String code, + String koreanName +) { + public static ClubDivisionResult from(ClubDivision division) { + return new ClubDivisionResult( + division.getName(), + division.getKorName() + ); + } +} diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java new file mode 100644 index 000000000..e5ba0fcda --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -0,0 +1,22 @@ +package com.kustacks.kuring.club.application.service; + +import com.kustacks.kuring.club.application.port.in.ClubQueryUseCase; +import com.kustacks.kuring.club.application.port.in.dto.ClubDivisionResult; +import com.kustacks.kuring.club.domain.ClubDivision; +import com.kustacks.kuring.common.annotation.UseCase; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Arrays; +import java.util.List; + +@UseCase +@Transactional(readOnly = true) +public class ClubQueryService implements ClubQueryUseCase { + + @Override + public List getClubDivisions() { + return Arrays.stream(ClubDivision.values()) + .map(ClubDivisionResult::from) + .toList(); + } +} diff --git a/src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java b/src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java index 5c019a824..7db0ebd2d 100644 --- a/src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java +++ b/src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java @@ -62,6 +62,9 @@ public enum ResponseCodeAndMessages { ACADEMIC_EVENT_SEARCH_SUCCESS(HttpStatus.OK.value(), "학사일정 조회에 성공했습니다."), ACADEMIC_EVENT_NOTIFICATION_UPDATE_SUCCESS(HttpStatus.OK.value(), "학사일정 알림 설정이 변경되었습니다."), + /* Club */ + CLUB_DIVISION_SEARCH_SUCCESS(HttpStatus.OK.value(), "지원하는 동아리 소속 조회에 성공하였습니다"), + /** * ErrorCodes about auth */ From 4c42e72481081092c4a16597230e365fb0f147d7 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sun, 15 Feb 2026 21:51:08 +0900 Subject: [PATCH 02/34] =?UTF-8?q?[test]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=86=8C=EC=86=8D=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/adapter/in/web/ClubQueryApiV2.java | 2 +- .../service/ClubQueryServiceTest.java | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java index 6b6d9cc77..1047ae51c 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java @@ -15,7 +15,7 @@ import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.CLUB_DIVISION_SEARCH_SUCCESS; -@Tag(name = "Club Query", description = "동아리 정보 조회") +@Tag(name = "Club-Query", description = "동아리 정보 조회") @Validated @RequiredArgsConstructor @RestWebAdapter(path = "/api/v2/clubs") diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java new file mode 100644 index 000000000..b84f27e3c --- /dev/null +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java @@ -0,0 +1,35 @@ +package com.kustacks.kuring.club.application.service; + +import com.kustacks.kuring.club.application.port.in.dto.ClubDivisionResult; +import com.kustacks.kuring.club.domain.ClubDivision; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + + +@DisplayName("서비스 : ClubQueryService") +class ClubQueryServiceTest { + + private final ClubQueryService clubQueryService = new ClubQueryService(); + + @Test + @DisplayName("동아리 소속 목록을 정상적으로 조회한다") + void getClubDivisions_success() { + // when + List results = clubQueryService.getClubDivisions(); + + List expected = + Arrays.stream(ClubDivision.values()) + .map(d -> new ClubDivisionResult(d.getName(), d.getKorName())) + .toList(); + + // then + assertThat(results) + .containsExactlyInAnyOrderElementsOf(expected); + } + +} From 7090b054a06a2d9ecd2fa571cdbd9c0ec855256d Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Mon, 16 Feb 2026 20:00:46 +0900 Subject: [PATCH 03/34] =?UTF-8?q?[fix]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=86=8C=EC=86=8D=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EA=B5=AC=EC=A1=B0=EB=A5=BC=20=EB=AA=85?= =?UTF-8?q?=EC=84=B8=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kuring/club/adapter/in/web/ClubQueryApiV2.java | 7 +++++-- .../club/adapter/in/web/dto/ClubDivisionListResponse.java | 8 ++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionListResponse.java diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java index 1047ae51c..89384c846 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java @@ -1,5 +1,6 @@ package com.kustacks.kuring.club.adapter.in.web; +import com.kustacks.kuring.club.adapter.in.web.dto.ClubDivisionListResponse; import com.kustacks.kuring.club.adapter.in.web.dto.ClubDivisionResponse; import com.kustacks.kuring.club.application.port.in.ClubQueryUseCase; import com.kustacks.kuring.common.annotation.RestWebAdapter; @@ -25,14 +26,16 @@ public class ClubQueryApiV2 { @Operation(summary = "동아리 소속 목록 조회", description = "서버가 지원하는 동아리 소속 목록을 조회합니다") @GetMapping("/divisions") - public ResponseEntity>> getSupportedClubDivisions() { + public ResponseEntity> getSupportedClubDivisions() { List divisions = clubQueryUseCase.getClubDivisions() .stream() .map(ClubDivisionResponse::from) .toList(); - return ResponseEntity.ok().body(new BaseResponse<>(CLUB_DIVISION_SEARCH_SUCCESS, divisions)); + ClubDivisionListResponse response = new ClubDivisionListResponse(divisions); + + return ResponseEntity.ok().body(new BaseResponse<>(CLUB_DIVISION_SEARCH_SUCCESS, response)); } } \ No newline at end of file diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionListResponse.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionListResponse.java new file mode 100644 index 000000000..dc86c9f29 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionListResponse.java @@ -0,0 +1,8 @@ +package com.kustacks.kuring.club.adapter.in.web.dto; + +import java.util.List; + +public record ClubDivisionListResponse( + List divisions +) { +} From d4d83cbf3b2f8e5023c159cee31d707d52a85872 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Wed, 18 Feb 2026 09:11:39 +0900 Subject: [PATCH 04/34] =?UTF-8?q?[feat]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20api=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/adapter/in/web/ClubQueryApiV2.java | 27 +++++ .../adapter/in/web/dto/ClubItemResponse.java | 33 ++++++ .../adapter/in/web/dto/ClubListResponse.java | 26 +++++ .../persistence/ClubPersistenceAdapter.java | 31 ++++++ .../out/persistence/ClubQueryRepository.java | 12 ++ .../persistence/ClubQueryRepositoryImpl.java | 105 ++++++++++++++++++ .../out/persistence/ClubRepository.java | 7 ++ .../application/port/in/ClubQueryUseCase.java | 4 + .../port/in/dto/ClubItemResult.java | 17 +++ .../port/in/dto/ClubListCommand.java | 24 ++++ .../port/in/dto/ClubListResult.java | 11 ++ .../application/port/out/ClubQueryPort.java | 12 ++ .../port/out/dto/ClubReadModel.java | 40 +++++++ .../application/service/ClubQueryService.java | 58 ++++++++++ .../common/dto/ResponseCodeAndMessages.java | 1 + .../service/ClubQueryServiceTest.java | 12 +- 16 files changed, 419 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubItemResponse.java create mode 100644 src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubListResponse.java create mode 100644 src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java create mode 100644 src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java create mode 100644 src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java create mode 100644 src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubRepository.java create mode 100644 src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubItemResult.java create mode 100644 src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListCommand.java create mode 100644 src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListResult.java create mode 100644 src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java create mode 100644 src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubReadModel.java diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java index 89384c846..67fecdfac 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java @@ -2,19 +2,27 @@ import com.kustacks.kuring.club.adapter.in.web.dto.ClubDivisionListResponse; import com.kustacks.kuring.club.adapter.in.web.dto.ClubDivisionResponse; +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.ClubListCommand; +import com.kustacks.kuring.club.application.port.in.dto.ClubListResult; import com.kustacks.kuring.common.annotation.RestWebAdapter; +import com.kustacks.kuring.common.data.Cursor; import com.kustacks.kuring.common.dto.BaseResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; 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.RequestParam; import java.util.List; 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 @@ -38,4 +46,23 @@ public ResponseEntity> getSupportedClubDi return ResponseEntity.ok().body(new BaseResponse<>(CLUB_DIVISION_SEARCH_SUCCESS, response)); } + @Operation(summary = "동아리 목록 조회", description = "필터 조건에 맞는 동아리 목록을 커서 페이징으로 조회합니다") + @GetMapping + public ResponseEntity> getClubs( + @RequestParam(required = false) String category, + @RequestParam(required = false) String division, + @RequestParam(required = false) String cursor, + @RequestParam(defaultValue = "20") @Min(1) @Max(30) int size, + @RequestParam(defaultValue = "name") String sortBy + ) { + ClubListCommand command = new ClubListCommand(category, division, Cursor.from(cursor), size, sortBy); + + ClubListResult result = clubQueryUseCase.getClubs(command); + + ClubListResponse response = ClubListResponse.from(result); + + return ResponseEntity.ok().body(new BaseResponse<>(CLUB_LIST_SEARCH_SUCCESS, response)); + } + + } \ No newline at end of file diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubItemResponse.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubItemResponse.java new file mode 100644 index 000000000..0a4a907d0 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubItemResponse.java @@ -0,0 +1,33 @@ +package com.kustacks.kuring.club.adapter.in.web.dto; + +import com.kustacks.kuring.club.application.port.in.dto.ClubItemResult; + +import java.time.LocalDateTime; + +public record ClubItemResponse( + Long id, + String name, + String summary, + String iconImageUrl, + String category, + String division, + boolean isSubscribed, + int subscriberCount, + LocalDateTime recruitStartDate, + LocalDateTime recruitEndDate +) { + public static ClubItemResponse from(ClubItemResult result) { + return new ClubItemResponse( + result.id(), + result.name(), + result.summary(), + result.iconImageUrl(), + result.category(), + result.division(), + result.isSubscribed(), + result.subscriberCount(), + result.recruitStartDate(), + result.recruitEndDate() + ); + } +} diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubListResponse.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubListResponse.java new file mode 100644 index 000000000..c754cdb98 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubListResponse.java @@ -0,0 +1,26 @@ +package com.kustacks.kuring.club.adapter.in.web.dto; + +import com.kustacks.kuring.club.application.port.in.dto.ClubListResult; + +import java.util.List; + +public record ClubListResponse( + List clubs, + Long cursor, + boolean hasNext, + int totalCount +) { + + public static ClubListResponse from(ClubListResult result) { + List clubs = result.clubs().stream() + .map(ClubItemResponse::from) + .toList(); + + return new ClubListResponse( + clubs, + result.cursor(), + result.hasNext(), + result.totalCount() + ); + } +} diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java new file mode 100644 index 000000000..719169053 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java @@ -0,0 +1,31 @@ +package com.kustacks.kuring.club.adapter.out.persistence; + +import com.kustacks.kuring.club.application.port.out.ClubQueryPort; +import com.kustacks.kuring.club.application.port.out.dto.ClubReadModel; +import com.kustacks.kuring.common.annotation.PersistenceAdapter; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +@PersistenceAdapter +@RequiredArgsConstructor +public class ClubPersistenceAdapter implements ClubQueryPort { + + private final ClubRepository clubRepository; + + @Override + public List searchClubs( + String category, + List divisions, + String cursor, + int size, + String sortBy + ) { + return clubRepository.searchClubs(category, divisions, cursor, size, sortBy); + } + + @Override + public int countClubs(String category, List divisions) { + return clubRepository.countClubs(category, divisions); + } +} diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java new file mode 100644 index 000000000..cb831304d --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java @@ -0,0 +1,12 @@ +package com.kustacks.kuring.club.adapter.out.persistence; + +import com.kustacks.kuring.club.application.port.out.dto.ClubReadModel; + +import java.util.List; + +public interface ClubQueryRepository { + + List searchClubs(String category, List divisions, String cursor, int size, String sortBy); + + int countClubs(String category, List divisions); +} diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java new file mode 100644 index 000000000..5d213c3da --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java @@ -0,0 +1,105 @@ +package com.kustacks.kuring.club.adapter.out.persistence; + +import com.kustacks.kuring.club.application.port.out.dto.ClubReadModel; +import com.kustacks.kuring.club.application.port.out.dto.QClubReadModel; +import com.kustacks.kuring.club.domain.ClubCategory; +import com.kustacks.kuring.club.domain.ClubDivision; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static com.kustacks.kuring.club.domain.QClub.club; + +@RequiredArgsConstructor +class ClubQueryRepositoryImpl implements ClubQueryRepository { + + private final JPAQueryFactory queryFactory; + + @Override + @Transactional(readOnly = true) + public List searchClubs( + String category, + List divisions, + String cursor, + int size, + String sortBy + ) { + + return queryFactory + .select(new QClubReadModel( + club.id, + club.name, + club.summary, + club.posterImagePath, + club.category.stringValue(), + club.division.stringValue(), + club.recruitStartAt, + club.recruitEndAt + )) + .from(club) + .where( + categoryEq(category), + divisionIn(divisions), + idAfterCursor(cursor) + ) + .orderBy(getOrderSpecifiers(sortBy)) + .limit(size) + .fetch(); + } + + @Override + @Transactional(readOnly = true) + public int countClubs(String category, List divisions) { + + Long count = queryFactory + .select(club.count()) + .from(club) + .where( + categoryEq(category), + divisionIn(divisions) + ) + .fetchOne(); + + return count == null ? 0 : count.intValue(); + } + + private BooleanExpression categoryEq(String category) { + if (category == null) return null; + return club.category.eq(ClubCategory.fromName(category)); + } + + private BooleanExpression divisionIn(List divisions) { + if (divisions == null || divisions.isEmpty()) return null; + + return club.division.in( + divisions.stream() + .map(ClubDivision::fromName) + .toList() + ); + } + + private BooleanExpression idAfterCursor(String cursor) { + if (cursor == null || cursor.equals("0")) return null; + return club.id.gt(Long.parseLong(cursor)); + } + + private OrderSpecifier[] getOrderSpecifiers(String sortBy) { + + if ("recruitEndDate".equals(sortBy)) { + return new OrderSpecifier[]{ + club.recruitEndAt.asc(), + club.id.asc() + }; + } + + return new OrderSpecifier[]{ + club.name.asc(), + club.id.asc() + }; + } + +} diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubRepository.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubRepository.java new file mode 100644 index 000000000..6c1aad753 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubRepository.java @@ -0,0 +1,7 @@ +package com.kustacks.kuring.club.adapter.out.persistence; + +import com.kustacks.kuring.club.domain.Club; +import org.springframework.data.jpa.repository.JpaRepository; + +interface ClubRepository extends JpaRepository, ClubQueryRepository { +} diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java index 6e23b81ca..09eb91d54 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java @@ -1,9 +1,13 @@ package com.kustacks.kuring.club.application.port.in; 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 java.util.List; public interface ClubQueryUseCase { List getClubDivisions(); + + ClubListResult getClubs(ClubListCommand command); } diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubItemResult.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubItemResult.java new file mode 100644 index 000000000..85132225e --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubItemResult.java @@ -0,0 +1,17 @@ +package com.kustacks.kuring.club.application.port.in.dto; + +import java.time.LocalDateTime; + +public record ClubItemResult( + Long id, + String name, + String summary, + String iconImageUrl, + String category, + String division, + boolean isSubscribed, + int subscriberCount, + LocalDateTime recruitStartDate, + LocalDateTime recruitEndDate +) { +} diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListCommand.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListCommand.java new file mode 100644 index 000000000..a1422a099 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListCommand.java @@ -0,0 +1,24 @@ +package com.kustacks.kuring.club.application.port.in.dto; + +import com.kustacks.kuring.common.data.Cursor; + +import java.util.Arrays; +import java.util.List; + +public record ClubListCommand( + String category, + String division, + Cursor cursor, + int size, + String sortBy +) { + public List divisionList() { + if (division == null || division.isBlank()) { + return null; + } + + return Arrays.stream(division.split(",")) + .map(String::trim) + .toList(); + } +} diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListResult.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListResult.java new file mode 100644 index 000000000..be7245f97 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListResult.java @@ -0,0 +1,11 @@ +package com.kustacks.kuring.club.application.port.in.dto; + +import java.util.List; + +public record ClubListResult( + List clubs, + Long cursor, + boolean hasNext, + int totalCount +) { +} diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java new file mode 100644 index 000000000..25f1b9bc9 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java @@ -0,0 +1,12 @@ +package com.kustacks.kuring.club.application.port.out; + +import com.kustacks.kuring.club.application.port.out.dto.ClubReadModel; + +import java.util.List; + +public interface ClubQueryPort { + + List searchClubs(String category, List divisions, String cursor, int size, String sortBy); + + int countClubs(String category, List divisions); +} diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubReadModel.java b/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubReadModel.java new file mode 100644 index 000000000..45f7ca5f4 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubReadModel.java @@ -0,0 +1,40 @@ +package com.kustacks.kuring.club.application.port.out.dto; + +import com.querydsl.core.annotations.QueryProjection; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +public class ClubReadModel { + + private final Long id; + private final String name; + private final String summary; + private final String iconImageUrl; + private final String category; + private final String division; + private final LocalDateTime recruitStartDate; + private final LocalDateTime recruitEndDate; + + @QueryProjection + public ClubReadModel( + Long id, + String name, + String summary, + String iconImageUrl, + String category, + String division, + LocalDateTime recruitStartDate, + LocalDateTime recruitEndDate + ) { + this.id = id; + this.name = name; + this.summary = summary; + this.iconImageUrl = iconImageUrl; + this.category = category; + this.division = division; + this.recruitStartDate = recruitStartDate; + this.recruitEndDate = recruitEndDate; + } +} diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index e5ba0fcda..ccca4150f 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -2,8 +2,15 @@ import com.kustacks.kuring.club.application.port.in.ClubQueryUseCase; import com.kustacks.kuring.club.application.port.in.dto.ClubDivisionResult; +import com.kustacks.kuring.club.application.port.in.dto.ClubItemResult; +import com.kustacks.kuring.club.application.port.in.dto.ClubListCommand; +import com.kustacks.kuring.club.application.port.in.dto.ClubListResult; +import com.kustacks.kuring.club.application.port.out.ClubQueryPort; +import com.kustacks.kuring.club.application.port.out.dto.ClubReadModel; import com.kustacks.kuring.club.domain.ClubDivision; import com.kustacks.kuring.common.annotation.UseCase; +import com.kustacks.kuring.common.data.CursorBasedList; +import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; @@ -11,12 +18,63 @@ @UseCase @Transactional(readOnly = true) +@RequiredArgsConstructor public class ClubQueryService implements ClubQueryUseCase { + private final ClubQueryPort clubQueryPort; + @Override public List getClubDivisions() { return Arrays.stream(ClubDivision.values()) .map(ClubDivisionResult::from) .toList(); } + + @Override + public ClubListResult getClubs(ClubListCommand command) { + + int limit = Math.min(command.size(), 30); + + CursorBasedList cursorBasedList = CursorBasedList.of( + limit, + club -> club.getId().toString(), + searchSize -> clubQueryPort.searchClubs( + command.category(), + command.divisionList(), + command.cursor().getStringCursor(), + searchSize, + command.sortBy() + ) + ); + + List items = + cursorBasedList.getContents() + .stream() + .map(r -> new ClubItemResult( + r.getId(), + r.getName(), + r.getSummary(), + r.getIconImageUrl(), + r.getCategory().toLowerCase(), + r.getDivision().toLowerCase(), + false, // 추후 구독 기능 + 0, // 추후 구독 기능 + r.getRecruitStartDate(), + r.getRecruitEndDate() + )) + .toList(); + + int totalCount = clubQueryPort.countClubs(command.category(), command.divisionList()); + + Long nextCursor = cursorBasedList.getEndCursor() == null + ? null + : Long.valueOf(cursorBasedList.getEndCursor()); + + return new ClubListResult( + items, + nextCursor, + cursorBasedList.hasNext(), + totalCount + ); + } } diff --git a/src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java b/src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java index 7db0ebd2d..25f89a645 100644 --- a/src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java +++ b/src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java @@ -64,6 +64,7 @@ public enum ResponseCodeAndMessages { /* Club */ CLUB_DIVISION_SEARCH_SUCCESS(HttpStatus.OK.value(), "지원하는 동아리 소속 조회에 성공하였습니다"), + CLUB_LIST_SEARCH_SUCCESS(HttpStatus.OK.value(), "동아리 목록 조회에 성공하였습니다"), /** * ErrorCodes about auth diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java index b84f27e3c..6a1f7d388 100644 --- a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java @@ -1,9 +1,14 @@ package com.kustacks.kuring.club.application.service; import com.kustacks.kuring.club.application.port.in.dto.ClubDivisionResult; +import com.kustacks.kuring.club.application.port.out.ClubQueryPort; import com.kustacks.kuring.club.domain.ClubDivision; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; import java.util.Arrays; import java.util.List; @@ -12,9 +17,14 @@ @DisplayName("서비스 : ClubQueryService") +@ExtendWith(MockitoExtension.class) class ClubQueryServiceTest { - private final ClubQueryService clubQueryService = new ClubQueryService(); + @Mock + private ClubQueryPort clubQueryPort; + + @InjectMocks + private ClubQueryService clubQueryService; @Test @DisplayName("동아리 소속 목록을 정상적으로 조회한다") From a2be583498bf89bedfae6083a6ebc41a0d403854 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Thu, 19 Feb 2026 13:48:08 +0900 Subject: [PATCH 05/34] =?UTF-8?q?[fix]:=20cursor=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/adapter/in/web/dto/ClubListResponse.java | 2 +- .../out/persistence/ClubPersistenceAdapter.java | 11 +++++++++-- .../club/application/port/in/dto/ClubListResult.java | 2 +- .../club/application/port/out/ClubQueryPort.java | 3 ++- .../club/application/service/ClubQueryService.java | 6 ++---- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubListResponse.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubListResponse.java index c754cdb98..6aafa950e 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubListResponse.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubListResponse.java @@ -6,7 +6,7 @@ public record ClubListResponse( List clubs, - Long cursor, + String cursor, boolean hasNext, int totalCount ) { diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java index 719169053..9a1c0c465 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java @@ -3,6 +3,7 @@ import com.kustacks.kuring.club.application.port.out.ClubQueryPort; import com.kustacks.kuring.club.application.port.out.dto.ClubReadModel; import com.kustacks.kuring.common.annotation.PersistenceAdapter; +import com.kustacks.kuring.common.data.Cursor; import lombok.RequiredArgsConstructor; import java.util.List; @@ -17,11 +18,17 @@ public class ClubPersistenceAdapter implements ClubQueryPort { public List searchClubs( String category, List divisions, - String cursor, + Cursor cursor, int size, String sortBy ) { - return clubRepository.searchClubs(category, divisions, cursor, size, sortBy); + return clubRepository.searchClubs( + category, + divisions, + cursor == null ? null : cursor.getStringCursor(), + size, + sortBy + ); } @Override diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListResult.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListResult.java index be7245f97..a10860e82 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListResult.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListResult.java @@ -4,7 +4,7 @@ public record ClubListResult( List clubs, - Long cursor, + String cursor, boolean hasNext, int totalCount ) { diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java index 25f1b9bc9..b7b9c4f6a 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java @@ -1,12 +1,13 @@ package com.kustacks.kuring.club.application.port.out; import com.kustacks.kuring.club.application.port.out.dto.ClubReadModel; +import com.kustacks.kuring.common.data.Cursor; import java.util.List; public interface ClubQueryPort { - List searchClubs(String category, List divisions, String cursor, int size, String sortBy); + List searchClubs(String category, List divisions, Cursor cursor, int size, String sortBy); int countClubs(String category, List divisions); } diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index ccca4150f..9879a3278 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -41,7 +41,7 @@ public ClubListResult getClubs(ClubListCommand command) { searchSize -> clubQueryPort.searchClubs( command.category(), command.divisionList(), - command.cursor().getStringCursor(), + command.cursor(), searchSize, command.sortBy() ) @@ -66,9 +66,7 @@ public ClubListResult getClubs(ClubListCommand command) { int totalCount = clubQueryPort.countClubs(command.category(), command.divisionList()); - Long nextCursor = cursorBasedList.getEndCursor() == null - ? null - : Long.valueOf(cursorBasedList.getEndCursor()); + String nextCursor = cursorBasedList.getEndCursor(); return new ClubListResult( items, From 9c44e7dbcd799ce34a35e532be9f1b4d95398faa Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Thu, 19 Feb 2026 13:48:42 +0900 Subject: [PATCH 06/34] =?UTF-8?q?[test]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ClubQueryServiceTest.java | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java index 6a1f7d388..c48c98b25 100644 --- a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java @@ -1,8 +1,12 @@ package com.kustacks.kuring.club.application.service; 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.club.application.port.out.ClubQueryPort; +import com.kustacks.kuring.club.application.port.out.dto.ClubReadModel; import com.kustacks.kuring.club.domain.ClubDivision; +import com.kustacks.kuring.common.data.Cursor; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -10,11 +14,13 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; - +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @DisplayName("서비스 : ClubQueryService") @ExtendWith(MockitoExtension.class) @@ -26,6 +32,40 @@ class ClubQueryServiceTest { @InjectMocks private ClubQueryService clubQueryService; + private List mockReadModels = + List.of( + new ClubReadModel( + 1L, + "쿠링", + "건국대 공지사항 앱 만드는 개발 동아리", + "icon-url-1", + "ACADEMIC", + "CENTRAL", + LocalDateTime.of(2025, 3, 1, 0, 0), + LocalDateTime.of(2025, 3, 31, 23, 59) + ), + new ClubReadModel( + 2L, + "쿠잇", + "건국대 개발 동아리", + "icon-url-2", + "ACADEMIC", + "ENGINEERING", + LocalDateTime.of(2025, 3, 1, 0, 0), + LocalDateTime.of(2025, 3, 31, 23, 59) + ), + new ClubReadModel( + 3L, + "DIUS", + "건국대 공과대학 댄스 동아리", + "icon-url-3", + "CULTURE_ART", + "ENGINEERING", + LocalDateTime.of(2025, 2, 20, 0, 0), + LocalDateTime.of(2025, 3, 31, 23, 59) + ) + ); + @Test @DisplayName("동아리 소속 목록을 정상적으로 조회한다") void getClubDivisions_success() { @@ -42,4 +82,38 @@ void getClubDivisions_success() { .containsExactlyInAnyOrderElementsOf(expected); } + @Test + @DisplayName("동아리 목록을 정상적으로 조회한다") + void getClubs_success() { + + // given + String category = "academic"; + String divisions = "central,engineering"; + Cursor cursor = Cursor.from(null); + int size = 10; + String sortBy = "name"; + + ClubListCommand command = new ClubListCommand(category, divisions, cursor, size, sortBy); + + List divisionList = List.of("central", "engineering"); + + when(clubQueryPort.searchClubs(category, divisionList, cursor, size + 1, sortBy)) + .thenReturn(mockReadModels); + + when(clubQueryPort.countClubs(category, divisionList)) + .thenReturn(2); + + // when + ClubListResult result = clubQueryService.getClubs(command); + + // then + assertThat(result.totalCount()).isEqualTo(2); + assertThat(result.clubs()).hasSize(3); + assertThat(result.hasNext()).isFalse(); + assertThat(result.cursor()).isNull(); + + verify(clubQueryPort).searchClubs(category, divisionList, cursor, size + 1, sortBy); + verify(clubQueryPort).countClubs(category, divisionList); + } + } From d818cac1402f218f619661a3174b217d8533ca4c Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Fri, 20 Feb 2026 20:46:13 +0900 Subject: [PATCH 07/34] =?UTF-8?q?[feat]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20api=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/adapter/in/web/ClubQueryApiV2.java | 43 ++++++ .../in/web/dto/ClubDetailResponse.java | 68 ++++++++++ .../persistence/ClubPersistenceAdapter.java | 18 +++ .../out/persistence/ClubQueryRepository.java | 4 + .../persistence/ClubQueryRepositoryImpl.java | 122 +++++++++++++++++- .../persistence/ClubSubscribeRepository.java | 11 ++ .../application/port/in/ClubQueryUseCase.java | 4 + .../port/in/dto/ClubDetailResult.java | 36 ++++++ .../application/port/out/ClubQueryPort.java | 8 ++ .../port/out/dto/ClubDetailDto.java | 84 ++++++++++++ .../port/out/dto/ClubReadModel.java | 10 +- .../application/service/ClubQueryService.java | 50 ++++++- .../club/domain/ClubRecruitmentStatus.java | 18 +++ .../common/dto/ResponseCodeAndMessages.java | 1 + .../common/exception/code/ErrorCode.java | 5 +- 15 files changed, 471 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java create mode 100644 src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java create mode 100644 src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java create mode 100644 src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubDetailDto.java create mode 100644 src/main/java/com/kustacks/kuring/club/domain/ClubRecruitmentStatus.java diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java index 67fecdfac..82ed6f74d 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java @@ -1,15 +1,23 @@ 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.ClubDivisionResponse; 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.ClubDetailResult; 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.data.Cursor; 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 jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; @@ -17,10 +25,14 @@ 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 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; @@ -30,6 +42,10 @@ @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 = "서버가 지원하는 동아리 소속 목록을 조회합니다") @@ -64,5 +80,32 @@ public ResponseEntity> getClubs( 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> getClubDetail( + @PathVariable Long id, + @RequestHeader(value = FCM_TOKEN_HEADER_KEY, required = false) String userToken, + @RequestHeader(value = AuthorizationExtractor.AUTHORIZATION, required = false) String bearerToken + ) { + Long loginUserId = null; + + if (bearerToken != null) { + String jwt = extractAuthorizationValue(bearerToken, AuthorizationType.BEARER); + + if (!jwtTokenProvider.validateToken(jwt)) { + throw new InvalidStateException(ErrorCode.JWT_INVALID_TOKEN); + } + + loginUserId = Long.parseLong(jwtTokenProvider.getPrincipal(jwt)); + } + + ClubDetailResult result = clubQueryUseCase.getClubDetail(id, userToken, loginUserId); + + ClubDetailResponse response = ClubDetailResponse.from(result); + + return ResponseEntity.ok().body(new BaseResponse<>(CLUB_DETAIL_SEARCH_SUCCESS, response)); + } } \ No newline at end of file diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java new file mode 100644 index 000000000..a9c94980d --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java @@ -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, + int 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) { + Location location = null; + if (result.location() != null) { + location = new Location( + result.location().building(), + result.location().room(), + result.location().lon(), + result.location().lat() + ); + } + + 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 + ); + } + + public record Location( + String building, + String room, + Double lon, + Double lat + ) { + } +} diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java index 9a1c0c465..f6946bdd2 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java @@ -1,18 +1,21 @@ package com.kustacks.kuring.club.adapter.out.persistence; import com.kustacks.kuring.club.application.port.out.ClubQueryPort; +import com.kustacks.kuring.club.application.port.out.dto.ClubDetailDto; import com.kustacks.kuring.club.application.port.out.dto.ClubReadModel; import com.kustacks.kuring.common.annotation.PersistenceAdapter; import com.kustacks.kuring.common.data.Cursor; import lombok.RequiredArgsConstructor; import java.util.List; +import java.util.Optional; @PersistenceAdapter @RequiredArgsConstructor public class ClubPersistenceAdapter implements ClubQueryPort { private final ClubRepository clubRepository; + private final ClubSubscribeRepository clubSubscribeRepository; @Override public List searchClubs( @@ -35,4 +38,19 @@ public List searchClubs( public int countClubs(String category, List divisions) { return clubRepository.countClubs(category, divisions); } + + @Override + public Optional findClubDetailById(Long id) { + return clubRepository.findClubDetailById(id); + } + + @Override + public int countSubscribers(Long clubId) { + return clubSubscribeRepository.countByClubId(clubId); + } + + @Override + public boolean existsSubscription(Long clubId, Long loginUserId) { + return clubSubscribeRepository.existsByClubIdAndUser_LoginUserId(clubId, loginUserId); + } } diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java index cb831304d..9b5dde660 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java @@ -1,12 +1,16 @@ 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 java.util.List; +import java.util.Optional; public interface ClubQueryRepository { List searchClubs(String category, List divisions, String cursor, int size, String sortBy); int countClubs(String category, List divisions); + + Optional findClubDetailById(Long id); } diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java index 5d213c3da..b6efe422a 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java @@ -1,18 +1,25 @@ 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.application.port.out.dto.QClubReadModel; import com.kustacks.kuring.club.domain.ClubCategory; import com.kustacks.kuring.club.domain.ClubDivision; +import com.kustacks.kuring.club.domain.ClubRecruitmentStatus; +import com.kustacks.kuring.club.domain.ClubSnsType; +import com.querydsl.core.Tuple; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import static com.kustacks.kuring.club.domain.QClub.club; +import static com.kustacks.kuring.club.domain.QClubSns.clubSns; @RequiredArgsConstructor class ClubQueryRepositoryImpl implements ClubQueryRepository { @@ -35,8 +42,8 @@ public List searchClubs( club.name, club.summary, club.posterImagePath, - club.category.stringValue(), - club.division.stringValue(), + club.category, + club.division, club.recruitStartAt, club.recruitEndAt )) @@ -67,6 +74,94 @@ public int countClubs(String category, List divisions) { return count == null ? 0 : count.intValue(); } + @Override + @Transactional(readOnly = true) + public Optional findClubDetailById(Long id) { + + LocalDateTime now = LocalDateTime.now(); + + List tuples = queryFactory + .select( + club.id, + club.name, + club.summary, + club.category, + club.division, + club.description, + club.qualifications, + club.recruitStartAt, + club.recruitEndAt, + club.isAlways, + club.applyUrl, + club.posterImagePath, + club.building, + club.room, + club.lon, + club.lat, + clubSns.type, + clubSns.url + ) + .from(club) + .leftJoin(club.homepageUrls, clubSns) + .where(club.id.eq(id)) + .fetch(); + + if (tuples.isEmpty()) { + return Optional.empty(); + } + + Tuple first = tuples.get(0); + + String instagram = null; + String youtube = null; + String etc = null; + + for (Tuple t : tuples) { + ClubSnsType type = t.get(clubSns.type); + String url = t.get(clubSns.url); + + if (type == null) continue; + + switch (type) { + case INSTAGRAM -> instagram = url; + case YOUTUBE -> youtube = url; + case ETC -> etc = url; + } + } + + ClubRecruitmentStatus recruitmentStatus = calculateRecruitmentStatus( + first.get(club.recruitStartAt), + first.get(club.recruitEndAt), + first.get(club.isAlways), + now + ); + + return Optional.of( + new ClubDetailDto( + first.get(club.id), + first.get(club.name), + first.get(club.summary), + first.get(club.category), + first.get(club.division), + instagram, + youtube, + etc, + first.get(club.description), + first.get(club.qualifications), + recruitmentStatus, + first.get(club.recruitStartAt), + first.get(club.recruitEndAt), + first.get(club.applyUrl), + first.get(club.posterImagePath), + first.get(club.building), + first.get(club.room), + first.get(club.lon), + first.get(club.lat) + ) + ); + } + + private BooleanExpression categoryEq(String category) { if (category == null) return null; return club.category.eq(ClubCategory.fromName(category)); @@ -102,4 +197,27 @@ private OrderSpecifier[] getOrderSpecifiers(String sortBy) { }; } + private ClubRecruitmentStatus calculateRecruitmentStatus( + LocalDateTime start, + LocalDateTime end, + Boolean isAlways, + LocalDateTime now + ) { + + if (Boolean.TRUE.equals(isAlways)) { + return ClubRecruitmentStatus.ALWAYS; + } + + if (start != null && now.isBefore(start)) { + return ClubRecruitmentStatus.BEFORE; + } + + if (end != null && now.isAfter(end)) { + return ClubRecruitmentStatus.CLOSED; + } + + return ClubRecruitmentStatus.RECRUITING; + } + + } diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java new file mode 100644 index 000000000..28bc9d91e --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java @@ -0,0 +1,11 @@ +package com.kustacks.kuring.club.adapter.out.persistence; + +import com.kustacks.kuring.club.domain.ClubSubscribe; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ClubSubscribeRepository extends JpaRepository { + + int countByClubId(Long clubId); + + boolean existsByClubIdAndUser_LoginUserId(Long clubId, Long loginUserId); +} \ No newline at end of file diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java index 09eb91d54..34ea4fc8b 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java @@ -1,5 +1,6 @@ package com.kustacks.kuring.club.application.port.in; +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; @@ -10,4 +11,7 @@ public interface ClubQueryUseCase { List getClubDivisions(); ClubListResult getClubs(ClubListCommand command); + + ClubDetailResult getClubDetail(Long id, String userToken, Long loginUserId); + } diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java new file mode 100644 index 000000000..5ecba2b5a --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java @@ -0,0 +1,36 @@ +package com.kustacks.kuring.club.application.port.in.dto; + +import com.kustacks.kuring.club.domain.ClubCategory; +import com.kustacks.kuring.club.domain.ClubDivision; +import com.kustacks.kuring.club.domain.ClubRecruitmentStatus; + +import java.time.LocalDateTime; + +public record ClubDetailResult( + Long id, + String name, + String summary, + ClubCategory category, + ClubDivision division, + int subscriberCount, + boolean isSubscribed, + String instagramUrl, + String youtubeUrl, + String etcUrl, + String description, + String qualifications, + ClubRecruitmentStatus recruitmentStatus, + LocalDateTime recruitStartAt, + LocalDateTime recruitEndAt, + String applyUrl, + String posterImageUrl, + Location location +) { + public record Location( + String building, + String room, + Double lon, + Double lat + ) { + } +} diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java index b7b9c4f6a..4b22663eb 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java @@ -1,13 +1,21 @@ package com.kustacks.kuring.club.application.port.out; +import com.kustacks.kuring.club.application.port.out.dto.ClubDetailDto; import com.kustacks.kuring.club.application.port.out.dto.ClubReadModel; import com.kustacks.kuring.common.data.Cursor; import java.util.List; +import java.util.Optional; public interface ClubQueryPort { List searchClubs(String category, List divisions, Cursor cursor, int size, String sortBy); int countClubs(String category, List divisions); + + Optional findClubDetailById(Long id); + + int countSubscribers(Long clubId); + + boolean existsSubscription(Long clubId, Long loginUserId); } diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubDetailDto.java b/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubDetailDto.java new file mode 100644 index 000000000..83f9c589f --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubDetailDto.java @@ -0,0 +1,84 @@ +package com.kustacks.kuring.club.application.port.out.dto; + +import com.kustacks.kuring.club.domain.ClubCategory; +import com.kustacks.kuring.club.domain.ClubDivision; +import com.kustacks.kuring.club.domain.ClubRecruitmentStatus; +import com.querydsl.core.annotations.QueryProjection; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ClubDetailDto { + + private Long id; + private String name; + private String summary; + private ClubCategory category; + private ClubDivision division; + + private String instagramUrl; + private String youtubeUrl; + private String etcUrl; + + private String description; + private String qualifications; + private ClubRecruitmentStatus recruitmentStatus; + + private LocalDateTime recruitStartAt; + private LocalDateTime recruitEndAt; + + private String applyUrl; + private String posterImagePath; + + private String building; + private String room; + private Double lon; + private Double lat; + + @QueryProjection + public ClubDetailDto( + Long id, + String name, + String summary, + ClubCategory category, + ClubDivision division, + String instagramUrl, + String youtubeUrl, + String etcUrl, + String description, + String qualifications, + ClubRecruitmentStatus recruitmentStatus, + LocalDateTime recruitStartAt, + LocalDateTime recruitEndAt, + String applyUrl, + String posterImagePath, + String building, + String room, + Double lon, + Double lat + ) { + this.id = id; + this.name = name; + this.summary = summary; + this.category = category; + this.division = division; + this.instagramUrl = instagramUrl; + this.youtubeUrl = youtubeUrl; + this.etcUrl = etcUrl; + this.description = description; + this.qualifications = qualifications; + this.recruitmentStatus = recruitmentStatus; + this.recruitStartAt = recruitStartAt; + this.recruitEndAt = recruitEndAt; + this.applyUrl = applyUrl; + this.posterImagePath = posterImagePath; + this.building = building; + this.room = room; + this.lon = lon; + this.lat = lat; + } +} \ No newline at end of file diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubReadModel.java b/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubReadModel.java index 45f7ca5f4..e55a3a54c 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubReadModel.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubReadModel.java @@ -1,5 +1,7 @@ package com.kustacks.kuring.club.application.port.out.dto; +import com.kustacks.kuring.club.domain.ClubCategory; +import com.kustacks.kuring.club.domain.ClubDivision; import com.querydsl.core.annotations.QueryProjection; import lombok.Getter; @@ -12,8 +14,8 @@ public class ClubReadModel { private final String name; private final String summary; private final String iconImageUrl; - private final String category; - private final String division; + private final ClubCategory category; + private final ClubDivision division; private final LocalDateTime recruitStartDate; private final LocalDateTime recruitEndDate; @@ -23,8 +25,8 @@ public ClubReadModel( String name, String summary, String iconImageUrl, - String category, - String division, + ClubCategory category, + ClubDivision division, LocalDateTime recruitStartDate, LocalDateTime recruitEndDate ) { diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index 9879a3278..fe1c8b94b 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -1,21 +1,26 @@ package com.kustacks.kuring.club.application.service; import com.kustacks.kuring.club.application.port.in.ClubQueryUseCase; +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.ClubItemResult; import com.kustacks.kuring.club.application.port.in.dto.ClubListCommand; import com.kustacks.kuring.club.application.port.in.dto.ClubListResult; import com.kustacks.kuring.club.application.port.out.ClubQueryPort; +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.ClubDivision; import com.kustacks.kuring.common.annotation.UseCase; import com.kustacks.kuring.common.data.CursorBasedList; +import com.kustacks.kuring.common.exception.NotFoundException; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; import java.util.List; +import static com.kustacks.kuring.common.exception.code.ErrorCode.CLUB_NOT_FOUND; + @UseCase @Transactional(readOnly = true) @RequiredArgsConstructor @@ -55,8 +60,8 @@ public ClubListResult getClubs(ClubListCommand command) { r.getName(), r.getSummary(), r.getIconImageUrl(), - r.getCategory().toLowerCase(), - r.getDivision().toLowerCase(), + r.getCategory().getName(), + r.getDivision().getName(), false, // 추후 구독 기능 0, // 추후 구독 기능 r.getRecruitStartDate(), @@ -75,4 +80,45 @@ public ClubListResult getClubs(ClubListCommand command) { totalCount ); } + + @Override + public ClubDetailResult getClubDetail(Long id, String userToken, Long loginUserId) { + + ClubDetailDto dto = clubQueryPort.findClubDetailById(id) + .orElseThrow(() -> new NotFoundException(CLUB_NOT_FOUND)); + + int subscriberCount = clubQueryPort.countSubscribers(id); + + boolean isSubscribed = false; + if (loginUserId != null) { + isSubscribed = clubQueryPort.existsSubscription(id, loginUserId); + } + + return new ClubDetailResult( + dto.getId(), + dto.getName(), + dto.getSummary(), + dto.getCategory(), + dto.getDivision(), + subscriberCount, + isSubscribed, + dto.getInstagramUrl(), + dto.getYoutubeUrl(), + dto.getEtcUrl(), + dto.getDescription(), + dto.getQualifications(), + dto.getRecruitmentStatus(), + dto.getRecruitStartAt(), + dto.getRecruitEndAt(), + dto.getApplyUrl(), + dto.getPosterImagePath(), + new ClubDetailResult.Location( + dto.getBuilding(), + dto.getRoom(), + dto.getLon(), + dto.getLat() + ) + ); + } + } diff --git a/src/main/java/com/kustacks/kuring/club/domain/ClubRecruitmentStatus.java b/src/main/java/com/kustacks/kuring/club/domain/ClubRecruitmentStatus.java new file mode 100644 index 000000000..a38e88f3e --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/domain/ClubRecruitmentStatus.java @@ -0,0 +1,18 @@ +package com.kustacks.kuring.club.domain; + +import lombok.Getter; + +@Getter +public enum ClubRecruitmentStatus { + + ALWAYS("always"), + BEFORE("before"), + RECRUITING("recruiting"), + CLOSED("closed"); + + private final String value; + + ClubRecruitmentStatus(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java b/src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java index 25f89a645..2484bb05a 100644 --- a/src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java +++ b/src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java @@ -65,6 +65,7 @@ public enum ResponseCodeAndMessages { /* Club */ CLUB_DIVISION_SEARCH_SUCCESS(HttpStatus.OK.value(), "지원하는 동아리 소속 조회에 성공하였습니다"), CLUB_LIST_SEARCH_SUCCESS(HttpStatus.OK.value(), "동아리 목록 조회에 성공하였습니다"), + CLUB_DETAIL_SEARCH_SUCCESS(HttpStatus.OK.value(), "동아리 상세 조회에 성공하였습니다"), /** * ErrorCodes about auth diff --git a/src/main/java/com/kustacks/kuring/common/exception/code/ErrorCode.java b/src/main/java/com/kustacks/kuring/common/exception/code/ErrorCode.java index 184dce6dc..fe3c30365 100644 --- a/src/main/java/com/kustacks/kuring/common/exception/code/ErrorCode.java +++ b/src/main/java/com/kustacks/kuring/common/exception/code/ErrorCode.java @@ -59,7 +59,7 @@ public enum ErrorCode { CLUB_CATEGORY_NOT_SUPPORTED(HttpStatus.BAD_REQUEST, "서버에서 지원하지 않는 동아리 카테고리입니다."), CLUB_DIVISION_NOT_SUPPORTED(HttpStatus.BAD_REQUEST, "서버에서 지원하지 않는 동아리 소속입니다."), - + CLUB_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 동아리를 찾을 수 없습니다."), STAFF_SCRAPER_EXCEED_RETRY_LIMIT("교직원 업데이트 재시도 횟수를 초과했습니다."), STAFF_SCRAPER_CANNOT_SCRAP("건국대학교 홈페이지가 불안정합니다. 교직원 정보를 가져올 수 없습니다."), @@ -113,8 +113,7 @@ public enum ErrorCode { QUESTION_COUNT_NOT_ENOUGH(HttpStatus.TOO_MANY_REQUESTS, "남은 질문 횟수가 부족합니다."), STORAGE_S3_SDK_PROBLEM(HttpStatus.INTERNAL_SERVER_ERROR, "S3 클라이언트 통신 간 에러가 발생했습니다."), - FILE_IO_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, "파일을 읽어들이는데 문제가 발생했습니다.") - ; + FILE_IO_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, "파일을 읽어들이는데 문제가 발생했습니다."); private final HttpStatus httpStatus; private final String message; From 840644f432631038f25e1f7e96bc2ef322ced1a0 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Fri, 20 Feb 2026 21:08:29 +0900 Subject: [PATCH 08/34] =?UTF-8?q?[feat]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20API=EC=97=90=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EA=B5=AC=EB=8F=85=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/adapter/in/web/ClubQueryApiV2.java | 16 +++++++- .../application/port/in/ClubQueryUseCase.java | 2 +- .../application/service/ClubQueryService.java | 38 ++++++++++++------- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java index 82ed6f74d..d7b582ddb 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java @@ -63,17 +63,29 @@ public ResponseEntity> getSupportedClubDi } @Operation(summary = "동아리 목록 조회", description = "필터 조건에 맞는 동아리 목록을 커서 페이징으로 조회합니다") + @SecurityRequirement(name = JWT_TOKEN_HEADER_KEY) @GetMapping public ResponseEntity> getClubs( @RequestParam(required = false) String category, @RequestParam(required = false) String division, @RequestParam(required = false) String cursor, @RequestParam(defaultValue = "20") @Min(1) @Max(30) int size, - @RequestParam(defaultValue = "name") String sortBy + @RequestParam(defaultValue = "name") String sortBy, + @RequestHeader(value = AuthorizationExtractor.AUTHORIZATION, required = false) String bearerToken ) { + Long loginUserId = null; + + if (bearerToken != null) { + String jwt = extractAuthorizationValue(bearerToken, AuthorizationType.BEARER); + + if (jwtTokenProvider.validateToken(jwt)) { + loginUserId = Long.parseLong(jwtTokenProvider.getPrincipal(jwt)); + } + } + ClubListCommand command = new ClubListCommand(category, division, Cursor.from(cursor), size, sortBy); - ClubListResult result = clubQueryUseCase.getClubs(command); + ClubListResult result = clubQueryUseCase.getClubs(command, loginUserId); ClubListResponse response = ClubListResponse.from(result); diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java index 34ea4fc8b..e0ed4fb04 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java @@ -10,7 +10,7 @@ public interface ClubQueryUseCase { List getClubDivisions(); - ClubListResult getClubs(ClubListCommand command); + ClubListResult getClubs(ClubListCommand command, Long loginUserId); ClubDetailResult getClubDetail(Long id, String userToken, Long loginUserId); diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index fe1c8b94b..d9fb1d284 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -36,7 +36,7 @@ public List getClubDivisions() { } @Override - public ClubListResult getClubs(ClubListCommand command) { + public ClubListResult getClubs(ClubListCommand command, Long loginUserId) { int limit = Math.min(command.size(), 30); @@ -55,18 +55,30 @@ public ClubListResult getClubs(ClubListCommand command) { List items = cursorBasedList.getContents() .stream() - .map(r -> new ClubItemResult( - r.getId(), - r.getName(), - r.getSummary(), - r.getIconImageUrl(), - r.getCategory().getName(), - r.getDivision().getName(), - false, // 추후 구독 기능 - 0, // 추후 구독 기능 - r.getRecruitStartDate(), - r.getRecruitEndDate() - )) + .map(r -> { + + int subscriberCount = + clubQueryPort.countSubscribers(r.getId()); + + boolean isSubscribed = false; + if (loginUserId != null) { + isSubscribed = + clubQueryPort.existsSubscription(r.getId(), loginUserId); + } + + return new ClubItemResult( + r.getId(), + r.getName(), + r.getSummary(), + r.getIconImageUrl(), + r.getCategory().getName(), + r.getDivision().getName(), + isSubscribed, + subscriberCount, + r.getRecruitStartDate(), + r.getRecruitEndDate() + ); + }) .toList(); int totalCount = clubQueryPort.countClubs(command.category(), command.divisionList()); From 26f1195560af0e47c996e84dc56a64aa2fdc07d5 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Fri, 20 Feb 2026 21:08:56 +0900 Subject: [PATCH 09/34] =?UTF-8?q?[test]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ClubQueryServiceTest.java | 158 +++++++++++++++++- 1 file changed, 150 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java index c48c98b25..83fb6d1de 100644 --- a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java @@ -1,11 +1,15 @@ package com.kustacks.kuring.club.application.service; +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.club.application.port.out.ClubQueryPort; +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.ClubCategory; import com.kustacks.kuring.club.domain.ClubDivision; +import com.kustacks.kuring.club.domain.ClubRecruitmentStatus; import com.kustacks.kuring.common.data.Cursor; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -17,8 +21,12 @@ import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -39,8 +47,8 @@ class ClubQueryServiceTest { "쿠링", "건국대 공지사항 앱 만드는 개발 동아리", "icon-url-1", - "ACADEMIC", - "CENTRAL", + ClubCategory.ACADEMIC, + ClubDivision.CENTRAL, LocalDateTime.of(2025, 3, 1, 0, 0), LocalDateTime.of(2025, 3, 31, 23, 59) ), @@ -49,8 +57,8 @@ class ClubQueryServiceTest { "쿠잇", "건국대 개발 동아리", "icon-url-2", - "ACADEMIC", - "ENGINEERING", + ClubCategory.ACADEMIC, + ClubDivision.ENGINEERING, LocalDateTime.of(2025, 3, 1, 0, 0), LocalDateTime.of(2025, 3, 31, 23, 59) ), @@ -59,8 +67,8 @@ class ClubQueryServiceTest { "DIUS", "건국대 공과대학 댄스 동아리", "icon-url-3", - "CULTURE_ART", - "ENGINEERING", + ClubCategory.CULTURE_ART, + ClubDivision.ENGINEERING, LocalDateTime.of(2025, 2, 20, 0, 0), LocalDateTime.of(2025, 3, 31, 23, 59) ) @@ -92,6 +100,7 @@ void getClubs_success() { Cursor cursor = Cursor.from(null); int size = 10; String sortBy = "name"; + Long loginUserId = 100L; ClubListCommand command = new ClubListCommand(category, divisions, cursor, size, sortBy); @@ -103,17 +112,150 @@ void getClubs_success() { when(clubQueryPort.countClubs(category, divisionList)) .thenReturn(2); + when(clubQueryPort.countSubscribers(anyLong())) + .thenReturn(10); + + when(clubQueryPort.existsSubscription(anyLong(), anyLong())) + .thenReturn(true); + // when - ClubListResult result = clubQueryService.getClubs(command); + ClubListResult result = clubQueryService.getClubs(command, loginUserId); // then assertThat(result.totalCount()).isEqualTo(2); assertThat(result.clubs()).hasSize(3); assertThat(result.hasNext()).isFalse(); assertThat(result.cursor()).isNull(); - + assertThat(result.clubs().get(0).subscriberCount()).isEqualTo(10); + assertThat(result.clubs().get(0).isSubscribed()).isTrue(); verify(clubQueryPort).searchClubs(category, divisionList, cursor, size + 1, sortBy); verify(clubQueryPort).countClubs(category, divisionList); } + @Test + @DisplayName("비로그인 사용자는 목록에서 구독 여부를 조회하지 않는다") + void getClubs_withoutLogin() { + //given + String category = "academic"; + String divisions = "central,engineering"; + Cursor cursor = Cursor.from(null); + int size = 10; + String sortBy = "name"; + + ClubListCommand command = new ClubListCommand(category, divisions, cursor, size, sortBy); + + List divisionList = List.of("central", "engineering"); + + when(clubQueryPort.searchClubs(category, divisionList, cursor, size + 1, sortBy)) + .thenReturn(mockReadModels); + + when(clubQueryPort.countClubs(category, divisionList)) + .thenReturn(2); + + when(clubQueryPort.countSubscribers(anyLong())) + .thenReturn(5); + + //when + ClubListResult result = clubQueryService.getClubs(command, null); + + //then + assertThat(result.clubs().get(0).isSubscribed()).isFalse(); + verify(clubQueryPort, never()).existsSubscription(anyLong(), anyLong()); + } + + @Test + @DisplayName("동아리 상세 정보를 정상적으로 조회한다") + void getClubDetail_success() { + // given + Long clubId = 1L; + String userToken = "fcm-token"; + Long loginUserId = 100L; + + ClubDetailDto dto = new ClubDetailDto( + 1L, + "쿠링", + "건국대 공지사항 앱 만드는 개발 동아리", + ClubCategory.ACADEMIC, + ClubDivision.CENTRAL, + "instagram-url", + "youtube-url", + null, + "상세 설명", + "지원 자격", + ClubRecruitmentStatus.RECRUITING, + LocalDateTime.of(2025, 3, 1, 0, 0), + LocalDateTime.of(2025, 3, 31, 23, 59), + "apply-url", + "poster-path", + "공학관", + "101호", + 127.0, + 37.5 + ); + + when(clubQueryPort.findClubDetailById(clubId)) + .thenReturn(Optional.of(dto)); + + when(clubQueryPort.countSubscribers(clubId)) + .thenReturn(10); + + when(clubQueryPort.existsSubscription(clubId, loginUserId)) + .thenReturn(true); + + // when + ClubDetailResult result = clubQueryService.getClubDetail(clubId, userToken, loginUserId); + + // then + assertThat(result.id()).isEqualTo(1L); + assertThat(result.name()).isEqualTo("쿠링"); + assertThat(result.subscriberCount()).isEqualTo(10); + assertThat(result.isSubscribed()).isTrue(); + assertThat(result.location().building()).isEqualTo("공학관"); + assertThat(result.recruitmentStatus()) + .isEqualTo(ClubRecruitmentStatus.RECRUITING); + assertThat(result.category()) + .isEqualTo(ClubCategory.ACADEMIC); + assertThat(result.division()) + .isEqualTo(ClubDivision.CENTRAL); + + verify(clubQueryPort).findClubDetailById(clubId); + verify(clubQueryPort).countSubscribers(clubId); + verify(clubQueryPort).existsSubscription(clubId, loginUserId); + } + + @Test + @DisplayName("비로그인 사용자는 구독 여부를 조회하지 않는다") + void getClubDetail_withoutLogin() { + + Long clubId = 1L; + String userToken = "fcm-token"; + + ClubDetailDto dto = new ClubDetailDto( + 1L, "쿠링", "건국대 공지사항 앱 만드는 개발 동아리", + ClubCategory.ACADEMIC, ClubDivision.CENTRAL, + null, null, null, + null, null, + ClubRecruitmentStatus.RECRUITING, + null, null, + null, null, + null, null, + null, null + ); + + when(clubQueryPort.findClubDetailById(clubId)) + .thenReturn(Optional.of(dto)); + + when(clubQueryPort.countSubscribers(clubId)) + .thenReturn(5); + + // when + ClubDetailResult result = + clubQueryService.getClubDetail(clubId, userToken, null); + + // then + assertThat(result.isSubscribed()).isFalse(); + verify(clubQueryPort).countSubscribers(clubId); + verify(clubQueryPort, never()).existsSubscription(anyLong(), any()); + assertThat(result.subscriberCount()).isEqualTo(5); + } } From 2b65645b305ce2f684d006914bf4230e7481bcd7 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sun, 22 Feb 2026 20:44:13 +0900 Subject: [PATCH 10/34] =?UTF-8?q?[fix]:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D/=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20ap?= =?UTF-8?q?i=20-=20JWT=20=EA=B8=B0=EB=B0=98=20email=20=EC=9D=B8=EC=A6=9D?= =?UTF-8?q?=20=EB=B0=A9=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/adapter/in/web/ClubQueryApiV2.java | 42 +++++++++---------- .../application/port/in/ClubQueryUseCase.java | 4 +- .../application/service/ClubQueryService.java | 21 ++++++---- .../service/ClubQueryServiceTest.java | 27 +++++++++++- 4 files changed, 60 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java index d7b582ddb..5a9e21063 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java @@ -30,6 +30,7 @@ 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; @@ -73,19 +74,11 @@ public ResponseEntity> getClubs( @RequestParam(defaultValue = "name") String sortBy, @RequestHeader(value = AuthorizationExtractor.AUTHORIZATION, required = false) String bearerToken ) { - Long loginUserId = null; - - if (bearerToken != null) { - String jwt = extractAuthorizationValue(bearerToken, AuthorizationType.BEARER); - - if (jwtTokenProvider.validateToken(jwt)) { - loginUserId = Long.parseLong(jwtTokenProvider.getPrincipal(jwt)); - } - } + String email = resolveLoginEmail(bearerToken); ClubListCommand command = new ClubListCommand(category, division, Cursor.from(cursor), size, sortBy); - ClubListResult result = clubQueryUseCase.getClubs(command, loginUserId); + ClubListResult result = clubQueryUseCase.getClubs(command, email); ClubListResponse response = ClubListResponse.from(result); @@ -101,23 +94,26 @@ public ResponseEntity> getClubDetail( @RequestHeader(value = FCM_TOKEN_HEADER_KEY, required = false) String userToken, @RequestHeader(value = AuthorizationExtractor.AUTHORIZATION, required = false) String bearerToken ) { - Long loginUserId = null; - - if (bearerToken != null) { - String jwt = extractAuthorizationValue(bearerToken, AuthorizationType.BEARER); - - if (!jwtTokenProvider.validateToken(jwt)) { - throw new InvalidStateException(ErrorCode.JWT_INVALID_TOKEN); - } - - loginUserId = Long.parseLong(jwtTokenProvider.getPrincipal(jwt)); - } + String email = resolveLoginEmail(bearerToken); - - ClubDetailResult result = clubQueryUseCase.getClubDetail(id, userToken, loginUserId); + ClubDetailResult result = clubQueryUseCase.getClubDetail(id, userToken, email); 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); + } } \ No newline at end of file diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java index e0ed4fb04..72dc888b3 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java @@ -10,8 +10,8 @@ public interface ClubQueryUseCase { List getClubDivisions(); - ClubListResult getClubs(ClubListCommand command, Long loginUserId); + ClubListResult getClubs(ClubListCommand command, String email); - ClubDetailResult getClubDetail(Long id, String userToken, Long loginUserId); + ClubDetailResult getClubDetail(Long id, String userToken, String email); } diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index d9fb1d284..2e45e7d5f 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -13,11 +13,14 @@ import com.kustacks.kuring.common.annotation.UseCase; import com.kustacks.kuring.common.data.CursorBasedList; import com.kustacks.kuring.common.exception.NotFoundException; +import com.kustacks.kuring.user.application.port.out.RootUserQueryPort; +import com.kustacks.kuring.user.domain.RootUser; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; import java.util.List; +import java.util.Optional; import static com.kustacks.kuring.common.exception.code.ErrorCode.CLUB_NOT_FOUND; @@ -27,6 +30,7 @@ public class ClubQueryService implements ClubQueryUseCase { private final ClubQueryPort clubQueryPort; + private final RootUserQueryPort rootUserQueryPort; @Override public List getClubDivisions() { @@ -36,7 +40,10 @@ public List getClubDivisions() { } @Override - public ClubListResult getClubs(ClubListCommand command, Long loginUserId) { + public ClubListResult getClubs(ClubListCommand command, String email) { + + Optional optionalRootUser = rootUserQueryPort.findRootUserByEmail(email); + Long loginUserId = optionalRootUser.map(RootUser::getId).orElse(null); int limit = Math.min(command.size(), 30); @@ -62,8 +69,7 @@ public ClubListResult getClubs(ClubListCommand command, Long loginUserId) { boolean isSubscribed = false; if (loginUserId != null) { - isSubscribed = - clubQueryPort.existsSubscription(r.getId(), loginUserId); + isSubscribed = clubQueryPort.existsSubscription(r.getId(), loginUserId); } return new ClubItemResult( @@ -83,18 +89,19 @@ public ClubListResult getClubs(ClubListCommand command, Long loginUserId) { int totalCount = clubQueryPort.countClubs(command.category(), command.divisionList()); - String nextCursor = cursorBasedList.getEndCursor(); - return new ClubListResult( items, - nextCursor, + cursorBasedList.getEndCursor(), cursorBasedList.hasNext(), totalCount ); } @Override - public ClubDetailResult getClubDetail(Long id, String userToken, Long loginUserId) { + public ClubDetailResult getClubDetail(Long id, String userToken, String email) { + + Optional optionalRootUser = rootUserQueryPort.findRootUserByEmail(email); + Long loginUserId = optionalRootUser.map(RootUser::getId).orElse(null); ClubDetailDto dto = clubQueryPort.findClubDetailById(id) .orElseThrow(() -> new NotFoundException(CLUB_NOT_FOUND)); diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java index 83fb6d1de..cbdf15887 100644 --- a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java @@ -11,6 +11,8 @@ import com.kustacks.kuring.club.domain.ClubDivision; import com.kustacks.kuring.club.domain.ClubRecruitmentStatus; import com.kustacks.kuring.common.data.Cursor; +import com.kustacks.kuring.user.application.port.out.RootUserQueryPort; +import com.kustacks.kuring.user.domain.RootUser; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -26,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -37,6 +40,9 @@ class ClubQueryServiceTest { @Mock private ClubQueryPort clubQueryPort; + @Mock + private RootUserQueryPort rootUserQueryPort; + @InjectMocks private ClubQueryService clubQueryService; @@ -100,8 +106,15 @@ void getClubs_success() { Cursor cursor = Cursor.from(null); int size = 10; String sortBy = "name"; + + String email = "test@test.com"; Long loginUserId = 100L; + RootUser rootUser = mock(RootUser.class); + when(rootUser.getId()).thenReturn(loginUserId); + when(rootUserQueryPort.findRootUserByEmail(email)) + .thenReturn(Optional.of(rootUser)); + ClubListCommand command = new ClubListCommand(category, divisions, cursor, size, sortBy); List divisionList = List.of("central", "engineering"); @@ -119,7 +132,7 @@ void getClubs_success() { .thenReturn(true); // when - ClubListResult result = clubQueryService.getClubs(command, loginUserId); + ClubListResult result = clubQueryService.getClubs(command, email); // then assertThat(result.totalCount()).isEqualTo(2); @@ -128,6 +141,8 @@ void getClubs_success() { assertThat(result.cursor()).isNull(); assertThat(result.clubs().get(0).subscriberCount()).isEqualTo(10); assertThat(result.clubs().get(0).isSubscribed()).isTrue(); + + verify(rootUserQueryPort).findRootUserByEmail(email); verify(clubQueryPort).searchClubs(category, divisionList, cursor, size + 1, sortBy); verify(clubQueryPort).countClubs(category, divisionList); } @@ -169,8 +184,15 @@ void getClubDetail_success() { // given Long clubId = 1L; String userToken = "fcm-token"; + + String email = "test@test.com"; Long loginUserId = 100L; + RootUser rootUser = mock(RootUser.class); + when(rootUser.getId()).thenReturn(loginUserId); + when(rootUserQueryPort.findRootUserByEmail(email)) + .thenReturn(Optional.of(rootUser)); + ClubDetailDto dto = new ClubDetailDto( 1L, "쿠링", @@ -203,7 +225,7 @@ void getClubDetail_success() { .thenReturn(true); // when - ClubDetailResult result = clubQueryService.getClubDetail(clubId, userToken, loginUserId); + ClubDetailResult result = clubQueryService.getClubDetail(clubId, userToken, email); // then assertThat(result.id()).isEqualTo(1L); @@ -218,6 +240,7 @@ void getClubDetail_success() { assertThat(result.division()) .isEqualTo(ClubDivision.CENTRAL); + verify(rootUserQueryPort).findRootUserByEmail(email); verify(clubQueryPort).findClubDetailById(clubId); verify(clubQueryPort).countSubscribers(clubId); verify(clubQueryPort).existsSubscription(clubId, loginUserId); From 8cec7117c46cef933f79290671f580c963ff8110 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sun, 22 Feb 2026 21:02:54 +0900 Subject: [PATCH 11/34] =?UTF-8?q?[refactor]:=20club=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20api=20-=20=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EA=B0=80=20=EC=97=86=EC=9D=84=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20location=EC=9D=84=20null=EB=A1=9C=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/dto/ClubDetailResponse.java | 18 +++++++-------- .../application/service/ClubQueryService.java | 22 ++++++++++++++----- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java index a9c94980d..02f670769 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java @@ -26,15 +26,15 @@ public record ClubDetailResponse( ) { public static ClubDetailResponse from(ClubDetailResult result) { - Location location = null; - if (result.location() != null) { - location = new Location( - result.location().building(), - result.location().room(), - result.location().lon(), - result.location().lat() - ); - } + + Location location = result.location() == null ? + null + : new Location( + result.location().building(), + result.location().room(), + result.location().lon(), + result.location().lat() + ); return new ClubDetailResponse( result.id(), diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index 2e45e7d5f..842e39cbf 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -113,6 +113,15 @@ public ClubDetailResult getClubDetail(Long id, String userToken, String email) { isSubscribed = clubQueryPort.existsSubscription(id, loginUserId); } + ClubDetailResult.Location location = hasLocation(dto) ? + new ClubDetailResult.Location( + dto.getBuilding(), + dto.getRoom(), + dto.getLon(), + dto.getLat() + ) + : null; + return new ClubDetailResult( dto.getId(), dto.getName(), @@ -131,13 +140,14 @@ public ClubDetailResult getClubDetail(Long id, String userToken, String email) { dto.getRecruitEndAt(), dto.getApplyUrl(), dto.getPosterImagePath(), - new ClubDetailResult.Location( - dto.getBuilding(), - dto.getRoom(), - dto.getLon(), - dto.getLat() - ) + location ); } + private boolean hasLocation(ClubDetailDto dto) { + return dto.getBuilding() != null + || dto.getRoom() != null + || dto.getLon() != null + || dto.getLat() != null; + } } From c67ff244966b5866cf17c4508a08f5b834ab6ed2 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sun, 22 Feb 2026 21:42:01 +0900 Subject: [PATCH 12/34] =?UTF-8?q?[fix]:=20name/recruitEndDate=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=20=EC=8B=9C=20=EB=B3=B5=ED=95=A9=20=EC=BB=A4=EC=84=9C?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/ClubQueryRepositoryImpl.java | 69 ++++++++++++++++--- .../application/service/ClubQueryService.java | 16 ++++- 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java index b6efe422a..d0068a111 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java @@ -51,7 +51,7 @@ public List searchClubs( .where( categoryEq(category), divisionIn(divisions), - idAfterCursor(cursor) + cursorCondition(sortBy, cursor) ) .orderBy(getOrderSpecifiers(sortBy)) .limit(size) @@ -177,23 +177,74 @@ private BooleanExpression divisionIn(List divisions) { ); } - private BooleanExpression idAfterCursor(String cursor) { + private BooleanExpression cursorCondition(String sortBy, String cursor) { + if (cursor == null || cursor.equals("0")) return null; - return club.id.gt(Long.parseLong(cursor)); + + try { + + String[] parts = cursor.split("\\|"); + if (parts.length < 2) return null; + + return switch (sortBy) { + + case "name" -> { + String lastName = parts[0]; + Long lastId = Long.parseLong(parts[1]); + + yield club.name.gt(lastName) + .or( + club.name.eq(lastName) + .and(club.id.gt(lastId)) + ); + } + + case "recruitEndDate" -> { + Long lastId = Long.parseLong(parts[1]); + + if ("null".equals(parts[0])) { + yield club.recruitEndAt.isNull() + .and(club.id.gt(lastId)) + .or(club.recruitEndAt.isNotNull()); + } + + LocalDateTime lastDate = LocalDateTime.parse(parts[0]); + + yield club.recruitEndAt.gt(lastDate) + .or( + club.recruitEndAt.eq(lastDate) + .and(club.id.gt(lastId)) + ); + } + + default -> { + Long lastId = Long.parseLong(cursor); + yield club.id.gt(lastId); + } + }; + + } catch (Exception e) { + return null; + } } private OrderSpecifier[] getOrderSpecifiers(String sortBy) { - if ("recruitEndDate".equals(sortBy)) { - return new OrderSpecifier[]{ + return switch (sortBy) { + + case "name" -> new OrderSpecifier[]{ + club.name.asc(), + club.id.asc() + }; + + case "recruitEndDate" -> new OrderSpecifier[]{ club.recruitEndAt.asc(), club.id.asc() }; - } - return new OrderSpecifier[]{ - club.name.asc(), - club.id.asc() + default -> new OrderSpecifier[]{ + club.id.asc() + }; }; } diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index 842e39cbf..09225ff49 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -49,7 +49,7 @@ public ClubListResult getClubs(ClubListCommand command, String email) { CursorBasedList cursorBasedList = CursorBasedList.of( limit, - club -> club.getId().toString(), + club -> generateCursor(club, command.sortBy()), searchSize -> clubQueryPort.searchClubs( command.category(), command.divisionList(), @@ -150,4 +150,18 @@ private boolean hasLocation(ClubDetailDto dto) { || dto.getLon() != null || dto.getLat() != null; } + + private String generateCursor(ClubReadModel club, String sortBy) { + return switch (sortBy) { + case "name" -> club.getName() + "|" + club.getId(); + case "recruitEndDate" -> { + if (club.getRecruitEndDate() == null) { + yield "null|" + club.getId(); + } + yield club.getRecruitEndDate() + + "|" + club.getId(); + } + default -> club.getId().toString(); + }; + } } From e4e7276d306d3181c7f0177c082f327f4476c833 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sun, 22 Feb 2026 21:52:43 +0900 Subject: [PATCH 13/34] =?UTF-8?q?[refactor]:=20ClubDetail=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=8B=9C=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?userToken=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java | 2 +- .../kuring/club/application/port/in/ClubQueryUseCase.java | 2 +- .../kuring/club/application/service/ClubQueryService.java | 2 +- .../club/application/service/ClubQueryServiceTest.java | 6 ++---- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java index 5a9e21063..3f4408702 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java @@ -96,7 +96,7 @@ public ResponseEntity> getClubDetail( ) { String email = resolveLoginEmail(bearerToken); - ClubDetailResult result = clubQueryUseCase.getClubDetail(id, userToken, email); + ClubDetailResult result = clubQueryUseCase.getClubDetail(id, email); ClubDetailResponse response = ClubDetailResponse.from(result); diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java index 72dc888b3..c57184ce6 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java @@ -12,6 +12,6 @@ public interface ClubQueryUseCase { ClubListResult getClubs(ClubListCommand command, String email); - ClubDetailResult getClubDetail(Long id, String userToken, String email); + ClubDetailResult getClubDetail(Long id, String email); } diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index 09225ff49..63b1e695a 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -98,7 +98,7 @@ public ClubListResult getClubs(ClubListCommand command, String email) { } @Override - public ClubDetailResult getClubDetail(Long id, String userToken, String email) { + public ClubDetailResult getClubDetail(Long id, String email) { Optional optionalRootUser = rootUserQueryPort.findRootUserByEmail(email); Long loginUserId = optionalRootUser.map(RootUser::getId).orElse(null); diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java index cbdf15887..594c77015 100644 --- a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java @@ -183,7 +183,6 @@ void getClubs_withoutLogin() { void getClubDetail_success() { // given Long clubId = 1L; - String userToken = "fcm-token"; String email = "test@test.com"; Long loginUserId = 100L; @@ -225,7 +224,7 @@ void getClubDetail_success() { .thenReturn(true); // when - ClubDetailResult result = clubQueryService.getClubDetail(clubId, userToken, email); + ClubDetailResult result = clubQueryService.getClubDetail(clubId, email); // then assertThat(result.id()).isEqualTo(1L); @@ -251,7 +250,6 @@ void getClubDetail_success() { void getClubDetail_withoutLogin() { Long clubId = 1L; - String userToken = "fcm-token"; ClubDetailDto dto = new ClubDetailDto( 1L, "쿠링", "건국대 공지사항 앱 만드는 개발 동아리", @@ -273,7 +271,7 @@ void getClubDetail_withoutLogin() { // when ClubDetailResult result = - clubQueryService.getClubDetail(clubId, userToken, null); + clubQueryService.getClubDetail(clubId, null); // then assertThat(result.isSubscribed()).isFalse(); From 9d613f47686d0111671d142c897a9463ae980a11 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sun, 22 Feb 2026 22:11:09 +0900 Subject: [PATCH 14/34] =?UTF-8?q?[refactor]:=20club=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EA=B5=AC=EB=8F=85=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EA=B4=80=EB=A0=A8=20N+1=20=EC=BF=BC?= =?UTF-8?q?=EB=A6=AC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/ClubPersistenceAdapter.java | 41 ++++++++++++++++ .../persistence/ClubSubscribeRepository.java | 6 +++ .../application/port/out/ClubQueryPort.java | 5 ++ .../application/service/ClubQueryService.java | 49 ++++++++++--------- .../service/ClubQueryServiceTest.java | 25 +++++++--- 5 files changed, 97 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java index f6946bdd2..23ee7b6a9 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java @@ -3,12 +3,15 @@ import com.kustacks.kuring.club.application.port.out.ClubQueryPort; 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.ClubSubscribe; import com.kustacks.kuring.common.annotation.PersistenceAdapter; import com.kustacks.kuring.common.data.Cursor; import lombok.RequiredArgsConstructor; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; @PersistenceAdapter @RequiredArgsConstructor @@ -53,4 +56,42 @@ public int countSubscribers(Long clubId) { public boolean existsSubscription(Long clubId, Long loginUserId) { return clubSubscribeRepository.existsByClubIdAndUser_LoginUserId(clubId, loginUserId); } + + @Override + public Map countSubscribersByClubIds(List clubIds) { + + if (clubIds == null || clubIds.isEmpty()) { + return Map.of(); + } + + List subscriptions = clubSubscribeRepository.findByClubIdIn(clubIds); + + return subscriptions.stream() + .collect(Collectors.groupingBy( + sub -> sub.getClub().getId(), + Collectors.collectingAndThen( + Collectors.counting(), + Long::intValue + ) + )); + } + + @Override + public Map findSubscribedClubIds( + List clubIds, + Long loginUserId + ) { + + if (clubIds == null || clubIds.isEmpty() || loginUserId == null) { + return Map.of(); + } + + List subscriptions = clubSubscribeRepository.findByClubIdInAndUser_LoginUserId(clubIds, loginUserId); + + return subscriptions.stream() + .collect(Collectors.toMap( + sub -> sub.getClub().getId(), + sub -> true + )); + } } diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java index 28bc9d91e..253dbded5 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java @@ -3,9 +3,15 @@ import com.kustacks.kuring.club.domain.ClubSubscribe; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface ClubSubscribeRepository extends JpaRepository { int countByClubId(Long clubId); boolean existsByClubIdAndUser_LoginUserId(Long clubId, Long loginUserId); + + List findByClubIdIn(List clubIds); + + List findByClubIdInAndUser_LoginUserId(List clubIds, Long loginUserId); } \ No newline at end of file diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java index 4b22663eb..250a3d4d0 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java @@ -5,6 +5,7 @@ import com.kustacks.kuring.common.data.Cursor; import java.util.List; +import java.util.Map; import java.util.Optional; public interface ClubQueryPort { @@ -18,4 +19,8 @@ public interface ClubQueryPort { int countSubscribers(Long clubId); boolean existsSubscription(Long clubId, Long loginUserId); + + Map countSubscribersByClubIds(List clubIds); + + Map findSubscribedClubIds(List clubIds, Long loginUserId); } diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index 63b1e695a..850df1d4a 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Optional; import static com.kustacks.kuring.common.exception.code.ErrorCode.CLUB_NOT_FOUND; @@ -59,34 +60,36 @@ public ClubListResult getClubs(ClubListCommand command, String email) { ) ); + List clubIds = cursorBasedList.getContents() + .stream() + .map(ClubReadModel::getId) + .toList(); + + Map subscriberCountMap = clubQueryPort.countSubscribersByClubIds(clubIds); + + Map subscribedMap = loginUserId != null + ? clubQueryPort.findSubscribedClubIds(clubIds, loginUserId) + : Map.of(); + + List items = cursorBasedList.getContents() .stream() - .map(r -> { - - int subscriberCount = - clubQueryPort.countSubscribers(r.getId()); - - boolean isSubscribed = false; - if (loginUserId != null) { - isSubscribed = clubQueryPort.existsSubscription(r.getId(), loginUserId); - } - - return new ClubItemResult( - r.getId(), - r.getName(), - r.getSummary(), - r.getIconImageUrl(), - r.getCategory().getName(), - r.getDivision().getName(), - isSubscribed, - subscriberCount, - r.getRecruitStartDate(), - r.getRecruitEndDate() - ); - }) + .map(r -> new ClubItemResult( + r.getId(), + r.getName(), + r.getSummary(), + r.getIconImageUrl(), + r.getCategory().getName(), + r.getDivision().getName(), + subscribedMap.getOrDefault(r.getId(), false), + subscriberCountMap.getOrDefault(r.getId(), 0), + r.getRecruitStartDate(), + r.getRecruitEndDate() + )) .toList(); + int totalCount = clubQueryPort.countClubs(command.category(), command.divisionList()); return new ClubListResult( diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java index 594c77015..fc3525087 100644 --- a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java @@ -23,6 +23,7 @@ import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; @@ -125,11 +126,19 @@ void getClubs_success() { when(clubQueryPort.countClubs(category, divisionList)) .thenReturn(2); - when(clubQueryPort.countSubscribers(anyLong())) - .thenReturn(10); + when(clubQueryPort.countSubscribersByClubIds(any())) + .thenReturn(Map.of( + 1L, 10, + 2L, 10, + 3L, 10 + )); - when(clubQueryPort.existsSubscription(anyLong(), anyLong())) - .thenReturn(true); + when(clubQueryPort.findSubscribedClubIds(any(), anyLong())) + .thenReturn(Map.of( + 1L, true, + 2L, true, + 3L, true + )); // when ClubListResult result = clubQueryService.getClubs(command, email); @@ -167,8 +176,12 @@ void getClubs_withoutLogin() { when(clubQueryPort.countClubs(category, divisionList)) .thenReturn(2); - when(clubQueryPort.countSubscribers(anyLong())) - .thenReturn(5); + when(clubQueryPort.countSubscribersByClubIds(any())) + .thenReturn(Map.of( + 1L, 5, + 2L, 5, + 3L, 5 + )); //when ClubListResult result = clubQueryService.getClubs(command, null); From e0a0241b3b638cc9f11c339a442da2158f1a7ff1 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Mon, 23 Feb 2026 15:03:39 +0900 Subject: [PATCH 15/34] =?UTF-8?q?[fix]:=20recruitEndDate=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=B3=B5=ED=95=A9=20?= =?UTF-8?q?=EC=BB=A4=EC=84=9C(group|date|id)=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/ClubPersistenceAdapter.java | 7 +- .../out/persistence/ClubQueryRepository.java | 3 +- .../persistence/ClubQueryRepositoryImpl.java | 79 +++++++++++++------ .../application/port/out/ClubQueryPort.java | 3 +- .../application/service/ClubQueryService.java | 25 ++++-- .../service/ClubQueryServiceTest.java | 23 ++++-- 6 files changed, 102 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java index 23ee7b6a9..1886ecaad 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java @@ -8,6 +8,7 @@ import com.kustacks.kuring.common.data.Cursor; import lombok.RequiredArgsConstructor; +import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.Optional; @@ -26,14 +27,16 @@ public List searchClubs( List divisions, Cursor cursor, int size, - String sortBy + String sortBy, + LocalDateTime now ) { return clubRepository.searchClubs( category, divisions, cursor == null ? null : cursor.getStringCursor(), size, - sortBy + sortBy, + now ); } diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java index 9b5dde660..cc65e55a8 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java @@ -3,12 +3,13 @@ import com.kustacks.kuring.club.application.port.out.dto.ClubDetailDto; import com.kustacks.kuring.club.application.port.out.dto.ClubReadModel; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; public interface ClubQueryRepository { - List searchClubs(String category, List divisions, String cursor, int size, String sortBy); + List searchClubs(String category, List divisions, String cursor, int size, String sortBy, LocalDateTime now); int countClubs(String category, List divisions); diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java index d0068a111..0a652d2d8 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java @@ -10,6 +10,8 @@ import com.querydsl.core.Tuple; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.CaseBuilder; +import com.querydsl.core.types.dsl.NumberExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; @@ -33,7 +35,8 @@ public List searchClubs( List divisions, String cursor, int size, - String sortBy + String sortBy, + LocalDateTime now ) { return queryFactory @@ -51,9 +54,9 @@ public List searchClubs( .where( categoryEq(category), divisionIn(divisions), - cursorCondition(sortBy, cursor) + cursorCondition(sortBy, cursor, now) ) - .orderBy(getOrderSpecifiers(sortBy)) + .orderBy(getOrderSpecifiers(sortBy, now)) .limit(size) .fetch(); } @@ -177,18 +180,19 @@ private BooleanExpression divisionIn(List divisions) { ); } - private BooleanExpression cursorCondition(String sortBy, String cursor) { + private BooleanExpression cursorCondition(String sortBy, String cursor, LocalDateTime now) { if (cursor == null || cursor.equals("0")) return null; try { String[] parts = cursor.split("\\|"); - if (parts.length < 2) return null; return switch (sortBy) { case "name" -> { + if (parts.length < 2) yield null; + String lastName = parts[0]; Long lastId = Long.parseLong(parts[1]); @@ -200,21 +204,34 @@ private BooleanExpression cursorCondition(String sortBy, String cursor) { } case "recruitEndDate" -> { - Long lastId = Long.parseLong(parts[1]); - - if ("null".equals(parts[0])) { - yield club.recruitEndAt.isNull() - .and(club.id.gt(lastId)) - .or(club.recruitEndAt.isNotNull()); + if (parts.length < 3) yield null; + + int lastGroup = Integer.parseInt(parts[0]); + String lastDateStr = parts[1]; + Long lastId = Long.parseLong(parts[2]); + + NumberExpression currentGroup = recruitmentGroup(now); + + BooleanExpression groupCondition = currentGroup.gt(lastGroup); + + BooleanExpression sameGroupCondition; + + if ("null".equals(lastDateStr)) { + sameGroupCondition = currentGroup.eq(lastGroup) + .and(club.id.gt(lastId)); + } else { + LocalDateTime lastDate = LocalDateTime.parse(lastDateStr); + sameGroupCondition = currentGroup.eq(lastGroup) + .and( + club.recruitEndAt.gt(lastDate) + .or( + club.recruitEndAt.eq(lastDate) + .and(club.id.gt(lastId)) + ) + ); } + yield groupCondition.or(sameGroupCondition); - LocalDateTime lastDate = LocalDateTime.parse(parts[0]); - - yield club.recruitEndAt.gt(lastDate) - .or( - club.recruitEndAt.eq(lastDate) - .and(club.id.gt(lastId)) - ); } default -> { @@ -228,7 +245,7 @@ private BooleanExpression cursorCondition(String sortBy, String cursor) { } } - private OrderSpecifier[] getOrderSpecifiers(String sortBy) { + private OrderSpecifier[] getOrderSpecifiers(String sortBy, LocalDateTime now) { return switch (sortBy) { @@ -237,10 +254,19 @@ private OrderSpecifier[] getOrderSpecifiers(String sortBy) { club.id.asc() }; - case "recruitEndDate" -> new OrderSpecifier[]{ - club.recruitEndAt.asc(), - club.id.asc() - }; + case "recruitEndDate" -> { + + var statusOrder = new CaseBuilder() + .when(club.recruitEndAt.isNull()).then(2) + .when(club.recruitEndAt.lt(now)).then(1) + .otherwise(0); + + yield new OrderSpecifier[]{ + statusOrder.asc(), + club.recruitEndAt.asc().nullsLast(), + club.id.asc() + }; + } default -> new OrderSpecifier[]{ club.id.asc() @@ -248,6 +274,13 @@ private OrderSpecifier[] getOrderSpecifiers(String sortBy) { }; } + private NumberExpression recruitmentGroup(LocalDateTime now) { + return new CaseBuilder() + .when(club.recruitEndAt.isNull()).then(2) + .when(club.recruitEndAt.lt(now)).then(1) + .otherwise(0); + } + private ClubRecruitmentStatus calculateRecruitmentStatus( LocalDateTime start, LocalDateTime end, diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java index 250a3d4d0..f3b5fde1a 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java @@ -4,13 +4,14 @@ import com.kustacks.kuring.club.application.port.out.dto.ClubReadModel; import com.kustacks.kuring.common.data.Cursor; +import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.Optional; public interface ClubQueryPort { - List searchClubs(String category, List divisions, Cursor cursor, int size, String sortBy); + List searchClubs(String category, List divisions, Cursor cursor, int size, String sortBy, LocalDateTime now); int countClubs(String category, List divisions); diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index 850df1d4a..a9f65ac86 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -18,6 +18,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -48,15 +49,18 @@ public ClubListResult getClubs(ClubListCommand command, String email) { int limit = Math.min(command.size(), 30); + LocalDateTime now = LocalDateTime.now(); + CursorBasedList cursorBasedList = CursorBasedList.of( limit, - club -> generateCursor(club, command.sortBy()), + club -> generateCursor(club, command.sortBy(), now), searchSize -> clubQueryPort.searchClubs( command.category(), command.divisionList(), command.cursor(), searchSize, - command.sortBy() + command.sortBy(), + now ) ); @@ -154,15 +158,24 @@ private boolean hasLocation(ClubDetailDto dto) { || dto.getLat() != null; } - private String generateCursor(ClubReadModel club, String sortBy) { + private String generateCursor(ClubReadModel club, String sortBy, LocalDateTime now) { return switch (sortBy) { case "name" -> club.getName() + "|" + club.getId(); case "recruitEndDate" -> { + int group; if (club.getRecruitEndDate() == null) { - yield "null|" + club.getId(); + group = 2; + } else if (club.getRecruitEndDate().isBefore(now)) { + group = 1; + } else { + group = 0; } - yield club.getRecruitEndDate() - + "|" + club.getId(); + + String datePart = club.getRecruitEndDate() == null + ? "null" + : club.getRecruitEndDate().toString(); + + yield group + "|" + datePart + "|" + club.getId(); } default -> club.getId().toString(); }; diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java index fc3525087..a339e7651 100644 --- a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java @@ -29,6 +29,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -120,8 +121,14 @@ void getClubs_success() { List divisionList = List.of("central", "engineering"); - when(clubQueryPort.searchClubs(category, divisionList, cursor, size + 1, sortBy)) - .thenReturn(mockReadModels); + when(clubQueryPort.searchClubs( + eq(category), + eq(divisionList), + eq(cursor), + eq(size + 1), + eq(sortBy), + any(LocalDateTime.class) + )).thenReturn(mockReadModels); when(clubQueryPort.countClubs(category, divisionList)) .thenReturn(2); @@ -152,7 +159,7 @@ void getClubs_success() { assertThat(result.clubs().get(0).isSubscribed()).isTrue(); verify(rootUserQueryPort).findRootUserByEmail(email); - verify(clubQueryPort).searchClubs(category, divisionList, cursor, size + 1, sortBy); + verify(clubQueryPort).searchClubs(eq(category), eq(divisionList), eq(cursor), eq(size + 1), eq(sortBy), any(LocalDateTime.class)); verify(clubQueryPort).countClubs(category, divisionList); } @@ -170,8 +177,14 @@ void getClubs_withoutLogin() { List divisionList = List.of("central", "engineering"); - when(clubQueryPort.searchClubs(category, divisionList, cursor, size + 1, sortBy)) - .thenReturn(mockReadModels); + when(clubQueryPort.searchClubs( + eq(category), + eq(divisionList), + eq(cursor), + eq(size + 1), + eq(sortBy), + any(LocalDateTime.class) + )).thenReturn(mockReadModels); when(clubQueryPort.countClubs(category, divisionList)) .thenReturn(2); From 2c6280e5fab17e15c19695ed82dfe9bdf2f82ad4 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Wed, 25 Feb 2026 19:44:06 +0900 Subject: [PATCH 16/34] =?UTF-8?q?[refactor]:=20Persistence=EC=97=90?= =?UTF-8?q?=EC=84=9C=20findSubscribedClubIds=20=EA=B0=80=EA=B3=B5=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20Service=EB=A1=9C=20=EC=B1=85=EC=9E=84=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/ClubPersistenceAdapter.java | 19 ++++++------------- .../application/port/out/ClubQueryPort.java | 2 +- .../application/service/ClubQueryService.java | 11 ++++++++--- .../service/ClubQueryServiceTest.java | 8 ++------ 4 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java index 1886ecaad..52284fbd0 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java @@ -80,21 +80,14 @@ public Map countSubscribersByClubIds(List clubIds) { } @Override - public Map findSubscribedClubIds( + public List findSubscribedClubIds( List clubIds, Long loginUserId ) { - - if (clubIds == null || clubIds.isEmpty() || loginUserId == null) { - return Map.of(); - } - - List subscriptions = clubSubscribeRepository.findByClubIdInAndUser_LoginUserId(clubIds, loginUserId); - - return subscriptions.stream() - .collect(Collectors.toMap( - sub -> sub.getClub().getId(), - sub -> true - )); + return clubSubscribeRepository + .findByClubIdInAndUser_LoginUserId(clubIds, loginUserId) + .stream() + .map(sub -> sub.getClub().getId()) + .toList(); } } diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java index f3b5fde1a..fcfcb805a 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java @@ -23,5 +23,5 @@ public interface ClubQueryPort { Map countSubscribersByClubIds(List clubIds); - Map findSubscribedClubIds(List clubIds, Long loginUserId); + List findSubscribedClubIds(List clubIds, Long loginUserId); } diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index a9f65ac86..906bdc57b 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import static com.kustacks.kuring.common.exception.code.ErrorCode.CLUB_NOT_FOUND; @@ -71,10 +72,14 @@ public ClubListResult getClubs(ClubListCommand command, String email) { Map subscriberCountMap = clubQueryPort.countSubscribersByClubIds(clubIds); - Map subscribedMap = loginUserId != null - ? clubQueryPort.findSubscribedClubIds(clubIds, loginUserId) - : Map.of(); + List subscribedClubIds = List.of(); + if (loginUserId != null && !clubIds.isEmpty()) { + subscribedClubIds = clubQueryPort.findSubscribedClubIds(clubIds, loginUserId); + } + + Map subscribedMap = subscribedClubIds.stream() + .collect(Collectors.toMap(id -> id, id -> true)); List items = cursorBasedList.getContents() diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java index a339e7651..5de344daa 100644 --- a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java @@ -141,11 +141,7 @@ void getClubs_success() { )); when(clubQueryPort.findSubscribedClubIds(any(), anyLong())) - .thenReturn(Map.of( - 1L, true, - 2L, true, - 3L, true - )); + .thenReturn(List.of(1L, 2L, 3L)); // when ClubListResult result = clubQueryService.getClubs(command, email); @@ -201,7 +197,7 @@ void getClubs_withoutLogin() { //then assertThat(result.clubs().get(0).isSubscribed()).isFalse(); - verify(clubQueryPort, never()).existsSubscription(anyLong(), anyLong()); + verify(clubQueryPort, never()).findSubscribedClubIds(any(), anyLong()); } @Test From 3bbe835393b2b1f8af58574fde2e440c39a2c362 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Wed, 25 Feb 2026 20:16:24 +0900 Subject: [PATCH 17/34] =?UTF-8?q?[refactor]=20ClubDetailCommand=20?= =?UTF-8?q?=EB=8F=84=EC=9E=85=20=EB=B0=8F=20email=EC=9D=84=20Command?= =?UTF-8?q?=EC=97=90=20=ED=8F=AC=ED=95=A8=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/adapter/in/web/ClubQueryApiV2.java | 9 ++++++--- .../application/port/in/ClubQueryUseCase.java | 5 +++-- .../port/in/dto/ClubDetailCommand.java | 7 +++++++ .../port/in/dto/ClubListCommand.java | 3 ++- .../application/service/ClubQueryService.java | 9 ++++++--- .../service/ClubQueryServiceTest.java | 20 ++++++++++++------- 6 files changed, 37 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailCommand.java diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java index 3f4408702..cf30104b6 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java @@ -8,6 +8,7 @@ import com.kustacks.kuring.club.adapter.in.web.dto.ClubDivisionResponse; 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.ClubListCommand; import com.kustacks.kuring.club.application.port.in.dto.ClubListResult; @@ -76,9 +77,9 @@ public ResponseEntity> getClubs( ) { String email = resolveLoginEmail(bearerToken); - ClubListCommand command = new ClubListCommand(category, division, Cursor.from(cursor), size, sortBy); + ClubListCommand command = new ClubListCommand(category, division, Cursor.from(cursor), size, sortBy, email); - ClubListResult result = clubQueryUseCase.getClubs(command, email); + ClubListResult result = clubQueryUseCase.getClubs(command); ClubListResponse response = ClubListResponse.from(result); @@ -96,7 +97,9 @@ public ResponseEntity> getClubDetail( ) { String email = resolveLoginEmail(bearerToken); - ClubDetailResult result = clubQueryUseCase.getClubDetail(id, email); + ClubDetailCommand command = new ClubDetailCommand(id, email); + + ClubDetailResult result = clubQueryUseCase.getClubDetail(command); ClubDetailResponse response = ClubDetailResponse.from(result); diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java index c57184ce6..82d69f4e5 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubQueryUseCase.java @@ -1,5 +1,6 @@ package com.kustacks.kuring.club.application.port.in; +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; @@ -10,8 +11,8 @@ public interface ClubQueryUseCase { List getClubDivisions(); - ClubListResult getClubs(ClubListCommand command, String email); + ClubListResult getClubs(ClubListCommand command); - ClubDetailResult getClubDetail(Long id, String email); + ClubDetailResult getClubDetail(ClubDetailCommand command); } diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailCommand.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailCommand.java new file mode 100644 index 000000000..a7f152882 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailCommand.java @@ -0,0 +1,7 @@ +package com.kustacks.kuring.club.application.port.in.dto; + +public record ClubDetailCommand( + Long clubId, + String email +) { +} diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListCommand.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListCommand.java index a1422a099..da93febc5 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListCommand.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListCommand.java @@ -10,7 +10,8 @@ public record ClubListCommand( String division, Cursor cursor, int size, - String sortBy + String sortBy, + String email ) { public List divisionList() { if (division == null || division.isBlank()) { diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index 906bdc57b..1b33c8d9b 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -1,6 +1,7 @@ package com.kustacks.kuring.club.application.service; 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.ClubItemResult; @@ -43,8 +44,8 @@ public List getClubDivisions() { } @Override - public ClubListResult getClubs(ClubListCommand command, String email) { - + public ClubListResult getClubs(ClubListCommand command) { + String email = command.email(); Optional optionalRootUser = rootUserQueryPort.findRootUserByEmail(email); Long loginUserId = optionalRootUser.map(RootUser::getId).orElse(null); @@ -110,7 +111,9 @@ public ClubListResult getClubs(ClubListCommand command, String email) { } @Override - public ClubDetailResult getClubDetail(Long id, String email) { + public ClubDetailResult getClubDetail(ClubDetailCommand command) { + Long id = command.clubId(); + String email = command.email(); Optional optionalRootUser = rootUserQueryPort.findRootUserByEmail(email); Long loginUserId = optionalRootUser.map(RootUser::getId).orElse(null); diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java index 5de344daa..e70f58349 100644 --- a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java @@ -1,5 +1,6 @@ package com.kustacks.kuring.club.application.service; +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; @@ -117,7 +118,7 @@ void getClubs_success() { when(rootUserQueryPort.findRootUserByEmail(email)) .thenReturn(Optional.of(rootUser)); - ClubListCommand command = new ClubListCommand(category, divisions, cursor, size, sortBy); + ClubListCommand command = new ClubListCommand(category, divisions, cursor, size, sortBy, email); List divisionList = List.of("central", "engineering"); @@ -144,7 +145,7 @@ void getClubs_success() { .thenReturn(List.of(1L, 2L, 3L)); // when - ClubListResult result = clubQueryService.getClubs(command, email); + ClubListResult result = clubQueryService.getClubs(command); // then assertThat(result.totalCount()).isEqualTo(2); @@ -168,8 +169,9 @@ void getClubs_withoutLogin() { Cursor cursor = Cursor.from(null); int size = 10; String sortBy = "name"; + String email = null; - ClubListCommand command = new ClubListCommand(category, divisions, cursor, size, sortBy); + ClubListCommand command = new ClubListCommand(category, divisions, cursor, size, sortBy, email); List divisionList = List.of("central", "engineering"); @@ -193,7 +195,7 @@ void getClubs_withoutLogin() { )); //when - ClubListResult result = clubQueryService.getClubs(command, null); + ClubListResult result = clubQueryService.getClubs(command); //then assertThat(result.clubs().get(0).isSubscribed()).isFalse(); @@ -209,6 +211,8 @@ void getClubDetail_success() { String email = "test@test.com"; Long loginUserId = 100L; + ClubDetailCommand command = new ClubDetailCommand(clubId, email); + RootUser rootUser = mock(RootUser.class); when(rootUser.getId()).thenReturn(loginUserId); when(rootUserQueryPort.findRootUserByEmail(email)) @@ -246,7 +250,7 @@ void getClubDetail_success() { .thenReturn(true); // when - ClubDetailResult result = clubQueryService.getClubDetail(clubId, email); + ClubDetailResult result = clubQueryService.getClubDetail(command); // then assertThat(result.id()).isEqualTo(1L); @@ -272,6 +276,9 @@ void getClubDetail_success() { void getClubDetail_withoutLogin() { Long clubId = 1L; + String email = null; + + ClubDetailCommand command = new ClubDetailCommand(clubId, email); ClubDetailDto dto = new ClubDetailDto( 1L, "쿠링", "건국대 공지사항 앱 만드는 개발 동아리", @@ -292,8 +299,7 @@ void getClubDetail_withoutLogin() { .thenReturn(5); // when - ClubDetailResult result = - clubQueryService.getClubDetail(clubId, null); + ClubDetailResult result = clubQueryService.getClubDetail(command); // then assertThat(result.isSubscribed()).isFalse(); From 018f9b968631c16e449a7ab81f3b7ee880c98bd9 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Wed, 25 Feb 2026 20:36:10 +0900 Subject: [PATCH 18/34] =?UTF-8?q?[refactor]=20=EB=AF=B8=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=8B=9C=20loginUserId=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/application/service/ClubQueryService.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index 1b33c8d9b..e7012e76e 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -46,8 +46,7 @@ public List getClubDivisions() { @Override public ClubListResult getClubs(ClubListCommand command) { String email = command.email(); - Optional optionalRootUser = rootUserQueryPort.findRootUserByEmail(email); - Long loginUserId = optionalRootUser.map(RootUser::getId).orElse(null); + Optional rootUser = rootUserQueryPort.findRootUserByEmail(email); int limit = Math.min(command.size(), 30); @@ -75,7 +74,8 @@ public ClubListResult getClubs(ClubListCommand command) { List subscribedClubIds = List.of(); - if (loginUserId != null && !clubIds.isEmpty()) { + if (rootUser.isPresent()) { + Long loginUserId = rootUser.get().getId(); subscribedClubIds = clubQueryPort.findSubscribedClubIds(clubIds, loginUserId); } @@ -115,8 +115,7 @@ public ClubDetailResult getClubDetail(ClubDetailCommand command) { Long id = command.clubId(); String email = command.email(); - Optional optionalRootUser = rootUserQueryPort.findRootUserByEmail(email); - Long loginUserId = optionalRootUser.map(RootUser::getId).orElse(null); + Optional rootUser = rootUserQueryPort.findRootUserByEmail(email); ClubDetailDto dto = clubQueryPort.findClubDetailById(id) .orElseThrow(() -> new NotFoundException(CLUB_NOT_FOUND)); @@ -124,7 +123,8 @@ public ClubDetailResult getClubDetail(ClubDetailCommand command) { int subscriberCount = clubQueryPort.countSubscribers(id); boolean isSubscribed = false; - if (loginUserId != null) { + if (rootUser.isPresent()) { + Long loginUserId = rootUser.get().getId(); isSubscribed = clubQueryPort.existsSubscription(id, loginUserId); } From 0be4f6b78b86844ad339a3bb5276029dad1723d9 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Wed, 25 Feb 2026 22:04:08 +0900 Subject: [PATCH 19/34] =?UTF-8?q?[refactor]=20hasLocation()=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=84=20Service=EC=97=90=EC=84=9C=20ClubDetailDto?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/application/port/out/dto/ClubDetailDto.java | 7 +++++++ .../club/application/service/ClubQueryService.java | 10 +--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubDetailDto.java b/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubDetailDto.java index 83f9c589f..d60898ba6 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubDetailDto.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubDetailDto.java @@ -81,4 +81,11 @@ public ClubDetailDto( this.lon = lon; this.lat = lat; } + + public boolean hasLocation() { + return building != null + || room != null + || lon != null + || lat != null; + } } \ No newline at end of file diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index e7012e76e..75306506d 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -73,7 +73,6 @@ public ClubListResult getClubs(ClubListCommand command) { Map subscriberCountMap = clubQueryPort.countSubscribersByClubIds(clubIds); List subscribedClubIds = List.of(); - if (rootUser.isPresent()) { Long loginUserId = rootUser.get().getId(); subscribedClubIds = clubQueryPort.findSubscribedClubIds(clubIds, loginUserId); @@ -128,7 +127,7 @@ public ClubDetailResult getClubDetail(ClubDetailCommand command) { isSubscribed = clubQueryPort.existsSubscription(id, loginUserId); } - ClubDetailResult.Location location = hasLocation(dto) ? + ClubDetailResult.Location location = dto.hasLocation() ? new ClubDetailResult.Location( dto.getBuilding(), dto.getRoom(), @@ -159,13 +158,6 @@ public ClubDetailResult getClubDetail(ClubDetailCommand command) { ); } - private boolean hasLocation(ClubDetailDto dto) { - return dto.getBuilding() != null - || dto.getRoom() != null - || dto.getLon() != null - || dto.getLat() != null; - } - private String generateCursor(ClubReadModel club, String sortBy, LocalDateTime now) { return switch (sortBy) { case "name" -> club.getName() + "|" + club.getId(); From 18dbfc36f4e574b4dcaa98a01264b7922664453f Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Wed, 25 Feb 2026 22:44:44 +0900 Subject: [PATCH 20/34] =?UTF-8?q?[refactor]=20ClubDetailResponse=20Locatio?= =?UTF-8?q?n=20=EB=A7=A4=ED=95=91=20=EA=B5=AC=EC=A1=B0=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20from()=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/dto/ClubDetailResponse.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java index 02f670769..2de6aa35a 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java @@ -26,16 +26,6 @@ public record ClubDetailResponse( ) { public static ClubDetailResponse from(ClubDetailResult result) { - - Location location = result.location() == null ? - null - : new Location( - result.location().building(), - result.location().room(), - result.location().lon(), - result.location().lat() - ); - return new ClubDetailResponse( result.id(), result.name(), @@ -54,7 +44,7 @@ public static ClubDetailResponse from(ClubDetailResult result) { result.recruitEndAt(), result.applyUrl(), result.posterImageUrl(), - location + Location.from(result.location()) ); } @@ -64,5 +54,15 @@ public record Location( 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() + ); + } } } From 64f8efe5ae6da0ca6f617b48e0670470b29efb85 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Wed, 25 Feb 2026 23:34:02 +0900 Subject: [PATCH 21/34] =?UTF-8?q?[refactor]=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EA=B5=AC=EC=B2=B4=ED=99=94=20=EB=B0=8F=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=88=9C=EC=84=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/ClubPersistenceAdapter.java | 36 ++++++++++--------- .../out/persistence/ClubQueryRepository.java | 2 +- .../persistence/ClubQueryRepositoryImpl.java | 2 +- .../application/port/out/ClubQueryPort.java | 10 +++--- .../application/service/ClubQueryService.java | 2 +- .../service/ClubQueryServiceTest.java | 6 ++-- 6 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java index 52284fbd0..c093272ec 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java @@ -41,8 +41,8 @@ public List searchClubs( } @Override - public int countClubs(String category, List divisions) { - return clubRepository.countClubs(category, divisions); + public int countClubsByCategoryAndDivisions(String category, List divisions) { + return clubRepository.countClubsByCategoryAndDivisions(category, divisions); } @Override @@ -51,13 +51,26 @@ public Optional findClubDetailById(Long id) { } @Override - public int countSubscribers(Long clubId) { - return clubSubscribeRepository.countByClubId(clubId); + public boolean existsSubscription(Long clubId, Long loginUserId) { + return clubSubscribeRepository.existsByClubIdAndUser_LoginUserId(clubId, loginUserId); } @Override - public boolean existsSubscription(Long clubId, Long loginUserId) { - return clubSubscribeRepository.existsByClubIdAndUser_LoginUserId(clubId, loginUserId); + public List findSubscribedClubIds( + List clubIds, + Long loginUserId + ) { + return clubSubscribeRepository + .findByClubIdInAndUser_LoginUserId(clubIds, loginUserId) + .stream() + .map(sub -> sub.getClub().getId()) + .toList(); + } + + + @Override + public int countSubscribers(Long clubId) { + return clubSubscribeRepository.countByClubId(clubId); } @Override @@ -79,15 +92,4 @@ public Map countSubscribersByClubIds(List clubIds) { )); } - @Override - public List findSubscribedClubIds( - List clubIds, - Long loginUserId - ) { - return clubSubscribeRepository - .findByClubIdInAndUser_LoginUserId(clubIds, loginUserId) - .stream() - .map(sub -> sub.getClub().getId()) - .toList(); - } } diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java index cc65e55a8..f4909627e 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java @@ -11,7 +11,7 @@ public interface ClubQueryRepository { List searchClubs(String category, List divisions, String cursor, int size, String sortBy, LocalDateTime now); - int countClubs(String category, List divisions); + int countClubsByCategoryAndDivisions(String category, List divisions); Optional findClubDetailById(Long id); } diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java index 0a652d2d8..cc806bae1 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java @@ -63,7 +63,7 @@ public List searchClubs( @Override @Transactional(readOnly = true) - public int countClubs(String category, List divisions) { + public int countClubsByCategoryAndDivisions(String category, List divisions) { Long count = queryFactory .select(club.count()) diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java index fcfcb805a..de82f2dcc 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java @@ -13,15 +13,15 @@ public interface ClubQueryPort { List searchClubs(String category, List divisions, Cursor cursor, int size, String sortBy, LocalDateTime now); - int countClubs(String category, List divisions); + int countClubsByCategoryAndDivisions(String category, List divisions); Optional findClubDetailById(Long id); - int countSubscribers(Long clubId); - boolean existsSubscription(Long clubId, Long loginUserId); - Map countSubscribersByClubIds(List clubIds); - List findSubscribedClubIds(List clubIds, Long loginUserId); + + int countSubscribers(Long clubId); + + Map countSubscribersByClubIds(List clubIds); } diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index 75306506d..e7f6ac9d6 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -99,7 +99,7 @@ public ClubListResult getClubs(ClubListCommand command) { .toList(); - int totalCount = clubQueryPort.countClubs(command.category(), command.divisionList()); + int totalCount = clubQueryPort.countClubsByCategoryAndDivisions(command.category(), command.divisionList()); return new ClubListResult( items, diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java index e70f58349..66f57cadc 100644 --- a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java @@ -131,7 +131,7 @@ void getClubs_success() { any(LocalDateTime.class) )).thenReturn(mockReadModels); - when(clubQueryPort.countClubs(category, divisionList)) + when(clubQueryPort.countClubsByCategoryAndDivisions(category, divisionList)) .thenReturn(2); when(clubQueryPort.countSubscribersByClubIds(any())) @@ -157,7 +157,7 @@ void getClubs_success() { verify(rootUserQueryPort).findRootUserByEmail(email); verify(clubQueryPort).searchClubs(eq(category), eq(divisionList), eq(cursor), eq(size + 1), eq(sortBy), any(LocalDateTime.class)); - verify(clubQueryPort).countClubs(category, divisionList); + verify(clubQueryPort).countClubsByCategoryAndDivisions(category, divisionList); } @Test @@ -184,7 +184,7 @@ void getClubs_withoutLogin() { any(LocalDateTime.class) )).thenReturn(mockReadModels); - when(clubQueryPort.countClubs(category, divisionList)) + when(clubQueryPort.countClubsByCategoryAndDivisions(category, divisionList)) .thenReturn(2); when(clubQueryPort.countSubscribersByClubIds(any())) From ab207035fa8463f087b3f1416782557cf70c20bc Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Fri, 27 Feb 2026 20:24:01 +0900 Subject: [PATCH 22/34] =?UTF-8?q?[comment]=20=EB=A8=B8=EC=A7=80=20?= =?UTF-8?q?=EC=A0=84=20=EC=A3=BC=EC=84=9D=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/out/persistence/ClubPersistenceAdapter.java | 1 + .../adapter/out/persistence/ClubQueryRepositoryImpl.java | 8 ++++++-- .../kuring/club/application/service/ClubQueryService.java | 3 +++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java index c093272ec..ac044488d 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java @@ -82,6 +82,7 @@ public Map countSubscribersByClubIds(List clubIds) { List subscriptions = clubSubscribeRepository.findByClubIdIn(clubIds); + // groupby로 해도 될듯 return subscriptions.stream() .collect(Collectors.groupingBy( sub -> sub.getClub().getId(), diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java index cc806bae1..687e43c83 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java @@ -65,6 +65,7 @@ public List searchClubs( @Transactional(readOnly = true) public int countClubsByCategoryAndDivisions(String category, List divisions) { + //jpa로 해도! Long count = queryFactory .select(club.count()) .from(club) @@ -74,6 +75,7 @@ public int countClubsByCategoryAndDivisions(String category, List divisi ) .fetchOne(); + //count는 null일수 없음 return count == null ? 0 : count.intValue(); } @@ -180,12 +182,13 @@ private BooleanExpression divisionIn(List divisions) { ); } + //서비스로 private BooleanExpression cursorCondition(String sortBy, String cursor, LocalDateTime now) { if (cursor == null || cursor.equals("0")) return null; try { - + // 언더바로 String[] parts = cursor.split("\\|"); return switch (sortBy) { @@ -205,7 +208,7 @@ private BooleanExpression cursorCondition(String sortBy, String cursor, LocalDat case "recruitEndDate" -> { if (parts.length < 3) yield null; - + //스트링 int lastGroup = Integer.parseInt(parts[0]); String lastDateStr = parts[1]; Long lastId = Long.parseLong(parts[2]); @@ -281,6 +284,7 @@ private NumberExpression recruitmentGroup(LocalDateTime now) { .otherwise(0); } + // 얘도 서비스쪽에서 해야될듯 private ClubRecruitmentStatus calculateRecruitmentStatus( LocalDateTime start, LocalDateTime end, diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index e7f6ac9d6..e3d086bb2 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -72,6 +72,7 @@ public ClubListResult getClubs(ClubListCommand command) { Map subscriberCountMap = clubQueryPort.countSubscribersByClubIds(clubIds); + // 구독 관련 메서드화 하면 좋을듯! List subscribedClubIds = List.of(); if (rootUser.isPresent()) { Long loginUserId = rootUser.get().getId(); @@ -81,6 +82,7 @@ public ClubListResult getClubs(ClubListCommand command) { Map subscribedMap = subscribedClubIds.stream() .collect(Collectors.toMap(id -> id, id -> true)); + //items -> clubItemResults로 이름 수정? List items = cursorBasedList.getContents() .stream() @@ -116,6 +118,7 @@ public ClubDetailResult getClubDetail(ClubDetailCommand command) { Optional rootUser = rootUserQueryPort.findRootUserByEmail(email); + // dto -> readmodel로 이름 수정 ClubDetailDto dto = clubQueryPort.findClubDetailById(id) .orElseThrow(() -> new NotFoundException(CLUB_NOT_FOUND)); From cd4eb4b21c087535fd11658154c8fd4469d75e98 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Fri, 27 Feb 2026 20:55:56 +0900 Subject: [PATCH 23/34] =?UTF-8?q?[merge]=20develop=20=EB=B8=8C=EB=9E=9C?= =?UTF-8?q?=EC=B9=98=20=EB=A8=B8=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/ClubPersistenceAdapter.java | 49 +- .../out/persistence/ClubQueryRepository.java | 3 + .../persistence/ClubQueryRepositoryImpl.java | 23 +- .../persistence/ClubSubscribeRepository.java | 10 +- .../port/in/ClubNotificationUseCase.java | 6 + .../port/in/ClubSubscriptionUseCase.java | 10 + .../port/in/dto/ClubSubscriptionCommand.java | 7 + .../application/port/out/ClubQueryPort.java | 11 +- .../port/out/ClubSubscriptionCommandPort.java | 11 + .../port/out/ClubSubscriptionQueryPort.java | 8 + .../service/ClubCommandService.java | 97 + .../service/ClubNotificationService.java | 82 + .../com/kustacks/kuring/club/domain/Club.java | 1 + .../kuring/club/domain/ClubSubscribe.java | 13 +- .../common/dto/ResponseCodeAndMessages.java | 2 + .../common/exception/code/ErrorCode.java | 3 + .../common/featureflag/KuringFeatures.java | 3 +- .../port/out/dto/NoticeMessageDto.java | 16 +- .../kuring/message/domain/MessageType.java | 3 +- .../in/web/UserClubSubscriptionApiV2.java | 80 + .../UserClubSubscriptionCountResponse.java | 6 + .../web/dto/UserClubSubscriptionRequest.java | 8 + .../ClubNotificationScheduler.java | 31 + src/main/resources/ai/data/vectorstore.json | 9290 +++++++++++++++++ ...217__Alter_club_subscribe_to_root_user.sql | 37 + .../kuring/acceptance/UserAcceptanceTest.java | 61 +- .../kustacks/kuring/acceptance/UserStep.java | 40 + .../kuring/archunit/DependencyRuleTests.java | 326 +- .../ClubPersistenceAdapterTest.java | 54 + .../service/ClubCommandServiceTest.java | 192 + .../service/ClubNotificationServiceTest.java | 105 + .../kuring/support/DatabaseConfigurator.java | 12 + .../ClubNotificationSchedulerTest.java | 46 + 33 files changed, 10472 insertions(+), 174 deletions(-) create mode 100644 src/main/java/com/kustacks/kuring/club/application/port/in/ClubNotificationUseCase.java create mode 100644 src/main/java/com/kustacks/kuring/club/application/port/in/ClubSubscriptionUseCase.java create mode 100644 src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubSubscriptionCommand.java create mode 100644 src/main/java/com/kustacks/kuring/club/application/port/out/ClubSubscriptionCommandPort.java create mode 100644 src/main/java/com/kustacks/kuring/club/application/port/out/ClubSubscriptionQueryPort.java create mode 100644 src/main/java/com/kustacks/kuring/club/application/service/ClubCommandService.java create mode 100644 src/main/java/com/kustacks/kuring/club/application/service/ClubNotificationService.java create mode 100644 src/main/java/com/kustacks/kuring/user/adapter/in/web/UserClubSubscriptionApiV2.java create mode 100644 src/main/java/com/kustacks/kuring/user/adapter/in/web/dto/UserClubSubscriptionCountResponse.java create mode 100644 src/main/java/com/kustacks/kuring/user/adapter/in/web/dto/UserClubSubscriptionRequest.java create mode 100644 src/main/java/com/kustacks/kuring/worker/notification/ClubNotificationScheduler.java create mode 100644 src/main/resources/ai/data/vectorstore.json create mode 100644 src/main/resources/db/migration/V260217__Alter_club_subscribe_to_root_user.sql create mode 100644 src/test/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapterTest.java create mode 100644 src/test/java/com/kustacks/kuring/club/application/service/ClubCommandServiceTest.java create mode 100644 src/test/java/com/kustacks/kuring/club/application/service/ClubNotificationServiceTest.java create mode 100644 src/test/java/com/kustacks/kuring/worker/notification/ClubNotificationSchedulerTest.java diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java index ac044488d..d8c58a65b 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java @@ -1,13 +1,18 @@ package com.kustacks.kuring.club.adapter.out.persistence; 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.ClubSubscribe; import com.kustacks.kuring.common.annotation.PersistenceAdapter; import com.kustacks.kuring.common.data.Cursor; +import com.kustacks.kuring.user.domain.RootUser; import lombok.RequiredArgsConstructor; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Map; @@ -16,7 +21,7 @@ @PersistenceAdapter @RequiredArgsConstructor -public class ClubPersistenceAdapter implements ClubQueryPort { +public class ClubPersistenceAdapter implements ClubQueryPort, ClubSubscriptionCommandPort, ClubSubscriptionQueryPort { private final ClubRepository clubRepository; private final ClubSubscribeRepository clubSubscribeRepository; @@ -50,9 +55,14 @@ public Optional findClubDetailById(Long id) { return clubRepository.findClubDetailById(id); } +// @Override +// public boolean existsSubscription(Long clubId, Long loginUserId) { +// return clubSubscribeRepository.existsByClubIdAndUser_LoginUserId(clubId, loginUserId); +// } + @Override - public boolean existsSubscription(Long clubId, Long loginUserId) { - return clubSubscribeRepository.existsByClubIdAndUser_LoginUserId(clubId, loginUserId); + public Optional findClubById(Long id) { + return clubRepository.findById(id); } @Override @@ -93,4 +103,37 @@ public Map countSubscribersByClubIds(List clubIds) { )); } + @Override + public List findClubsBetweenDates(LocalDateTime start, LocalDateTime end) { + return clubRepository.findClubsBetweenDates(start, end); + } + + @Override + public List findNextDayRecruitEndClubs(LocalDateTime now) { + LocalDate tomorrow = now.toLocalDate().plusDays(1); + LocalDateTime startInclusive = tomorrow.atStartOfDay(); + LocalDateTime endExclusive = tomorrow.plusDays(1).atStartOfDay(); + return findClubsBetweenDates(startInclusive, endExclusive); + } + + + @Override + public boolean existsSubscription(Long rootUserId, Long clubId) { + return clubSubscribeRepository.existsByRootUserIdAndClubId(rootUserId, clubId); + } + + @Override + public void saveSubscription(RootUser rootUser, Club club) { + clubSubscribeRepository.save(new ClubSubscribe(rootUser, club)); + } + + @Override + public void deleteSubscription(RootUser rootUser, Club club) { + clubSubscribeRepository.deleteByRootUserAndClub(rootUser, club); + } + + @Override + public long countSubscriptions(Long rootUserId) { + return clubSubscribeRepository.countByRootUserId(rootUserId); + } } diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java index f4909627e..896137e04 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java @@ -2,6 +2,7 @@ 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 java.time.LocalDateTime; import java.util.List; @@ -14,4 +15,6 @@ public interface ClubQueryRepository { int countClubsByCategoryAndDivisions(String category, List divisions); Optional findClubDetailById(Long id); + + List findClubsBetweenDates(LocalDateTime start, LocalDateTime end); } diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java index 687e43c83..07b372223 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java @@ -3,6 +3,7 @@ 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.application.port.out.dto.QClubReadModel; +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.ClubRecruitmentStatus; @@ -28,6 +29,26 @@ class ClubQueryRepositoryImpl implements ClubQueryRepository { private final JPAQueryFactory queryFactory; + @Override + public List findClubsBetweenDates(LocalDateTime start, LocalDateTime end) { + return queryFactory.selectFrom(club) + .where( + club.isAlways.isFalse(), + recruitEndAtGoe(start), + recruitEndAtLt(end) + ) + .fetch(); + } + + private BooleanExpression recruitEndAtGoe(LocalDateTime start) { + return start != null ? club.recruitEndAt.goe(start) : null; + } + + private BooleanExpression recruitEndAtLt(LocalDateTime end) { + return end != null ? club.recruitEndAt.lt(end) : null; + } + + @Override @Transactional(readOnly = true) public List searchClubs( @@ -306,6 +327,4 @@ private ClubRecruitmentStatus calculateRecruitmentStatus( return ClubRecruitmentStatus.RECRUITING; } - - } diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java index 253dbded5..32f24ef8a 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java @@ -1,14 +1,22 @@ package com.kustacks.kuring.club.adapter.out.persistence; +import com.kustacks.kuring.club.domain.Club; import com.kustacks.kuring.club.domain.ClubSubscribe; +import com.kustacks.kuring.user.domain.RootUser; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; -public interface ClubSubscribeRepository extends JpaRepository { +interface ClubSubscribeRepository extends JpaRepository { int countByClubId(Long clubId); + long countByRootUserId(Long rootUserId); + + boolean existsByRootUserIdAndClubId(Long rootUserId, Long clubId); + + void deleteByRootUserAndClub(RootUser rootUser, Club club); + boolean existsByClubIdAndUser_LoginUserId(Long clubId, Long loginUserId); List findByClubIdIn(List clubIds); diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/ClubNotificationUseCase.java b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubNotificationUseCase.java new file mode 100644 index 000000000..acebfc78e --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubNotificationUseCase.java @@ -0,0 +1,6 @@ +package com.kustacks.kuring.club.application.port.in; + +public interface ClubNotificationUseCase { + + void sendDeadlineNotifications(); +} diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/ClubSubscriptionUseCase.java b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubSubscriptionUseCase.java new file mode 100644 index 000000000..85891f22d --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/ClubSubscriptionUseCase.java @@ -0,0 +1,10 @@ +package com.kustacks.kuring.club.application.port.in; + +import com.kustacks.kuring.club.application.port.in.dto.ClubSubscriptionCommand; + +public interface ClubSubscriptionUseCase { + + long addSubscription(ClubSubscriptionCommand command); + + long removeSubscription(ClubSubscriptionCommand command); +} diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubSubscriptionCommand.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubSubscriptionCommand.java new file mode 100644 index 000000000..1175fea28 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubSubscriptionCommand.java @@ -0,0 +1,7 @@ +package com.kustacks.kuring.club.application.port.in.dto; + +public record ClubSubscriptionCommand( + String email, + Long clubId +) { +} diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java index de82f2dcc..f063357e0 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java @@ -2,6 +2,7 @@ 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.common.data.Cursor; import java.time.LocalDateTime; @@ -11,12 +12,14 @@ public interface ClubQueryPort { + Optional findClubById(Long id); + + Optional findClubDetailById(Long id); + List searchClubs(String category, List divisions, Cursor cursor, int size, String sortBy, LocalDateTime now); int countClubsByCategoryAndDivisions(String category, List divisions); - Optional findClubDetailById(Long id); - boolean existsSubscription(Long clubId, Long loginUserId); List findSubscribedClubIds(List clubIds, Long loginUserId); @@ -24,4 +27,8 @@ public interface ClubQueryPort { int countSubscribers(Long clubId); Map countSubscribersByClubIds(List clubIds); + + List findClubsBetweenDates(LocalDateTime start, LocalDateTime end); + + List findNextDayRecruitEndClubs(LocalDateTime now); } diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubSubscriptionCommandPort.java b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubSubscriptionCommandPort.java new file mode 100644 index 000000000..75999a458 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubSubscriptionCommandPort.java @@ -0,0 +1,11 @@ +package com.kustacks.kuring.club.application.port.out; + +import com.kustacks.kuring.club.domain.Club; +import com.kustacks.kuring.user.domain.RootUser; + +public interface ClubSubscriptionCommandPort { + + void saveSubscription(RootUser rootUser, Club club); + + void deleteSubscription(RootUser rootUser, Club club); +} diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubSubscriptionQueryPort.java b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubSubscriptionQueryPort.java new file mode 100644 index 000000000..d912a4783 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubSubscriptionQueryPort.java @@ -0,0 +1,8 @@ +package com.kustacks.kuring.club.application.port.out; + +public interface ClubSubscriptionQueryPort { + + boolean existsSubscription(Long rootUserId, Long clubId); + + long countSubscriptions(Long rootUserId); +} diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubCommandService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubCommandService.java new file mode 100644 index 000000000..f8efbe602 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubCommandService.java @@ -0,0 +1,97 @@ +package com.kustacks.kuring.club.application.service; + +import com.kustacks.kuring.club.application.port.in.ClubSubscriptionUseCase; +import com.kustacks.kuring.club.application.port.in.dto.ClubSubscriptionCommand; +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.domain.Club; +import com.kustacks.kuring.common.annotation.UseCase; +import com.kustacks.kuring.common.exception.InvalidStateException; +import com.kustacks.kuring.common.exception.code.ErrorCode; +import com.kustacks.kuring.common.properties.ServerProperties; +import com.kustacks.kuring.user.application.port.out.RootUserQueryPort; +import com.kustacks.kuring.user.application.port.out.UserEventPort; +import com.kustacks.kuring.user.application.port.out.UserQueryPort; +import com.kustacks.kuring.user.domain.RootUser; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@UseCase +@Transactional +@RequiredArgsConstructor +public class ClubCommandService implements ClubSubscriptionUseCase { + + private static final String CLUB_TOPIC_PREFIX = "club."; + + private final ServerProperties serverProperties; + private final ClubQueryPort clubQueryPort; + private final ClubSubscriptionCommandPort clubSubscriptionCommandPort; + private final ClubSubscriptionQueryPort countSubscriptionsQueryPort; + private final RootUserQueryPort rootUserQueryPort; + private final UserQueryPort userQueryPort; + private final UserEventPort userEventPort; + + @Override + public long addSubscription(ClubSubscriptionCommand command) { + RootUser rootUser = findRootUserByEmail(command.email()); + Club club = findClubById(command.clubId()); + + if (isAlreadySubscription(rootUser, club)) { + throw new InvalidStateException(ErrorCode.CLUB_ALREADY_SUBSCRIBED); + } + + clubSubscriptionCommandPort.saveSubscription(rootUser, club); + subscribeAllLoggedInDevices(rootUser.getId(), makeTopic(club)); + + return countSubscriptionsQueryPort.countSubscriptions(rootUser.getId()); + } + + @Override + public long removeSubscription(ClubSubscriptionCommand command) { + RootUser rootUser = findRootUserByEmail(command.email()); + Club club = findClubById(command.clubId()); + + if (!isAlreadySubscription(rootUser, club)) { + throw new InvalidStateException(ErrorCode.CLUB_NOT_SUBSCRIBED); + } + clubSubscriptionCommandPort.deleteSubscription(rootUser, club); + unsubscribeAllLoggedInDevices(rootUser.getId(), makeTopic(club)); + + return countSubscriptionsQueryPort.countSubscriptions(rootUser.getId()); + } + + private boolean isAlreadySubscription(RootUser rootUser, Club club) { + return countSubscriptionsQueryPort.existsSubscription(rootUser.getId(), club.getId()); + } + + private RootUser findRootUserByEmail(String email) { + return rootUserQueryPort.findRootUserByEmail(email) + .orElseThrow(() -> new InvalidStateException(ErrorCode.ROOT_USER_NOT_FOUND)); + } + + private Club findClubById(Long id) { + return clubQueryPort.findClubById(id) + .orElseThrow(() -> new InvalidStateException(ErrorCode.CLUB_NOT_FOUND)); + } + + private void subscribeAllLoggedInDevices(Long rootUserId, String topic) { + userQueryPort.findByLoggedInUserId(rootUserId) + .forEach(user -> userEventPort.subscribeEvent(user.getFcmToken(), topic)); + + log.info("동아리 토픽 구독 완료. rootUserId={}, topic={}", rootUserId, topic); + } + + private void unsubscribeAllLoggedInDevices(Long rootUserId, String topic) { + userQueryPort.findByLoggedInUserId(rootUserId) + .forEach(user -> userEventPort.unsubscribeEvent(user.getFcmToken(), topic)); + + log.info("동아리 토픽 구독 해제 완료. rootUserId={}, topic={}", rootUserId, topic); + } + + private String makeTopic(Club club) { + return serverProperties.ifDevThenAddSuffix(CLUB_TOPIC_PREFIX + club.getId()); + } +} diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubNotificationService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubNotificationService.java new file mode 100644 index 000000000..80c740a22 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubNotificationService.java @@ -0,0 +1,82 @@ +package com.kustacks.kuring.club.application.service; + +import com.google.firebase.messaging.Message; +import com.google.firebase.messaging.Notification; +import com.kustacks.kuring.club.application.port.in.ClubNotificationUseCase; +import com.kustacks.kuring.club.application.port.out.ClubQueryPort; +import com.kustacks.kuring.club.domain.Club; +import com.kustacks.kuring.common.annotation.UseCase; +import com.kustacks.kuring.common.properties.ServerProperties; +import com.kustacks.kuring.message.application.port.out.FirebaseMessagingPort; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +import static com.kustacks.kuring.message.domain.MessageType.CLUB; + +@Slf4j +@UseCase +@RequiredArgsConstructor +public class ClubNotificationService implements ClubNotificationUseCase { + + private static final String CLUB_TOPIC_PREFIX = "club."; + private static final String D_DAY_1_TITLE = "[D-1] %s 동아리 모집"; + private static final String D_DAY_1_BODY = "내일 마감되기 전에 지원하세요!"; + + private final ClubQueryPort clubQueryPort; + private final FirebaseMessagingPort firebaseMessagingPort; + private final ServerProperties serverProperties; + + @Override + public void sendDeadlineNotifications() { + List clubs = findDeadlineClubs(); + clubs.forEach(this::sendDeadLineClubNotification); + } + + private void sendDeadLineClubNotification(Club club) { + try { + Message message = buildMessage(club); + firebaseMessagingPort.send(message); + log.info("동아리 마감 알림 발송 완료. clubId={}", club.getId()); + } catch (Exception e) { + log.error("동아리 마감 알림 발송 실패. clubId={}", club.getId(), e); + } + } + + private List findDeadlineClubs() { + return clubQueryPort.findNextDayRecruitEndClubs(LocalDateTime.now()); + } + + private Message buildMessage(Club club) { + String messageTitle = String.format(D_DAY_1_TITLE, club.getName()); + + return Message.builder() + .setNotification(buildNotification(messageTitle, D_DAY_1_BODY)) + .setTopic(buildTopic(club)) + .putAllData(buildMessageData(messageTitle, D_DAY_1_BODY, club)) + .build(); + } + + private Map buildMessageData(String title, String body, Club club) { + return Map.of( + "clubId", String.valueOf(club.getId()), + "title", title, + "body", body, + "messageType", CLUB.getValue() + ); + } + + private String buildTopic(Club club) { + return serverProperties.ifDevThenAddSuffix(CLUB_TOPIC_PREFIX + club.getId()); + } + + private Notification buildNotification(String title, String body) { + return Notification.builder() + .setTitle(title) + .setBody(body) + .build(); + } +} diff --git a/src/main/java/com/kustacks/kuring/club/domain/Club.java b/src/main/java/com/kustacks/kuring/club/domain/Club.java index 944e18177..dae03fbbf 100644 --- a/src/main/java/com/kustacks/kuring/club/domain/Club.java +++ b/src/main/java/com/kustacks/kuring/club/domain/Club.java @@ -74,4 +74,5 @@ public class Club { @Column(columnDefinition = "TEXT") private String qualifications; + } diff --git a/src/main/java/com/kustacks/kuring/club/domain/ClubSubscribe.java b/src/main/java/com/kustacks/kuring/club/domain/ClubSubscribe.java index 03fe7ca68..a4ce7796e 100644 --- a/src/main/java/com/kustacks/kuring/club/domain/ClubSubscribe.java +++ b/src/main/java/com/kustacks/kuring/club/domain/ClubSubscribe.java @@ -1,6 +1,6 @@ package com.kustacks.kuring.club.domain; -import com.kustacks.kuring.user.domain.User; +import com.kustacks.kuring.user.domain.RootUser; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -18,7 +18,7 @@ @Table( name = "club_subscribe", uniqueConstraints = { - @UniqueConstraint(columnNames = {"club_id", "user_id"}) + @UniqueConstraint(columnNames = {"club_id", "root_user_id"}) } ) @Getter @@ -34,6 +34,11 @@ public class ClubSubscribe { private Club club; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; + @JoinColumn(name = "root_user_id", nullable = false) + private RootUser rootUser; + + public ClubSubscribe(RootUser rootUser, Club club) { + this.rootUser = rootUser; + this.club = club; + } } diff --git a/src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java b/src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java index 2484bb05a..fd9d73890 100644 --- a/src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java +++ b/src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java @@ -66,6 +66,8 @@ public enum ResponseCodeAndMessages { CLUB_DIVISION_SEARCH_SUCCESS(HttpStatus.OK.value(), "지원하는 동아리 소속 조회에 성공하였습니다"), CLUB_LIST_SEARCH_SUCCESS(HttpStatus.OK.value(), "동아리 목록 조회에 성공하였습니다"), CLUB_DETAIL_SEARCH_SUCCESS(HttpStatus.OK.value(), "동아리 상세 조회에 성공하였습니다"), + CLUB_SUBSCRIPTION_ADD_SUCCESS(HttpStatus.OK.value(), "구독에 성공했습니다."), + CLUB_SUBSCRIPTION_DELETE_SUCCESS(HttpStatus.OK.value(), "구독이 취소되었습니다."), /** * ErrorCodes about auth diff --git a/src/main/java/com/kustacks/kuring/common/exception/code/ErrorCode.java b/src/main/java/com/kustacks/kuring/common/exception/code/ErrorCode.java index fe3c30365..3da5889e7 100644 --- a/src/main/java/com/kustacks/kuring/common/exception/code/ErrorCode.java +++ b/src/main/java/com/kustacks/kuring/common/exception/code/ErrorCode.java @@ -60,6 +60,9 @@ public enum ErrorCode { CLUB_CATEGORY_NOT_SUPPORTED(HttpStatus.BAD_REQUEST, "서버에서 지원하지 않는 동아리 카테고리입니다."), CLUB_DIVISION_NOT_SUPPORTED(HttpStatus.BAD_REQUEST, "서버에서 지원하지 않는 동아리 소속입니다."), CLUB_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 동아리를 찾을 수 없습니다."), + CLUB_ALREADY_SUBSCRIBED(HttpStatus.BAD_REQUEST, "이미 구독한 동아리입니다."), + CLUB_NOT_SUBSCRIBED(HttpStatus.BAD_REQUEST, "구독하지 않은 동아리입니다."), + STAFF_SCRAPER_EXCEED_RETRY_LIMIT("교직원 업데이트 재시도 횟수를 초과했습니다."), STAFF_SCRAPER_CANNOT_SCRAP("건국대학교 홈페이지가 불안정합니다. 교직원 정보를 가져올 수 없습니다."), diff --git a/src/main/java/com/kustacks/kuring/common/featureflag/KuringFeatures.java b/src/main/java/com/kustacks/kuring/common/featureflag/KuringFeatures.java index 661c8f3f8..87eefa3db 100644 --- a/src/main/java/com/kustacks/kuring/common/featureflag/KuringFeatures.java +++ b/src/main/java/com/kustacks/kuring/common/featureflag/KuringFeatures.java @@ -10,7 +10,8 @@ public enum KuringFeatures { UPDATE_USER(new Feature("update_user")), UPDATE_STAFF(new Feature("update_staff")), UPDATE_ACADEMIC_EVENT(new Feature("update_academic_event")), - NOTIFY_ACADEMIC_EVENT(new Feature("notify_academic_event")); + NOTIFY_ACADEMIC_EVENT(new Feature("notify_academic_event")), + NOTIFY_CLUB_DEADLINE(new Feature("notify_club_deadline")); private final Feature feature; diff --git a/src/main/java/com/kustacks/kuring/message/application/port/out/dto/NoticeMessageDto.java b/src/main/java/com/kustacks/kuring/message/application/port/out/dto/NoticeMessageDto.java index 5ce298555..7e1c3f7ec 100644 --- a/src/main/java/com/kustacks/kuring/message/application/port/out/dto/NoticeMessageDto.java +++ b/src/main/java/com/kustacks/kuring/message/application/port/out/dto/NoticeMessageDto.java @@ -1,14 +1,18 @@ package com.kustacks.kuring.message.application.port.out.dto; -import com.kustacks.kuring.notice.domain.*; -import lombok.*; +import com.kustacks.kuring.notice.domain.DepartmentNotice; +import com.kustacks.kuring.notice.domain.Notice; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import org.springframework.util.Assert; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class NoticeMessageDto { - private Long id; + private String id; private String type; @@ -25,7 +29,7 @@ public class NoticeMessageDto { private String baseUrl; @Builder - private NoticeMessageDto(Long id, String articleId, String postedDate, String subject, String category, String categoryKorName, String baseUrl) { + private NoticeMessageDto(String id, String articleId, String postedDate, String subject, String category, String categoryKorName, String baseUrl) { Assert.notNull(id, "id must not be empty"); Assert.notNull(articleId, "articleId must not be null"); Assert.notNull(postedDate, "postedDate must not be null"); @@ -46,7 +50,7 @@ private NoticeMessageDto(Long id, String articleId, String postedDate, String su public static NoticeMessageDto from(Notice notice) { return NoticeMessageDto.builder() - .id(notice.getId()) + .id(String.valueOf(notice.getId())) .articleId(notice.getArticleId()) .postedDate(notice.getPostedDate()) .subject(notice.getSubject()) @@ -58,7 +62,7 @@ public static NoticeMessageDto from(Notice notice) { public static NoticeMessageDto from(DepartmentNotice departmentNotice) { return NoticeMessageDto.builder() - .id(departmentNotice.getId()) + .id(String.valueOf(departmentNotice.getId())) .articleId(departmentNotice.getArticleId()) .postedDate(departmentNotice.getPostedDate()) .subject(departmentNotice.getSubject()) diff --git a/src/main/java/com/kustacks/kuring/message/domain/MessageType.java b/src/main/java/com/kustacks/kuring/message/domain/MessageType.java index 35f830d68..1a0534abf 100644 --- a/src/main/java/com/kustacks/kuring/message/domain/MessageType.java +++ b/src/main/java/com/kustacks/kuring/message/domain/MessageType.java @@ -9,7 +9,8 @@ public enum MessageType { NOTICE("notice"), ADMIN("admin"), - ACADEMIC("academic"); + ACADEMIC("academic"), + CLUB("club"); private final String value; } diff --git a/src/main/java/com/kustacks/kuring/user/adapter/in/web/UserClubSubscriptionApiV2.java b/src/main/java/com/kustacks/kuring/user/adapter/in/web/UserClubSubscriptionApiV2.java new file mode 100644 index 000000000..fba0ddf41 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/user/adapter/in/web/UserClubSubscriptionApiV2.java @@ -0,0 +1,80 @@ +package com.kustacks.kuring.user.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.application.port.in.ClubSubscriptionUseCase; +import com.kustacks.kuring.club.application.port.in.dto.ClubSubscriptionCommand; +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 com.kustacks.kuring.user.adapter.in.web.dto.UserClubSubscriptionCountResponse; +import com.kustacks.kuring.user.adapter.in.web.dto.UserClubSubscriptionRequest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; + +import static com.kustacks.kuring.auth.authentication.AuthorizationExtractor.extractAuthorizationValue; +import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.CLUB_SUBSCRIPTION_ADD_SUCCESS; +import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.CLUB_SUBSCRIPTION_DELETE_SUCCESS; + +@Tag(name = "User-Club-Subscription", description = "동아리 구독") +@Validated +@RequiredArgsConstructor +@RestWebAdapter(path = "/api/v2/users/subscriptions/clubs") +class UserClubSubscriptionApiV2 { + + private static final String FCM_TOKEN_HEADER_KEY = "User-Token"; + private static final String JWT_TOKEN_HEADER_KEY = "JWT"; + + + private final ClubSubscriptionUseCase clubSubscriptionUseCase; + private final JwtTokenProvider jwtTokenProvider; + + @Operation(summary = "사용자 동아리 구독 추가") + @SecurityRequirement(name = FCM_TOKEN_HEADER_KEY) + @SecurityRequirement(name = JWT_TOKEN_HEADER_KEY) + @PostMapping + public ResponseEntity> addSubscription( + @RequestHeader(FCM_TOKEN_HEADER_KEY) String userToken, + @RequestHeader(AuthorizationExtractor.AUTHORIZATION) String bearerToken, + @Valid @RequestBody UserClubSubscriptionRequest request + ) { + String email = validateJwtAndGetEmail(extractAuthorizationValue(bearerToken, AuthorizationType.BEARER)); + long subscriptionCount = clubSubscriptionUseCase.addSubscription(new ClubSubscriptionCommand(email, request.id())); + + return ResponseEntity.ok(new BaseResponse<>(CLUB_SUBSCRIPTION_ADD_SUCCESS, new UserClubSubscriptionCountResponse(subscriptionCount))); + } + + @Operation(summary = "사용자 동아리 구독 제거") + @SecurityRequirement(name = FCM_TOKEN_HEADER_KEY) + @SecurityRequirement(name = JWT_TOKEN_HEADER_KEY) + @DeleteMapping("/{clubId}") + public ResponseEntity> deleteSubscription( + @RequestHeader(FCM_TOKEN_HEADER_KEY) String userToken, + @RequestHeader(AuthorizationExtractor.AUTHORIZATION) String bearerToken, + @PathVariable Long clubId + ) { + String email = validateJwtAndGetEmail(extractAuthorizationValue(bearerToken, AuthorizationType.BEARER)); + long subscriptionCount = clubSubscriptionUseCase.removeSubscription(new ClubSubscriptionCommand(email, clubId)); + + return ResponseEntity.ok(new BaseResponse<>(CLUB_SUBSCRIPTION_DELETE_SUCCESS, new UserClubSubscriptionCountResponse(subscriptionCount))); + } + + private String validateJwtAndGetEmail(String jwtToken) { + if (!jwtTokenProvider.validateToken(jwtToken)) { + throw new InvalidStateException(ErrorCode.JWT_INVALID_TOKEN); + } + return jwtTokenProvider.getPrincipal(jwtToken); + } +} diff --git a/src/main/java/com/kustacks/kuring/user/adapter/in/web/dto/UserClubSubscriptionCountResponse.java b/src/main/java/com/kustacks/kuring/user/adapter/in/web/dto/UserClubSubscriptionCountResponse.java new file mode 100644 index 000000000..c114ddbb4 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/user/adapter/in/web/dto/UserClubSubscriptionCountResponse.java @@ -0,0 +1,6 @@ +package com.kustacks.kuring.user.adapter.in.web.dto; + +public record UserClubSubscriptionCountResponse( + Long subscriptionCount +) { +} diff --git a/src/main/java/com/kustacks/kuring/user/adapter/in/web/dto/UserClubSubscriptionRequest.java b/src/main/java/com/kustacks/kuring/user/adapter/in/web/dto/UserClubSubscriptionRequest.java new file mode 100644 index 000000000..a0043fe1f --- /dev/null +++ b/src/main/java/com/kustacks/kuring/user/adapter/in/web/dto/UserClubSubscriptionRequest.java @@ -0,0 +1,8 @@ +package com.kustacks.kuring.user.adapter.in.web.dto; + +import jakarta.validation.constraints.NotNull; + +public record UserClubSubscriptionRequest( + @NotNull Long id +) { +} diff --git a/src/main/java/com/kustacks/kuring/worker/notification/ClubNotificationScheduler.java b/src/main/java/com/kustacks/kuring/worker/notification/ClubNotificationScheduler.java new file mode 100644 index 000000000..11206b023 --- /dev/null +++ b/src/main/java/com/kustacks/kuring/worker/notification/ClubNotificationScheduler.java @@ -0,0 +1,31 @@ +package com.kustacks.kuring.worker.notification; + +import com.kustacks.kuring.club.application.port.in.ClubNotificationUseCase; +import com.kustacks.kuring.common.featureflag.FeatureFlags; +import com.kustacks.kuring.common.featureflag.KuringFeatures; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ClubNotificationScheduler { + + private final ClubNotificationUseCase clubNotificationUseCase; + private final FeatureFlags featureFlags; + + @Scheduled(cron = "0 0 18 * * *", zone = "Asia/Seoul") + public void sendClubDeadlineNotifications() { + if (featureFlags.isEnabled(KuringFeatures.NOTIFY_CLUB_DEADLINE.getFeature())) { + log.info("******** 동아리 마감 임박 알림 발송 시작 ********"); + try { + clubNotificationUseCase.sendDeadlineNotifications(); + log.info("******** 동아리 마감 임박 알림 발송 완료 ********"); + } catch (Exception e) { + log.error("동아리 마감 임박 알림 발송 중 오류가 발생했습니다.", e); + } + } + } +} diff --git a/src/main/resources/ai/data/vectorstore.json b/src/main/resources/ai/data/vectorstore.json new file mode 100644 index 000000000..5d5bfe3c5 --- /dev/null +++ b/src/main/resources/ai/data/vectorstore.json @@ -0,0 +1,9290 @@ +{ + "8d3f0c19-be3f-44db-8a42-ee9759f26fb7": { + "embedding": [ + -0.023222258, + 0.03466137, + 0.02653358, + 0.00173495, + -0.02375981, + 0.012825998, + -3.6990317E-4, + 0.021480588, + -0.03485489, + -0.012471213, + 0.029135333, + -0.0011160929, + 0.027092634, + 0.012331449, + 0.020749517, + 0.00491054, + -0.043262206, + 0.023028739, + -0.025028434, + 0.026598087, + 0.026555082, + 7.088971E-4, + 0.0063592433, + 0.0065043825, + -0.011471366, + 0.0014513911, + 0.010455391, + -0.051088966, + -0.018008001, + -0.01716942, + 0.042724654, + -0.013395802, + -0.010256497, + 0.016857639, + -0.042724654, + 0.016212577, + 0.008057908, + 0.023372773, + 9.281176E-5, + -0.035757978, + -0.012912005, + 0.015029961, + -0.013997861, + -0.037499648, + -0.02653358, + 0.04360624, + 0.013965608, + 0.010794049, + 0.033177726, + 0.03547845, + -0.021211812, + 0.0145031605, + 0.010912311, + 0.031887602, + 0.012352951, + 0.07874066, + 0.03433884, + 0.018169267, + 0.043649245, + -0.036553554, + 0.020050699, + -0.0028544026, + -0.015814789, + 0.011116581, + -0.006380745, + -0.019169115, + -0.03930582, + 0.063259155, + -0.06024886, + -0.0037494272, + 0.007466601, + -0.024619894, + 0.075601354, + -0.01275074, + -0.0025560611, + 0.0050261137, + -0.029242843, + -0.010923062, + -0.020072201, + 0.021588098, + 0.033779785, + -0.0069666775, + -0.025243454, + 0.024641396, + 0.021061298, + -0.008289056, + -0.091598906, + -0.022168655, + -0.012729238, + -0.0077461284, + -0.034682874, + 0.029608378, + -0.03833823, + 0.05121798, + 0.08054683, + -0.0021569284, + 0.01439565, + -0.05895873, + -0.019416388, + 0.0069505507, + 0.0012000854, + -0.072118014, + 0.042961176, + -0.040853973, + 0.02726465, + 0.0033086343, + -0.061667997, + 0.015416999, + -0.025135944, + -0.017760728, + -0.066914506, + 0.015298737, + -0.012825998, + 0.016115816, + 0.05263712, + -0.0034833387, + -0.047992665, + 0.009138389, + -0.05061592, + -0.054959346, + -0.014180629, + 0.0627431, + 0.055991445, + -0.024877919, + 0.01821227, + 0.008068659, + 0.005864695, + 0.032747686, + -0.0832991, + -0.020319475, + 0.018889587, + -0.038488742, + -0.016051311, + -0.014782688, + 0.004131089, + 0.0062571084, + 0.015621269, + -0.017330686, + -0.033242233, + -0.06149598, + 0.045928467, + 0.009761949, + 0.013406553, + 0.023372773, + -0.022598697, + -0.0040719584, + -0.02767319, + 0.0031043643, + -0.0025654682, + -0.017835984, + 0.024942426, + 0.010939188, + -0.023114748, + -0.04648752, + 0.018373536, + 0.019996945, + -0.002171711, + 0.06128096, + -0.020534497, + -0.007015057, + -0.0012141962, + 0.009589933, + 0.02797422, + 0.031522065, + -0.004921291, + -0.026985124, + -0.028812801, + -0.032489657, + -0.07284908, + 0.015287986, + 0.022039643, + -0.046831552, + -0.017147917, + 0.0065258844, + -0.005821691, + 0.013331296, + -0.0070688124, + -0.03816621, + 0.016631868, + 0.013105525, + -0.0010441953, + 0.007998778, + -0.0018128951, + -7.727314E-4, + -0.049282793, + -0.023609295, + 0.03199511, + -0.046272498, + 0.022039643, + 0.037005097, + 0.05934577, + -0.016298585, + 0.017051158, + -0.006859167, + -0.032640174, + 0.011697138, + 0.010460767, + -0.038080204, + -0.034489352, + -0.037800677, + -0.017825233, + -0.004031642, + 0.032962706, + -0.005483033, + -0.014223633, + -0.03947784, + -0.01522348, + 0.03999389, + 0.04463834, + -0.01768547, + 0.04812168, + -0.006101218, + -0.021555847, + 0.014513912, + -0.008369689, + 0.076805465, + -0.0038381233, + 0.0031151155, + 0.0028221493, + -0.008993249, + -0.005821691, + -0.05194905, + -0.023329768, + 0.043477226, + 0.031973608, + 0.01912611, + 0.06695751, + 0.028554777, + 0.011019821, + -0.058141652, + -0.021749364, + 0.067688584, + 3.4772913E-4, + 0.029651383, + 0.012213187, + -0.023738308, + -0.03515592, + -0.003580098, + -0.040875476, + 0.02395333, + 0.021631103, + 0.02530796, + -0.046831552, + 0.034274332, + 0.018341284, + -0.0036768576, + 0.0060743405, + 0.007654744, + 0.075945385, + 0.030597474, + 0.02097529, + 0.0031178033, + 0.0015494945, + -0.04751962, + -0.01676088, + -0.024297362, + 0.024856417, + 0.049970858, + 0.053798232, + -0.02767319, + 0.016965149, + -0.027587183, + -0.04248813, + 0.014653675, + 0.016954398, + 0.018180018, + 0.017545706, + 0.05831367, + -0.048465714, + -0.007703124, + 0.007310711, + -0.014642924, + 0.009342658, + 0.01707266, + -0.028146237, + 0.040230412, + 0.0057088053, + 0.02642607, + 0.012965761, + 0.04923979, + -0.0421656, + 0.04648752, + 0.037091106, + -0.036639564, + 0.012406706, + 0.0062356065, + -0.011804648, + 0.00470627, + 0.010783299, + 0.038531747, + 0.046100482, + -0.01841654, + -0.02356629, + -0.02446938, + 0.037155613, + 0.013299043, + -0.015040712, + -0.028361257, + -0.020986041, + 0.028769797, + -0.037607156, + 0.03003842, + -0.0046901437, + -0.012976511, + -0.042294614, + 0.035800982, + 0.0339518, + 0.0050852443, + 0.009611434, + -0.0044428697, + 0.013223786, + 0.0076493686, + -0.03773617, + -0.0028006474, + 0.04713258, + 5.8962766E-4, + -0.023673803, + -0.03631703, + 0.04072496, + -0.021727862, + 0.04842271, + 0.0049804216, + 0.0015105219, + -0.031049019, + 0.03661806, + 0.01973892, + -0.010498396, + 0.010364008, + -0.001869338, + -0.029479366, + -0.010847805, + -0.0077461284, + -8.6075556E-4, + 0.017986499, + -0.0066871503, + 0.030081425, + 0.04502538, + 0.05173403, + -0.02631856, + -0.00822455, + 0.0072999597, + 0.005249198, + -0.040058397, + 0.018029504, + -0.046315502, + -0.09030878, + 0.07886967, + 0.040875476, + 0.03311322, + 0.024490882, + -0.0012887815, + 0.01572878, + -0.020749517, + 0.031887602, + 0.019136861, + -0.02240518, + 0.017620964, + -0.023286764, + -0.0043676123, + 0.015599767, + 0.004504688, + 0.040552944, + 0.0370266, + 0.012825998, + 0.037972692, + -0.0048514092, + -0.0349624, + -0.016707124, + 0.031414554, + -0.058657702, + -0.011772395, + -0.04298268, + 0.008573959, + 0.0022442807, + -0.017825233, + -0.033457253, + 0.024727404, + 0.030360952, + 0.03238215, + 8.8225765E-4, + 0.048465714, + 0.012084175, + -0.010062979, + -0.030188935, + 0.034016307, + 0.022684706, + 0.01757796, + 0.007192449, + 0.033930298, + 0.0390908, + 0.0054561556, + 0.027436668, + 0.029909408, + -0.04291817, + 0.02993091, + -0.012514217, + 0.029285848, + 0.042445127, + -0.031414554, + -0.063775204, + -0.0059292014, + 0.013460308, + -0.011847652, + -0.054271277, + 0.020685012, + 0.013019516, + 0.055604406, + -0.005595919, + 0.04678855, + -0.039499342, + 0.011525121, + -0.06304413, + 0.009143764, + 0.0045987596, + 0.011449863, + -0.04700357, + -0.031027516, + 0.02952237, + -0.017749976, + -0.09521126, + -0.017190922, + 0.016438348, + -0.014535413, + -0.043756753, + -0.055088356, + -0.035220426, + 0.008031031, + -0.0013707583, + 0.016782383, + -0.009890961, + -0.06588241, + -0.009450169, + -0.07422522, + -0.014868695, + -0.00858471, + -0.029887905, + 0.01976042, + 0.010536024, + -0.016019057, + 0.010611282, + 0.01810476, + 0.043348216, + -0.042423625, + 6.181851E-4, + -0.026404567, + -0.0062678596, + 0.021211812, + -0.0012659356, + 0.08166494, + -0.01121334, + -0.0058969483, + 0.0073160864, + 0.01100907, + 0.0057249316, + -0.019781923, + -0.027135639, + 0.033156224, + -0.039886378, + -0.018438043, + -0.0287913, + 0.061538983, + 0.06007684, + 0.030253442, + 0.0023491033, + 0.0034779632, + -0.019394886, + -0.04825069, + 0.03915531, + -0.004932042, + 0.007692373, + 6.0508226E-4, + -0.012965761, + -0.05061592, + -0.027479673, + -0.017008154, + -0.013653828, + -0.07624641, + -0.05973281, + 0.008934119, + -0.007934271, + -0.014320392, + -0.03416682, + -0.015180476, + -0.016492104, + 0.01593305, + 0.052680124, + 0.058270667, + 0.035951495, + 0.003488714, + 0.016395343, + 0.006251733, + 0.04545542, + 0.042036586, + -0.0072838333, + -0.017932743, + 0.024598392, + -0.0095684305, + -0.028834304, + -0.014051616, + 0.006348492, + -0.02324376, + -0.022641702, + -0.0015481506, + -0.05491634, + 0.051260985, + -0.022147153, + -0.018814329, + -0.022147153, + -0.016588863, + 0.018642312, + 0.018620811, + -0.00920827, + 0.012191686, + 0.016599614, + -0.0015172412, + 0.024555387, + -0.0025049935, + 0.008407317, + -0.026168045, + -0.012191686, + 0.005665801, + 0.0013358174, + 0.034747377, + -0.01830903, + -0.007289209, + -0.011750893, + 0.017190922, + -0.044595335, + 0.053325184, + -0.025867015, + -0.0041472157, + -0.010041476, + 7.962493E-4, + 0.004222473, + 0.035112914, + -0.017137166, + 0.051992055, + -0.01521273, + 0.034983903, + -0.02345878, + -0.011482117, + 0.001712104, + 0.0034833387, + -0.015911547, + 0.014030114, + 0.01625558, + 0.043799758, + 0.0042735403, + -0.02240518, + 0.008960997, + 0.013621574, + -0.0056765517, + 0.013385052, + -0.011557374, + -0.013793591, + 0.07121492, + 0.011159585, + -0.0028409637, + 0.0044670594, + -0.03868226, + 0.020007696, + -0.010245746, + -0.03270468, + -0.007783757, + -0.008901865, + -0.03952084, + -0.001171864, + -0.07826761, + -2.3971472E-4, + 0.0080794105, + -0.009041629, + 0.014202131, + -0.03382279, + 0.00878898, + 0.011353103, + 0.009761949, + -0.022383677, + -0.0064882557, + 0.0014540789, + -0.008031031, + -0.016535107, + 0.030296445, + 0.013213035, + -0.010068354, + -0.03487639, + 0.028038727, + -0.004440182, + 0.010299501, + -0.036768574, + -0.01563202, + 0.0051739407, + 0.039434835, + 0.02560899, + 0.009063131, + -0.008154668, + -0.013481811, + -0.019803425, + 0.030704986, + 0.022039643, + -0.039843373, + 0.0059560793, + 0.013976359, + 0.001892184, + -0.045111388, + 0.007643993, + -0.036145013, + 0.007955774, + -0.010853181, + -0.022534192, + 0.004391802, + 0.03382279, + 0.019136861, + -0.0037548025, + 0.005168565, + 0.010546776, + -0.009251274, + -0.02272771, + -0.0037789925, + -0.022878224, + 0.02436187, + 0.02838276, + 0.03558596, + 0.002748236, + 0.009890961, + 0.0195454, + 0.008498701, + -0.0021528967, + -0.014632173, + 0.023480283, + 0.027608685, + -0.020738766, + 0.025867015, + 0.048293695, + -0.031973608, + 0.010960691, + 0.0056120455, + 0.004389114, + -0.050142877, + 0.042036586, + -0.0442083, + 0.0032683178, + -0.0043622367, + 0.014739683, + 0.0035935368, + 0.012127179, + -0.02251269, + -0.0019566903, + -0.026340062, + -0.017137166, + -0.01965291, + 0.0010986225, + -0.02127632, + -0.020308726, + -0.02406084, + 0.011041324, + -0.04472435, + 0.011987415, + -0.0038891907, + -0.016115816, + 0.028748294, + -0.051863045, + 0.024748906, + -0.006160349, + -0.018997097, + -0.005585168, + -0.018352034, + -0.037413638, + -0.021072049, + 0.010643535, + 0.01378284, + -0.034317337, + 0.04915378, + -0.019706666, + -8.762102E-4, + -0.01665337, + 0.001408387, + 0.0021596162, + -0.007880516, + 0.013954856, + 0.029264346, + 0.0048594726, + 0.010503772, + -0.009170642, + -0.028554777, + -0.013589322, + -0.0060420875, + 0.040703457, + -0.02046999, + 0.025135944, + -0.015664274, + 0.028253747, + -0.010471518, + 0.0370266, + -0.030489964, + -0.033392746, + 0.0884166, + 0.0010327722, + -0.011998166, + 0.0066065174, + -0.0035317184, + 0.0123422, + -0.0034188323, + 0.0016435661, + -0.009272777, + -0.042122595, + -0.030489964, + -0.018395038, + 0.0046901437, + 0.040660452, + -0.0062893615, + -0.014836443, + 0.0034268957, + -0.016062062, + -0.01686839, + -0.015373995, + 0.015868543, + 0.001756452, + -0.049755838, + 0.023308266, + -0.00864384, + 0.0072838333, + 0.028984819, + 0.01357857, + -0.023630798, + -0.0075472337, + 0.01162188, + -0.027028129, + -0.020007696, + -0.003332824, + -0.007590238, + -0.012987263, + 0.026684094, + -0.016954398, + -0.024189852, + 0.022276167, + 0.034381844, + -0.026361562, + 0.01778223, + -0.0034107692, + -0.009176017, + -0.02272771, + -0.024813412, + 0.023136249, + 0.033027213, + -0.043326713, + 0.0068161627, + -0.018459545, + 1.8343971E-4, + -0.029264346, + 0.029801898, + -0.006665648, + 0.0051148096, + -8.661311E-4, + -0.009552304, + 0.033672273, + -0.07155896, + 0.01573953, + 0.014116122, + -0.007138694, + -9.870804E-4, + 0.015782535, + 0.0277807, + 0.010079105, + -0.0055690417, + 0.024125345, + -0.014438654, + -0.025802508, + -0.007520356, + -3.6351974E-4, + 0.015997555, + 0.004475123, + -0.04485336, + 0.031135028, + 0.045756448, + 0.04074646, + -0.022039643, + -0.0038461864, + -0.053841233, + -0.0174812, + -0.016029809, + -0.0052115694, + -0.001351272, + 0.03238215, + 0.0010200054, + -0.022125652, + -0.03726312, + 0.0047680885, + -9.111511E-4, + -0.011976665, + 0.010256497, + -0.019459393, + 4.6363883E-4, + -0.03221013, + -0.015427751, + -0.021824623, + -4.098836E-4, + -0.0020642008, + -0.012567972, + 0.03156507, + -0.027501173, + 0.021115053, + 0.022985734, + 0.002961913, + 0.016965149, + 0.04760563, + 0.0050852443, + 0.013589322, + 0.026340062, + 0.0029699763, + 4.2769E-4, + -0.008396567, + 0.018072506, + 0.026254052, + 0.015524509, + 0.0050852443, + 0.02952237, + -0.018158516, + -0.008165419, + -0.043047186, + -0.046315502, + 0.031199533, + 0.022082647, + -0.0012807183, + 0.025781007, + 0.008858861, + 0.029242843, + -0.009466295, + 0.012901254, + 0.005147063, + 0.003628478, + 0.01893259, + 0.025243454, + 0.027931215, + -0.009998472, + 0.020631256, + -0.008111664, + -0.026361562, + 0.009896337, + -0.0047331476, + -0.0043165446, + 0.022598697, + -0.015137472, + 0.041843068, + -0.0033838914, + 0.005698054, + 0.017717723, + -0.02767319, + -0.016320087, + -0.01665337, + 0.01572878, + -0.00812779, + 0.009944717, + 0.009025503, + -0.018072506, + 0.03928432, + -0.037456643, + 0.0017510765, + 0.009697443, + 0.014148376, + -0.015761033, + 0.021115053, + -0.017416693, + -5.3856015E-4, + -0.033564765, + 0.033543263, + 0.015191227, + -0.010401636, + 0.015782535, + 0.01768547, + -0.031844597, + 0.006778534, + -0.007127943, + 0.020523746, + -0.034596864, + 0.023415776, + -0.0053755227, + -0.007321462, + 0.00581094, + -0.012245441, + -0.033865795, + -5.980269E-4, + -0.013567819, + -0.011245593, + 0.004243975, + 0.021964386, + 0.003297883, + -0.012686234, + 0.015470754, + 3.3807E-5, + 0.020201214, + -0.011062826, + -0.011783145, + 0.016631868, + -0.015395497, + 0.0095953075, + -0.021813871, + -0.01193366, + -0.017491952, + -0.012933508, + 0.022577194, + -0.00383006, + 0.016094316, + -0.0034322713, + 0.03672557, + -0.024318865, + 0.0056281723, + -0.007939647, + 0.02715714, + 0.031113526, + 0.0026326622, + 9.917839E-4, + 0.019244371, + 0.0034161448, + -0.012524968, + 0.016191075, + -0.007574111, + -0.0041633425, + -0.01666412, + 0.050142877, + -0.0034000182, + -0.0025721877, + -0.028812801, + 0.011267096, + 5.328487E-4, + -0.0057034297, + 0.024232857, + 0.02406084, + -0.012320698, + -0.011922909, + 0.0071440698, + 0.0017067285, + 0.0065850154, + 0.017341437, + 0.005832442, + 0.015384746, + 0.028554777, + -0.028662287, + -0.0047116457, + -0.032962706, + -0.027092634, + -0.014008612, + 0.010423139, + 0.007789132, + 0.012578723, + 0.01739519, + -0.028662287, + 0.014836443, + 0.008229925, + -0.019781923, + -0.0010979505, + -0.042445127, + -0.003064048, + -0.014771936, + -0.030382454, + 0.014062367, + 0.037929688, + 0.004311169, + 0.008326685, + 0.019996945, + 0.019244371, + -0.009815704, + -0.018878836, + 0.019706666, + -0.050529912, + -0.004313857, + 0.007885892, + -0.018534802, + -0.01378284, + 0.03638154, + 0.06553837, + 0.02653358, + 0.06119495, + 0.008434195, + 0.034080815, + 0.01203042, + -0.0045315656, + 0.0054669064, + 0.005982957, + -0.0044455575, + 0.003569347, + -0.033607766, + 0.021534344, + 0.02767319, + 0.012309947, + -0.006826914, + 0.014105371, + 0.033242233, + -0.015761033, + 0.05440029, + -0.019459393, + -0.020297974, + -0.018857334, + 0.040101398, + -0.00414184, + 0.017728474, + -0.03547845, + -0.021351576, + -0.0058915727, + 0.01501921, + 0.0093802875, + -0.003050609, + -0.016803883, + -0.024426375, + 0.008004153, + 0.023050241, + 0.013557068, + -0.0021327387, + -0.048895754, + 0.02210415, + -0.010396261, + 0.021029044, + -0.009073882, + 0.01295501, + -0.024447877, + -0.019900184, + -0.010159737, + 0.008568583, + -0.018792827, + -0.007063437, + 0.001790049, + 0.01708341, + -8.920008E-5, + -0.023394275, + -0.020276472, + -0.044079285, + 0.015718028, + 0.027737698, + -0.006316239, + 0.041176505, + -0.032446656, + 0.00704731, + -0.006730154, + -0.007880516, + 0.007015057, + -0.0033543261, + -0.0035290306, + -0.045928467, + 0.023114748, + -0.02509294, + -0.04618649, + -0.019373383, + 0.005182004, + 0.008831983, + -0.010605906, + 0.025931522, + 0.008590085, + -0.0072354535, + 0.015191227, + 0.016621117, + -0.002206652, + 8.923368E-4, + 9.662502E-4, + 0.010643535, + 0.029393358, + 0.009219022, + 0.0022429368, + 0.027587183, + 0.024856417, + -0.018674565, + -0.014599919, + -0.02272771, + -0.0017524204, + 0.01357857, + -0.036661066, + 0.02889881, + 0.0047573377, + -0.03178009, + -0.0054910965, + -0.0011087016, + -0.031651076, + -0.03352176, + -0.010584404, + -0.007385968, + 0.032661676, + 0.05534638, + 0.01829828, + 0.030274944, + -0.015642771, + 0.003897254, + 0.011847652, + -0.029113831, + 0.013492562, + -0.03833823, + 0.013868849, + -0.028769797, + -0.006445252, + 0.012965761, + 0.021824623, + 0.002600409, + -0.009670565, + -0.010767172, + -0.017663967, + 0.0039832625, + 0.03238215, + -0.0015105219, + -0.029500868, + 0.007724626, + -0.0017631714, + 0.0048809745, + 0.02048074, + -0.008133166, + 0.009643688, + 0.009611434, + -4.3172167E-5, + 0.0017389816, + -0.012578723, + 0.029350353, + 0.026103538, + 0.04270315, + -0.02210415, + 0.024297362, + -0.003695672, + 0.021749364, + -0.034596864, + -0.019190615, + -0.009907088, + 0.016707124, + 0.005044928, + 0.010003848, + -0.008434195, + -0.027522676, + -0.00704731, + -0.022039643, + -5.644299E-4, + -0.021545095, + 0.017362937, + -0.026619589, + 0.043649245, + 0.007891268, + -0.020556, + 0.015610518, + -0.023265263, + 0.009041629, + -0.017438196, + 0.016889893, + -0.020190462, + 0.038123205, + 0.010810176, + -0.007106441, + -0.024082342, + -0.02631856, + -0.0021421458, + -0.01973892, + 0.011847652, + -0.02786671, + -0.017556457, + -0.05070193, + 0.057453588, + 0.00198088, + 0.03238215, + -0.0013499281, + -0.019609908, + 0.007853638, + 0.03012443, + -0.011428361, + 0.017846735, + -0.026942119, + 0.01923362, + 0.04863773, + 0.017352188, + -0.0015253045, + 0.034704376, + -0.009761949, + 0.010202742, + 0.019824928, + 0.008149292, + 0.030403957, + 0.0146106705, + -0.0011973977, + -0.010659661, + 0.007219327, + 0.0065742643, + -0.015083716, + 0.025178948, + 0.020642007, + 0.0058270665, + -0.028855806, + 0.013170031, + 0.032317642, + 0.029242843, + -0.040015392, + -0.06270009, + -8.681469E-4, + -0.011320851, + 0.008461073, + 0.020824775, + -0.027630186, + -0.024748906, + 0.032059617, + 0.04175706, + 0.0039026295, + 0.00596683, + 0.0072247023, + -0.004671329, + -0.0350054, + 0.02015821, + 0.017524203, + 0.04450933, + -0.041219506, + 0.018964844, + -0.031930603, + 0.0061227204, + -0.0075848624, + -0.010729543, + 0.03003842, + 0.0076977485, + -0.005945328, + -0.006714028, + -0.005945328, + 0.027544178, + -0.0018989034, + 0.03033945, + -5.74509E-4, + -0.062055033, + -0.0195454, + -0.02048074, + -0.009563055, + 0.006499007, + 0.0069182976, + -0.03969286, + -0.007945023, + -0.039112303, + 0.003141993, + 0.00452619, + -0.025350966, + -0.015148222, + 0.033994805, + 0.0062087285, + 0.023265263, + 0.04124101, + 0.009294279, + -0.01121334, + 0.010885433, + -0.021459086, + -0.047476616, + 0.0023504472, + -0.022168655, + 0.003254879, + 0.008358938, + 0.019695915, + 0.020383982, + -0.01623408, + -0.015943801, + -0.026705597, + -0.020642007, + 0.010600531, + -8.567239E-5, + 0.008100913, + -0.012019669, + -0.009847958, + 0.0081600435, + 0.021330073, + -0.010283375, + -0.001655661, + -0.005585168, + 0.01182615, + -0.008557832, + 0.012438959, + 0.0130947735, + 9.924559E-4, + -0.015782535, + -0.019631408, + 0.015481506, + -0.010342506, + 0.0012531687, + 0.01841654, + 0.025028434, + 0.012406706, + -0.014599919, + -0.004585321, + -0.006283986, + 0.012385204, + 0.0010334442, + -0.009815704, + 0.038015697, + -0.013213035, + 0.0010415075, + 0.016019057, + 0.008192296, + 0.003932195, + 0.013729085, + -0.004988485, + 0.035112914, + -0.0318661, + 0.0048675356, + -0.016363092, + -0.016997403, + -0.022340672, + 0.030296445, + -0.0054239025, + 0.008342811, + 0.0020668884, + 2.6743225E-4, + 8.493326E-4, + -0.039864875, + 0.033392746, + -0.014868695, + 0.028468767, + 0.040488437, + -0.027587183, + 0.009907088, + 0.010326379, + -0.021039795, + 0.033693776, + -0.015008459, + 0.0049132276, + 0.020125957, + 0.039929383, + -0.019308878, + -0.0483367, + -0.0024162973, + -0.04255264, + -0.0045154393, + 0.010283375, + 0.01840579, + 0.067946605, + 0.025135944, + -0.014857945, + 0.00925665, + -0.013514064, + 9.192144E-4, + -0.013944106, + -0.011600378, + -0.0035102163, + 0.018792827, + 0.010186615, + 0.015524509, + 0.0059507038, + 0.002330289, + -0.04825069, + 3.163159E-4, + 0.011406859, + 0.0066172685, + -0.056507494, + 0.004179469, + 0.013675329, + 0.0021004854, + 0.0064560026, + -0.014578418, + 0.020383982, + 0.015535261, + 0.009917839, + -0.0017188233, + -0.001869338, + 0.02077102, + -0.020642007, + 9.071195E-4, + 0.018061757, + 0.001678507, + -0.014718181, + 0.009831831, + 0.026383065, + 0.022276167, + -0.023286764, + 0.032790687, + -0.01656736, + 0.01069729, + 0.009928591, + -0.027071131, + 0.015890045, + -0.014567667, + -0.011342353, + -0.0096221855, + 0.0070903143, + 0.02272771, + -0.01747045, + 0.008880364, + 0.023888823, + 0.030016918, + 6.202849E-5, + -0.025372468, + 0.046143487, + 0.0030049172, + 0.033156224, + 0.019093856, + 0.03074799, + -0.0069344244, + 0.0033193852, + -0.008342811, + 0.0041284016, + 0.013342047, + -0.017739225, + 0.0034994653, + -0.009498549, + 0.012374453, + -0.0022120276, + -0.0029027823, + 0.014180629, + -0.013030267, + 0.0038219965, + -0.006386121, + -3.7964628E-4, + -0.009858709, + 0.04945481, + 0.0029323476, + -0.02395333, + 0.011073576, + 0.035198923, + -0.015857792, + 0.018180018, + -0.009498549, + 6.6522096E-4, + -0.06106594, + 0.0037736169, + -0.027286153, + 0.022254664, + -0.031543568, + 8.681469E-4, + 0.004359549, + -0.015890045, + 0.027415166, + 0.019083105, + 0.02797422, + -0.0016690998, + 0.003007605, + 0.008649216, + -0.06510833, + 0.045498423, + 0.0016072813, + 0.048465714, + 0.0169759, + 0.018889587, + 0.01768547, + 0.015879294, + 0.007477352, + 0.022491187, + 0.020512994, + 0.053454198, + 0.0017994562, + 0.016911395, + -4.0685988E-4, + -0.026404567, + -0.004663266, + 9.225741E-4, + 0.032253135, + 0.023372773, + 0.02941486, + 0.024533885, + 0.023996333, + 0.024791911, + -0.023587793, + -0.011912159, + -0.025953023, + -1.5471426E-4, + -0.021136556, + 0.019857181, + 0.005913075, + -0.014180629, + -0.044552334, + 0.020211965, + 0.008950246, + -0.014782688, + -0.032360647, + 0.008095537, + 0.0012350263, + 0.019781923, + 0.0109499395, + -0.03754265, + 0.007294584, + -0.016244829, + -0.015492257, + 0.011471366, + -0.008767477, + 0.029264346, + 0.033371244, + 4.1257136E-4, + 0.041800063, + 0.016685622, + 0.007740753, + 0.0030694234, + -0.020695763, + 0.07379518, + -0.025953023, + -0.017373689, + -0.015890045, + -0.040574446, + -0.0077998834, + 0.032747686, + -0.006101218, + -0.043025684, + -0.021985888, + -0.021351576, + 0.01378284, + 0.018943341, + -0.019115359, + 0.012266942, + 0.0037171738, + -0.023910325, + -0.024404872, + -0.068462655, + 0.0087406, + -0.043455724, + -0.021921381, + -0.0047573377, + 0.017857486, + 0.0025627804, + -0.0046659536, + -0.012879753, + 0.0027321095, + -0.025157446, + 0.0031796216, + -0.022749212, + 0.0055475393, + 0.020287223, + 0.010046852, + 0.023910325, + 0.011256345, + -0.015234231, + 0.027221646, + 0.010724167, + 0.027028129, + 0.0029135332, + 0.0059507038, + -0.00910076, + -0.028511772, + -0.0080256555, + 0.0079719005, + -0.009794203, + -0.04825069, + 0.021738613, + -7.54589E-4, + 0.001318347, + 0.020792522, + 0.02601753, + 0.05749659, + -0.009563055, + -0.0066280193, + 0.0105414, + -0.027178643, + -0.0019365321, + 0.01584704, + 0.019362632, + 0.019470142, + -0.0050288015, + -0.009143764, + -5.829082E-4 + ], + "content": "● 등록금 전액 완납 또는 분할납부 1차분을 정해진 기간에 미납할 경우 분할납부 신청은 자동 취소되며, 미납 등록금은 이후\n 추가 등록기간에 전액 납부해야 함.\n ● 분할납부는 현금납부제이므로 신용카드로 납부 불가\n ● 휴학 및 자퇴는 4차분 분할납부 등록금까지 일괄 완납해야 가능\n ● 분할납부 등록금을 완납하지 않을 경우 미등록 제적 처리\n ● 분할납부 2~4차 지연 납부할 경우 1년간 분할납부 신청 자격 상실\n ● 학사업무의 원활한 진행을 위하여 등록금 분할납부 인원을 제한할 수 있음.\n\n7 등록 유의사항\n1) 기간 내 미등록시 제적처리 되오니 등록금 납부 또는 휴학처리\n2) 전용계좌 입금은 아래와 같이 선택 입금 가능 (기타납입금 先 입금 불가) ①등록금+기타납입금, ②등록금, ③등록금 입금완료 후 기타납입금 추가 입금\n3) 기타납입금 납부여부는 자율적으로 선택 가능\n4) 등록금 납부 후 취소는 불가함\n5) 등록금 반환은 자퇴의 경우에만 환불기준에 의거하여 가능\n6) 등록관련 문의: 총무처 재무팀 ☎ 02-450-4152\n\n8 기타 유의사항\n학사정보시스템 등록고지서 출력 오류(스크립트 오류 등) 발생 시 원격문의 : 원격지원센터 ☎ 02-450-3887\n\n문의\n\n● 장학금 및 학자금대출\n- 학 부 : 학생복지처 장학복지팀 ☎ 02-450-3211~2\n 건국사랑/장학사정관장학/기금장학: 02-450-3967\n 신입생장학/성적장학/프라임장학/외국인장학: 02-450-3211\n 국가장학금(1,2유형)/학자금대출/자퇴/희망사다리장학: 02-450-3212", + "id": "8d3f0c19-be3f-44db-8a42-ee9759f26fb7", + "metadata": { + "charset": "UTF-8", + "filename": "ku-uni-register.txt", + "source": "ku-uni-register.txt" + }, + "media": [] + }, + "c1b4b082-c59a-4cfb-ac22-a3d9a0d753df": { + "embedding": [ + -0.02368085, + 0.022880545, + 0.01381041, + -0.030822044, + -0.0031473576, + 0.006658957, + -0.002594582, + 0.017555432, + -0.05031668, + -0.0030524496, + -0.008700764, + 0.007838896, + 0.046417754, + 0.022962628, + 0.027169365, + -0.02028468, + -0.04941377, + 0.033264004, + -0.041287586, + 0.029693408, + 0.024501678, + -0.016621742, + 0.019217607, + -0.0178222, + 0.01470306, + -0.010906735, + 0.035972733, + -0.03800428, + 0.0027523346, + -0.013379476, + 0.058401823, + -0.026163852, + -0.005489279, + 0.008136445, + -0.02604073, + 0.03470045, + 0.018201834, + 0.04535068, + -0.03716293, + -0.051137507, + 7.9453463E-4, + 0.0016647392, + 0.012435526, + -0.05831974, + -0.03293567, + 0.05343582, + 0.006263934, + -0.0058535207, + 0.006325496, + 0.029447159, + 6.340566E-5, + -0.011758343, + 0.03837365, + 0.053846236, + 0.0039091874, + 0.064516984, + 0.025096778, + 0.031827558, + 0.035069823, + -0.020099996, + 0.020110255, + -0.0037065458, + -0.020223118, + -0.008100534, + 7.0026785E-4, + -0.05072709, + -0.017011635, + 0.038147923, + -0.04354486, + -0.0016172852, + 0.018037667, + 0.013574422, + 0.067882374, + -0.019525416, + -0.0015454629, + 0.05462602, + -0.03870198, + 0.008110794, + -0.02513782, + 0.050850216, + 0.033551294, + 0.0027369442, + -0.04966002, + 0.01042963, + 0.023557728, + -0.01806845, + -0.09004469, + -0.007920978, + 0.010034607, + 0.007490044, + -0.026964158, + 0.032299533, + -0.05491331, + 0.09299967, + 0.07424378, + -0.0034089962, + 0.016180547, + -0.061767213, + -0.02669739, + 0.009193259, + 0.0025573883, + -0.0627522, + 0.023270437, + -0.048921276, + 0.024932612, + 0.008664852, + -0.061479922, + 0.0066025252, + -0.011183765, + -0.03258682, + -0.07481836, + 0.033612855, + -0.0044991565, + 0.023188356, + 0.060248684, + -0.0029036747, + -0.022347009, + -0.020951603, + -0.034720972, + -0.058401823, + 0.0028421127, + 0.033510253, + 0.037306577, + -0.009418987, + 0.006166461, + 0.02636906, + 0.016026642, + 0.037901673, + -0.09784255, + -0.040405195, + 0.0011966114, + -0.031129854, + -0.025363546, + -0.018879015, + 0.008603291, + 0.015431543, + -0.0030524496, + -8.855951E-4, + -0.024973653, + -0.041308105, + 0.04810045, + 0.004106699, + 0.025999688, + 0.0048659635, + -0.032648385, + 0.02548667, + -0.03252526, + 0.014579935, + 0.0013723198, + -0.021279933, + 0.004868529, + 0.004658192, + 0.0018622507, + -0.041267063, + 0.02086952, + 0.036075335, + -0.011235066, + 0.04173904, + -0.006381928, + -0.0077875936, + 0.00951646, + -0.013635985, + 0.04052832, + 0.018242875, + -0.015205815, + -0.049290646, + -0.007946629, + -0.058483906, + -0.07998957, + 0.008064623, + 0.043052364, + -0.0063973186, + -0.018201834, + 0.020972123, + 0.015369981, + 0.041800603, + -0.0021251717, + -0.020110255, + 0.02300367, + 0.0073771803, + 0.016662782, + 0.011809645, + 4.7999126E-4, + -0.0026753822, + -0.04056936, + -0.026245935, + 0.04268299, + -0.019781925, + 0.0028754587, + 0.014231084, + 0.050809175, + -0.009685756, + 0.03710137, + -0.0011818623, + -0.0016929551, + -0.011265847, + 0.0046197157, + -0.024788968, + 0.0124047445, + -0.027456654, + -0.018212093, + -0.024706885, + 0.0060895085, + -0.035808567, + 0.0073566595, + -0.01744257, + -0.0024137436, + 0.02854425, + 0.05163, + 0.014364468, + 0.058524948, + -0.011317149, + -0.010629706, + 0.02269586, + -8.503252E-4, + 0.045679007, + -0.013759108, + 0.0038117142, + -0.014364468, + -0.0018199268, + -0.01470306, + -0.024440117, + -0.039543327, + 0.031704433, + 0.02579448, + 0.0150211295, + 0.056637045, + 0.047690034, + 6.8680116E-4, + -0.055939343, + -0.022942107, + 0.036649913, + 0.0060382066, + 0.01990505, + 0.011491574, + -0.028400606, + -0.022839503, + -0.013820671, + -0.047977325, + 0.014395249, + 0.0343516, + 0.005027564, + -0.057129543, + 0.037573345, + 0.0046299757, + -0.021341495, + 0.020048693, + 0.015226336, + 0.020602752, + 0.032443177, + 0.010968298, + 0.008085144, + 0.020171817, + -0.045555886, + -0.018530164, + -0.01998713, + 0.03839417, + 0.039235517, + 0.008880319, + -0.029241953, + 0.013102447, + -0.023332, + -0.04383215, + 0.021526182, + 0.03987166, + 0.01629341, + 0.039748535, + 0.074736275, + -0.056021426, + -0.032114845, + 0.018817453, + -0.034269515, + 0.022347009, + 0.025917605, + -0.04670504, + 0.044570893, + 1.755479E-4, + 0.026799994, + 0.020305201, + 0.032689426, + -0.045268595, + 0.0447761, + 0.013051146, + -0.063080534, + 0.0043785977, + 0.033325564, + -0.027169365, + -0.0035552059, + 0.0016390884, + 0.043093406, + 0.07547502, + -0.021628784, + -0.027764464, + -0.009562632, + 0.01535972, + -0.0029216302, + -0.014436291, + -0.03151975, + -0.03433108, + 0.02940612, + -0.0070385896, + 0.0017211711, + 0.0067051286, + 0.0017596474, + -0.042847157, + 0.057622038, + 0.027784985, + 0.014343947, + 0.005058345, + -0.0062434133, + -0.0013299958, + 0.014323426, + -0.028585292, + 0.0014774882, + 0.039830618, + -0.027128324, + -0.014323426, + -0.017227102, + 0.034556806, + 2.1819242E-4, + 0.04015895, + -0.0239271, + 0.011409492, + 0.007213015, + 0.04452985, + 0.024091264, + -0.014949307, + -0.009988436, + 0.014015617, + -0.034536287, + -0.045555886, + -0.016478097, + 0.0060022958, + 0.027969671, + -0.022757422, + 0.03470045, + 0.051383756, + 0.021833992, + -0.03771699, + -0.016744865, + -0.02271638, + -0.0011658305, + -0.027436133, + 0.014046398, + -0.02940612, + -0.07260212, + 0.053764153, + 0.022921586, + 0.021444099, + -0.006623046, + 0.022141801, + -0.021895554, + 0.015503366, + 0.042806115, + 0.023968142, + -0.049331687, + 0.03779907, + 0.014990348, + 0.008957271, + 0.020777177, + -0.019720362, + 0.04760795, + 0.027210407, + -0.008793106, + 0.03660887, + -0.05002939, + -0.043750066, + -0.022203363, + 0.037881155, + -0.05860703, + -0.0051122117, + -0.025281465, + 0.018899536, + -0.015164774, + -0.002057197, + 0.006315236, + 0.027107803, + 0.029098308, + 0.04699233, + 0.0113889715, + 0.040241033, + -0.012158496, + -0.0049198302, + -0.034495246, + 0.03287411, + 0.02244961, + -0.0052250754, + 0.017350225, + 0.03039111, + 0.03069892, + 0.016252369, + 0.036567833, + 0.017021894, + -0.023373041, + 0.04297028, + 0.009357425, + 0.0016929551, + 0.024665844, + -0.037080847, + -0.041041337, + -0.0032961324, + 0.006720519, + 0.0294882, + -0.064640105, + 0.005699616, + -0.015882997, + 0.048346695, + -0.013358955, + 0.03133506, + -0.030226946, + 0.013759108, + -0.05462602, + 0.0070693702, + -0.01594456, + -0.002255991, + -0.036711477, + -0.026882077, + 0.0356444, + -0.033571813, + -0.057252664, + -0.009147088, + -0.0075003044, + -0.024358034, + -0.05770412, + -0.07050902, + -0.061520964, + 0.03804532, + -0.01996661, + 0.03825053, + -0.026984679, + -0.07481836, + -0.03868146, + -0.08200059, + 0.0075721266, + -0.019576717, + -0.028380085, + 0.0022624037, + 0.02423491, + 0.02177243, + 0.029385598, + 0.003129402, + 0.045596927, + -0.044119436, + 0.005766308, + -0.016118985, + 0.0038886666, + -0.01531868, + 0.01721684, + 0.0712888, + 0.0011966114, + -0.013974575, + 0.0145388935, + 0.029118828, + -0.010085909, + -0.020212859, + -0.02702572, + 0.022593256, + -0.041554354, + -0.012117455, + 0.0056072725, + 0.041267063, + 0.050809175, + 0.0030216684, + 8.7084586E-4, + -0.017309183, + -0.009937134, + -0.024440117, + 0.024686364, + -1.2176131E-4, + -0.003470558, + 0.015380241, + -0.008233918, + -0.03870198, + 0.0015249422, + -0.017842721, + 0.03281255, + -0.035500757, + -0.03714241, + -0.013430778, + -0.030678399, + -0.0028164617, + -0.032340575, + 0.005648314, + -0.011799385, + 0.0074336124, + -0.0028472429, + 0.0570885, + 0.031807035, + -0.021177331, + -0.009870442, + 0.028195398, + 0.058771197, + 0.012948542, + -0.00553032, + -0.013995096, + 0.010285985, + -0.0066127856, + -7.05398E-4, + -0.038086362, + -0.003929708, + -0.045843173, + -0.0196588, + 0.00952672, + -0.025527712, + 0.04781316, + -0.024029704, + -0.028564772, + -0.032709945, + -0.0016018947, + 0.03408483, + 0.01626263, + 0.009280472, + 0.0032730466, + 0.01871485, + 0.01563675, + 0.0318686, + 0.015431543, + -0.0015826565, + -0.039645933, + 0.010527103, + -0.0070488495, + 0.017709337, + 0.011081161, + -0.025117299, + -0.01288698, + 0.01875589, + 0.0107220495, + -0.03558284, + 0.045514844, + -0.04206737, + 0.008787977, + -0.023085753, + 0.008249309, + 0.008259569, + 0.02825696, + 0.004178521, + 0.047074415, + 7.2142977E-4, + 0.03404379, + -0.010896475, + -0.028667374, + 0.009167609, + 0.016067684, + -0.016549919, + -0.0019699843, + 0.025609795, + 0.04691025, + -0.0024945438, + -0.022552215, + 0.0038707112, + 0.013964315, + -0.00119084, + 0.010609185, + -0.024440117, + 0.006361407, + 0.07157609, + 0.0016955202, + -0.00552519, + 0.0039091874, + -0.015523886, + 0.058442865, + 0.0034782533, + -0.022962628, + -0.0077773333, + -0.0038245397, + -0.032299533, + 0.011758343, + -0.06812862, + -0.0012350878, + 0.028277481, + -0.002146975, + 0.0046479315, + -0.01840704, + -0.010383459, + 0.02059249, + 0.0011889163, + -0.025404587, + -0.002146975, + 0.013995096, + -0.0102398135, + -0.0017583648, + 0.026512705, + 0.012486827, + -0.011091421, + -0.039235517, + 0.048675027, + 0.0010382176, + 0.0072437963, + -0.03806584, + -0.031786516, + 0.003868146, + 0.03806584, + 0.04022051, + -0.007895327, + -0.023578249, + -0.0062280227, + -0.02885206, + 0.019494636, + -0.0030524496, + -0.03738866, + -0.013081926, + 0.010896475, + -0.0041246545, + -0.032258492, + -0.0022098196, + -0.018879015, + -9.5549366E-4, + -0.036978245, + -0.018796932, + -0.0019456159, + 0.042806115, + 0.02366033, + -0.009957654, + 0.028811019, + 0.020407805, + -0.011512095, + -0.027702903, + 0.009342034, + -0.011542876, + 0.009752448, + 0.019617759, + 0.045473803, + -0.0010504017, + 0.02121837, + 0.037019286, + -0.010516843, + -0.01806845, + 0.0015441803, + -8.9136657E-4, + 0.021628784, + -0.0031268368, + -0.0031088814, + 0.024501678, + -0.030309027, + -0.009367685, + -2.5266074E-4, + 0.0067461696, + -0.058237657, + 0.054379772, + -0.026676869, + 0.021751909, + -0.004175956, + 0.010701529, + 0.007305358, + 0.014374728, + -0.0015223771, + 0.001612155, + -0.03312036, + -0.01257917, + -0.016406275, + -0.0044324645, + -0.03314088, + -0.013010104, + -0.026184373, + 0.033859104, + -0.036362626, + 0.009942264, + -0.017986367, + -0.026492184, + 0.017011635, + -0.043667983, + 0.022818984, + -0.0072848373, + -0.019710103, + -0.01810949, + -0.020695094, + -0.026738431, + -0.017534912, + -3.3955293E-4, + 0.007802984, + -0.013225571, + 0.05926369, + 0.0022649688, + -0.023290958, + 0.008495557, + 0.005709876, + 0.0025804741, + 0.0038347999, + 0.012497087, + 0.025999688, + -0.003096056, + 0.0033833452, + -0.015246857, + -0.022059718, + -0.010337287, + -0.0013479515, + 0.031417143, + -0.027826026, + 0.022203363, + -0.019248387, + 0.025589274, + 0.0049865227, + 0.030309027, + -0.01815053, + -0.013174269, + 0.06935986, + 0.017032156, + -0.0142208235, + 9.6447143E-4, + 0.0098550515, + 0.025199382, + -0.0061254194, + -0.007756813, + 0.007638819, + -0.033551294, + -0.020828478, + -0.029775491, + 0.009090656, + 0.059961393, + -0.019063702, + -3.373085E-4, + -7.823505E-4, + -0.0034089962, + -0.01565727, + 0.0035167297, + 0.006053597, + -0.011583918, + -0.049824182, + 0.014867225, + -0.0046504964, + -0.0073771803, + 0.039686974, + 0.029631846, + 0.006284455, + -0.0031473576, + 0.012733075, + -0.005024999, + -0.009259951, + -0.025014695, + -0.02735405, + -0.0064640106, + 0.014928786, + -0.017073195, + -0.008916231, + 0.0024509374, + 0.025691878, + -0.04071301, + 0.018468602, + 0.015708571, + -0.011214545, + -0.026533224, + -0.034125872, + 0.032668903, + 0.039194476, + -0.04243674, + 0.03063736, + -0.02057197, + 0.015246857, + -0.021382537, + 0.043462776, + -0.026574265, + 0.0034526025, + -0.003473123, + -0.019597238, + 0.031232458, + -0.064516984, + 0.03227901, + 0.022223884, + -0.017852983, + 0.005699616, + 0.024029704, + 0.027210407, + 0.019104743, + 1.3025815E-4, + 0.043257568, + 0.0073566595, + -0.02731301, + 0.014928786, + -0.022408571, + 0.027805505, + 0.01719632, + -0.04011791, + 0.01043476, + 0.028318522, + 0.03470045, + -0.019597238, + -0.03714241, + -0.02975497, + 0.008008191, + 0.026328018, + -0.0037834982, + -0.0064588804, + 0.044570893, + 0.008095404, + -0.004565849, + -0.036649913, + 0.0045222426, + 0.012014852, + 0.002793376, + 0.021403058, + -0.01992557, + 0.018263396, + -0.02390658, + -0.011378711, + -0.012845939, + 0.018540423, + -0.0022406005, + -0.018796932, + 0.014374728, + -0.001051043, + 0.01473384, + 0.004386293, + 0.0041195243, + -0.014641497, + 0.037080847, + -0.0032910022, + 0.023455124, + 0.008141575, + 5.2680407E-4, + -0.0028446778, + -0.028626332, + 0.030001217, + 0.031642873, + 0.035726484, + 0.0141387405, + 0.024460638, + -7.791441E-4, + -0.0072027547, + -0.035316072, + -0.030801523, + 0.03041163, + 0.005586752, + 0.015185295, + 0.018314697, + -0.007484914, + 0.02210076, + -0.015369981, + 0.027477175, + -0.014826183, + 0.01932021, + -0.0041297846, + 0.027169365, + 0.0016814123, + -0.011173504, + 0.023701372, + -0.008623811, + -0.02210076, + -0.0013453864, + -0.018581465, + 0.004855703, + 0.014939046, + -0.0017378441, + 0.05491331, + 0.01318453, + -0.0030447543, + 0.030185904, + -0.021033686, + -0.010958037, + -0.0041272193, + 0.017924804, + 2.2829244E-4, + -0.011809645, + 0.046417754, + 0.009342034, + 0.008731545, + -0.019566458, + -0.0098396605, + 0.0066076554, + -0.010752831, + -0.040671967, + 0.02546615, + -0.021998158, + 0.014600456, + -0.010763091, + 0.034187432, + 0.018294176, + 0.0075105648, + 0.010049998, + 0.029077787, + -0.012137976, + 2.1578766E-4, + -0.024706885, + 0.017339965, + -0.032648385, + 0.009680625, + -0.012743335, + 0.0055713616, + 0.019751143, + -0.015513626, + -0.047977325, + 0.0014954438, + -0.03006278, + -0.006905205, + 0.005104516, + 0.0047684903, + -0.0032627864, + -0.016673043, + 0.030760482, + -0.010301376, + 0.024481157, + 0.0064896615, + -0.006361407, + 0.025281465, + -0.012312401, + 0.0073156185, + -0.032792028, + -0.025876563, + -0.034720972, + 9.0868084E-4, + 0.019094482, + -0.006043337, + 0.013092186, + 0.015790654, + 0.04383215, + -0.023742413, + -0.00828522, + -0.017114237, + 0.03221745, + 0.0318686, + 0.007905588, + 0.0034551676, + 0.026143331, + 0.0016275456, + -0.016036903, + 0.006192112, + 0.007869677, + 0.004075918, + -0.024316993, + 0.045843173, + -0.0090393545, + -0.010557884, + -0.026799994, + 0.0361369, + 0.021238891, + -0.007561866, + 0.0055662314, + 0.0037911935, + -0.02181347, + -0.007797854, + -0.0048710937, + -0.011204286, + 0.0037014156, + -0.02179295, + -0.0044529852, + 0.02546615, + 0.012148236, + -0.021300454, + -0.007813245, + -0.03069892, + -0.01381041, + 0.009890962, + 0.015185295, + 0.014764621, + -0.0023829627, + 5.8644224E-4, + -0.047525868, + 0.0077465526, + 0.022347009, + -0.021957116, + -0.015759874, + -0.06193138, + -4.4568328E-4, + -0.012363703, + -0.016642263, + 0.018037667, + 0.036362626, + 0.009413857, + 0.035972733, + -0.0018404474, + 0.029816533, + 0.006284455, + -0.021608263, + 0.011789124, + -0.04112342, + 0.016826948, + 0.009701146, + -0.036690954, + -0.021608263, + 0.049331687, + 0.056062467, + 0.027723424, + 0.041226022, + 0.024994174, + 0.0151545135, + 0.005099386, + 0.018776411, + 0.009926873, + -0.0052327705, + 2.6997505E-4, + 0.007423352, + -0.010639966, + 0.028667374, + 0.021033686, + -0.014610716, + 5.348199E-4, + 0.01905344, + 0.043093406, + -0.003863016, + 0.044570893, + -0.021649305, + -0.035685442, + -0.019145783, + 0.0107220495, + -0.01228162, + 0.004942916, + -0.030822044, + -0.025691878, + -0.017360486, + 0.011163244, + -0.011430012, + -0.004671017, + -0.016036903, + -0.032915153, + -0.002071305, + 0.014641497, + -0.0015903518, + -0.010383459, + -0.036629394, + 0.034166913, + -0.0013915579, + 0.011132463, + 0.0032986975, + -0.004850573, + -0.008095404, + -0.014323426, + 0.0017955585, + 0.017637515, + -0.014436291, + -0.0032397006, + 0.00675643, + 0.016334452, + -0.003652679, + -0.03527503, + 0.0032063546, + -0.0324637, + 0.020931082, + 0.028585292, + 0.0037245015, + 0.029447159, + -0.0356444, + 0.023783455, + 0.013933534, + -0.025712397, + 0.012969063, + -0.011460793, + -0.0037860633, + -0.03069892, + 0.022223884, + -0.012712554, + -0.02735405, + -0.023290958, + -0.0045530233, + 0.034454204, + -0.011932769, + 0.011748083, + -0.0011183764, + 0.0024599151, + 0.0011651892, + 0.007705511, + 0.012199538, + 0.017842721, + 0.017227102, + 0.025999688, + 0.027148845, + 0.0028908493, + 0.006946246, + 0.025199382, + 0.02055145, + 0.0053815455, + -0.01718606, + -0.01135819, + -0.0103218965, + 0.005668835, + -0.030226946, + 0.032422658, + 0.005797089, + -0.023208877, + -0.022223884, + -0.016303672, + -0.028626332, + -0.04141071, + -0.013461559, + -0.03894823, + 0.043093406, + 0.021916075, + 0.037634905, + 0.025856042, + -0.0037758031, + 0.01780168, + 0.027559258, + -0.023783455, + 0.024809489, + -0.019145783, + -0.0016249805, + -0.024275951, + 0.009470289, + 0.01380015, + 0.034269515, + -0.0025073693, + -0.0067307795, + 0.0016211328, + -0.012261099, + -0.0061562005, + 0.04448881, + 0.015369981, + -0.011296628, + 0.006776951, + 0.0019879397, + -0.011696781, + 0.02300367, + -0.009998696, + -0.0040682224, + 0.028113317, + 0.01043476, + 0.0101731215, + -0.01014234, + 0.007885067, + 0.023639811, + 0.035993252, + -0.037491262, + 0.005094256, + 0.00552519, + 0.0026779473, + -0.008244178, + 9.984588E-4, + 0.0034833835, + 0.0075259553, + -0.021505661, + 0.028031234, + 0.005668835, + -0.001612155, + 0.010198772, + -0.020674573, + -0.007638819, + 0.0048967446, + 0.0027831157, + -0.023557728, + 0.05224562, + 0.009767839, + 0.01074257, + 0.038476255, + -0.016724344, + -0.006658957, + 0.0042221276, + 0.02361929, + -0.035870127, + 0.050480846, + 0.0047607953, + -6.630741E-4, + -0.027230928, + -0.027661862, + 0.0032294402, + -0.019494636, + -0.0042426484, + -0.013317914, + -0.029241953, + -0.042559866, + 0.06648697, + 0.020223118, + 0.018879015, + -0.005278942, + -0.034269515, + 0.0016993679, + 0.021669826, + -0.007331009, + 0.014549154, + -0.014210563, + 0.030185904, + 0.045186512, + 0.011450533, + 0.012158496, + 0.013913013, + -0.023783455, + 0.015872737, + -0.009480549, + 0.01596508, + 0.024275951, + 0.013492339, + 0.018899536, + -0.0051532527, + 0.0018032538, + 0.026923118, + -0.02454272, + 0.016190808, + 0.02729249, + 0.03498774, + -0.019566458, + -1.239256E-4, + 0.027210407, + 0.016980853, + -0.035213467, + -0.034187432, + 0.0062126326, + -0.035480235, + 0.021300454, + 0.019176565, + -0.014846704, + -0.025076257, + 0.022080239, + 0.04629463, + -0.0040707877, + 0.008387824, + 0.01412848, + 0.0027831157, + -0.039091874, + 0.006407579, + 0.0045530233, + 0.06587134, + -0.024973653, + 0.024132306, + -0.014949307, + 0.017381007, + -0.03377702, + 3.0428305E-4, + 0.042724032, + 0.031088812, + -0.03069892, + -0.00644349, + -0.026245935, + 0.03839417, + 0.004237518, + 0.018889276, + 0.014518373, + -0.08039998, + -0.025055736, + -0.036362626, + 0.011799385, + 0.008526338, + -0.012168757, + -0.03962541, + -0.0051737735, + -0.022839503, + 2.7799094E-4, + 0.0037373267, + -0.022880545, + -0.009065005, + 0.027743945, + -0.0061869817, + 0.013594943, + 0.03467993, + 0.014856964, + -0.0065204427, + 0.029980697, + -0.03710137, + -0.038496774, + -0.0072489264, + -0.010316766, + 0.008623811, + 0.009639584, + 0.021854512, + 0.0013723198, + -0.015000609, + -0.03747074, + -0.032627862, + -0.037286054, + 0.008192877, + -0.017319445, + 0.026266456, + -0.0059715146, + -0.014518373, + 0.014846704, + 0.032792028, + 0.017883763, + 0.008341651, + 0.0056585744, + -0.008823887, + -0.016498618, + 0.016816689, + 0.011142723, + -0.011440272, + -0.025527712, + -4.52096E-4, + 0.016683303, + -0.021854512, + -0.015759874, + 0.044119436, + 0.030165384, + 0.02790811, + 0.0029036747, + -0.0071360627, + -0.011840425, + 0.023475645, + -0.010999079, + -0.0045581535, + 0.040384676, + -0.018047929, + -0.007023199, + 0.025076257, + 9.5036346E-4, + -0.0073412694, + 0.018920057, + 0.0049916524, + 0.042765073, + -0.015267378, + -0.0064947917, + -0.008213398, + -0.009444638, + -0.028133838, + 0.023455124, + -0.004511982, + 0.019504895, + 0.001349234, + 3.308958E-4, + 0.009352295, + -0.044283602, + -0.006104899, + -0.01568805, + 0.022203363, + 0.011768604, + -0.03160183, + 0.01936125, + 0.0132050505, + -0.0032961324, + 0.019289428, + -0.011799385, + -0.008567379, + 0.01996661, + 0.016416535, + -0.03654731, + -0.025732918, + -0.0053045927, + -0.040035825, + -0.017319445, + -0.0011478749, + 0.027230928, + 0.055159558, + 0.03720397, + -7.4130914E-4, + 0.02423491, + -3.5398154E-4, + 0.022983149, + -0.0028395476, + 0.0054533677, + -0.013215311, + 0.0331614, + 0.014990348, + 0.037286054, + -0.00828009, + -0.011286368, + -0.047977325, + 0.021341495, + 6.120931E-4, + 0.003155053, + -0.03502878, + -0.0012658688, + 0.013010104, + -0.012753596, + -5.9702323E-4, + -0.0054431073, + 0.052204583, + 0.009013704, + 0.0036655045, + -0.02244961, + -0.0037245015, + 0.002118759, + -0.004889049, + 0.008603291, + 0.0047992715, + 0.030329548, + -0.0066692173, + 0.0040733526, + 0.020028172, + 0.0239271, + -0.031704433, + 0.019268908, + -0.032073807, + 0.01380015, + 0.013307653, + -0.03474149, + -0.0026138201, + 1.1013827E-4, + -0.0068692937, + 0.018879015, + 0.013954055, + 0.03221745, + -0.018571205, + 0.0041836514, + 0.019874267, + 0.01716554, + -0.0011523638, + -0.016703824, + 0.043216527, + -0.0037065458, + 0.029200912, + 0.013307653, + 0.021074727, + 0.0075362157, + 0.006535833, + 0.012066153, + -0.0067513, + -0.007582387, + -0.020633532, + 0.017001374, + -0.026656348, + 0.023475645, + 7.59906E-4, + -0.0046838424, + -0.009654975, + 9.6447143E-4, + 0.004442725, + -0.011563397, + -0.005976645, + 1.4572882E-4, + 0.03258682, + 0.011378711, + -0.028872581, + 0.005771438, + 0.025835522, + -0.021074727, + -0.006628176, + -0.017370746, + -8.612268E-4, + -0.036403667, + -0.003868146, + -0.00644862, + 0.011635219, + -0.023249917, + 0.0063973186, + -0.01105038, + -0.010650227, + 0.0115223555, + 0.0022431656, + 0.025876563, + 0.017432308, + 0.01809923, + 0.009141958, + -0.059756186, + 0.04206737, + 0.035459716, + 0.05027564, + 0.029282995, + -2.2909403E-4, + 0.01691929, + 0.034659408, + 0.019812705, + 0.021731388, + 0.010947777, + 0.021998158, + -0.014241344, + 0.020017913, + 0.009629324, + -0.035377633, + -0.013759108, + -0.016365234, + 0.029385598, + 0.013759108, + 0.03529555, + 0.019689582, + 0.0014159261, + 0.042600907, + -0.004181086, + -0.012394484, + -0.007818375, + 0.0071052816, + -0.009742187, + 0.005489279, + 0.01134793, + -0.037286054, + -0.044940263, + 0.026615307, + 0.006320366, + -6.2123116E-5, + -0.0030857956, + 0.006992418, + -0.009578022, + 0.015790654, + 9.368968E-4, + -0.034454204, + 0.0064640106, + 0.0013530815, + -0.01653966, + 0.010029477, + -0.008659722, + 0.0017429743, + 0.027764464, + -0.011183765, + 0.06369615, + 6.677233E-5, + 0.025876563, + 0.015411022, + -0.040302593, + 0.06923673, + -0.022736901, + -0.0027497697, + 0.0064588804, + -0.01074257, + -0.0059150825, + 0.044160478, + -6.6435663E-4, + -0.031109333, + -0.028995706, + 0.004627411, + 0.0047710557, + 0.0040117907, + -0.022798464, + -7.733727E-4, + -0.013666766, + -0.02515834, + -0.0081723565, + -0.0582787, + 0.023578249, + -0.052286662, + -0.028667374, + -0.0022983148, + 0.022203363, + 0.0071052816, + -0.0057868287, + -0.0017929934, + -0.0077773333, + -0.0048710937, + 0.0032781768, + -0.010368068, + 0.012774116, + 0.02546615, + 0.0060279462, + 0.030432152, + -0.0030447543, + -0.023188356, + 0.026266456, + -0.0033653898, + 0.018879015, + -0.0048531382, + 0.012384224, + 0.0042785592, + -0.02267534, + 0.022080239, + 0.006966767, + -0.0033294784, + -0.05614455, + 0.014651758, + 0.0113889715, + 0.019166304, + 0.02515834, + 0.01867381, + 0.026964158, + -0.013697546, + -0.0032627864, + 0.007018069, + -0.03958437, + 0.0068590334, + 0.016980853, + 0.011789124, + 0.0023662895, + 0.0017814506, + -0.014343947, + -0.008331391 + ], + "content": "2024년 2학기 등록일정 안내\n\n1 등록기간\n\n구분\n\n일정\n\n대상\n\n1차등록\n\n2024. 8. 19.(월) 09:00 ~ 8. 23.(금) 16:00\n\n일반학생, 재입학생,\n\n차액고지(전과 및 소속변경)\n\n2차등록\n\n2024. 9. 2.(월) 09:00 ~ 9. 6.(금) 16:00\n\n일반학생,  \n\n분납승인학생 1차분납부\n\n\n\n수업연한초과자등록\n\n\n\n2024. 9. 2.(월) 09:00 ~ 9. 6.(금) 16:00\n\n5번항 참조\n\n분할납부신청\n\n2024. 8. 14.(수) 09:00 ~ 8. 23.(금) 16:00\n\n6번항 참조  \n\n\n\n\n\n2 등록고지서 출력\n※출력관련 문의 : 총무처 재무팀 ☎ 02-450-4152\n1) 등록안내문 발송(e-mail) 및 고지서 출력 : 2024. 8. 8.(목) 14:00 예정 [고지서출력버튼]\n ● 학생 전자우편(e-mail)발송주소는 개인별 portal_id@konkuk.ac.kr로 발송\n ● 보호자 전자우편(e-mail) 입력방법\n 입력방법\n- 학 부 : 학사정보시스템→ 학사 → 학적→ 인적사항변경\n- 대학원 : 학사정보시스템→ 대학원→ 대학원학적→ 학적관리→ 인적사항변경\n\n2) 교내·외 장학금 및 학자금 대출 관련 안내\n※ 전액 장학생의 경우 8월 19일(월) 등록처리 예정이며, 기타납입금 납부 희망 시 전용계좌로 입금문의\n\n- 학 부 : 학생복지처 장학복지팀 ☎ 02-450-3211~2\n 건국사랑/장학사정관장학/기금장학: 02-450-3967\n 신입생장학/성적장학/프라임장학/외국인장학: 02-450-3211\n 국가장학금(1,2유형)/학자금대출/자퇴/희망사다리장학: 02-450-3212", + "id": "c1b4b082-c59a-4cfb-ac22-a3d9a0d753df", + "metadata": { + "charset": "UTF-8", + "filename": "ku-uni-register.txt", + "source": "ku-uni-register.txt" + }, + "media": [] + }, + "72add540-b979-420f-beb7-b628ea753711": { + "embedding": [ + -0.006695475, + -6.185447E-4, + 0.026478054, + 0.011274876, + -0.01671156, + -0.038306363, + -0.029928882, + 0.026738493, + -0.010520685, + -0.06029183, + 0.0326418, + -0.016451119, + 0.0045359945, + 0.027650032, + 0.036483284, + -0.01639686, + -0.027411297, + 0.0581215, + -0.03702587, + 0.028778605, + 0.02762833, + 0.008692181, + 0.0095386105, + 0.0049429317, + -0.010808255, + -0.021497142, + 0.010168007, + -0.021931207, + 6.5754284E-4, + -0.018447824, + 0.06888635, + -0.019088073, + 4.3237087E-4, + -0.028648386, + -0.062418755, + 0.05794787, + 0.013163066, + 0.03496405, + 0.008073636, + -0.041648675, + -0.02278849, + 0.02181184, + -0.026217613, + -0.03630966, + -0.013553725, + 0.054822594, + -0.014128863, + -0.0019573683, + -0.01031993, + 0.017937796, + -0.012056196, + 0.014855924, + 0.013119658, + 0.05694952, + -0.010927623, + 0.047920935, + -0.008789846, + 0.021323515, + 0.020694118, + -0.047009397, + -0.0028268578, + 0.007867455, + -0.011882569, + -0.007623292, + 0.011166359, + 0.0033233212, + -0.037937406, + 0.05638523, + -0.067974806, + 0.016027903, + 0.003334173, + -0.022093982, + 0.06701986, + -0.018263346, + -0.0054719504, + 0.031838775, + -0.027215967, + -0.007324871, + -0.0378723, + 0.028626682, + 0.05638523, + -0.0063210926, + -0.013759906, + 0.0041263443, + -0.026239317, + 0.006592384, + -0.10408913, + 0.0055669025, + -0.022549752, + 0.006250557, + -0.025653327, + 0.009885863, + -0.043015987, + 0.055300064, + 0.06901657, + 0.021703323, + -0.0062885378, + -0.055300064, + -0.014128863, + 0.05278248, + 0.016581338, + -0.061116558, + 0.045707196, + -0.06320008, + 0.03500746, + -0.0034128474, + -0.055647317, + 0.03160004, + -0.02665168, + -0.0011753705, + -0.04696599, + 0.0267819, + -0.0064567383, + 0.018795077, + 0.052608855, + 0.0012113167, + -0.035636857, + 0.004394923, + -0.034204435, + -0.07014514, + 0.02226761, + 5.8259856E-4, + 0.06428524, + -0.03748164, + 0.006977618, + 0.004186028, + -0.046228077, + 0.049526982, + -0.06333029, + -0.02031431, + -0.002288344, + -0.047877528, + -0.04605445, + -0.011339986, + 0.016603041, + 0.019218292, + 4.8391626E-4, + -0.0038414882, + -0.02430772, + -0.02207228, + 0.041887414, + 0.029494815, + 0.057253364, + -0.004167038, + -0.023331072, + 0.012370894, + -0.00982618, + 0.020346865, + -0.02853987, + 0.007748086, + 0.011741498, + 0.015257436, + 0.0058816005, + -0.043146204, + 0.04407945, + 0.015398507, + -0.046141263, + 0.082255594, + -0.034768723, + 0.004297258, + -0.010504408, + 0.021269256, + 0.024025578, + 0.019196589, + -0.028973935, + -0.024003875, + -0.02044453, + -0.00493208, + -0.05317314, + 0.018729968, + 0.01823079, + -0.008111617, + -0.049830828, + 0.03160004, + 0.0027888769, + 0.027281076, + 0.020336013, + -0.01940277, + 0.012121306, + 0.0067714364, + -0.010368763, + 0.008084488, + -0.010390466, + -0.0023073344, + -0.060508862, + -0.022332719, + 0.013108807, + -0.032620095, + 0.024372831, + 0.03422614, + 0.0490061, + -0.022376126, + 0.033531632, + -0.017937796, + -0.045273133, + 0.012457707, + -0.0066412166, + -0.020661563, + -0.026933823, + 0.0036353066, + -0.002558279, + -0.0092944475, + 0.03116597, + 0.026825307, + -0.019891094, + -0.02065071, + -0.01750373, + 0.036722023, + 0.044839066, + -0.0038930334, + 0.045446757, + -0.020943707, + -0.05408468, + 0.0012696444, + 0.006283112, + 0.029234376, + 0.0013727351, + 0.020270903, + 0.0061257626, + -0.0024999515, + -0.038957465, + -0.05121984, + -0.05851216, + 0.038436584, + 0.024915414, + 0.02317915, + 0.07557097, + 0.011112101, + -0.024568161, + -0.07852262, + -0.005219649, + 0.055647317, + 0.0052711945, + 0.013542873, + 0.013792462, + -0.00659781, + -0.029972289, + -0.01867571, + -0.03422614, + -0.0074659428, + 0.006462164, + 0.015756613, + -0.020140683, + 0.043081094, + -0.00835578, + -0.037286308, + 0.021920355, + 0.010444724, + 0.023005523, + 0.025523107, + -0.0086053675, + 0.0037383973, + 0.004679779, + -0.050091267, + -0.038957465, + -0.005870749, + 0.018133126, + 0.038306363, + 0.04123631, + -0.026174206, + 0.014552078, + -0.0313613, + -0.022441236, + 0.0056916964, + 0.010086619, + 0.036548395, + 0.011198915, + 0.008475147, + -0.03756845, + 0.014226528, + -0.018002907, + 6.602558E-4, + 0.008621645, + -0.0066195135, + -0.008268966, + 0.03324949, + -0.009457223, + 0.030145915, + 0.014193973, + 0.034269545, + -0.050872587, + 0.024872007, + 0.011752349, + -0.013282433, + 0.0019098924, + 0.008062785, + -0.04329813, + -0.006798566, + 0.029364595, + 0.019088073, + 0.050091267, + -6.219358E-4, + -0.05885941, + -0.04368879, + 0.028648386, + -0.012294932, + -0.01652708, + -0.010390466, + -0.018252494, + 0.028127506, + -0.032120917, + 0.030666795, + -0.022766786, + -0.008632497, + -0.03617944, + 0.011144656, + 0.022571456, + 0.028778605, + 0.0050785774, + 0.013814165, + -0.033271194, + 0.016223233, + -0.014638891, + -0.0036732873, + 0.029625036, + -0.017145624, + -0.047877528, + -0.020596454, + 0.028561572, + -0.018903594, + 0.046922583, + 0.006885379, + -0.008692181, + -0.017775021, + 0.035919, + 0.026369536, + 0.018610599, + 0.008344928, + -0.007748086, + -0.0333146, + -0.004815425, + -0.0160062, + 0.048485223, + 0.015007848, + -0.050091267, + 0.0026315278, + 0.03585389, + 0.0640248, + -0.024264315, + -0.013108807, + 0.006684623, + 0.0011238252, + -0.028214319, + 0.01698285, + -0.05278248, + -0.0627226, + 0.052869294, + 0.074876465, + 0.07005832, + 0.0041236314, + 0.011958531, + 0.0095874425, + 0.03350993, + -2.7044374E-4, + 0.015105513, + -0.058251716, + 0.024003875, + -0.031968996, + 0.006082356, + 0.02736789, + 0.004074799, + 0.030818718, + 0.021084778, + 0.008268966, + 0.041605268, + -0.014562929, + -0.030731905, + 0.0031876755, + 0.032186028, + -0.0796946, + -0.007623292, + -0.01005949, + 0.0077643637, + -0.013694797, + -0.011958531, + -0.0056808447, + 0.030623388, + 0.058555566, + 0.035376415, + -0.005214223, + 0.051046215, + 0.0067388816, + -0.010395891, + -0.0015097373, + 0.028496463, + -0.0010234473, + 0.01581087, + -0.0077969185, + 0.043167908, + 0.036939055, + 0.029408002, + 0.010748571, + 0.028214319, + -0.04631489, + 0.033010755, + -1.2292898E-5, + -0.0022042438, + 0.044101153, + -0.060074795, + -0.056645673, + 0.03442147, + 0.022853598, + 0.014595484, + -0.057209957, + 0.029928882, + -0.0022747796, + 0.089938566, + 0.0075093494, + 0.044383295, + -0.014193973, + 0.0034074215, + -0.035875592, + -3.2597374E-5, + -0.002498595, + 0.029928882, + -0.05612479, + -0.04084565, + 0.041561864, + 6.087104E-4, + -0.0927166, + -0.002608468, + 0.0057839355, + -0.017384361, + -0.04479566, + -0.050915994, + -0.05121984, + 0.01168724, + 0.029538222, + 0.019283403, + -0.023222554, + -0.064719304, + -0.004283693, + -0.082081966, + -0.00640248, + -0.012848367, + -0.037459936, + 0.05977095, + 0.015376804, + 0.009869586, + 0.0071892254, + 0.00266137, + 0.03702587, + -0.021269256, + 0.014779963, + 0.0055831796, + -0.007655847, + 0.007021025, + -0.0021594807, + 0.04657533, + -0.016440267, + -0.011448503, + 0.027823659, + 0.049266543, + 0.002548784, + -0.00999438, + -0.037785485, + 0.014866776, + -0.050091267, + -0.00698847, + -0.03513768, + 0.046531923, + 0.052348413, + 0.042169556, + 0.01168724, + -0.011166359, + 0.02148629, + -0.042777248, + 0.029169265, + 0.0037438232, + -0.016179826, + 0.005002616, + -0.01042302, + -0.026673384, + 2.7637824E-4, + 0.022810193, + -0.0026857862, + -0.04748687, + -0.05382424, + 0.0033260342, + 0.017992055, + -0.014139715, + -0.019880243, + -0.01652708, + -0.013239027, + 0.0092944475, + 0.055300064, + 0.06172425, + 0.041822303, + 0.00858909, + -0.015018699, + 0.027671736, + 0.028995639, + 0.025501404, + -0.007948842, + -0.013228175, + 0.032337952, + 0.0019221005, + -0.03385718, + -0.0135103185, + 0.0014527661, + 0.027085746, + -0.01920744, + 5.2359264E-4, + -0.03474702, + 0.015517876, + -0.027823659, + -0.002296483, + -0.0404984, + -0.026955526, + 0.0065327003, + 6.9993216E-4, + -0.016049607, + 0.0013401802, + -0.011980234, + 0.010547815, + 0.014096308, + -0.013727351, + 0.024850305, + -0.03188218, + -0.025588218, + -0.012370894, + 0.012262377, + 0.03780719, + -0.03847999, + 0.01874082, + 3.3296797E-5, + 0.0019804281, + -0.06519678, + 0.0075527565, + -0.036656912, + 0.0058110645, + -0.03969538, + 0.018708264, + -0.01038504, + 0.037112683, + -0.013021993, + 0.03828466, + -0.002160837, + 0.021974614, + -0.04891929, + -0.024003875, + -0.024720084, + -0.026434647, + -0.013911829, + -0.00532274, + -0.0018949714, + 0.027476406, + 1.834609E-4, + 8.3489966E-4, + 0.017926944, + -0.014497819, + -0.0029679295, + 0.023461292, + -0.0016196105, + -0.006576107, + 0.035094272, + 0.024720084, + -9.596938E-4, + -1.4988857E-4, + -0.017872686, + 0.018274197, + -0.006163744, + -0.028908826, + -0.005892452, + 0.0044112005, + -0.032815423, + -0.0073574265, + -0.06289623, + -0.025349481, + 0.010325355, + -0.017590543, + 0.040020928, + -0.012566224, + 0.018393565, + 0.033075865, + -0.011030714, + -0.02050964, + 0.021410327, + -0.012685592, + -0.0017023544, + -0.013271581, + -0.0042782673, + 0.022679972, + -0.020194942, + -0.026695088, + 0.033792075, + -0.013466911, + 0.01149191, + -0.01992365, + -0.026933823, + -0.0041561862, + 0.033553336, + 0.021399476, + 0.010276523, + -0.03691735, + -0.0060172463, + -0.024676677, + 0.035919, + 0.01678752, + -0.016624745, + 0.009793624, + -0.019543841, + -0.0044735973, + -0.024568161, + -0.006348222, + -0.0267819, + -0.008290669, + -0.01692859, + 0.019196589, + 0.028604979, + 0.024785195, + 0.03624455, + 0.003874043, + -4.1117622E-5, + 0.012099602, + -0.012598779, + -0.050481927, + 0.01319562, + -0.034595095, + 0.042495105, + 0.06454568, + 0.037438232, + -0.013423505, + 0.010797403, + 0.016548783, + 0.008339502, + 0.011676388, + -0.0088983625, + 0.01984769, + -0.010097471, + -0.0029597906, + -0.011242321, + 0.0267819, + -0.033271194, + -0.011144656, + 0.0069559147, + -0.019891094, + -0.034074217, + 0.047269836, + -0.030037398, + -0.015094661, + -0.0013998643, + 0.008708458, + -0.007368278, + 0.018578043, + -0.0064838678, + -0.034855537, + -0.017775021, + -0.030232728, + -0.007590737, + -0.004698769, + -0.0027481832, + -0.013564576, + -0.05174072, + 0.022875302, + -0.039044276, + 0.0017823854, + -0.040151145, + -0.014660594, + 0.019608952, + -0.03930472, + 0.020987112, + 0.005534347, + -0.0034426895, + -0.0039879857, + -0.03390059, + -0.03344482, + -0.013835868, + 0.009571165, + -0.025957175, + -0.01789439, + 0.072879754, + -0.016798371, + -0.01784013, + -0.027780253, + -7.9420593E-4, + 0.0111555075, + -0.013966088, + 0.016288344, + 0.009934695, + 5.6903396E-4, + -1.0311198E-5, + -0.022506345, + -0.020618156, + 0.007010173, + -0.015203177, + 0.0326635, + -0.004183315, + 0.025219262, + -0.018143978, + 0.03346652, + 2.009253E-4, + 0.037438232, + -0.020607304, + -0.006760585, + 0.07136052, + 0.0035023736, + -0.017427769, + 0.0038414882, + -0.002560992, + 0.033683557, + 0.0040286793, + 0.0067551592, + -0.036157735, + -0.019348511, + -0.015170623, + -0.0028485612, + 0.0048669702, + 0.015507024, + -0.023157446, + -0.024090689, + 3.6827824E-4, + -0.0011957174, + -0.031144267, + 0.009055711, + -0.0011780835, + 0.0015924813, + -0.055213254, + 0.009153376, + -0.01103614, + 0.0051843813, + 0.009712237, + 0.0150295505, + -0.028257726, + -0.01652708, + 0.007650421, + -0.023765137, + 0.028018989, + 0.009332429, + -0.002881116, + -0.022289312, + 0.014454413, + -0.03926131, + 0.0052413526, + -0.01776417, + 0.041778896, + -0.030059101, + -0.021236701, + -0.010119174, + -0.013043697, + -0.025718438, + -0.015311694, + 0.010444724, + 0.009582017, + -0.04891929, + 0.016049607, + -0.020748377, + 0.011611277, + -0.024850305, + 0.026478054, + 0.006722604, + 0.005423118, + 0.02156225, + -0.0067714364, + 0.022332719, + -0.051436875, + 0.021453734, + 0.030319542, + -0.010466428, + 0.008583664, + 0.014508671, + 0.045750603, + 0.042560216, + 0.03153493, + 0.04010774, + -0.024481349, + -0.024633272, + -0.008751865, + -0.0061962986, + 0.03887065, + -0.0024918127, + -0.020997964, + 0.013043697, + 0.03891406, + 0.052305005, + -0.02142118, + -0.014996996, + -0.040737137, + 0.025588218, + -0.0042131576, + -0.0046580755, + 0.012425153, + 0.038262956, + 0.014291638, + -0.029364595, + -0.013217323, + -0.0024823176, + 9.6851075E-4, + -0.011524464, + 0.0074767945, + -0.031100862, + 0.018317604, + -0.03377037, + -0.013911829, + -0.02337448, + 0.038241256, + 0.011264024, + -0.019663211, + 0.0424517, + -0.0011326422, + -0.0044410424, + 0.009777347, + -0.010216839, + 0.010699738, + 0.039955817, + 0.015159771, + 0.015127216, + 0.01613642, + 0.021323515, + 0.008616219, + -0.004519717, + 0.03513768, + 6.951846E-4, + 0.03207751, + -0.0209003, + 0.017677356, + 0.011274876, + 0.025848657, + -0.059944578, + -0.029733552, + 0.035832185, + 0.019250847, + 0.008648774, + 0.0254797, + 0.0050487355, + 0.0150295505, + -0.039326422, + 0.022419533, + 0.0055533377, + 0.0066086617, + 0.013857571, + 0.031795368, + -0.00607693, + -0.011448503, + 0.01724329, + 1.20046505E-4, + -0.013966088, + 0.0027034201, + -0.016809223, + 8.09127E-4, + 0.019608952, + -0.033943996, + 0.015018699, + -0.0018746245, + 0.003038465, + 0.02867009, + -0.03270691, + -0.014020346, + -0.011513612, + -0.012012789, + 0.013890127, + -6.483868E-4, + -0.010260246, + -0.007276039, + 0.051263247, + -0.019446177, + 0.020032167, + 0.006690049, + 0.0050840033, + -0.015300842, + 0.020140683, + -0.027476406, + -0.006500145, + -0.027780253, + 0.019836837, + 9.624067E-4, + -0.036114328, + 0.007623292, + 0.0101246, + -0.014497819, + 0.02665168, + -0.0041399086, + 0.017406065, + -0.05456215, + 0.036722023, + -0.025197558, + -0.01862145, + 0.0030086231, + -0.0022788488, + -0.029538222, + 1.8329134E-4, + -0.014269935, + -0.004815425, + 0.018415269, + 0.026174206, + -0.020694118, + -0.014779963, + 0.005572328, + -0.020585602, + 0.0014351322, + 0.017427769, + 0.011198915, + 0.013814165, + -0.025219262, + -0.002368375, + -0.013228175, + 0.003079159, + -0.012425153, + -0.0011828311, + 0.01711307, + 0.0063265185, + -0.02045538, + -0.019391919, + 0.026347833, + -0.020021316, + -0.0017444046, + -0.019782579, + 0.024068985, + 0.027346186, + 0.0059141554, + -0.022159092, + 0.02762833, + -0.010325355, + 0.0022313728, + 0.012077899, + 0.013488615, + -0.025132447, + -0.019543841, + 0.0047204727, + -0.0032419339, + 0.009723089, + -0.03663521, + -0.0069287857, + 0.014910183, + 0.007834899, + 0.021779284, + 0.013835868, + -0.026825307, + -0.0071295416, + 0.0085077025, + -0.011708942, + 0.030037398, + 0.0026667959, + 0.010471853, + 0.024741787, + 0.02541459, + -0.01149191, + -0.016244937, + -0.031578336, + -0.023504699, + 0.022289312, + 0.030601684, + -0.012392597, + 0.023765137, + -4.3813582E-4, + -0.023070632, + -0.0067443075, + -0.009245615, + -0.02580525, + -0.013141362, + -0.06333029, + -0.01215386, + -0.017807577, + -0.022039725, + 0.022213351, + 0.03311927, + 0.017579691, + 0.012794109, + 0.028322836, + 0.016440267, + 0.009354132, + -0.024394535, + 0.025067337, + -0.061029743, + -0.008106191, + -0.011665536, + -0.020129832, + -0.030536575, + 0.028040692, + 0.03676543, + 0.004804573, + 0.06684624, + 0.0149318855, + 0.028496463, + 0.002605755, + -0.0026071116, + -0.019272551, + -0.0027346187, + -0.02045538, + 0.020922003, + -0.011437651, + 0.024134094, + 0.016386008, + -0.008556535, + -0.002593547, + 0.0075798854, + 0.0035376416, + -0.0077101053, + 0.05929348, + -0.048702255, + -0.0222025, + -0.011513612, + 0.019359363, + -0.03160004, + -0.015018699, + -0.030883828, + -0.014964441, + -0.0015789167, + -0.008860381, + -0.0058219163, + -0.021920355, + -0.01893615, + -0.02710745, + -0.0010872008, + 0.010265672, + -0.011860866, + -0.0033070438, + -0.016765816, + 0.029429706, + -0.02142118, + 0.019760875, + -0.011144656, + -0.010954752, + 0.005827342, + -0.004356942, + -0.012392597, + 0.0040558083, + -0.014085457, + -0.009066563, + 0.016896037, + -0.008079062, + 0.0050731516, + -0.0031659722, + -0.012837515, + -0.013054549, + 0.0048886733, + 0.017948648, + 0.0024972386, + 0.028822012, + -0.021800987, + 0.0036461581, + 0.0014514097, + -0.0076070144, + 0.0046417983, + -0.004549559, + -0.0027156281, + -0.021529697, + 0.0031822496, + -0.016689856, + -0.0076775504, + -0.025154151, + -0.0024579014, + 0.008480573, + 0.00333146, + 0.0025542097, + 0.014291638, + 0.003399283, + 0.006082356, + 0.020227497, + 0.013749055, + -0.021670768, + 0.013043697, + 0.014964441, + 0.020433677, + 0.017015405, + -0.010753997, + 0.032381356, + 0.0287352, + -0.0036325937, + -0.0056537157, + -0.016103866, + -0.0030981493, + 0.028127506, + -0.022462938, + 0.03416103, + -0.003781804, + -0.029993992, + 0.020943707, + -0.0016630171, + -0.0431245, + -0.024112392, + -0.006657494, + 0.0056971223, + 0.020325162, + 0.040715434, + 0.016440267, + 0.016635597, + 0.00832865, + 0.0016440267, + 0.018436972, + -0.032902237, + 0.018024608, + -0.023765137, + 0.023135742, + -0.027411297, + -0.020021316, + 0.013835868, + 0.006896231, + 2.7171542E-5, + -0.010596647, + -0.010374188, + -0.031339597, + -0.004389497, + 0.04036818, + -0.005425831, + -0.03541982, + 0.0010424377, + -0.017883537, + 0.0039852727, + 0.02762833, + -0.022506345, + 0.0072489097, + 0.001935665, + 0.011676388, + 0.0029109581, + -0.009619998, + 0.01593024, + 0.01587598, + 0.05460556, + -0.015539579, + 8.545683E-4, + 0.008220133, + 0.030905532, + 0.0052630557, + -0.03709098, + 8.0844876E-4, + 0.017623099, + -0.007178374, + -0.021551399, + -0.014443561, + 0.010797403, + 0.0066086617, + -0.0018461389, + -0.013401802, + -0.03513768, + 0.003038465, + -0.027020637, + 0.047052804, + 0.014400154, + -0.0109113455, + 0.0088006975, + -0.02489371, + 9.780059E-4, + 0.01678752, + 0.02710745, + -0.0058761747, + 0.017742466, + -0.0062180017, + 0.012718147, + -0.033401415, + -0.020498788, + 0.0018095145, + -0.05035171, + -0.0035620579, + -0.004508865, + 0.0080031, + -0.03377037, + 0.054431934, + 0.003947292, + 0.025327777, + -0.012392597, + -0.026868714, + 0.014443561, + 0.017536284, + -0.020021316, + 0.016429415, + -0.0046906304, + 0.033358008, + 0.011329134, + 0.014356748, + 0.008464295, + 0.026825307, + 1.0707538E-4, + 0.00884953, + -0.0052006585, + 0.0060932077, + 0.040997576, + 0.01581087, + 0.010130025, + -0.035224494, + 0.019131478, + -2.3754964E-4, + -0.0136079835, + 0.037394825, + 0.07939076, + 0.017655652, + -0.012327488, + 0.0011326422, + 0.024611568, + 0.022636566, + -0.025761845, + -0.039174497, + -0.013694797, + -0.019229144, + 0.0072597614, + 0.027194263, + -0.010596647, + -0.03153493, + 0.005187094, + 0.033553336, + -0.0043108226, + -0.0018773373, + -0.0019288828, + -0.009582017, + -0.023765137, + 0.022039725, + 0.012880922, + 0.037199493, + -0.0326418, + -0.0033450245, + -0.016060458, + 0.022831894, + 0.029885475, + 0.0033721537, + 0.023548106, + -0.0018922584, + -0.022528049, + 0.008627071, + -0.007650421, + 0.038697023, + 0.012045344, + 0.0063916286, + -0.0102710975, + -0.051046215, + -0.011372541, + -0.022224203, + -2.634919E-4, + 0.010108323, + 0.0078240475, + -0.036396474, + -0.0011963956, + -0.009793624, + 0.006700901, + -0.02697723, + -0.034920648, + -0.045359943, + 0.046792362, + 0.017970352, + 0.008399186, + 0.026369536, + 0.018165682, + -0.027194263, + 0.014497819, + -0.023309369, + -0.037676968, + 0.009386687, + -0.039760485, + 5.6716886E-5, + 0.008892937, + 0.023721732, + 0.0069016567, + -0.0055994573, + -0.017992055, + -0.03381378, + -0.018209087, + -0.0054638116, + -0.009869586, + 0.032164324, + 0.013662241, + -0.025197558, + -0.013662241, + 0.03763356, + -0.0058164904, + -0.01704796, + -0.01280496, + 0.012436003, + -0.020227497, + 0.016472822, + -0.0015490747, + -0.014052901, + -0.011242321, + -0.014052901, + 0.0111555075, + 0.008377482, + -0.0022503634, + 0.0135103185, + 0.02886542, + 0.01815483, + -0.013239027, + -0.00914795, + 0.0054963664, + 0.0062125763, + 0.0023019086, + -0.020292606, + 0.035333008, + -0.002065885, + -0.020477084, + 0.019218292, + 0.027519813, + 1.7769595E-4, + 0.012946032, + -2.3364983E-4, + 0.012826663, + -0.015886832, + -0.011405096, + 0.0021323515, + -0.011567871, + -0.006760585, + 0.009120821, + -0.0065327003, + -0.013662241, + 0.0062993895, + 0.025002228, + 0.009082841, + -0.0037980815, + 0.021931207, + -0.002786164, + 0.024286019, + 0.015355101, + -0.025175855, + 0.0030574556, + -0.022245906, + -0.01168724, + 0.015517876, + 0.0027169846, + 0.010070342, + 0.02580525, + 0.030731905, + -0.007232632, + -0.047009397, + -0.012902625, + -0.033401415, + -0.020813486, + 0.022875302, + 0.0030574556, + 0.06276601, + 0.028887123, + -0.0063970545, + 0.020205794, + -0.023309369, + 0.0045712623, + -0.010846236, + -0.025761845, + -0.0020943705, + 0.021399476, + -0.015214029, + -0.0050487355, + -0.013401802, + -0.005515357, + -0.04557698, + -0.02337448, + -0.018339308, + 0.021464586, + -0.08603197, + 0.01048813, + 0.03416103, + 0.0069559147, + 0.0052956105, + -0.049483575, + 0.0066412166, + -0.0060335235, + 0.022441236, + 0.001983141, + 0.009142525, + 0.001890902, + -0.008464295, + 0.014226528, + 0.015528727, + 0.01378161, + -0.010574944, + 4.00155E-5, + 0.016342603, + -3.7133027E-4, + -1.2165729E-4, + 0.032381356, + -0.01423738, + 0.03965197, + -0.001440558, + -0.021106482, + 0.029017342, + -0.014476116, + 0.0052711945, + -0.019945353, + -0.022441236, + -5.6360813E-4, + -0.02298382, + 1.1563802E-4, + 0.026955526, + 0.038545102, + 0.0049564964, + -0.010781125, + 0.020553047, + 0.007986823, + 0.023765137, + -0.008344928, + 0.035940703, + -0.011372541, + -0.015344249, + -0.015962794, + -0.0016562348, + 0.020238347, + -0.01986939, + 0.007948842, + 0.005412266, + 0.01149191, + -0.034052514, + -0.0129677355, + 0.014356748, + 0.0017661079, + 4.656041E-4, + -0.0111555075, + -0.002460614, + -0.011708942, + 0.03780719, + -0.0076721245, + -0.010162581, + 0.011470206, + 0.010650906, + 0.0075473306, + 0.009549462, + 0.011426799, + 0.015289991, + -0.030362949, + 0.013228175, + -0.021833543, + 0.02645635, + -0.023092335, + 0.038653616, + 0.007883732, + 0.0058056386, + 0.007113264, + -0.0024280592, + 0.024068985, + -0.002136421, + 0.00292181, + 0.004926654, + -0.067193486, + 0.017666504, + 0.0046363724, + 0.027932176, + 0.0065435516, + 0.023591511, + 0.01345606, + 0.014280786, + 0.025588218, + 0.042538512, + 0.021410327, + 0.05768743, + 0.0013293285, + 0.021345217, + -0.001425637, + -0.0112531725, + 0.009902141, + -0.014345896, + 0.028301133, + 0.037199493, + 0.052261602, + 0.024676677, + 0.016635597, + 0.013423505, + -0.02710745, + -0.017232439, + -0.019858541, + -0.0037845168, + -0.022614863, + 0.028496463, + 2.9180796E-4, + -0.022191647, + -0.048745662, + 0.029559925, + -0.025783546, + -0.019391919, + -0.0077643637, + -0.006695475, + 0.014226528, + 0.014910183, + -0.016049607, + -0.053216547, + 0.0014161417, + 0.0038279234, + -0.013336692, + 0.016027903, + 0.004397636, + 0.043384943, + 0.03481213, + -0.01482337, + 0.035159383, + 0.04134483, + 0.001336789, + 0.02156225, + -0.009885863, + 0.06806162, + -0.019239996, + -0.038935762, + -0.0034019959, + -0.024763491, + 0.030666795, + 0.021442883, + -0.0027346187, + -0.0116004255, + -0.023786841, + -0.020075573, + -0.0032582113, + 0.013087103, + -0.028040692, + 0.011839163, + -0.02045538, + -0.009527759, + -0.007438814, + -0.052652262, + 0.014454413, + -0.025566515, + 0.002628815, + 2.2508719E-5, + 0.026434647, + 0.003293479, + 0.0062993895, + -0.0018664857, + -0.009137099, + -0.014779963, + 0.006993896, + -0.010607499, + 0.009066563, + 0.027454702, + 0.010585795, + 0.03149152, + -0.023591511, + 0.004297258, + 0.037438232, + -0.005610309, + 0.0192617, + -0.004164325, + -0.0024619706, + -0.01671156, + 8.193004E-4, + -0.0037926557, + 0.008306947, + -0.0034236992, + -0.052565448, + 0.013618835, + -0.01254452, + 0.005444821, + 0.04540335, + 0.03585389, + 0.025154151, + -0.01659219, + 0.017460324, + 0.021855246, + -0.033878885, + 0.015062106, + 0.010743145, + 0.020227497, + 0.022050576, + -0.013314988, + -0.0116980905, + -0.028691793 + ], + "content": "교외장학/보훈장학/통일부장학/국제화장학/멘토링장학: 02-450-3669\n 국가우수/농어촌희망재단장학/포상장학/공로장학/국고사업장학(혁신사업): 02-450-3511\n 국가근로장학/대학생청소년교육지원사업/건국가족장학/일감호장학: 02-450-3512\n\n- 일반대학원 : 일반대학원 행정실 ☎ 02-450-3552\n- 특수/전문대학원 : 각 대학원 행정실 대표번호 ☎ 02-450-3114\n\n3 등록금 납부방법\n1) 전용계좌 입금(전용계좌번호는 매 학기 다르게 부여됨)\n ● 등록고지서에 기재된 전용계좌로 입금 시 등록 처리됨.\n ● 각 개인별 전용계좌번호(예금주: 학생본인)는 등록금 수납을 위한 학생 지정 가상계좌이므로 송금시 보내는 사람(송금인 명)은\n 학생 본인이 아니어도 됨.(타인이 입금하여도 무방함)\n ● 입금액이 고지서 금액과 일치해야 입금 가능하며, 타 은행을 이용하여 송금하는 경우 수수료는 본인이 부담.\n2) 은행방문\n ● 등록금고지서를 지참하여 해당 은행에 영업시간 내 방문하여 납부함.\n3) 삼성카드 납부\n※ 삼성카드 등록금 수납 후 결제취소는 불가함.\n※ 분할납부 신청 시 카드납부는 불가함.\n※ 2024학년도 2학기 차액고지자(전과, 소속변경 등)는 카드납부 불가하며,\n 신한은행 전용계좌로 입금해야 함. (고지서 참조)\n※ 등록금 카드결제 금액은 연말정산 소득공제 시 카드 사용금액에서 제외됨 (교육비공제 대상)\n ● 삼성카드 홈페이지 납부\n https://www.samsungcard.com/personal/services/UHPPPS1000M0.jsp?codeDv=tuition", + "id": "72add540-b979-420f-beb7-b628ea753711", + "metadata": { + "charset": "UTF-8", + "filename": "ku-uni-register.txt", + "source": "ku-uni-register.txt" + }, + "media": [] + }, + "69300c46-11f3-4eee-baeb-b39cf057b452": { + "embedding": [ + -0.036749937, + 0.010507261, + 0.034222487, + -0.014930299, + -0.006833287, + 0.02299164, + -0.021259114, + 0.025906362, + -0.03446708, + -0.030533226, + 0.021136818, + 0.0048561688, + -0.005375927, + 4.7994795E-4, + 0.023970008, + -0.010364583, + -0.04557563, + 0.049040683, + -0.025580239, + 0.03593463, + 0.01960812, + -0.01302452, + 0.008897032, + 0.0020280748, + -0.01180156, + 8.860088E-4, + 0.04557563, + -0.028739551, + -0.010099608, + -0.009564564, + 0.055807725, + -0.019801755, + 0.0011471616, + -0.021381412, + -0.06000655, + 0.032917995, + 0.031756185, + 0.04598328, + -0.013421982, + -0.040724557, + -0.019067978, + 0.019363528, + 0.008300838, + -0.07191002, + 0.01961831, + 0.05968043, + 0.017987698, + 0.0020204312, + 0.045901753, + 0.026232485, + -0.015297187, + 7.8091066E-4, + 0.049448334, + 0.06754813, + -0.0074651493, + 0.046676293, + 0.01677493, + 0.016448807, + 0.004718586, + -0.03210269, + 0.013605426, + -0.008795118, + -0.011974813, + -0.020973757, + 0.0027440158, + -0.0343244, + 0.0024459192, + 0.07504895, + -0.031267002, + 0.0012968468, + -0.0037504095, + -0.032856848, + 0.077943295, + -0.038665906, + -0.02413307, + 0.019404292, + -0.02849496, + 0.008204021, + -0.008107203, + 0.020464191, + 0.024805699, + -0.0019503658, + -0.027679654, + 0.029452944, + -0.025457943, + -0.026843963, + -0.09400483, + -0.0072460356, + 0.0017108696, + 0.0117811775, + -0.04235517, + 0.001614052, + -0.048999917, + 0.06347161, + 0.059028186, + 0.0138704, + 0.006884244, + -0.03171542, + 0.0070676873, + 0.02405154, + 0.009528894, + -0.06824115, + 0.029697536, + -0.06400155, + 0.0030956166, + -0.015052595, + -0.037504096, + 0.033651773, + -0.021809448, + -0.03271417, + -0.018650135, + 0.025111439, + 0.007954333, + 0.007169601, + 0.057438336, + 0.00444342, + -0.04912221, + -0.024316514, + -0.057886757, + -0.043292772, + 0.012728971, + 0.029391797, + 0.046309404, + -0.05438094, + 0.036994528, + -0.018894726, + 0.0079441415, + 0.03379445, + -0.061922524, + -0.004917317, + 0.025784066, + -0.060699563, + -0.026864346, + -0.018181333, + 0.023032406, + 0.02666052, + -0.0151443165, + -0.024642637, + -0.024887228, + -0.0028866944, + 0.03301991, + 0.014420732, + 0.039012413, + 0.024561105, + -0.030349782, + 0.0069352, + -0.020637443, + 0.040867236, + -0.030349782, + 0.014125184, + 0.0140028875, + 0.005768293, + 0.0017924003, + -0.020454, + 0.03931815, + -0.0022548318, + -0.03507856, + 0.051282775, + -0.022787815, + 0.019190274, + 0.030431312, + -0.019445058, + 0.037177972, + 0.0012350618, + -0.037361417, + -0.014879342, + -0.005370831, + -0.013493321, + -0.085444115, + -0.009029519, + 0.0072154617, + -0.023460442, + 0.0070320177, + -0.0013771035, + -0.0053912136, + 0.032286134, + -0.003880349, + -0.02266552, + 0.01202577, + 0.0016802956, + 0.0076434975, + -0.007093166, + -0.027985392, + -0.002435728, + -0.04924451, + -0.0052026743, + 0.02535603, + -0.0049682735, + 0.03118547, + 0.050671294, + 0.05609308, + -0.035119325, + 0.047899254, + -0.024703784, + -0.022094805, + 0.016601676, + 0.023419676, + -0.023725417, + -0.020015772, + -0.008779831, + -0.025865596, + 0.0045529767, + 0.025091056, + -0.020932993, + 0.021585237, + -0.04826614, + -0.0018752047, + 0.027088556, + 0.048551496, + 0.018680708, + 0.04582022, + -0.017997889, + -0.014074227, + -0.002555476, + -0.038502846, + 0.064490736, + 0.0032510343, + 0.03990925, + 0.036138456, + 0.014461498, + -0.009870304, + -0.063716196, + -0.019740608, + 0.02052534, + 0.04135642, + 0.025621004, + 0.02266552, + 0.040541112, + 0.0327957, + -0.10109799, + -0.024622254, + 0.04846997, + 3.770792E-4, + 0.02160562, + -0.014726472, + -0.008280456, + -0.021503707, + -0.018242482, + -0.022420926, + -0.0067211823, + 0.019394102, + 0.025865596, + -0.025987891, + 0.051364306, + -0.013523895, + -0.0077657937, + 0.0022612014, + 0.027394297, + 0.038869735, + 0.013513704, + 0.007332662, + 0.011159507, + 0.03216384, + -0.038563993, + -0.033916745, + -0.033998277, + 0.0053147785, + 0.05409558, + 0.04296665, + -0.047695424, + 0.0077403155, + 0.002145275, + -0.034059424, + 0.0026956068, + 0.023175085, + 0.015969815, + 0.033651773, + 0.033142205, + -0.01785521, + -0.012300936, + 0.0022127926, + -0.019710032, + 0.019414485, + 0.02405154, + -0.002207697, + 0.015745604, + 0.0041784453, + 0.04626864, + -0.0151239345, + 0.02757774, + -0.017243732, + 0.02566177, + -0.010751854, + -0.010160756, + 0.004947891, + 0.0033223736, + -0.031165088, + 0.027985392, + 0.026905112, + -0.0025911457, + 0.04345583, + -0.0093454495, + -0.0019975007, + -0.033386797, + 0.014430923, + -0.042029046, + -0.02765927, + -0.021564854, + 0.012647441, + 0.034507845, + -0.0013669122, + 0.018731665, + -0.018904917, + -3.5064545E-4, + -0.013493321, + 0.031307768, + 0.022889728, + -8.235869E-4, + 0.016591486, + 0.0154908225, + 0.005967024, + 0.028372664, + -0.047043182, + 0.036342286, + 0.03340718, + 0.0050013955, + -0.031389296, + -0.013197773, + 0.03124662, + -0.012667824, + 0.06518375, + 0.0070880703, + 0.0014497167, + -0.008158159, + 0.048510734, + 0.025865596, + -0.01899664, + 0.004682916, + 0.018507456, + -0.03330527, + -0.011404099, + 0.023276998, + 0.035262004, + 0.013279303, + -0.024540724, + 0.013513704, + 0.03707606, + 0.030716669, + -0.016387658, + 0.0074498625, + -0.0143901585, + -0.021972507, + -0.0565415, + 0.0016535433, + -0.031511594, + -0.052301906, + 0.06257477, + 0.032265753, + 0.03407981, + 0.0029172683, + 0.043618895, + -0.0014777429, + 0.004524951, + 0.009967121, + 0.036525726, + -0.01647938, + 0.023154702, + -0.03379445, + 0.011291994, + 0.018629752, + -0.004119845, + 0.04145833, + 0.03805443, + 0.016581295, + 0.030737052, + -0.0037529573, + -0.028209602, + -0.011077976, + 0.019271806, + -0.06538758, + -0.044556495, + -0.03982772, + 0.027822332, + -0.017315071, + -0.031674653, + 0.007775985, + 0.022787815, + 0.043088943, + 0.011006637, + 0.0034803392, + 0.05307645, + -0.019597929, + -0.03705568, + -0.007470245, + 0.021931743, + 0.02368465, + 0.021544471, + 0.027536975, + 0.05001905, + 0.05898742, + 0.03171542, + 0.031002026, + 0.024989141, + -0.057030685, + 0.05010058, + 0.008856266, + 0.010731471, + 0.046920884, + -0.05715298, + -0.049978282, + 0.013829635, + 0.044026546, + 0.020494765, + -0.059191246, + 0.007184888, + -0.017274305, + 0.075945795, + -0.025865596, + 0.033590626, + 0.0038828969, + 0.0035058176, + -0.061800227, + 0.019017022, + 0.0077963676, + 0.023399293, + -0.044067312, + -0.047002416, + 0.047247007, + -0.005345353, + -0.092455745, + -0.015307378, + 0.01838516, + -0.0112206545, + -0.048062313, + -0.042803586, + -0.040520728, + -0.0019860354, + 0.032082308, + 0.024948377, + -0.0118729, + -0.04037805, + -0.024785316, + -0.055196244, + -0.009488128, + 0.025702534, + -0.037585627, + 0.0068689566, + 0.02335853, + 0.010466496, + -0.009865208, + 0.005243439, + 0.04973369, + -0.018884536, + -0.020138068, + -0.027965011, + 0.01424748, + 0.023521591, + -0.0031159993, + 0.06497992, + -0.02305279, + 0.0065988866, + 0.009401502, + 0.027842714, + -2.5096152E-4, + -0.019017022, + -0.0695864, + 0.015562162, + -0.03884935, + -0.006741565, + -0.013034712, + 0.041091442, + 0.03218422, + 0.013992696, + 0.0031287384, + -0.018976256, + 0.01555197, + -0.036179222, + 0.042477466, + -6.783604E-4, + 0.016591486, + 0.018507456, + -0.010140373, + -0.011770986, + -0.0071594096, + 0.02735353, + 0.0021975057, + -0.05323951, + -0.04651323, + 0.0070829745, + 0.002024253, + -0.016825886, + -0.03440593, + 0.028658021, + -0.0025656675, + 0.0014624558, + 0.05735681, + 0.041641776, + -7.089344E-4, + -0.02368465, + -0.021422176, + 0.030635139, + 0.041254506, + 0.06277859, + -0.0059975977, + -0.00854543, + 0.003363139, + -0.0032612258, + 0.010048652, + -0.013778678, + -0.021462942, + -0.0065377383, + -0.006996348, + -6.3313637E-4, + -0.035404682, + 0.032979146, + -0.044841852, + -0.042314403, + -0.024234984, + -0.012097109, + 0.031165088, + 0.014899725, + -0.012861459, + 0.0138704, + -0.0033249215, + 0.00563071, + 0.013595235, + -0.020392852, + -0.001232514, + -0.023868095, + -0.0024127974, + -0.033774067, + -0.002675224, + 0.018558413, + -0.023643887, + 0.0038064618, + -0.022482075, + 0.008402752, + -0.04223287, + 0.026252868, + -0.0424367, + 0.019343145, + -0.041172974, + -0.0021554665, + -0.012728971, + 0.03609769, + 0.021422176, + 0.048510734, + -0.021503707, + -8.0829987E-4, + -0.021116436, + -0.016112493, + -0.016326511, + -0.016652634, + -0.025600621, + -0.004975917, + 0.004170802, + 0.047287773, + -0.0040688887, + -0.032061923, + 0.0013057642, + -0.015908666, + 0.012963372, + 0.03118547, + -0.011434672, + -0.0073734275, + 0.07060554, + 0.013493321, + -0.009381119, + -0.007317375, + -0.029289883, + 0.034446698, + -0.018425925, + -0.0026522938, + -0.019312572, + -0.020657826, + -0.040398434, + -8.3202656E-5, + -0.0536064, + -0.009753103, + 0.029045291, + 0.0028433811, + 0.024398046, + -0.020056538, + -0.02052534, + -0.002296871, + -0.01524623, + -0.010221904, + 0.026578989, + 0.005559371, + -6.6044595E-5, + -0.015439865, + 0.010048652, + 0.023378912, + -0.01378887, + -0.04007231, + 0.034630142, + -0.013228347, + 0.008723779, + -0.01110855, + -0.016509956, + 0.006068937, + 0.05637844, + 0.01777368, + -0.0085912915, + -0.040806085, + -0.008015481, + -0.0023503755, + 0.027414678, + 0.007898281, + -0.025070673, + 7.7589466E-5, + -0.01095568, + -0.0068995305, + -0.041641776, + -0.0049376995, + -0.021483324, + 0.024296131, + -0.03110394, + 0.011190081, + 0.04174369, + 0.020025965, + 0.037646774, + -0.0074753407, + 0.019679459, + 0.0075721582, + -0.011455055, + -0.04871456, + 0.018211907, + 0.0059772152, + 0.016917609, + 0.040133458, + 0.029860597, + 0.003684166, + 0.03010519, + 0.016367277, + 0.0063950596, + -0.020331703, + 0.0033529478, + 0.05201655, + -0.006466399, + -0.0151239345, + -0.002495602, + 0.011913665, + -0.050752826, + 0.007363236, + -7.866433E-5, + 3.1179102E-4, + -0.012005387, + 0.04671706, + -0.04019461, + 0.016020771, + -0.020932993, + 0.010772236, + -0.012209213, + 0.020066729, + 0.0056205187, + -0.032041542, + -0.03110394, + -0.028026158, + -9.100539E-5, + -0.0050931172, + 0.0061249896, + -0.0171622, + -0.049896754, + 0.037116826, + -0.028413428, + 0.0045020203, + 0.01302452, + -0.036872234, + 0.008438421, + -0.03295876, + 0.007832037, + 0.0025299978, + -0.02099414, + -0.01617364, + -0.010598984, + -0.041111827, + -0.0017643741, + 0.03332565, + -0.03668879, + -0.024785316, + 0.052994918, + -0.027292382, + -0.009177293, + -0.019343145, + -0.010762045, + -0.010410444, + -0.008137777, + 0.008672822, + 0.02513182, + -0.033835217, + 0.02757774, + -0.020321513, + -0.020138068, + -0.030655522, + -0.03308106, + 0.015765987, + -0.0063390075, + 0.036953762, + -0.027149703, + 0.008336508, + -0.0064409208, + 0.045290273, + -0.021503707, + -0.015348143, + 0.06881186, + 0.026110189, + -0.003121095, + 0.011016828, + -0.009202771, + 0.03624037, + 0.00769955, + 0.00808682, + -0.009533989, + -0.039766572, + -6.3695817E-4, + -0.0037249313, + 5.156176E-4, + 0.012066535, + 0.002861216, + 0.0010891984, + -0.0013070381, + -0.021401793, + -0.025559856, + 0.005098213, + 1.4872973E-4, + -0.0063441033, + -0.07423365, + 0.024948377, + -0.0033529478, + 0.0057631973, + 0.0292695, + 0.01815076, + -0.0027440158, + -0.015684457, + 0.0046854643, + -0.0055440837, + -0.009620616, + 0.006028172, + -0.0028866944, + 5.872754E-4, + 0.030981645, + -0.047613896, + 0.008540335, + -0.032000776, + 0.015643692, + -0.02344006, + 0.012219405, + -0.0041147494, + -0.023317764, + -0.008937797, + -0.029921746, + -0.0016165997, + 0.04005193, + -0.019353336, + 0.0068536694, + -0.025519092, + -0.0034599567, + -0.03171542, + 0.02979945, + -0.0058345366, + 0.0028688596, + 0.019699842, + -0.020780122, + 0.02022979, + -0.03829902, + 0.016336702, + 0.026925495, + 0.003938949, + 0.0069199135, + 4.9045775E-4, + 0.017671768, + 0.03340718, + 0.0051466217, + 0.011241037, + 0.004030671, + -0.016642442, + -0.011291994, + -0.031674653, + 0.04296665, + 0.0042370455, + -0.012097109, + 0.024010774, + 0.07647574, + 0.03532315, + -0.015755797, + -0.03248996, + -0.037381798, + -0.0034115477, + 0.0098193465, + 0.005951737, + 0.001507043, + 0.052383438, + 0.024316514, + -0.026517842, + -0.03407981, + 0.016999139, + 0.019873094, + -0.005047256, + 0.011312377, + -0.0034268347, + 0.03440593, + -0.041030295, + -0.018639943, + -0.032367665, + 0.015531587, + 0.0073122797, + -0.028005775, + 0.04459726, + -0.022889728, + 0.021055289, + 0.010945489, + -0.0016229694, + -0.0046854643, + -0.0056510926, + -0.011444864, + 0.031898864, + 0.0464317, + -3.189568E-4, + 0.0012853815, + 0.010751854, + 0.016408041, + 0.035262004, + 0.030838965, + -0.008035864, + 0.045412567, + -0.018639943, + 0.010435922, + -0.05270956, + -0.019037405, + 0.028617255, + 0.021259114, + 0.0057937712, + 0.013850017, + -0.014471689, + 0.015572352, + -0.025804449, + 0.04223287, + -0.0084180385, + 0.031083558, + -0.0046803686, + 0.026150953, + 0.0127085885, + -0.012851267, + 0.032856848, + 0.0027185373, + 5.974667E-4, + 0.024724167, + -0.011088167, + -0.0015108647, + 0.0065530255, + 0.004489281, + 0.032836467, + 0.016112493, + -0.025947127, + 0.0068078088, + -0.063756965, + -0.027007025, + -5.0096755E-4, + 0.002980964, + -0.0066243648, + -0.005600136, + 0.001961831, + 0.00873397, + 0.014909917, + -0.032897614, + -0.003712192, + 0.023501208, + -0.00804096, + -0.0014216906, + 0.018762238, + -0.031226236, + -0.014155758, + -0.01655072, + 0.010721279, + 0.022196718, + -0.027761184, + 0.0128818415, + 0.0012057618, + -0.008851171, + 0.022563605, + -0.020566104, + 0.012759546, + -0.032530725, + 0.017977506, + -0.020311322, + -0.0044204895, + 0.017029712, + 0.01095568, + -0.016183833, + 0.016734164, + -0.019720225, + 0.0042829067, + 3.4459436E-4, + 0.04908145, + 0.004940247, + 0.004504568, + 0.020250173, + -0.021442559, + 0.026701285, + 0.002021705, + -0.032612257, + 0.021585237, + -0.018262863, + 0.00980406, + -0.018803004, + -0.0055135097, + -0.02368465, + 0.0027134416, + 0.02252284, + -0.010430827, + 0.005538988, + 0.0015478083, + 0.016560912, + 0.010018078, + -0.021238733, + -0.021055289, + 0.029473327, + 0.011128932, + -0.014298436, + -0.008341604, + 0.037381798, + -0.005951737, + 0.0018293437, + 0.008891935, + 0.017681958, + -0.0011917487, + -0.022196718, + 0.050304405, + -0.007444767, + 0.010390061, + -0.041499097, + 0.018517647, + -0.0048485254, + -0.00593645, + 0.027027408, + 0.0069250087, + 3.6911725E-4, + -0.016968565, + 0.022461692, + -0.019404292, + 0.0069301045, + 0.0053045875, + 0.019383911, + 0.015113743, + 0.033447947, + -0.028984142, + -0.01738641, + -0.012311127, + -0.019954626, + 0.0066906083, + 0.024398046, + 0.005375927, + 0.013167199, + -0.014838577, + -0.013085668, + 0.0024293584, + -0.017610619, + -0.024398046, + -0.03668879, + -0.047043182, + -0.017111244, + -0.0073479493, + -0.018833578, + 0.028087307, + 0.029045291, + 0.020372469, + 0.033366416, + 0.036668405, + 0.02843381, + -0.011098359, + -0.033631388, + 0.036811084, + -0.04798078, + -0.0042370455, + -0.019343145, + -0.030349782, + -0.01617364, + 0.052994918, + 0.039257005, + 0.025621004, + 0.052220378, + 0.0103798695, + 0.011302185, + 0.03218422, + 0.002164384, + -0.0037682443, + -0.0017529088, + -0.039725807, + 0.013320069, + -0.012453806, + 0.018701091, + 0.017916359, + -0.005345353, + 0.0058651106, + 0.008102108, + 0.032449197, + -0.011363333, + 0.07879937, + -0.024989141, + -0.009753103, + -0.003834488, + 0.032000776, + -0.014614368, + -0.0028128072, + -0.042477466, + -0.008825692, + 0.0032230082, + -0.007225653, + -0.025682153, + -0.010751854, + 0.0032331995, + -0.04610558, + 0.009319971, + 0.02827075, + -0.0038727056, + -0.0015516301, + -0.015072977, + 0.020586487, + -0.006833287, + 0.002416619, + -0.023012023, + -0.029982893, + -0.019750798, + -0.0136359995, + 0.0051721, + 5.6116015E-4, + -0.030349782, + 0.0051721, + -0.016611869, + -0.014808003, + 0.013309877, + -0.0074549583, + -0.0029427465, + -0.030757435, + 0.011251229, + 0.036036544, + 0.003164408, + 0.017202966, + -0.025172586, + -0.0067466605, + 0.0010688157, + -0.008382369, + -0.008713587, + 0.0021338097, + 0.0035185567, + -0.030512843, + 0.013004137, + -0.03049246, + -0.026069423, + -0.017304879, + -0.0025885978, + 0.031205853, + -1.0758621E-5, + -0.001429334, + -0.0054217875, + -0.007730124, + 0.008662631, + 0.02252284, + 0.011618117, + -6.85367E-4, + 0.0151239345, + 0.03921624, + 0.0043899156, + -0.0024561107, + -0.008993849, + 0.034263253, + 0.031980395, + 4.1128587E-6, + -0.028780317, + -0.016622059, + 0.006787426, + 0.018109994, + -0.020729166, + 0.041784454, + -0.019801755, + -0.020871844, + 0.006379773, + -0.0096919555, + -0.04720624, + -0.027007025, + 0.0015936693, + 0.0037860791, + 0.04712471, + 0.005564466, + 0.014145566, + 0.011964622, + -0.013258921, + 0.018374968, + 0.013126434, + -0.031287383, + 0.0035414873, + -0.02405154, + 0.007551776, + -0.010125087, + 0.0023618408, + 0.025009524, + 0.030757435, + 0.009090667, + -0.016418234, + -0.012779928, + -0.020759739, + -8.032042E-4, + 0.05034517, + -0.0016446259, + -0.024194218, + -0.001643352, + -0.0033274693, + 0.016000388, + 0.051445834, + -0.016622059, + 0.025804449, + 0.0349155, + -0.0029147204, + 0.016204216, + -0.015480631, + 0.013819444, + 0.004703299, + 0.04284435, + -0.033223737, + 0.012810502, + 0.021666769, + 0.01961831, + -0.004473994, + -0.025947127, + 0.017946932, + 0.034039043, + 0.010114895, + 0.009916164, + -0.0035669657, + 0.0083110295, + -0.017406791, + -0.01164869, + -0.012800311, + -0.023012023, + -0.0014204166, + -7.5288455E-4, + 0.066488236, + 0.01134295, + -0.014960873, + 0.032286134, + -0.023113936, + -0.0028968856, + -0.002845929, + 0.03295876, + -0.0074294796, + 0.02505029, + -6.605256E-4, + 0.006542834, + -0.03354986, + -0.028556107, + 0.00593645, + -0.025519092, + 0.013269112, + -0.0069046263, + -0.0030038946, + -0.015847519, + 0.051812723, + 0.017814446, + 0.037137207, + -0.026843963, + -0.012239788, + 0.0031236426, + 0.011740413, + -0.0040586973, + -3.910923E-4, + -0.003240843, + 0.027924245, + 0.039175473, + 0.010043556, + -0.0049376995, + 0.017946932, + 5.455547E-4, + 5.33134E-4, + -0.0011688182, + 0.00973272, + 0.034344785, + 0.0145837935, + 0.021809448, + -0.026843963, + -0.009931452, + -0.025784066, + -0.016194023, + 0.03516009, + 0.057805225, + 0.016234789, + -0.028087307, + -0.014206714, + 0.025865596, + 0.021748299, + -0.02435728, + -0.04096915, + -2.8870127E-4, + -0.030553607, + 0.024173835, + 0.016754547, + -0.019343145, + -0.046472467, + 0.020413235, + 0.052179612, + 0.017957125, + -0.009661381, + -0.0062472853, + -0.008336508, + -0.02496876, + 0.013452556, + 7.9237594E-4, + 0.06677359, + -0.035282385, + 0.009900877, + -0.028535726, + 0.020953376, + 0.03721874, + 0.004448516, + 9.43972E-4, + 0.0037249313, + -0.039644275, + 0.0025019716, + -0.034059424, + 0.029554857, + -0.008937797, + 0.010680514, + 0.016000388, + -0.048551496, + -0.009029519, + -0.034039043, + -0.010247382, + -0.0054727443, + 0.0014471689, + -0.043618895, + -0.013900975, + -0.019720225, + 0.0030905209, + -0.0112206545, + -0.035384297, + -0.0038370357, + 0.014196523, + 0.022237483, + 0.014369776, + 0.0048638126, + 0.0130754765, + -0.012076726, + 0.01256591, + -0.011424481, + -0.035730805, + -0.014125184, + -0.015358334, + -0.007704646, + 0.015113743, + 0.016937992, + -4.0387127E-5, + -0.02291011, + -0.051690426, + -0.026273249, + -0.015806753, + -0.011016828, + -0.016448807, + 0.020403042, + -0.033284884, + -0.0113939075, + -0.012117492, + 0.027944628, + 0.011241037, + -3.0908393E-4, + -0.0104461135, + 0.014512454, + -0.013962123, + 0.018120185, + 0.01647938, + -0.00547784, + -0.0013962123, + -0.009019327, + 0.0072817053, + -0.003592444, + 0.029310266, + 0.027068174, + 0.02788348, + -0.0022344491, + 0.008932701, + -0.0054676486, + -0.016499763, + -0.005238344, + 0.02291011, + -0.023276998, + 0.016102301, + -0.02152409, + -0.0034472174, + 0.0327957, + 0.016438616, + -0.017702341, + 0.0019185179, + -0.0023452798, + 0.02712932, + -0.017916359, + -0.006955583, + -0.01945525, + -0.013269112, + -0.030533226, + 0.007933951, + -0.014624559, + -0.012800311, + -0.015755797, + 0.010711088, + 0.016132876, + -0.0358531, + 0.0015643692, + -0.020861654, + 0.024948377, + 0.005248535, + -0.007893185, + 0.008647344, + 0.008846074, + -0.020708783, + 0.046839353, + 0.010619367, + -0.016693398, + 0.001825522, + 0.020871844, + -0.016387658, + -0.051282775, + 0.008647344, + -0.036342286, + -0.0118729, + 0.028087307, + 0.009182389, + 0.05421788, + 0.027516592, + -0.028291132, + 0.021279497, + -0.020871844, + -0.001383473, + -0.012555719, + -0.0018115089, + -0.017182583, + 0.0097123375, + -0.004851073, + 0.021361029, + -0.0020790314, + -0.014655133, + -0.029942129, + -0.0029198162, + -1.2293292E-4, + 0.0140028875, + -0.04773619, + 0.00823969, + 0.008683014, + 0.02030113, + 0.016907416, + -0.030370165, + 0.0053147785, + 0.0019707484, + 0.009900877, + -0.0075670625, + 0.0031720516, + -0.010018078, + -0.01617364, + 0.018904917, + -0.0023707582, + 0.016723974, + 0.002830642, + 0.025091056, + 0.019516397, + -0.0015516301, + -0.024377663, + 0.011597734, + -0.01333026, + 0.025600621, + 7.45241E-4, + -0.047940016, + 0.0077250283, + -0.019567354, + 0.011516203, + -0.010914914, + -0.004081628, + -0.0026828677, + -0.008336508, + 0.01463475, + 0.034140956, + 0.016030962, + 0.013136624, + -0.012576101, + 0.020270556, + 0.008830788, + 0.041499097, + 0.022013273, + 0.016836077, + -0.0049861083, + 0.014369776, + 0.008601483, + -0.017559662, + -0.01646919, + -0.0027083461, + 0.020932993, + -0.020331703, + 0.009584947, + -0.011750604, + -0.013452556, + 0.0035542264, + -0.03495626, + -0.011689456, + -0.006058746, + -0.0022777624, + -0.010069034, + 0.04312971, + 0.025987891, + -0.03049246, + 0.00973272, + 0.020637443, + -0.02368465, + 0.02635478, + -0.011913665, + 0.016968565, + -0.060088083, + 3.2580408E-4, + -0.022461692, + 0.026171336, + -0.018008081, + 0.019312572, + 0.012993946, + -0.016764738, + 0.01922085, + -0.0014853864, + 0.028617255, + 0.022482075, + 0.0069148177, + 0.011444864, + -0.047940016, + 0.0389105, + 0.012932798, + 0.021768682, + 0.009014232, + 0.0012267814, + -0.0052587264, + 0.031817332, + 0.023786565, + 0.01180156, + 0.023970008, + 0.056786094, + -0.030777818, + 0.018986449, + 0.0042523327, + -0.0110270195, + -0.019383911, + 0.014532837, + 0.021238733, + 0.009834634, + 0.04557563, + 0.011465247, + 0.025906362, + 0.02988098, + -0.027231235, + -0.007230749, + -0.015327761, + 0.023338147, + -0.043496598, + 0.026089806, + -0.0012013031, + 0.00377334, + -0.03723912, + 0.013269112, + 0.001993679, + -0.021381412, + -0.0104461135, + -0.003849775, + -0.0045122113, + 0.017661575, + -0.028454194, + -0.04810308, + 0.016571103, + -0.005778484, + 0.0047975685, + 0.008846074, + -0.0130754765, + 0.014461498, + 0.03293838, + -0.030859347, + 0.062004052, + 0.0025758587, + 0.0042370455, + 0.0494891, + -0.029534476, + 0.041662157, + -0.0069199135, + -0.023113936, + 0.017610619, + -0.022135569, + 0.0027083461, + 0.02291011, + 0.021768682, + -0.016683208, + -0.031898864, + -0.018986449, + 0.012433423, + 0.016428424, + -0.023338147, + 0.010568409, + -0.021707533, + -0.014135375, + -0.018874343, + -0.03609769, + 0.005174648, + -0.026395546, + -0.029228736, + -0.0097123375, + -0.0013350643, + 0.007042209, + 0.01678512, + -0.009060092, + 0.013371025, + -0.025335647, + 0.011455055, + -0.008331412, + 0.017447557, + 0.012362083, + -0.022196718, + 0.015541779, + -0.012841077, + -0.0076944544, + 0.012667824, + -0.009523798, + 0.0257433, + 0.0072052707, + 0.013819444, + 0.0014484428, + -0.012076726, + -0.0072969925, + 0.01271878, + -0.009167101, + -0.05446247, + 0.016234789, + -0.0017987698, + 0.008570909, + 0.051608898, + 0.025478326, + 0.03646458, + -0.024907611, + 0.019322762, + 0.0036128266, + -0.028739551, + 0.0112206545, + 0.0070880703, + 0.018181333, + -3.910923E-4, + -0.0011739138, + -0.011016828, + -0.0140028875 + ], + "content": "‣ 대학등록금납부서비스 ‣ 건국대학교 클릭\n ● 전화납부 : 삼성카드 콜센터 1688-9702 (등록금 납부전용 상담)\n ● 납부가능 시간 : 09:00 ~ 16:00\n ● 삼성카드 미소지자는 삼성카드 홈페이지에서 ‘빠른 카드 발급 서비스’ 신청을 통해 납부 가능.\n ● 할부수수료 안내 (아래 사항 이외의 할부수수료에 관한 사항은 삼성카드 콜센터로 문의 바랍니다.)\n\n할부개월\n\n이자부담\n\n이자면제\n\n2 ~ 6개월\n\n없음\n\n전체\n\n10개월\n\n1∼4회차\n\n5∼10회차\n\n12개월\n\n1∼5회차\n\n6∼12회차\n\n* 7-9개월, 11개월의 경우 전회차 이자 본인 부담\n\n4등록금 납부확인 방법\n1) 등록금납부확인서를 학교 홈페이지(Portal)상에서 직접확인 및 출력가능\n2) 홈페이지→ 학사안내→ 증명서발급→ 발급안내→ 등록금납부확인서 또는\n Portal→ 원스탑서비스→ 등록→ 등록금납부확인서\n3) 등록금은 현금영수증 발행대상이 아니며, 연말정산 교육비공제 대상임.\n (연말정산용 영수증으로 교육비납입증명서 발급 및 연말정산간소화 서비스에 자료제공)\n\n5 수업연한초과자 등록\n1) 등록기간 : 2024. 9. 2.(월) 09:00 ~ 9. 6.(금) 16:00\n2) 고지서 출력 : 2024. 8. 29.(목) 16:00부터 [고지서출력버튼]\n3) 등록금 책정기준(수강신청 학점이 아닌 졸업사정 시 신청학점에 따라 적용)\n ※ 졸업사정 시 신청학점(미졸코드) 문의: 소속 단과대학 행정실 (02-450-3114)\n\n과정\n\n졸업사정시 신청학점\n\n등록금액\n\n학부생 수업연한초과자\n\n1~3학점\n\n등록금의 1/6 납입\n\n4~6학점\n\n등록금의 1/3 납입", + "id": "69300c46-11f3-4eee-baeb-b39cf057b452", + "metadata": { + "charset": "UTF-8", + "filename": "ku-uni-register.txt", + "source": "ku-uni-register.txt" + }, + "media": [] + }, + "7810438e-926d-40cb-9236-f22abb0b6982": { + "embedding": [ + -0.0377768, + 0.026385175, + 0.04877786, + -0.008739005, + -0.03608433, + 0.006986864, + -0.021817677, + 0.04474197, + -0.014136465, + -0.028641803, + 0.020591721, + -0.0012401955, + 0.017022343, + 0.031202206, + 0.022349285, + -0.021850225, + -0.016197808, + 0.06231762, + -0.023759678, + 0.036518298, + 0.0049445084, + -0.017217629, + 0.007236395, + 0.007854798, + 0.0068675233, + 0.020960592, + 0.021969564, + -0.026103098, + 0.023282314, + -0.0057175113, + 0.04847408, + -0.019886525, + 0.011554364, + 0.018042166, + -0.051989213, + 0.031484284, + 0.03534659, + 0.03141919, + 0.0014673499, + -0.025777623, + 0.024128549, + -0.04274572, + -0.02423704, + -0.049862776, + -0.029292753, + 0.044655174, + 3.7294015E-4, + 0.017651595, + 0.025365354, + -0.01160861, + 0.0082562165, + 0.010805771, + 0.026385175, + 0.06448745, + -0.005025877, + 0.056415673, + 0.023542695, + 0.019864826, + 0.037104152, + -0.025647433, + 0.027079523, + 0.026016304, + -0.0060375617, + -0.028164439, + 9.2963804E-4, + -0.017402066, + -0.0029672473, + 0.024258738, + -0.044785365, + 0.01732612, + 0.013908633, + 0.021600693, + 0.07998007, + -0.027361602, + 1.0120239E-4, + 0.011120397, + -0.020808704, + 0.015839785, + -0.0430061, + 0.0013222423, + -4.0379245E-4, + 0.019322367, + -0.050036363, + 0.004513254, + 0.0067427577, + -0.008918016, + -0.08939714, + -0.022978537, + -0.004122684, + 0.008234519, + -0.03936078, + 0.024692705, + -0.015036946, + 0.03972965, + 0.036822077, + -0.024736103, + 0.03113711, + -0.036171127, + -0.02592951, + -0.004060301, + -0.042572133, + -0.05958363, + 0.021828525, + -0.0430061, + 0.0023908853, + -0.024323834, + -0.04443819, + -0.031072017, + -0.010963084, + -0.0097100055, + -0.08618579, + 0.027274808, + -0.01982143, + -0.0015988961, + 0.08536125, + -0.015894031, + -0.031766362, + -0.02436723, + -0.04339667, + -0.023412503, + 0.0128888115, + 0.028706897, + 0.06335914, + -0.030355971, + 0.0046298825, + 0.044568382, + 0.027665379, + 0.031375792, + -0.060060993, + -0.020841252, + -0.002637704, + -0.06153648, + -0.027882362, + 0.007778853, + 0.0029943704, + -0.0031408342, + 0.0018592761, + -9.004809E-4, + -0.016751114, + -0.023976661, + 0.04237685, + 0.002880454, + 0.005166916, + 0.003566664, + -0.035672065, + -0.021058235, + -0.047389165, + 0.013268532, + 0.016273752, + 0.020157754, + 0.010203642, + 0.020407284, + -0.026038002, + -0.07455548, + -0.012585035, + 0.03454375, + -0.029574832, + 0.06479123, + -0.018823305, + 0.01747801, + 0.0021888197, + 0.044958953, + 0.031028619, + 0.034717336, + 0.008630513, + -0.011060727, + 0.019604446, + -0.00867391, + -0.048951443, + 0.011673705, + 0.020136055, + -0.010556241, + -0.031701267, + 0.03933908, + -0.008961412, + 0.008885468, + 0.03324185, + -0.0073069143, + -7.3367497E-4, + 0.019105384, + 0.019224726, + 0.023477599, + 0.001942001, + -0.001951494, + -0.044199508, + -0.04246364, + 0.03758152, + -0.03061635, + 0.014429393, + 0.014809114, + 0.04367875, + -0.033589024, + 0.07255924, + 0.006645115, + -0.013984577, + -0.0123355035, + -0.026189892, + -0.055721324, + 0.0075944173, + -0.00808263, + -0.039859843, + -0.010664732, + 0.040879663, + -0.044134416, + -0.01655583, + -0.05277035, + 0.006308791, + 0.028229535, + 0.01486336, + 0.0033008594, + 0.06361952, + -0.011196341, + -0.017456312, + 0.010897989, + 0.018628022, + 0.0430495, + 0.0030404793, + 0.030225782, + 0.00306489, + -0.012053425, + -0.022783251, + -0.0032357643, + -0.040727776, + 0.02410685, + 0.02645027, + 0.026016304, + 0.016143562, + -0.020005865, + 0.007914468, + -0.036800377, + 0.0039707953, + 0.016252054, + 0.017228479, + 0.033198453, + -0.0015744854, + -0.038926814, + -0.011586911, + 0.033198453, + -0.03300317, + -4.282031E-4, + 0.032938074, + 0.015687896, + -0.020494077, + 0.030421067, + 0.014961002, + -0.029032374, + 0.034435257, + -0.0075835683, + 0.020526625, + 0.015177986, + -0.006634266, + -0.0208521, + 0.027535187, + -0.049472205, + -0.027361602, + -0.022804951, + -0.0068512494, + 0.008456926, + 0.018747361, + -0.0017602774, + -0.00730149, + -0.023694582, + -0.025083276, + 0.029184261, + -0.0024505558, + -0.00723097, + 0.02099314, + 0.04808351, + -0.057283606, + -0.025864417, + -0.014581282, + -0.0040006307, + 0.036149427, + 0.0045620752, + -0.03193995, + 0.026276683, + 0.004364078, + 0.034239974, + -6.021288E-4, + 0.019636994, + -0.03758152, + 0.052076004, + 0.010773224, + -0.04183439, + -0.005305243, + 0.032504108, + -0.0070248363, + 0.006764456, + 0.009308586, + 0.05984401, + 0.07455548, + -0.05138166, + -0.038254164, + -0.034456957, + -0.0037917842, + 0.024953086, + 0.028967278, + 0.010127698, + -0.0031652446, + 0.002926563, + -0.033871103, + 0.018226601, + -0.006569171, + -0.034326766, + 0.005009603, + 0.048170306, + 0.03415318, + 0.016371394, + 0.04534952, + 0.0012924072, + -0.014082219, + 0.003336119, + -0.01395203, + 0.034977715, + 0.019940771, + -0.02152475, + -0.019474257, + -0.0038568792, + 0.013659102, + 0.008126027, + 0.05007976, + -0.018834155, + -0.0034880075, + -0.04773634, + 0.031006921, + 0.024974784, + -0.015297326, + 0.021307765, + -0.0023773238, + -0.014906757, + -0.0019664117, + -0.017098289, + 0.0014687061, + 0.049472205, + 0.001997603, + -0.007247244, + 0.026385175, + 0.075683795, + -0.007220121, + -0.039534368, + 0.0054001734, + 0.03180976, + -0.032829583, + -0.0040657255, + -0.018324245, + -0.07655173, + 0.078374386, + 0.03456545, + 0.05615529, + 0.005842277, + 0.0078005516, + 0.021210123, + -0.015405818, + 0.031853158, + 0.027144618, + -0.034109782, + 0.04272402, + 7.081625E-5, + 0.01799877, + 0.028229535, + -0.005815154, + 0.05854211, + 0.03363242, + 0.01146757, + 0.025148371, + -0.028012551, + -0.029401245, + -0.008901742, + 0.050513726, + -0.046347644, + -0.022739856, + -0.022262493, + -0.008451502, + 0.017521406, + -0.025625734, + 0.004136245, + -0.0018145234, + 0.033220153, + 0.017532256, + 0.012704375, + 0.044025924, + -0.009389955, + -0.023824772, + -0.037885293, + 0.027318204, + -0.0055981707, + -0.0019297957, + 0.017315272, + 0.035628665, + 0.021437956, + 0.02358609, + 0.040207017, + 0.029943703, + -0.05385527, + 0.05684964, + 0.011847291, + -0.015894031, + 0.013279381, + -0.036409806, + -0.036952265, + 0.017163383, + -0.006894646, + -0.0038758651, + -0.020125207, + -0.003363242, + -0.0064389813, + 0.040857967, + -0.0074371044, + 0.046911802, + -0.017760087, + 0.019929921, + -0.03729944, + -0.014592131, + 0.004486131, + 0.027361602, + -0.03402299, + -0.02879369, + 0.017792635, + -0.015188835, + -0.07811401, + 0.009568966, + 0.025886115, + -0.04066268, + 0.011093274, + -0.046868406, + -0.070215814, + 0.018584624, + -0.039057005, + 0.0638799, + 4.5261372E-4, + -0.048604272, + -0.02527856, + -0.102589734, + 0.030985223, + -0.03506451, + -0.019137932, + 0.022392683, + 0.02569083, + 0.041248538, + -0.002323078, + -0.007534747, + 0.045219332, + -0.057630777, + -0.032482408, + -0.019072836, + -3.4852952E-4, + 0.025517242, + 0.0032276274, + 0.053594887, + 0.003428337, + 0.0072852164, + 1.7782464E-4, + 0.025300259, + -0.034912623, + 0.008174848, + -0.008120602, + 0.007936167, + -0.03298147, + -0.0054869666, + -0.00938453, + 0.030225782, + 0.046260852, + -0.011988331, + -0.01035553, + -0.02334741, + -0.001923015, + -0.03458715, + 0.016512433, + -0.020125207, + 0.008174848, + 0.026254985, + -0.02790406, + -0.033133358, + -0.045219332, + -0.003121848, + -0.0020206575, + -0.033176754, + -0.060408168, + 0.0018389339, + -0.020580871, + -0.02203466, + -0.048517477, + -0.025647433, + 0.0059073716, + -0.0063684615, + 0.027535187, + 0.07112715, + 0.04587028, + 0.029119166, + -0.017521406, + 0.041856088, + 0.032742787, + 0.025126673, + -0.0076161157, + -0.007545596, + 0.037820198, + 9.526925E-4, + -0.026667254, + -0.028338026, + -0.015470914, + -0.0064064334, + -0.037082456, + -0.007914468, + -0.034912623, + 0.0651818, + -0.019767184, + -0.04547971, + -0.031006921, + -0.004136245, + 0.008348434, + 0.009601514, + -0.015264779, + -0.035368286, + 0.013257683, + 0.029184261, + 0.010491145, + 0.01930067, + 0.016707718, + -0.03933908, + 0.015894031, + -0.0065366235, + 0.028316328, + -0.010350106, + -0.008641362, + -0.005646992, + 0.016111014, + 0.010485721, + -0.065615766, + 0.049211826, + -0.020700213, + -0.0013351256, + -0.016870456, + 0.025104973, + -0.0032411888, + 0.03493432, + 0.005831428, + 0.059323248, + -0.0038487422, + 0.034478657, + -0.029140865, + -0.028815389, + -0.0015175274, + -0.001691114, + -0.0037972087, + 0.0026716075, + 0.015177986, + 0.030052194, + 0.0140496725, + -0.005826003, + 0.025365354, + -0.009259765, + -0.03311166, + 0.013268532, + -0.006585445, + -0.011359079, + 0.06314216, + 0.011684554, + -0.005359489, + 0.014331751, + -0.0260597, + 0.04873446, + 0.02034219, + 0.003271024, + -9.2217926E-4, + -0.016002523, + -0.028815389, + 0.0047871955, + -0.007144177, + 0.0066396906, + -0.0071496014, + -0.03664849, + 0.0062925173, + -0.046608023, + 0.030030496, + 0.012096822, + -0.0042908457, + -0.017054891, + -0.0010483009, + 0.007768004, + -0.03675698, + 0.0040575885, + 0.033871103, + 0.029227657, + -0.012986454, + -0.017261026, + 0.004087424, + 3.0428526E-4, + 0.006775305, + -0.021546448, + 0.009628637, + 0.015926579, + 0.021980414, + 0.039469272, + -0.015969975, + -0.019344065, + -0.035173003, + -0.0046407315, + 0.011803894, + 2.452929E-4, + -0.031636175, + 0.010360955, + 0.014017125, + 0.030985223, + -0.003780935, + 0.01785773, + -0.029054072, + 0.020494077, + -0.012237861, + 0.009194669, + -0.005185902, + 0.028511614, + 0.009612363, + -3.52937E-4, + 0.009167546, + 8.5233775E-4, + -0.017673295, + -0.018335093, + 0.0015446503, + 0.003957234, + 0.021437956, + 0.025972908, + 0.07195168, + -0.010230765, + 0.023694582, + 0.0027651817, + 0.0017996057, + -0.0073557356, + 0.002191532, + 0.0077842777, + 0.0140496725, + -0.0152105335, + -0.009558117, + 0.013235984, + -0.025625734, + 0.0038867143, + 0.014754868, + -8.6386496E-4, + -0.028771993, + 0.025886115, + -0.034088086, + 0.0152213825, + -0.032005046, + -0.0017155247, + -0.025604036, + 0.053291112, + -0.027947456, + -0.01565535, + -0.034456957, + -0.03193995, + -0.010106, + -0.02645027, + -0.016588377, + -0.047606148, + -0.0037049907, + 0.012791169, + -0.0073286127, + 0.005831428, + -2.722463E-4, + -0.02034219, + 0.034500353, + -0.05151185, + 0.047953323, + 0.02138371, + -0.01813981, + -1.4290388E-4, + -0.024801198, + -0.01879076, + -0.010024631, + 0.019257274, + 0.0023569816, + -0.029184261, + 0.03964286, + 0.009601514, + -0.017293574, + 8.204683E-4, + 0.009156697, + 0.007795127, + -0.027730472, + -0.010952235, + -0.00965576, + 0.009899866, + 0.004985193, + -0.009992084, + -0.01421241, + 0.0067861546, + 0.015687896, + 0.025669131, + -0.010491145, + 0.030464463, + -0.025126673, + 0.018150657, + 0.012541638, + 0.0352381, + -0.001170354, + -0.005853126, + 0.074338496, + 0.004393913, + -0.017358668, + -0.0012164629, + -0.036973964, + 0.018693116, + -0.0054110223, + -0.026428573, + 0.014971851, + -0.046564627, + -0.04587028, + -0.036323015, + 0.0029319876, + 0.050600518, + 0.010995631, + -0.028598405, + 0.0042149015, + -0.02725311, + -0.043917432, + -0.0035802254, + -0.012736923, + 0.013941181, + -0.064964816, + 0.035628665, + 0.0065366235, + -9.391311E-4, + 0.008999385, + 0.01601337, + -0.015818087, + -0.027339904, + -0.010523693, + 0.012877963, + -0.020081809, + 0.017803485, + -0.01983228, + -0.033285245, + 0.04708539, + -0.01643649, + 0.0043966253, + -0.009530994, + 0.05485339, + -0.0012659623, + 0.010030055, + 0.026016304, + -0.01996247, + -0.03233052, + -0.0326126, + 0.024345532, + 0.030507859, + -0.0573704, + -0.011293984, + -0.023021935, + -0.008608814, + -0.0130407, + 0.013897784, + -0.011104123, + 0.005213025, + -0.023434203, + -0.013572309, + 0.04315799, + -0.08614239, + 0.06153648, + -0.004610896, + -0.0067373333, + 0.02855501, + -0.013745896, + 0.019257274, + 5.821257E-4, + 0.011706252, + 0.048517477, + -0.021014838, + -0.034739036, + -0.025821019, + -0.010263313, + 0.02410685, + -0.004450871, + -0.0390787, + -0.0040386026, + 0.027882362, + 0.038948514, + -0.038275864, + -0.02569083, + -0.03495602, + 0.0047980445, + -0.010594212, + -0.008462351, + -0.009319435, + 0.047519356, + 0.022739856, + -0.025473846, + -0.027274808, + 0.023933263, + 0.027600283, + 0.00801211, + 0.0055032405, + 0.00433153, + 0.02436723, + -0.0028370575, + -0.025669131, + -0.008201971, + 0.029900307, + 0.011510967, + -0.031245602, + 0.026406875, + -0.011489268, + -5.6619094E-5, + 0.018421886, + 0.0068892217, + 0.0045783487, + 0.066136524, + -0.037733406, + 0.003102862, + 0.01680536, + -0.00501774, + -0.005063849, + -0.031874854, + 0.041769296, + 0.006330489, + 0.010263313, + -0.008174848, + 0.0031733816, + 0.0062382715, + 0.012411448, + -0.01447279, + -0.011185492, + 0.01747801, + -0.010350106, + 6.089095E-4, + 0.02894558, + 0.009286888, + 0.0059073716, + -0.017966222, + -0.011185492, + 0.0067210593, + 0.022078056, + -0.009373681, + 0.031853158, + -0.029531434, + -0.022327587, + 0.002192888, + -0.01718508, + 0.0012110383, + 0.027448395, + -0.01718508, + 0.00756187, + -0.0012876606, + -0.02176343, + 0.031050319, + -0.0047248127, + 3.0852322E-4, + 0.026320081, + -0.04183439, + -0.03113711, + -0.015253929, + 0.02176343, + -0.00639016, + 0.0015663486, + 0.011380777, + -0.0058368524, + 0.027491791, + -8.693108E-6, + -0.028055947, + 0.008928865, + 0.01023619, + -0.023369107, + 0.01056709, + 4.587164E-4, + 0.013094946, + -0.021253519, + 0.040489092, + 0.024215342, + -0.014570433, + 0.018617172, + 0.026971031, + -0.03458715, + 0.0030133564, + -0.01023619, + 0.025994606, + -0.005343215, + 9.20145E-4, + -0.009172971, + 0.0033117086, + 0.00964491, + 0.0017575652, + -0.06617992, + 0.013322778, + -0.01003548, + -0.0016409366, + -0.0011832374, + 0.013626555, + -0.025907813, + -0.0068078525, + 0.01798792, + 0.020559173, + 0.0058965227, + -3.1649056E-4, + 0.013507213, + 0.013539761, + -0.021036536, + -0.022078056, + -0.040857967, + -0.003818907, + 0.002527856, + 0.02358609, + -0.00501774, + -0.035672065, + 0.013626555, + 0.013648253, + -0.011836442, + -0.026038002, + -0.019919071, + -0.024193645, + 0.01276947, + 0.05450622, + -0.0049743433, + -0.008907166, + 0.0140713705, + -0.0149827, + -0.002489884, + 0.027057825, + -0.01263928, + -0.016393093, + -0.01746716, + 0.015340723, + -0.018063864, + -0.03560697, + -0.042593833, + 0.018411038, + 0.02267476, + 0.01601337, + 0.0085599935, + 0.015980825, + -0.026905935, + -0.012791169, + 0.019376613, + -0.0063684615, + 0.022587968, + -0.013008152, + -0.0134746665, + 0.043874033, + 0.026992729, + -0.027795568, + 0.010106, + -0.017044043, + 0.002872317, + -0.014234108, + 0.0025658282, + -4.8753447E-4, + 0.01680536, + 0.0035286918, + -0.0325909, + -0.02293514, + 0.0201903, + -0.017629897, + -0.044199508, + -0.050687313, + 0.034109782, + -0.0017412914, + -0.0038975636, + 0.014277505, + 0.020754458, + 0.004776346, + 0.008690183, + 0.00449698, + 0.021557296, + -0.014017125, + -0.016642623, + -0.011836442, + -0.02190447, + 0.022262493, + 0.015752992, + -0.008776977, + -0.024410628, + 0.028338026, + 0.036301315, + 0.015937427, + 0.0123355035, + 0.038905114, + 0.01615441, + 0.015156288, + -0.0074859257, + -0.0011025467, + 0.0041091223, + 9.391311E-4, + -4.756682E-4, + -0.012085973, + 0.022067208, + 0.03456545, + -0.0040114797, + -0.016599227, + -0.005307955, + 0.019094536, + 0.016219506, + 0.06583275, + -0.0066396906, + -0.019626144, + 0.0015839785, + 0.01421241, + -0.01733697, + 0.005202176, + -0.055200566, + -0.0038243316, + 0.013442119, + -0.023824772, + -0.02423704, + -0.041660804, + -0.0038026334, + -0.02086295, + -0.017629897, + 0.015362422, + -0.016664322, + -0.027687076, + -0.042702325, + 0.046608023, + -0.023629487, + 0.03024748, + -0.008587116, + 0.0033849403, + -9.411653E-4, + -0.029900307, + -0.018172355, + 0.017890278, + -8.8081683E-4, + -0.021253519, + 0.045783486, + 0.028902182, + 0.016393093, + -0.026688952, + -0.031093715, + -0.053942062, + 0.011272285, + 0.029119166, + 0.0025698966, + 0.02072191, + -0.031527683, + -0.011445872, + 0.017369518, + -0.025625734, + 0.011315682, + -0.010046329, + -0.011771347, + -0.03690887, + 0.032352217, + -0.0134746665, + -0.016913852, + 0.008722731, + -0.0034147755, + 0.038796622, + 0.0013073247, + 0.006829551, + 0.020494077, + 0.01708744, + 0.0055818968, + 0.025300259, + 0.0048088934, + -0.0015338011, + 0.00769206, + -0.002618718, + 0.032178633, + 0.013463818, + 0.0011547583, + 0.032634296, + 0.019137932, + -0.028359724, + -0.0058639753, + -0.01707659, + 0.018226601, + 0.01075695, + -0.03204844, + 0.020157754, + 0.006032137, + -0.044308, + -8.984467E-4, + -0.0042474493, + -0.023043633, + -0.040575888, + -0.017163383, + -0.039165497, + 0.038883418, + 0.027274808, + 0.012563337, + 0.022045508, + -0.018584624, + 0.021546448, + 0.026385175, + -0.0088041, + 0.007854798, + 0.0018104549, + 0.017651595, + -0.008348434, + -0.019083686, + 0.013322778, + 0.023087028, + 0.0028099346, + -0.014190711, + 0.004746511, + 0.0025685404, + -0.019420011, + 0.013637404, + -0.028511614, + -0.03805888, + -0.0068241265, + 0.0075401715, + 0.011825593, + 0.037125852, + -0.024085153, + -0.018671418, + 0.020613419, + -0.0061948746, + -0.001654498, + -0.02072191, + 0.007420831, + 0.006726484, + 0.037234344, + -0.0091295745, + 0.001746716, + 0.012020878, + -0.0040630135, + -0.013539761, + -0.011478419, + -0.008050082, + 0.002872317, + -0.02100399, + 0.0022349285, + -0.0012754552, + -0.013246834, + 2.4647953E-4, + -0.025104973, + 0.0044047623, + 0.0040928484, + 0.013930331, + -0.03191825, + 0.04181269, + 0.029509736, + 0.0053133797, + 0.019344065, + -0.041660804, + -0.0057554836, + -0.016599227, + 0.02686254, + -0.00518319, + 0.021437956, + 0.0149827, + 0.024150247, + -0.02112333, + -0.026645556, + 0.015709596, + -0.008440653, + -0.011717102, + -0.026471969, + 0.0073720096, + -0.026016304, + 0.06635351, + -0.0032818732, + 0.018389339, + 0.010729827, + -0.036605094, + 0.022501174, + 0.02517007, + 0.014255807, + 0.017933674, + -0.010941385, + 0.022240793, + 0.0053757625, + 0.024779499, + 0.00315982, + 0.020168602, + -0.022370985, + 0.0033659544, + -0.008516597, + 0.017402066, + 0.00886377, + 0.019083686, + 0.0155143095, + -0.019897373, + 9.262477E-4, + -0.0015487187, + -0.005158779, + 0.028403122, + 0.034239974, + 0.04274572, + -0.0046027596, + -0.0023637624, + 0.0286852, + 0.04198628, + -0.03899191, + -0.048951443, + -0.0010523692, + -0.0049906173, + 0.0045105414, + 0.02527856, + -0.02790406, + -0.020938894, + 0.03400129, + 0.037755102, + 0.01094681, + 0.0057554836, + -0.0026566898, + 0.002035575, + -0.049081635, + 0.011293984, + 0.022436079, + 0.04968919, + -0.030529559, + 0.0085545685, + -0.014158164, + -0.009856469, + -0.0273833, + 0.01108785, + 0.033176754, + 0.013897784, + 0.004643444, + -0.0045647873, + -0.009758826, + 0.033133358, + -5.7593825E-5, + 0.03202674, + -0.016371394, + -0.070302606, + -0.0033551052, + -0.049342014, + 0.005413735, + -0.005264559, + -0.017532256, + -0.042919308, + -0.017141685, + -0.04365705, + 0.0012734211, + 0.0031625323, + -0.008516597, + -0.012682677, + 0.033589024, + 0.016262902, + 0.021047385, + 0.033589024, + -1.3273279E-4, + -0.033220153, + 0.007133328, + 0.005351352, + -0.036539998, + 0.0055412124, + -0.00808263, + 0.021817677, + 0.021937016, + -0.013149192, + 0.0019447133, + -0.024345532, + -0.018432736, + -0.012812867, + 0.0064227073, + 7.587637E-4, + -0.01446194, + 0.016772814, + -0.012606733, + -0.021850225, + 0.010816621, + 0.030746542, + -0.052553367, + 0.006872948, + -0.018042166, + -0.00822367, + -0.02386817, + 0.0029509736, + 0.013518063, + 4.729559E-4, + -0.015004399, + -0.0075021996, + 0.0017914688, + -0.005335078, + 6.9977134E-4, + 0.041682504, + 0.02775217, + 0.014874209, + 0.007746306, + 3.4615624E-4, + 0.001923015, + 0.033480532, + -0.0055140895, + -7.018055E-4, + 0.036474902, + -0.0033768034, + -0.0035069934, + 0.00991614, + 0.0117496485, + 0.010464022, + 0.0072580935, + -0.008890893, + 0.027817266, + -0.018324245, + -0.0061677517, + -0.0030893006, + -0.015264779, + -0.01094681, + 0.020450681, + 0.010534542, + 0.025972908, + 0.013626555, + -1.09254506E-4, + -0.0073828585, + -0.014038823, + 0.013767594, + -0.008511172, + -0.017901126, + 0.021437956, + -0.03415318, + 1.9579357E-4, + 0.021730883, + -0.010643033, + 0.0391004, + 0.0019162343, + -0.020884648, + 0.013225135, + 0.0066017187, + -0.016989797, + -0.041270234, + 0.025495544, + -0.057934556, + -0.008440653, + 0.023651186, + -0.021275219, + 0.041790996, + 0.040988155, + -0.0011472995, + 0.026515367, + -0.028598405, + 0.0030025071, + -0.0038134824, + -0.017391216, + -0.0046136086, + 0.025582338, + 0.019127082, + 0.005684964, + -9.38453E-4, + -0.029813513, + -0.041248538, + 0.02490969, + 0.016631775, + 0.0015677047, + -0.04311459, + -0.010778648, + 0.021307765, + -0.0072960653, + -0.009042782, + -0.028967278, + 0.01928982, + -0.013973728, + 0.01538412, + 0.00453224, + -0.03430507, + 0.014418544, + 0.0049987542, + -0.019409161, + 0.0031354094, + -0.006959741, + -0.0063521876, + 0.035889048, + 0.026623858, + 0.02267476, + -0.028489914, + 0.023021935, + -0.037755102, + -0.010350106, + 0.029010674, + -0.023933263, + 0.003927399, + -0.015156288, + -0.019995017, + -0.002386817, + -0.0056361426, + -0.022718158, + -0.039187193, + 0.008635937, + 0.018302547, + 0.029618228, + 0.007442529, + 0.010550816, + 0.01263928, + 0.0035693762, + 0.04376554, + 0.012064274, + 0.054679807, + 0.0079687135, + -0.025625734, + 3.4369823E-5, + 0.00822367, + -0.0038053456, + -0.036713585, + 0.013387873, + -0.019636994, + 0.024193645, + 0.0056795394, + -0.0032059292, + 0.01983228, + 0.01981058, + -0.014418544, + -0.016360546, + -0.014527036, + -0.006834976, + 0.030811636, + -0.002192888, + -0.025126673, + 0.025495544, + 0.013160041, + -0.0035829376, + -0.016371394, + -0.014689773, + -0.0010394859, + -0.044481587, + 0.0071116295, + -0.016078467, + 0.006401009, + -0.020255396, + -0.003167957, + -0.00730149, + -0.008326736, + 0.009536418, + 0.016892154, + 0.040901363, + 0.0017385791, + 0.02254457, + 0.02556064, + -0.05381187, + 0.041921183, + 0.009145848, + 0.030746542, + 0.006791579, + -0.0015988961, + 0.02553894, + 0.030030496, + 4.095561E-4, + 0.019853977, + 0.021969564, + 0.032829583, + 0.006460679, + 0.011293984, + 0.03690887, + -0.04482876, + -0.009151273, + -0.025821019, + 0.035368286, + 0.00906448, + 0.027491791, + 0.0071387524, + 0.00579888, + 0.027687076, + -0.004190491, + 0.007263518, + -0.0025712529, + -0.016653473, + -0.0049390835, + 0.026493669, + 0.0075021996, + -0.04226836, + -0.058194935, + 0.024736103, + -0.024085153, + -0.009042782, + 0.023629487, + 0.019799732, + -0.008044658, + 0.009634061, + -0.0023298587, + -0.044373095, + 0.02100399, + -0.006623417, + -0.013463818, + -7.601198E-4, + 0.016794512, + 0.01134823, + 0.04743256, + -0.025191767, + 0.031592775, + 0.014754868, + -0.007849373, + 0.0062545454, + -0.017673295, + 0.067612015, + 0.009764251, + -0.023954963, + -0.015492612, + -0.008435228, + -0.006845825, + 0.018986044, + -0.014375147, + -0.008500323, + -0.0032493258, + 0.0014890482, + 0.01707659, + 0.0045349523, + -0.032395616, + -0.001986754, + -0.011955783, + -0.026103098, + -0.016197808, + -0.038254164, + 0.024540817, + -0.048604272, + 9.2963804E-4, + -0.009628637, + 0.008088054, + 0.017814333, + 5.563589E-4, + -0.025343657, + -0.010518268, + -0.007447954, + 0.014895908, + -0.026385175, + 0.03335034, + 0.013355326, + 0.003927399, + 0.049081635, + -0.00730149, + -0.018226601, + 0.026645556, + -0.004469857, + 0.014939304, + 0.021741733, + 0.024258738, + 0.016403941, + -0.03180976, + 0.01708744, + 0.017803485, + -5.1601353E-4, + -0.038796622, + -0.0058477013, + 0.0053730505, + 0.039946634, + 0.036409806, + 0.030507859, + 0.00599959, + -0.001876906, + 0.005592746, + 0.035737157, + -0.025799321, + 0.007106205, + -0.005858551, + -0.0024410628, + -0.01591573, + -0.010995631, + -0.009731703, + -0.0064715287 + ], + "content": "교외장학/보훈장학/통일부장학/국제화장학/멘토링장학: 02-450-3669\n 국가우수/농어촌희망재단장학/포상장학/공로장학/국고사업장학(혁신사업): 02-450-3511\n 국가근로장학/대학생청소년교육지원사업/건국가족장학/일감호장학: 02-450-3512\n\n- 일반대학원 : 일반대학원 행정실 ☎ 02-450-3552\n- 특수/전문대학원 : 각 대학원 행정실 대표번호 ☎ 02-450-3114\n● 등록금고지서 출력시 스크립트 오류 관련 : ☎ 02-450-3887\n● 등록관련 문의 : ☎ 02-450-4152", + "id": "7810438e-926d-40cb-9236-f22abb0b6982", + "metadata": { + "charset": "UTF-8", + "filename": "ku-uni-register.txt", + "source": "ku-uni-register.txt" + }, + "media": [] + }, + "cc3e9ddc-041a-41fc-a113-071f05cac5f6": { + "embedding": [ + -0.045629583, + 0.01201489, + 0.022776222, + -0.014493083, + 0.0011842526, + -0.0049708476, + 0.008500101, + 0.023856211, + -0.041772477, + -0.0060315523, + 0.0033291662, + 0.016797705, + -0.0059592314, + 0.021021238, + -0.003927018, + -0.023277646, + -0.052109525, + 0.020654812, + -0.020500528, + 0.033421837, + 0.03380755, + -0.010472047, + 0.0018815454, + 0.008326531, + -0.0010715527, + -0.03311327, + 0.03457897, + -0.05515664, + -0.008943669, + -0.0064269057, + 0.05619806, + -0.020269101, + -0.027173324, + -0.012506672, + 0.002757832, + 0.037394658, + 0.014676295, + 0.047480997, + -0.019420538, + -0.03506111, + -0.022853363, + 0.03610253, + -0.0115713235, + -0.062523715, + -0.008871348, + 0.052495237, + 0.019960532, + -0.016238423, + 0.018195905, + 0.042003904, + 0.003196578, + -0.015814142, + 0.031609, + 0.041001055, + 0.0030447044, + 0.06846366, + -8.1360864E-4, + 0.03008544, + 0.013712018, + -0.041039627, + 0.023354787, + -0.019208396, + -0.011465252, + -0.012034176, + 0.014338798, + -0.019690534, + -0.012786312, + 0.051338103, + -0.04076963, + -0.0017441359, + 0.0104238335, + 0.01048169, + 0.061135158, + -0.02541834, + -0.0049081696, + 0.06256229, + -0.037626084, + 0.012246317, + -0.016460206, + 0.016604848, + 0.03423183, + -0.012101675, + -0.011137399, + 0.0014837811, + -0.0071838633, + -0.01653735, + -0.08092212, + -0.003391844, + 0.0090159895, + -0.02524477, + -0.022255512, + 0.009768126, + -0.060942303, + 0.06819367, + 0.07440361, + -0.0077045728, + -0.0067499387, + -0.06626511, + -0.016527707, + 0.026768327, + 0.025321912, + -0.05087525, + 0.028291885, + -0.030355439, + 0.028156886, + -0.009392058, + -0.04832956, + 0.028504025, + -0.0052456665, + -0.030837577, + -0.02005696, + 0.015592358, + -0.019304825, + 0.037934653, + 0.04690243, + -0.00915581, + -0.04115534, + -0.02831117, + -0.03276613, + -0.058203757, + 0.0073718973, + 0.0048599555, + 0.031589713, + -0.02580405, + 0.003854697, + -0.004467013, + 0.006243693, + 0.02221694, + -0.05222524, + -0.00212382, + 0.018485188, + -0.0344054, + -0.056005202, + -0.0015078881, + 0.015013792, + 0.02235194, + 0.003495504, + -0.01928554, + -0.029275447, + -0.030066155, + 0.034077547, + 0.014541296, + 0.043469604, + -0.0022359171, + -0.028330456, + 0.013760231, + -0.015814142, + 0.035504676, + -0.04485816, + -0.016026283, + -0.010115265, + -0.004884063, + -0.0073236832, + -7.281044E-5, + 0.024049066, + 0.025881194, + -0.048020992, + 0.05723948, + -0.017675197, + 0.006865652, + -0.0040210346, + -0.02325836, + 0.026903326, + 0.0016549403, + 0.011012043, + -0.028986165, + -0.0128538115, + -0.039477497, + -0.09303344, + -1.472029E-4, + 0.017241271, + -0.028272599, + -0.002363684, + 0.018533403, + 0.02399121, + 0.008996704, + -0.02368264, + -0.044549596, + 0.043238178, + -0.0015090934, + 0.012294531, + 0.01026955, + -0.019111969, + -0.014290584, + -0.045359585, + -0.019999104, + 0.009150988, + -0.001970741, + 0.028060459, + 0.013760231, + 0.056005202, + -0.017173773, + 0.0058435183, + -0.007945642, + -0.029468304, + 0.01601664, + 0.027250467, + -0.022756936, + -0.011147041, + -0.019218039, + -0.0083168885, + -0.0054240576, + 0.024974773, + -0.033344697, + 0.03548539, + -0.025861908, + 0.022486938, + -2.4152124E-4, + 0.045012448, + -0.01532236, + 0.031628285, + -0.0127284555, + -0.021850515, + 0.038185365, + -0.027173324, + 0.05897518, + -0.016624134, + 0.028851166, + 0.038011797, + 0.02844617, + 0.005829054, + -0.07756644, + -0.043508176, + 0.01288274, + 0.056660913, + 0.02183123, + 0.057432335, + 0.044202454, + 0.01333595, + -0.06537797, + -0.027539749, + 0.051646672, + -0.009136524, + 0.02113695, + 0.015399503, + -0.008572422, + 0.0062967283, + -0.040653914, + -0.032785416, + -0.021445518, + 0.010327406, + 0.039400354, + -0.03465611, + 0.032168277, + 0.010067051, + -0.024955487, + 0.01385666, + -0.0014464154, + 0.050219543, + 0.04019106, + -8.7628665E-4, + -0.003628092, + 0.01569843, + -0.04559101, + -0.032785416, + -0.019275896, + 0.03752966, + 0.052495237, + 0.032168277, + -0.03583253, + -0.015023435, + -0.0030929183, + -0.03583253, + 0.0025119414, + 0.0097247325, + 0.027115468, + 0.030471152, + 0.050296683, + -0.015833426, + -0.017009845, + 0.003338809, + -0.016035926, + 0.01396273, + 0.013702375, + -0.046786718, + 0.013625233, + 0.037336804, + 0.03691252, + -0.01754984, + -0.0033749694, + -0.052572377, + 0.02061624, + 0.0141362995, + -0.019690534, + 0.028079744, + -0.0055928063, + -0.0069331513, + 0.008307246, + 0.034559686, + 0.021001952, + 0.06765367, + -0.013904873, + -0.03212971, + -0.031801853, + 0.030509721, + -0.029236877, + -0.012207746, + -0.035350394, + -0.009686162, + 0.02726975, + -0.014917364, + 0.013374521, + 0.012333102, + -0.008837598, + -0.0030447044, + 0.040923912, + 0.028774023, + 0.010317763, + 0.014579867, + 0.007849215, + 0.005665127, + 0.027616892, + -0.063333705, + 0.0058628037, + 0.020076245, + 0.010520262, + -0.01754984, + -0.027886888, + 0.049525265, + 0.0064510126, + 0.045282446, + 0.016305923, + 0.005452986, + 0.02771332, + 0.037452515, + 0.038763933, + -0.009594555, + -0.00592066, + -0.001504272, + -0.03635324, + -0.03401969, + -0.028774023, + 0.022062656, + 0.014194156, + 0.0070344, + 0.042736754, + 0.025225485, + 0.01633485, + -0.01817662, + 0.016315566, + -0.031898282, + 0.0029241699, + -0.03077972, + 0.0032881843, + -0.038628932, + -0.07745072, + 0.03899536, + 0.038011797, + 0.029121162, + 0.031473998, + -0.0025432804, + -0.02061624, + 0.028369028, + 0.03168614, + 0.020307671, + 0.0015814141, + 0.012930954, + -0.014252013, + 0.0016368601, + 0.0328047, + -0.024627633, + 0.031878997, + 0.016113067, + 0.027559035, + 0.04616958, + -0.0236055, + -0.03432826, + -0.027115468, + 0.03488754, + -0.051029537, + -0.0057374476, + -0.03700895, + 0.0053179874, + -0.02809903, + -0.01368309, + 0.033691835, + 0.022795506, + 0.044588163, + 0.043469604, + 0.014628081, + 0.04832956, + -0.01779091, + -0.018427333, + 0.004671922, + 0.0011131372, + 0.02956473, + 0.01640235, + 0.011118113, + 0.06024802, + 0.045359585, + 0.006653511, + 0.032168277, + 0.051338103, + -0.018446617, + 0.05793376, + 0.0036546097, + 0.017578768, + 0.035138253, + -0.07220506, + -0.04292961, + -3.836014E-4, + 0.031531855, + 0.006079766, + -0.10653332, + 0.018668402, + -0.0056361984, + 0.07802929, + 0.007733501, + 0.02969973, + -0.01640235, + -0.021059807, + -0.056120917, + 0.01772341, + -0.0141362995, + 0.03725966, + -0.036063958, + -0.062755145, + 0.035331108, + -0.0067113675, + -0.08153926, + 5.8338756E-4, + 0.016450565, + -0.024627633, + -0.050952394, + -0.06518512, + -0.039226785, + 0.030702578, + 0.042389613, + 0.016219137, + -0.03444397, + -0.07386361, + -0.013075595, + -0.06456798, + 0.0044935304, + -0.0043537105, + -0.03346041, + -0.0021804713, + 0.0071404707, + 0.018851614, + 0.030548293, + 0.012593457, + 0.038243223, + -0.0345404, + 0.023046218, + -0.02865831, + -0.006316014, + 0.015274147, + 0.014300227, + 0.06530084, + -0.0050190613, + -0.0068367235, + 0.028041173, + 0.03976678, + 0.0035871102, + -0.037799656, + -0.04046106, + 0.044241026, + -0.04258247, + 0.012284888, + -0.0029506874, + 0.05399951, + 0.02823403, + 0.013933802, + -0.0062967283, + 0.0035822887, + 0.015418788, + -0.020866953, + 0.021079093, + -0.021001952, + 0.0012668188, + 0.041463908, + 0.004105409, + -0.014377369, + -0.016035926, + -5.7969617E-5, + 0.013548091, + -0.048869554, + -0.04470388, + 0.016141996, + -0.006446191, + -0.005313166, + -0.03575539, + 0.006904223, + -0.022911219, + -0.0071115424, + 0.0638737, + 0.06576369, + 0.027616892, + -0.004295854, + -0.032187562, + 0.041463908, + 0.043932457, + 0.022255512, + -0.003196578, + -0.029275447, + -0.00860135, + -0.016546993, + 0.026826184, + -0.025630482, + 0.0013065953, + 0.005964053, + -0.015129505, + 0.016267352, + -0.021811944, + 0.06406656, + -0.051762387, + -0.0250712, + -0.04724957, + -0.04910098, + 0.043739602, + -0.011600251, + -0.028291885, + 0.0070681497, + -0.001915295, + 1.20308614E-4, + 0.026440473, + -0.012458458, + 0.0041150516, + -0.024184065, + -0.0016971274, + -0.0374718, + 0.0031797031, + 0.030721864, + -0.042389613, + -0.0131045235, + 0.0068897586, + 0.023914067, + -0.05122239, + 0.05554235, + -0.03899536, + 0.023914067, + -0.0070392215, + 0.0047514746, + -0.0046357615, + 0.036777522, + 0.018022336, + 0.021966228, + -0.0012897204, + 0.020847667, + -0.03008544, + -0.03077972, + -0.015129505, + -0.0090786675, + -0.023914067, + -0.013586662, + 0.009339022, + 0.03868679, + 0.0067547597, + -0.02325836, + 0.0023902014, + -0.029217592, + -4.0590033E-4, + 0.035736103, + -0.0020261868, + 0.0020671687, + 0.07120221, + 0.016913418, + 0.014965578, + -0.01504272, + -0.01065526, + 0.039053217, + -0.017086986, + -0.033036128, + -0.01643128, + 0.00518781, + -0.024820488, + -0.0022118103, + -0.07968785, + -0.0057904827, + 0.009960981, + -0.009040096, + 0.031782568, + -0.0019478394, + -0.0064895838, + 0.0141362995, + -0.02368264, + -0.0030784542, + 0.035774674, + -0.012911668, + -0.010134551, + -0.012863454, + 0.01824412, + 0.011523109, + -0.022756936, + -0.011725607, + 0.054230936, + -0.03338327, + 0.009951338, + -0.028947594, + -0.012024533, + 0.027983317, + 0.031782568, + 0.0344054, + 0.009686162, + -0.048753843, + -0.014039872, + 5.544592E-4, + 0.037028234, + -0.001069142, + -0.015958782, + 0.010337049, + 0.0027602427, + -0.004216301, + -0.027694033, + -0.0065377974, + -0.057779472, + -0.003628092, + -0.040731058, + -0.009758483, + 0.0048912945, + 0.035871103, + 0.042389613, + 0.0033050592, + 0.008900276, + 0.024531204, + -0.008745992, + -0.03367255, + 0.0053324513, + -0.018986613, + 0.017048417, + 0.04192676, + 0.029815443, + -0.012969525, + 0.01385666, + 0.039612498, + 0.013750589, + 0.0030061333, + -0.013220237, + 0.03380755, + 0.0096620545, + -0.026614044, + -0.020481242, + 0.028253313, + -0.023161931, + 0.0020744007, + -0.0045658513, + -0.0054095937, + -0.04007535, + 0.060633734, + -0.021387663, + -0.013143094, + -0.03536968, + -0.0043199607, + -0.012371673, + 0.0027337251, + -0.013818088, + -0.019324109, + -0.02221694, + -0.04443388, + -0.006079766, + 0.009300451, + -0.0035750568, + 0.007497253, + -0.06352656, + 0.032361135, + -0.057123765, + -0.011359182, + -0.017781267, + -0.01821519, + -0.016894132, + -0.039438926, + 0.013403449, + -0.017366627, + -0.014203799, + -0.019449465, + -0.014367727, + -0.034733254, + 0.010848115, + 0.029236877, + 0.009440271, + -0.029217592, + 0.048213847, + -0.02601619, + -0.02559191, + -0.019844819, + -0.018166978, + 7.18989E-4, + -0.0060749445, + 0.012564529, + 0.021194806, + -0.005829054, + 0.0075743953, + -0.009208845, + -0.01970982, + -0.0115809655, + -0.035774674, + 0.017405199, + -0.016604848, + 0.029622586, + -0.020731954, + 0.014107372, + -0.0014644956, + 0.03795394, + -0.03789608, + -0.008847241, + 0.07683358, + 0.028214743, + 0.0017007435, + -0.0066101183, + 0.021715516, + 0.0020587312, + -0.0042813895, + 0.0067210104, + -0.034598257, + -0.03172471, + 0.005322809, + 0.0031218466, + 0.023181217, + 0.027231181, + 8.057739E-4, + -0.008456709, + 0.0032881843, + 0.0041463906, + -0.020674098, + 0.016074497, + 0.010751688, + -0.011233826, + -0.05264952, + 0.0059158388, + -0.006769224, + -0.006494405, + 0.014348441, + 0.031281143, + -0.001168583, + -0.007198327, + 0.010279192, + -0.01264167, + -0.01765591, + 0.002131052, + 0.0011975114, + 0.021329805, + 0.023509072, + -0.04439531, + -0.006330478, + -0.008268675, + 0.029776871, + -0.033556838, + 0.0043971026, + 0.0016838686, + -0.009150988, + 0.008721885, + -0.04944812, + 0.025919763, + 0.030799005, + -0.0046815644, + 0.030471152, + -0.020886239, + 0.007897428, + -0.022911219, + 0.036816094, + -0.015978068, + 0.0021455162, + 0.0072127916, + -0.020269101, + 0.0234705, + -0.033441123, + 0.026363332, + 0.013094881, + -0.0078010005, + 0.0034255937, + 0.0077913576, + 0.01632521, + 0.01591057, + 0.0015512805, + 0.009744018, + -0.0022901576, + 4.411567E-4, + -0.008398852, + -0.008919561, + 0.017019488, + -0.010838472, + -0.04188819, + 0.008823134, + 0.045321018, + 0.026999755, + 6.400388E-4, + -0.03328684, + -0.032534704, + 0.008365102, + -0.0031146144, + -0.012198104, + 0.006716189, + 0.025611196, + 0.008620636, + -0.006007445, + -0.025746195, + -0.018996255, + 0.0021286414, + -0.002335961, + 0.0072272555, + -0.0091413455, + 0.013065953, + -0.036507525, + -0.016788062, + -0.02792546, + 0.032303277, + 0.02113695, + 0.003526843, + 0.023509072, + -0.0058724466, + 0.010983114, + 0.0057904827, + -0.005563878, + 0.00119269, + 0.017935552, + 0.0011517083, + 0.029256161, + 0.032611847, + -0.017626982, + 0.010134551, + -0.017318415, + 0.0041970154, + 0.023933353, + 0.03349898, + -0.0035533605, + 0.03176328, + -0.0018562331, + 0.010144194, + -0.050219543, + -0.042466756, + 0.027019039, + -0.008447066, + 0.018861257, + 0.02883188, + -0.018070549, + -0.0074056466, + -0.028947594, + 0.03488754, + 0.0017537787, + 0.012632028, + -0.0077527864, + 0.02649833, + 0.026440473, + -0.0041150516, + 0.0484067, + 0.0032978272, + 8.7447866E-4, + 0.015505573, + 0.006017088, + 0.012603099, + 0.012005248, + -0.018224834, + 0.024376921, + 0.003823358, + -0.0056844126, + 0.016248066, + -0.030509721, + -0.0110024, + -0.005385487, + -0.0029675623, + -0.01884197, + -6.334094E-4, + -0.002499888, + -0.0037196982, + 0.021734802, + -0.043893885, + 0.008765277, + 0.013924159, + 0.023586214, + -0.004339246, + 0.004450138, + -0.02399121, + 0.016064854, + 0.0037413945, + 0.029333305, + -0.0060122665, + -0.015004149, + -0.01121454, + -0.002569798, + -0.01549593, + 0.011204898, + -0.030239724, + 0.022178369, + -0.04983383, + 0.05662234, + -0.04161819, + 0.0064510126, + 0.028581169, + 0.0035533605, + -0.017752338, + -0.006383513, + -0.01817662, + 0.002217837, + 0.012660956, + 0.020326957, + -0.0011583377, + -0.009883839, + 0.019430181, + 0.011349539, + 0.031975422, + -0.01430987, + -0.0044718343, + 0.0014427993, + -0.015862355, + 0.022120513, + -0.039805353, + -0.018494831, + -0.029198306, + 0.0010124908, + 0.008191532, + -0.0019189111, + 0.0020707848, + -0.0034665756, + 0.027616892, + -0.0014235139, + -0.0220048, + -0.015264504, + 0.03349898, + 0.005342094, + 0.009560806, + -0.0074152895, + 0.01322988, + 0.0034979146, + -0.0035051466, + 0.008500101, + 0.030471152, + -0.013982016, + -0.018408047, + 0.039959636, + -0.019314466, + 0.016479492, + -0.031840425, + 0.02433835, + 0.034347545, + -0.008259032, + -1.3326609E-4, + 0.014001301, + -0.02433835, + 0.003526843, + 0.021291235, + -0.012612742, + 0.016305923, + -0.0020490885, + 0.011725607, + -4.4417006E-4, + 0.020731954, + -0.021850515, + -0.011725607, + -0.0071260063, + -0.033865407, + 0.011378468, + 0.023200503, + 0.013914516, + 1.346221E-4, + -0.020558383, + -0.0328047, + 0.019613393, + -0.0016742258, + -0.024261208, + -0.021966228, + -0.034270402, + -0.02952616, + -0.029757585, + -0.01378916, + 0.010664903, + 0.03847465, + 0.0034304152, + 0.037722513, + 0.013451663, + 0.04088534, + 0.002719261, + 0.0050190613, + 0.033036128, + -0.06406656, + -0.005202274, + -0.010211693, + 0.009392058, + -0.018330904, + 0.05766376, + 0.053498086, + 0.04076963, + 0.058628038, + 0.020789811, + 0.012998453, + 0.017781267, + -0.0023624785, + -0.010973471, + -0.0012511493, + -0.019767677, + 0.01681699, + -0.031184716, + 0.025129057, + 0.027346894, + -6.102065E-4, + -0.0024902453, + 0.029082593, + 0.03116543, + 6.3039607E-4, + 0.046131007, + -0.028272599, + 0.0074008256, + 0.009199202, + 0.043045323, + -0.009830804, + -0.009729554, + -0.02771332, + -0.021927657, + 0.020558383, + 0.00424764, + -0.024222637, + 0.009864553, + -0.015148791, + -0.034347545, + -7.1055157E-4, + 0.016653063, + 0.020596955, + 0.011224183, + -0.019623036, + 8.678492E-4, + -0.025437625, + 0.008403674, + -0.003432826, + -0.0118316775, + -0.009729554, + -0.009647591, + -0.011648465, + -0.0031555963, + -0.018784115, + 0.011889535, + -0.006571547, + 0.016161282, + 0.015717713, + -0.010751688, + -5.692247E-4, + -0.03583253, + 0.02823403, + 0.030143296, + -0.021541947, + 0.034289688, + -0.027964031, + 0.0075936806, + -0.007357433, + 0.0075502885, + 0.023181217, + -0.009999552, + -0.013760231, + -0.015457359, + 0.02601619, + -0.023721213, + -0.032091137, + -0.033055414, + -0.0066245827, + 0.02524477, + 0.0060219094, + -0.0026372974, + -0.009676519, + -0.0043971026, + 0.015515216, + 0.02242908, + 0.023489786, + -0.006653511, + 0.023721213, + 0.0250712, + 0.019121611, + 0.024396207, + 0.014396654, + 0.013837374, + 0.031069003, + -0.0036305026, + -0.03457897, + -0.010954186, + -0.0016935114, + 0.014579867, + -0.026710471, + 0.049255267, + 0.0011161505, + -0.026864756, + -0.0101731215, + 4.7129035E-4, + -0.04616958, + -0.016797705, + -0.03276613, + -0.0094643785, + 0.041001055, + 0.022776222, + 0.027539749, + 0.028851166, + 0.009396879, + 0.019623036, + 0.0056988765, + -0.02645976, + 0.009797053, + -0.0061231586, + 0.022911219, + -0.008750813, + -0.00442121, + 0.023933353, + 0.024897631, + 0.0053710225, + -0.0018996255, + -0.0011016864, + -0.021059807, + -0.003931839, + 0.03596753, + 0.0023709158, + -0.027848318, + 0.010529904, + -0.002116588, + 0.014975221, + 0.012149889, + -0.037028234, + 0.016151639, + 0.019565178, + -0.0010462404, + -1.732685E-4, + -0.02559191, + 0.008688135, + 0.01448344, + 0.03976678, + -0.03506111, + 0.005867625, + 0.018282691, + 0.017607696, + -0.010790259, + -0.016759133, + 0.02464692, + 0.038339652, + 0.0058483398, + 0.02580405, + -0.015602001, + 0.02844617, + -0.013181666, + -0.027848318, + -0.0025746194, + -0.014849865, + 0.020905524, + -0.023431929, + 0.045706727, + 0.0070247576, + -0.021676945, + 0.024743346, + -0.029333305, + -0.009295629, + 0.0030567579, + 0.025842622, + -0.030374723, + 0.025456911, + 0.007217613, + 0.015052363, + -0.045552444, + -0.004975669, + 0.0026035476, + -0.03297827, + 0.013075595, + -0.012632028, + -0.015920212, + -0.029121162, + 0.041656762, + 0.036931805, + 0.011127756, + -0.0085676005, + -0.015370574, + 0.015833426, + 0.01876483, + -0.009980266, + -0.009040096, + -0.008765277, + 0.028542597, + 0.0345404, + -0.0038836254, + 0.011474895, + 0.022699079, + -0.00832171, + 0.0041319267, + 0.0076418947, + 0.0128538115, + 0.04474245, + 0.01633485, + 0.03652681, + -0.015004149, + 0.0022539974, + 0.009989909, + -0.01758841, + 0.03444397, + 0.029989012, + 0.046246722, + -0.022024086, + -0.0049853115, + 0.02368264, + 0.036083244, + -0.034212545, + -0.04751957, + 0.017945193, + -0.03164757, + 0.022718364, + 0.019825533, + -0.023914067, + -0.04011392, + 0.014936649, + 0.03392326, + 0.0049105803, + 0.0142616555, + -7.9191243E-4, + -0.011552038, + -0.029776871, + 0.010259907, + 0.00912206, + 0.0457453, + -0.026691185, + 0.015920212, + -0.01832126, + 0.015119862, + 0.021715516, + -5.1860017E-4, + 0.007820286, + 0.015756285, + -0.024280492, + 0.01225596, + -0.025611196, + 0.019545894, + -0.0015536912, + 0.020905524, + 0.015081291, + -0.08177068, + -0.020596955, + -0.020134103, + -0.0047321892, + 0.017848765, + -0.0038281793, + -0.04736528, + -0.017241271, + -0.015833426, + 0.023181217, + -0.008803848, + -0.032399707, + -0.015900927, + 0.014975221, + 0.0051733456, + 0.017414842, + 0.015736999, + 0.0034497008, + -5.30955E-4, + 0.008880991, + -0.017868051, + -0.03182114, + -0.0053710225, + -0.006653511, + -5.6530733E-4, + 0.005554235, + 0.032958984, + 0.01469558, + -0.026556186, + -0.034771826, + -0.026556186, + -0.047596708, + 0.0039366605, + 0.00257703, + 0.019478394, + 0.009310094, + -0.012063105, + 2.1349694E-4, + 0.029892584, + 0.022178369, + -0.015235576, + 0.015428431, + 0.006528155, + -0.014252013, + -0.00344729, + 0.01511022, + -0.009112417, + -0.00501424, + -0.019516965, + 0.0069427937, + 0.0036232707, + 0.0034665756, + 0.039921064, + 0.031146144, + 7.1175693E-4, + -0.008152962, + 0.0031604175, + -0.005337273, + 0.0043850495, + -0.009498128, + -0.028947594, + 0.026401902, + -0.01458951, + -0.006981365, + 0.013461306, + 0.003792019, + -0.015862355, + 0.0117063215, + 0.0024613168, + 0.018253762, + -8.9195615E-4, + -0.0052842377, + -0.0154091455, + -0.010250264, + -0.027771175, + 0.016383065, + 0.0096524125, + 0.01629628, + 0.0064847623, + -0.0015777982, + -0.005279416, + -0.05122239, + 0.015949141, + -0.0030109547, + 0.035408247, + 0.02736618, + -0.0028156887, + 0.004937098, + 0.006079766, + -0.022602651, + 0.03423183, + -0.0071838633, + 0.006518512, + 0.030374723, + 0.043238178, + -0.027983317, + 6.490789E-4, + -0.012304174, + -0.017530555, + -0.0011951007, + 0.0129791675, + 0.021021238, + 0.06329514, + 0.014078443, + 0.017125558, + 0.0070777927, + -0.0062629785, + -0.012506672, + -0.0023926122, + 0.0035774675, + -0.009536698, + 0.028928308, + -0.0033822013, + 0.020307671, + -0.005356558, + -0.01852376, + -0.025264056, + 0.014811293, + 0.008861705, + 0.021561231, + -0.044356737, + -0.008196354, + 0.022602651, + 0.01591057, + 0.012169175, + -0.022795506, + 0.025784764, + 0.0049563833, + 0.017665554, + -0.03130043, + -0.0029193484, + 0.02991187, + 3.5316643E-4, + 0.019169826, + 0.031184716, + 0.03860965, + -0.0077913576, + 0.024473349, + 0.019738749, + 0.0016163692, + -0.0044284416, + 0.036816094, + -0.017193059, + 0.022621937, + 0.012149889, + -0.029815443, + -0.004546566, + 0.00922813, + 0.026093334, + -0.0061858366, + -0.00738154, + 0.011484538, + -0.017154487, + 0.011465252, + 0.021445518, + 0.015891284, + 0.01640235, + -0.02952616, + 0.025746195, + -4.9419195E-4, + 0.025553338, + 0.0017537787, + 0.025842622, + -0.004033088, + 0.017665554, + -0.0128538115, + -0.0014946292, + -0.0047032605, + -0.0010962624, + 0.027385464, + -0.005838697, + 0.010404548, + 0.0014343619, + -0.0020985077, + -0.012921311, + -0.0056361984, + -0.001849001, + -0.0018960095, + -0.01180275, + 0.010645618, + 0.028600454, + -0.0020093122, + -0.029063307, + 0.01549593, + 0.025148341, + -0.009960981, + 0.016951988, + -0.00790225, + 0.0035340749, + -0.0410782, + -0.011590608, + -0.017453412, + 0.022024086, + -0.01322988, + -0.0066101183, + -0.0012607921, + -0.012371673, + 0.037491087, + -0.0015814141, + 0.03353755, + 0.006055659, + 0.004380228, + 0.010944543, + -0.034424685, + 0.030818291, + 0.019960532, + 0.028156886, + -6.126172E-4, + 0.002468549, + 0.0046815644, + 0.017520912, + 0.02256408, + 0.017578768, + 0.027154038, + 0.040229633, + -0.012246317, + 0.014965578, + -0.0037582694, + -0.011658108, + -0.0061569083, + 0.004614065, + 0.026440473, + 0.0030037225, + 0.038320366, + 0.019902676, + 0.025495483, + 0.027867604, + -0.019381966, + -0.014425583, + -0.0021189984, + 0.009512592, + -0.033151843, + 0.02308479, + 0.008702599, + -0.0053613796, + -0.040615343, + 0.018398404, + -0.007506896, + -0.001406639, + -0.016219137, + -0.029121162, + 0.0058820895, + 0.017877694, + -0.01020205, + -0.04898527, + 0.007019936, + -0.01162918, + -0.0036256812, + 0.011532752, + -0.0023082378, + 0.026363332, + 0.023412643, + -0.010857758, + 0.05793376, + 0.014194156, + 0.008519387, + 0.038146794, + -0.04049963, + 0.08400781, + -0.03025901, + -0.020269101, + 0.013046667, + -0.022969076, + -0.009165452, + 0.039651066, + 0.01772341, + -0.006455834, + -0.036931805, + -5.996597E-4, + 0.004512816, + 0.019613393, + -0.025090486, + 0.014512368, + -0.016257709, + -0.0043826387, + -0.035350394, + -0.045629583, + 0.00932938, + -0.027211895, + -0.027906174, + -0.009960981, + 0.0070054717, + 0.004033088, + 0.014280941, + -0.01048169, + 0.0076660016, + -0.015688786, + -0.015968425, + -0.029468304, + 0.018330904, + 0.023142647, + -0.012747741, + 0.011523109, + -0.021966228, + -0.013827731, + 0.03826251, + -0.013191309, + 0.011098827, + -0.013808446, + 0.010009195, + -0.0022045781, + -0.007723858, + -0.0016911007, + -0.011532752, + 0.007179042, + -0.061366584, + 0.005679591, + -0.013442021, + 0.022448367, + 0.034173973, + 0.027867604, + 0.035350394, + -0.012911668, + 0.01643128, + -0.009671697, + -0.04578387, + -0.0028397955, + 0.019034827, + 0.017761981, + 0.0220048, + -0.006026731, + -0.008683314, + -0.033132557 + ], + "content": "7~9학점\n\n등록금의 1/2 납입\n\n10학점 이상\n\n등록금 전액\n\n대학원생 수업연한초과자\n\n1~3학점\n\n등록금의 1/2 납입\n\n4학점 이상\n\n등록금 전액\n\n6\n\n 분할납부 신청 및 등록\n\n\n\n1) 대상자 : 학부 및 대학원(특수대학원 포함) 재학생\n 단, 수업연한초과자는 학부에 한하여 전액납부 대상자만 신청가능함.\n\n ※신청제외 대상\n\n- 정부학자금 대출 등록 희망자\n - 카드납부 희망자\n\n- 2023학년도 2학기, 2024학년도 1학기 분할납부자 중 지연 납부자\n - 2024. 2학기 재입학생\n\n- 차액고지자(전과, 소속변경 등)\n\n2) 신청기간\n ● 신청기간 : 2024. 8. 14.(수) 09:00 ~ 8. 23.(금) 16:00(기간 외 신청불가)\n ● 신청방법 : 학사정보시스템 → 등록 →분할납부신청\n ● 1차분 고지서 출력 : 2024. 8. 29.(목) 16:00부터 [고지서출력버튼]\n\n3) 납부방법: 분할납부 고지서의 “신한은행 전용계좌”로 입금\n ※ 기타납입금 납부를 원할 경우 1차분 납부 시 합산하여 납부해야 함. (2차분부터 납부불가)\n\n4) 차수별 등록기간 및 금액\n차수\n납부기간\n등록금액\n비고\n1차\n\n2024. 9. 2.(월) 09:00 ~ 9. 6.(금) 16:00\n총 납부금의 30%\n분납신청후\n승인된 학생\n\n2차\n2024. 10. 2.(수) 09:00 ~ 10. 4.(금) 16:00\n총 납부금의 30%\n\n3차\n\n2024. 10. 28(월) 09:00 ~ 10. 30.(수) 16:00\n총 납부금의 20%\n\n4차\n2024. 11. 25.(월) 09:00 ~ 11. 27.(수) 16:00\n총 납부금의 20%\n\n5) 유의사항", + "id": "cc3e9ddc-041a-41fc-a113-071f05cac5f6", + "metadata": { + "charset": "UTF-8", + "filename": "ku-uni-register.txt", + "source": "ku-uni-register.txt" + }, + "media": [] + } +} \ No newline at end of file diff --git a/src/main/resources/db/migration/V260217__Alter_club_subscribe_to_root_user.sql b/src/main/resources/db/migration/V260217__Alter_club_subscribe_to_root_user.sql new file mode 100644 index 000000000..4d14da27b --- /dev/null +++ b/src/main/resources/db/migration/V260217__Alter_club_subscribe_to_root_user.sql @@ -0,0 +1,37 @@ +-- root_user로 바꾸면 데이터 이관을 해야하나, 아직 데이터가 존재하지 않는 개발 단계이므로 데이터 삭제하고 진행. +-- 1) 기존 device(user) 기준 구독 데이터는 유지하지 않는다. +DELETE +FROM club_subscribe; + +-- 2) 기존 user FK/unique/index 제약 제거 +ALTER TABLE club_subscribe +DROP +FOREIGN KEY fk_club_subscribe_user; + +ALTER TABLE club_subscribe +DROP INDEX uk_club_user; + +ALTER TABLE club_subscribe +DROP INDEX idx_club_subscribe_user; + +-- 3) root_user_id 컬럼 추가 +ALTER TABLE club_subscribe + ADD COLUMN root_user_id BIGINT NOT NULL; + +-- 4) 기존 user_id 컬럼 제거 +ALTER TABLE club_subscribe +DROP +COLUMN user_id; + +-- 5) root_user FK 추가 (동아리/계정 삭제 시 구독도 함께 삭제) +ALTER TABLE club_subscribe + ADD CONSTRAINT fk_club_subscribe_root_user + FOREIGN KEY (root_user_id) REFERENCES root_user (id) + ON DELETE CASCADE; + +-- 6) 동아리-계정 구독 중복 방지 unique 제약 추가 +ALTER TABLE club_subscribe + ADD CONSTRAINT uk_club_root_user UNIQUE (club_id, root_user_id); + +-- 7) root_user 기준 조회 성능을 위한 인덱스 추가 +CREATE INDEX idx_club_subscribe_root_user ON club_subscribe (root_user_id); \ No newline at end of file diff --git a/src/test/java/com/kustacks/kuring/acceptance/UserAcceptanceTest.java b/src/test/java/com/kustacks/kuring/acceptance/UserAcceptanceTest.java index 4a4fde583..706fd7665 100644 --- a/src/test/java/com/kustacks/kuring/acceptance/UserAcceptanceTest.java +++ b/src/test/java/com/kustacks/kuring/acceptance/UserAcceptanceTest.java @@ -16,6 +16,10 @@ import static com.kustacks.kuring.acceptance.EmailStep.회원가입_인증코드_이메일_전송_요청; import static com.kustacks.kuring.acceptance.UserStep.구독한_학과_목록_조회_요청; import static com.kustacks.kuring.acceptance.UserStep.남은_질문_횟수_조회; +import static com.kustacks.kuring.acceptance.UserStep.동아리_구독_제거_성공_응답_확인; +import static com.kustacks.kuring.acceptance.UserStep.동아리_구독_제거_요청; +import static com.kustacks.kuring.acceptance.UserStep.동아리_구독_추가_성공_응답_확인; +import static com.kustacks.kuring.acceptance.UserStep.동아리_구독_추가_요청; import static com.kustacks.kuring.acceptance.UserStep.로그아웃_요청; import static com.kustacks.kuring.acceptance.UserStep.로그아웃_응답_확인; import static com.kustacks.kuring.acceptance.UserStep.로그인_요청; @@ -58,6 +62,7 @@ class UserAcceptanceTest extends IntegrationTestSupport { public static final String NEW_EMAIL = "new-client@konkuk.ac.kr"; + private static final Long TEST_CLUB_ID = 1L; /** * Given: 가입되지 않은 사용자가 있다 @@ -481,7 +486,7 @@ void modify_password_with_access_token() { String accessToken = 사용자_로그인_되어_있음(USER_FCM_TOKEN, USER_EMAIL, USER_PASSWORD); // when - var 비밀번호_변경_응답 = 액세스_토큰으로_비밀번호_변경_요청(accessToken,"new_password"); + var 비밀번호_변경_응답 = 액세스_토큰으로_비밀번호_변경_요청(accessToken, "new_password"); // then 비밀번호_변경_응답_확인(비밀번호_변경_응답); @@ -523,4 +528,56 @@ void toggle_academic_event_notification() { // then 학사일정_알림_토글_응답_확인(두번째_토글_응답, true); } -} \ No newline at end of file + + @DisplayName("[v2] 사용자는 동아리를 구독할 수 있다") + @Test + void add_club_subscription_success() { + String accessToken = 사용자_로그인_되어_있음(USER_FCM_TOKEN, USER_EMAIL, USER_PASSWORD); + + var response = 동아리_구독_추가_요청(USER_FCM_TOKEN, accessToken, TEST_CLUB_ID); + + 동아리_구독_추가_성공_응답_확인(response, 1L); + } + + @DisplayName("[v2] 이미 구독한 동아리는 다시 구독할 수 없다") + @Test + void add_club_subscription_fail_when_already_subscribed() { + String accessToken = 사용자_로그인_되어_있음(USER_FCM_TOKEN, USER_EMAIL, USER_PASSWORD); + + 동아리_구독_추가_요청(USER_FCM_TOKEN, accessToken, TEST_CLUB_ID); + var response = 동아리_구독_추가_요청(USER_FCM_TOKEN, accessToken, TEST_CLUB_ID); + + 실패_응답_확인(response, HttpStatus.BAD_REQUEST); + } + + @DisplayName("[v2] 존재하지 않는 동아리 구독은 실패한다") + @Test + void add_club_subscription_fail_when_club_not_found() { + String accessToken = 사용자_로그인_되어_있음(USER_FCM_TOKEN, USER_EMAIL, USER_PASSWORD); + + var response = 동아리_구독_추가_요청(USER_FCM_TOKEN, accessToken, 99999L); + + 실패_응답_확인(response, HttpStatus.BAD_REQUEST); + } + + @DisplayName("[v2] 사용자는 동아리 구독을 취소할 수 있다") + @Test + void remove_club_subscription_success() { + String accessToken = 사용자_로그인_되어_있음(USER_FCM_TOKEN, USER_EMAIL, USER_PASSWORD); + + 동아리_구독_추가_요청(USER_FCM_TOKEN, accessToken, TEST_CLUB_ID); + var response = 동아리_구독_제거_요청(USER_FCM_TOKEN, accessToken, TEST_CLUB_ID); + + 동아리_구독_제거_성공_응답_확인(response, 0L); + } + + @DisplayName("[v2] 구독하지 않은 동아리 취소는 실패한다") + @Test + void remove_club_subscription_fail_when_not_subscribed() { + String accessToken = 사용자_로그인_되어_있음(USER_FCM_TOKEN, USER_EMAIL, USER_PASSWORD); + + var response = 동아리_구독_제거_요청(USER_FCM_TOKEN, accessToken, TEST_CLUB_ID); + + 실패_응답_확인(response, HttpStatus.BAD_REQUEST); + } +} diff --git a/src/test/java/com/kustacks/kuring/acceptance/UserStep.java b/src/test/java/com/kustacks/kuring/acceptance/UserStep.java index ae324c2e2..b1614de6b 100644 --- a/src/test/java/com/kustacks/kuring/acceptance/UserStep.java +++ b/src/test/java/com/kustacks/kuring/acceptance/UserStep.java @@ -4,6 +4,7 @@ import com.kustacks.kuring.user.adapter.in.web.dto.UserAcademicEventNotificationRequest; import com.kustacks.kuring.user.adapter.in.web.dto.UserBookmarkRequest; import com.kustacks.kuring.user.adapter.in.web.dto.UserCategoriesSubscribeRequest; +import com.kustacks.kuring.user.adapter.in.web.dto.UserClubSubscriptionRequest; import com.kustacks.kuring.user.adapter.in.web.dto.UserDepartmentsSubscribeRequest; import com.kustacks.kuring.user.adapter.in.web.dto.UserFeedbackRequest; import com.kustacks.kuring.user.adapter.in.web.dto.UserLoginRequest; @@ -84,6 +85,27 @@ public class UserStep { .extract(); } + public static ExtractableResponse 동아리_구독_추가_요청(String userToken, String accessToken, Long clubId) { + return RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("User-Token", userToken) + .header("Authorization", "Bearer " + accessToken) + .body(new UserClubSubscriptionRequest(clubId)) + .when().post("/api/v2/users/subscriptions/clubs") + .then().log().all() + .extract(); + } + + public static ExtractableResponse 동아리_구독_제거_요청(String userToken, String accessToken, Long clubId) { + return RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .header("User-Token", userToken) + .header("Authorization", "Bearer " + accessToken) + .when().delete("/api/v2/users/subscriptions/clubs/{clubId}", clubId) + .then().log().all() + .extract(); + } + public static void 학과_구독_응답_확인(ExtractableResponse response) { assertAll( () -> assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()), @@ -92,6 +114,24 @@ public class UserStep { ); } + public static void 동아리_구독_추가_성공_응답_확인(ExtractableResponse response, long expectedCount) { + assertAll( + () -> assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()), + () -> assertThat(response.jsonPath().getInt("code")).isEqualTo(200), + () -> assertThat(response.jsonPath().getString("message")).isEqualTo("구독에 성공했습니다."), + () -> assertThat(response.jsonPath().getLong("data.subscriptionCount")).isEqualTo(expectedCount) + ); + } + + public static void 동아리_구독_제거_성공_응답_확인(ExtractableResponse response, long expectedCount) { + assertAll( + () -> assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()), + () -> assertThat(response.jsonPath().getInt("code")).isEqualTo(200), + () -> assertThat(response.jsonPath().getString("message")).isEqualTo("구독이 취소되었습니다."), + () -> assertThat(response.jsonPath().getLong("data.subscriptionCount")).isEqualTo(expectedCount) + ); + } + public static void 사용자_학과_조회_응답_확인(ExtractableResponse response, List departments) { assertAll( () -> assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()), diff --git a/src/test/java/com/kustacks/kuring/archunit/DependencyRuleTests.java b/src/test/java/com/kustacks/kuring/archunit/DependencyRuleTests.java index eeb95e069..9374b2862 100644 --- a/src/test/java/com/kustacks/kuring/archunit/DependencyRuleTests.java +++ b/src/test/java/com/kustacks/kuring/archunit/DependencyRuleTests.java @@ -9,146 +9,146 @@ @DisplayName("헥사고날 아키텍처 검증") class DependencyRuleTests { - @DisplayName("User 아키텍처 검증") - @Test - void validateUserArchitecture() { - HexagonalArchitecture.boundedContext("com.kustacks.kuring.user") - - .withDomainLayer("domain") - - .withAdaptersLayer("adapter") - .incoming("in.web") - .outgoing("out.persistence") - .outgoing("out.event") - .and() - - .withApplicationLayer("application") - .services("service") - .incomingPorts("port.in") - .outgoingPorts("port.out") - .and() - - .withConfiguration("configuration") - .check(new ClassFileImporter() - .importPackages("com.kustacks.kuring.user..")); - } - - @DisplayName("Notice 아키텍처 검증") - @Test - void validateNoticeArchitecture() { - HexagonalArchitecture.boundedContext("com.kustacks.kuring.notice") - - .withDomainLayer("domain") - - .withAdaptersLayer("adapter") - .incoming("in.web") - .outgoing("out.persistence") - .and() - - .withApplicationLayer("application") - .services("service") - .incomingPorts("port.in") - .outgoingPorts("port.out") - .and() - - .withConfiguration("configuration") - .check(new ClassFileImporter() - .importPackages("com.kustacks.kuring.notice..")); - } - - @DisplayName("Admin 아키텍처 검증") - @Test - void validateAdminArchitecture() { - HexagonalArchitecture.boundedContext("com.kustacks.kuring.admin") - - .withDomainLayer("domain") - - .withAdaptersLayer("adapter") - .incoming("in.web") - .outgoing("out.persistence") - .outgoing("out.event") - .and() - - .withApplicationLayer("application") - .services("service") - .incomingPorts("port.in") - .outgoingPorts("port.out") - .and() - - .withConfiguration("configuration") - .check(new ClassFileImporter() - .importPackages("com.kustacks.kuring.admin..")); - } - - @DisplayName("Staff 아키텍처 검증") - @Test - void validateStaffArchitecture() { - HexagonalArchitecture.boundedContext("com.kustacks.kuring.staff") - - .withDomainLayer("domain") - - .withAdaptersLayer("adapter") - .incoming("in.web") - .outgoing("out.persistence") - .and() - - .withApplicationLayer("application") - .services("service") - .incomingPorts("port.in") - .outgoingPorts("port.out") - .and() - - .withConfiguration("configuration") - .check(new ClassFileImporter() - .importPackages("com.kustacks.kuring.staff..")); - } - - @DisplayName("Email 아키텍처 검증") - @Test - void validateEmailArchitecture() { - HexagonalArchitecture.boundedContext("com.kustacks.kuring.email") - - .withDomainLayer("domain") - - .withAdaptersLayer("adapter") - .incoming("in.web") - .outgoing("out.persistence") - .outgoing("out.email") - .and() - - .withApplicationLayer("application") - .services("service") - .incomingPorts("port.in") - .outgoingPorts("port.out") - .and() - - .withConfiguration("configuration") - .check(new ClassFileImporter() - .importPackages("com.kustacks.kuring.email..")); - } - - @DisplayName("Report 아키텍처 검증") - @Test - void validateReportArchitecture() { - HexagonalArchitecture.boundedContext("com.kustacks.kuring.report") - - .withDomainLayer("domain") - - .withAdaptersLayer("adapter") - .incoming("in.web") - .outgoing("out.persistence") - .and() - - .withApplicationLayer("application") - .services("service") - .incomingPorts("port.in") - .outgoingPorts("port.out") - .and() - - .withConfiguration("configuration") - .check(new ClassFileImporter() - .importPackages("com.kustacks.kuring.report..")); - } + @DisplayName("User 아키텍처 검증") + @Test + void validateUserArchitecture() { + HexagonalArchitecture.boundedContext("com.kustacks.kuring.user") + + .withDomainLayer("domain") + + .withAdaptersLayer("adapter") + .incoming("in.web") + .outgoing("out.persistence") + .outgoing("out.event") + .and() + + .withApplicationLayer("application") + .services("service") + .incomingPorts("port.in") + .outgoingPorts("port.out") + .and() + + .withConfiguration("configuration") + .check(new ClassFileImporter() + .importPackages("com.kustacks.kuring.user..")); + } + + @DisplayName("Notice 아키텍처 검증") + @Test + void validateNoticeArchitecture() { + HexagonalArchitecture.boundedContext("com.kustacks.kuring.notice") + + .withDomainLayer("domain") + + .withAdaptersLayer("adapter") + .incoming("in.web") + .outgoing("out.persistence") + .and() + + .withApplicationLayer("application") + .services("service") + .incomingPorts("port.in") + .outgoingPorts("port.out") + .and() + + .withConfiguration("configuration") + .check(new ClassFileImporter() + .importPackages("com.kustacks.kuring.notice..")); + } + + @DisplayName("Admin 아키텍처 검증") + @Test + void validateAdminArchitecture() { + HexagonalArchitecture.boundedContext("com.kustacks.kuring.admin") + + .withDomainLayer("domain") + + .withAdaptersLayer("adapter") + .incoming("in.web") + .outgoing("out.persistence") + .outgoing("out.event") + .and() + + .withApplicationLayer("application") + .services("service") + .incomingPorts("port.in") + .outgoingPorts("port.out") + .and() + + .withConfiguration("configuration") + .check(new ClassFileImporter() + .importPackages("com.kustacks.kuring.admin..")); + } + + @DisplayName("Staff 아키텍처 검증") + @Test + void validateStaffArchitecture() { + HexagonalArchitecture.boundedContext("com.kustacks.kuring.staff") + + .withDomainLayer("domain") + + .withAdaptersLayer("adapter") + .incoming("in.web") + .outgoing("out.persistence") + .and() + + .withApplicationLayer("application") + .services("service") + .incomingPorts("port.in") + .outgoingPorts("port.out") + .and() + + .withConfiguration("configuration") + .check(new ClassFileImporter() + .importPackages("com.kustacks.kuring.staff..")); + } + + @DisplayName("Email 아키텍처 검증") + @Test + void validateEmailArchitecture() { + HexagonalArchitecture.boundedContext("com.kustacks.kuring.email") + + .withDomainLayer("domain") + + .withAdaptersLayer("adapter") + .incoming("in.web") + .outgoing("out.persistence") + .outgoing("out.email") + .and() + + .withApplicationLayer("application") + .services("service") + .incomingPorts("port.in") + .outgoingPorts("port.out") + .and() + + .withConfiguration("configuration") + .check(new ClassFileImporter() + .importPackages("com.kustacks.kuring.email..")); + } + + @DisplayName("Report 아키텍처 검증") + @Test + void validateReportArchitecture() { + HexagonalArchitecture.boundedContext("com.kustacks.kuring.report") + + .withDomainLayer("domain") + + .withAdaptersLayer("adapter") + .incoming("in.web") + .outgoing("out.persistence") + .and() + + .withApplicationLayer("application") + .services("service") + .incomingPorts("port.in") + .outgoingPorts("port.out") + .and() + + .withConfiguration("configuration") + .check(new ClassFileImporter() + .importPackages("com.kustacks.kuring.report..")); + } @DisplayName("Calendar 아키텍처 검증") @Test @@ -173,16 +173,38 @@ void validateCalendarArchitecture() { .importPackages("com.kustacks.kuring.calendar..")); } - @DisplayName("테스트 페키지 의존성 검증") - @Test - void testPackageDependencies() { - noClasses() - .that() - .resideInAPackage("com.kustacks.kuring.user.domain..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("com.kustacks.kuring.user.application..") - .check(new ClassFileImporter() - .importPackages("com.kustacks.kuring.user..")); - } + @DisplayName("Club Domain 의존성 검증") + @Test + void validateClubDomainDependencies() { + HexagonalArchitecture.boundedContext("com.kustacks.kuring.club") + + .withDomainLayer("domain") + + .withAdaptersLayer("adapter") + .outgoing("out.persistence") + .and() + + .withApplicationLayer("application") + .services("service") + .incomingPorts("port.in") + .outgoingPorts("port.out") + .and() + + .withConfiguration("configuration") + .check(new ClassFileImporter() + .importPackages("com.kustacks.kuring.club..")); + } + + @DisplayName("테스트 페키지 의존성 검증") + @Test + void testPackageDependencies() { + noClasses() + .that() + .resideInAPackage("com.kustacks.kuring.user.domain..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("com.kustacks.kuring.user.application..") + .check(new ClassFileImporter() + .importPackages("com.kustacks.kuring.user..")); + } } diff --git a/src/test/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapterTest.java b/src/test/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapterTest.java new file mode 100644 index 000000000..074d63c0f --- /dev/null +++ b/src/test/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapterTest.java @@ -0,0 +1,54 @@ +package com.kustacks.kuring.club.adapter.out.persistence; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@DisplayName("ClubPersistenceAdapter") +class ClubPersistenceAdapterTest { + + @InjectMocks + private ClubPersistenceAdapter adapter; + + @Mock + private ClubRepository clubRepository; + + @Mock + private ClubSubscribeRepository clubSubscribeRepository; + + + @DisplayName("내일 마감 동아리 조회는 [내일 00:00, 내일모레 00:00) 범위를 사용한다") + @Test + void find_tomorrow_recruit_end_clubs_with_expected_window() { + //given + LocalDateTime now = LocalDateTime.of(2026, 2, 19, 18, 0, 0); + when(clubRepository.findClubsBetweenDates(any(LocalDateTime.class), any(LocalDateTime.class))).thenReturn(List.of()); + + //when + adapter.findNextDayRecruitEndClubs(now); + + //then + ArgumentCaptor startCaptor = ArgumentCaptor.forClass(LocalDateTime.class); + ArgumentCaptor endCaptor = ArgumentCaptor.forClass(LocalDateTime.class); + verify(clubRepository).findClubsBetweenDates(startCaptor.capture(), endCaptor.capture()); + + assertAll( + () -> assertThat(startCaptor.getValue()).isEqualTo(LocalDateTime.of(2026, 2, 20, 0, 0, 0)), + () -> assertThat(endCaptor.getValue()).isEqualTo(LocalDateTime.of(2026, 2, 21, 0, 0, 0)) + ); + } +} diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubCommandServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubCommandServiceTest.java new file mode 100644 index 000000000..13ea7a931 --- /dev/null +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubCommandServiceTest.java @@ -0,0 +1,192 @@ +package com.kustacks.kuring.club.application.service; + +import com.kustacks.kuring.club.application.port.in.dto.ClubSubscriptionCommand; +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.domain.Club; +import com.kustacks.kuring.common.exception.InvalidStateException; +import com.kustacks.kuring.common.exception.code.ErrorCode; +import com.kustacks.kuring.common.properties.ServerProperties; +import com.kustacks.kuring.user.application.port.out.RootUserQueryPort; +import com.kustacks.kuring.user.application.port.out.UserEventPort; +import com.kustacks.kuring.user.application.port.out.UserQueryPort; +import com.kustacks.kuring.user.domain.RootUser; +import com.kustacks.kuring.user.domain.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@DisplayName("ClubSubscriptionCommandService") +class ClubCommandServiceTest { + + @InjectMocks + private ClubCommandService service; + + @Mock + private ClubQueryPort clubQueryPort; + + @Mock + private ServerProperties serverProperties; + + @Mock + private ClubSubscriptionCommandPort clubSubscriptionCommandPort; + + @Mock + private ClubSubscriptionQueryPort clubSubscriptionQueryPort; + + @Mock + private RootUserQueryPort rootUserQueryPort; + + @Mock + private UserQueryPort userQueryPort; + + @Mock + private UserEventPort userEventPort; + + private RootUser rootUser; + + private Club club; + + @BeforeEach + void setUp() { + rootUser = rootUser(); + club = club(); + } + + @DisplayName("구독 추가 성공") + @Test + void add_subscription_success() { + //given + User user1 = new User("token-1"); + User user2 = new User("token-2"); + + when(club.getId()).thenReturn(1L); + when(serverProperties.ifDevThenAddSuffix(anyString())).thenReturn("club.1"); + when(rootUserQueryPort.findRootUserByEmail("client@konkuk.ac.kr")) + .thenReturn(Optional.of(rootUser)); + when(clubQueryPort.findClubById(1L)).thenReturn(Optional.of(club)); + when(userQueryPort.findByLoggedInUserId(1L)).thenReturn(List.of(user1, user2)); + when(clubSubscriptionQueryPort.countSubscriptions(1L)).thenReturn(1L); + + //when + long count = service.addSubscription(new ClubSubscriptionCommand("client@konkuk.ac.kr", 1L)); + + //then + assertAll( + () -> assertThat(count).isEqualTo(1L), + () -> verify(userEventPort).subscribeEvent("token-1", "club.1"), + () -> verify(userEventPort).subscribeEvent("token-2", "club.1") + ); + } + + @DisplayName("이미 구독된 동아리는 추가할 수 없다") + @Test + void add_subscription_fail_when_already_subscribed() { + //given + when(club.getId()).thenReturn(1L); + when(rootUserQueryPort.findRootUserByEmail("client@konkuk.ac.kr")) + .thenReturn(Optional.of(rootUser)); + when(clubQueryPort.findClubById(1L)).thenReturn(Optional.of(club)); + when(clubSubscriptionQueryPort.existsSubscription(1L, 1L)).thenReturn(Boolean.TRUE); + + //when & then + assertAll( + () -> assertThatThrownBy(() -> service.addSubscription(new ClubSubscriptionCommand("client@konkuk.ac.kr", 1L))) + .isInstanceOf(InvalidStateException.class) + .extracting(ex -> ((InvalidStateException) ex).getErrorCode()) + .isEqualTo(ErrorCode.CLUB_ALREADY_SUBSCRIBED), + () -> verify(userEventPort, never()).subscribeEvent(anyString(), anyString()) + ); + } + + @DisplayName("구독 제거 성공") + @Test + void remove_subscription_success() { + //given + User user1 = new User("token-1"); + + when(club.getId()).thenReturn(1L); + when(serverProperties.ifDevThenAddSuffix(anyString())).thenReturn("club.1"); + when(rootUserQueryPort.findRootUserByEmail("client@konkuk.ac.kr")) + .thenReturn(Optional.of(rootUser)); + when(clubQueryPort.findClubById(1L)).thenReturn(Optional.of(club)); + when(clubSubscriptionQueryPort.existsSubscription(1L, club.getId())).thenReturn(Boolean.TRUE); + when(userQueryPort.findByLoggedInUserId(1L)).thenReturn(List.of(user1)); + + //when + long count = service.removeSubscription(new ClubSubscriptionCommand("client@konkuk.ac.kr", 1L)); + + //then + assertAll( + () -> assertThat(count).isEqualTo(0L), + () -> verify(userEventPort).unsubscribeEvent("token-1", "club.1") + ); + } + + @DisplayName("구독하지 않은 동아리는 제거할 수 없다") + @Test + void remove_subscription_fail_when_not_subscribed() { + //given + when(club.getId()).thenReturn(1L); + when(rootUserQueryPort.findRootUserByEmail("client@konkuk.ac.kr")) + .thenReturn(Optional.of(rootUser)); + when(clubQueryPort.findClubById(1L)).thenReturn(Optional.of(club)); + when(clubSubscriptionQueryPort.existsSubscription(1L, club.getId())).thenReturn(Boolean.FALSE); + + //when & then + assertAll( + () -> assertThatThrownBy(() -> service.removeSubscription(new ClubSubscriptionCommand("client@konkuk.ac.kr", 1L))) + .isInstanceOf(InvalidStateException.class) + .extracting(ex -> ((InvalidStateException) ex).getErrorCode()) + .isEqualTo(ErrorCode.CLUB_NOT_SUBSCRIBED), + () -> verify(userEventPort, never()).unsubscribeEvent(anyString(), anyString()) + ); + } + + @DisplayName("존재하지 않는 동아리는 구독할 수 없다") + @Test + void add_subscription_fail_when_club_not_found() { + //given + when(rootUserQueryPort.findRootUserByEmail("client@konkuk.ac.kr")) + .thenReturn(Optional.of(rootUser)); + when(clubQueryPort.findClubById(1L)).thenReturn(Optional.empty()); + + //when & then + assertAll( + () -> assertThatThrownBy(() -> service.addSubscription(new ClubSubscriptionCommand("client@konkuk.ac.kr", 1L))) + .isInstanceOf(InvalidStateException.class) + .extracting(ex -> ((InvalidStateException) ex).getErrorCode()) + .isEqualTo(ErrorCode.CLUB_NOT_FOUND), + () -> verify(userEventPort, never()).subscribeEvent(anyString(), anyString()) + ); + } + + private RootUser rootUser() { + RootUser rootUser = new RootUser("client@konkuk.ac.kr", "password", "nickname"); + ReflectionTestUtils.setField(rootUser, "id", 1L); + return rootUser; + } + + private Club club() { + return mock(Club.class); + } +} diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubNotificationServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubNotificationServiceTest.java new file mode 100644 index 000000000..2dc0b23ab --- /dev/null +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubNotificationServiceTest.java @@ -0,0 +1,105 @@ +package com.kustacks.kuring.club.application.service; + +import com.google.firebase.messaging.Message; +import com.kustacks.kuring.club.application.port.out.ClubQueryPort; +import com.kustacks.kuring.club.domain.Club; +import com.kustacks.kuring.common.properties.ServerProperties; +import com.kustacks.kuring.message.application.port.out.FirebaseMessagingPort; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@DisplayName("ClubNotificationService") +class ClubNotificationServiceTest { + + @InjectMocks + private ClubNotificationService service; + + @Mock + private ClubQueryPort clubQueryPort; + + @Mock + private FirebaseMessagingPort firebaseMessagingPort; + + @Mock + private ServerProperties serverProperties; + + private Club club1; + + private Club club2; + + @BeforeEach + void setUp() { + club1 = club(1L, "쿠링"); + club2 = club(2L, "리드미"); + + when(serverProperties.ifDevThenAddSuffix("club.1")).thenReturn("club.1.dev"); + when(serverProperties.ifDevThenAddSuffix("club.2")).thenReturn("club.2.dev"); + when(clubQueryPort.findNextDayRecruitEndClubs(any(LocalDateTime.class))) + .thenReturn(List.of(club1, club2)); + } + + @DisplayName("내일 마감 대상 목록을 모두 발송한다") + @Test + void send_deadline_notifications_only_for_targets() throws Exception { + //when + service.sendDeadlineNotifications(); + + //then + ArgumentCaptor captor = ArgumentCaptor.forClass(Message.class); + verify(firebaseMessagingPort, times(2)).send(captor.capture()); + + Message sent = captor.getAllValues().get(0); // 1번 ID에 대한 Club 메시지 + String topic = (String) ReflectionTestUtils.getField(sent, "topic"); + + Map data = (Map) ReflectionTestUtils.getField(sent, "data"); + assertAll( + () -> assertThat(topic).isEqualTo("club.1.dev"), + () -> assertThat(data).containsEntry("clubId", "1"), + () -> assertThat(data).containsEntry("messageType", "club") + ); + } + + @DisplayName("발송 실패가 발생해도 다른 대상은 계속 발송") + @Test + void should_continue_even_if_one_send_fails() throws Exception { + //given + doThrow(new RuntimeException("send fail")) + .doNothing() + .when(firebaseMessagingPort) + .send(any(Message.class)); + + //when + service.sendDeadlineNotifications(); + + //then + verify(firebaseMessagingPort, times(2)).send(any(Message.class)); + } + + private Club club(Long id, String name) { + Club club = mock(Club.class); + when(club.getId()).thenReturn(id); + when(club.getName()).thenReturn(name); + return club; + } +} diff --git a/src/test/java/com/kustacks/kuring/support/DatabaseConfigurator.java b/src/test/java/com/kustacks/kuring/support/DatabaseConfigurator.java index 9872aa84d..fb34e8972 100644 --- a/src/test/java/com/kustacks/kuring/support/DatabaseConfigurator.java +++ b/src/test/java/com/kustacks/kuring/support/DatabaseConfigurator.java @@ -116,6 +116,7 @@ public void loadData() { log.info("[DatabaseConfigurator] init start"); initAdmin(); + initClub(); initUser(); initRootUser(); initUserCategory(); @@ -130,6 +131,17 @@ public void loadData() { log.info("[DatabaseConfigurator] init complete"); } + private void initClub() { + jdbcTemplate.update( + "INSERT INTO club (name, summary, category, division, is_always) VALUES (?, ?, ?, ?, ?)", + "테스트동아리1", + "테스트 요약", + "ACADEMIC", + "CENTRAL", + false + ); + } + private void setCharsetAllTable() { for (String tableName : tableNames) { jdbcTemplate.execute("ALTER TABLE " + tableName + " CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); diff --git a/src/test/java/com/kustacks/kuring/worker/notification/ClubNotificationSchedulerTest.java b/src/test/java/com/kustacks/kuring/worker/notification/ClubNotificationSchedulerTest.java new file mode 100644 index 000000000..2fb5b544a --- /dev/null +++ b/src/test/java/com/kustacks/kuring/worker/notification/ClubNotificationSchedulerTest.java @@ -0,0 +1,46 @@ +package com.kustacks.kuring.worker.notification; + +import com.kustacks.kuring.club.application.port.in.ClubNotificationUseCase; +import com.kustacks.kuring.common.featureflag.KuringFeatures; +import com.kustacks.kuring.support.IntegrationTestSupport; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.SpyBean; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@DisplayName("통합 테스트 : ClubNotificationScheduler") +class ClubNotificationSchedulerTest extends IntegrationTestSupport { + + @Autowired + private ClubNotificationScheduler scheduler; + + @SpyBean + private ClubNotificationUseCase clubNotificationUseCase; + + @DisplayName("Feature Flag가 OFF이면 동아리 알림을 발송하지 않는다") + @Test + void should_not_send_notification_when_feature_flag_disabled() { + featureFlagsSupport.setMapProperty(KuringFeatures.NOTIFY_CLUB_DEADLINE.getFeature().value(), false); + + assertThatCode(() -> scheduler.sendClubDeadlineNotifications()) + .doesNotThrowAnyException(); + + verify(clubNotificationUseCase, never()).sendDeadlineNotifications(); + } + + @DisplayName("Feature Flag가 ON이면 동아리 알림 발송 유즈케이스를 호출한다") + @Test + void should_call_use_case_when_feature_flag_enabled() { + featureFlagsSupport.setMapProperty(KuringFeatures.NOTIFY_CLUB_DEADLINE.getFeature().value(), true); + + assertThatCode(() -> scheduler.sendClubDeadlineNotifications()) + .doesNotThrowAnyException(); + + verify(clubNotificationUseCase, times(1)).sendDeadlineNotifications(); + } +} From 34a716bf35def971251dbd3666d26b54a03d8563 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sat, 28 Feb 2026 11:17:09 +0900 Subject: [PATCH 24/34] =?UTF-8?q?[refactor]=20=EB=8F=99=EC=95=84=EB=A6=AC?= =?UTF-8?q?=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20=EC=BB=A4=EC=84=9C?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=95=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/adapter/in/web/ClubQueryApiV2.java | 10 +- .../adapter/in/web/dto/ClubItemResponse.java | 33 ----- .../adapter/in/web/dto/ClubListResponse.java | 17 +-- .../persistence/ClubPersistenceAdapter.java | 16 +-- .../out/persistence/ClubQueryRepository.java | 2 +- .../persistence/ClubQueryRepositoryImpl.java | 117 +----------------- .../port/in/dto/ClubListCommand.java | 5 - .../port/in/dto/ClubListResult.java | 5 +- .../application/port/out/ClubQueryPort.java | 5 +- .../application/service/ClubQueryService.java | 64 ++-------- .../service/ClubQueryServiceTest.java | 35 +----- 11 files changed, 25 insertions(+), 284 deletions(-) delete mode 100644 src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubItemResponse.java diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java index cf30104b6..a121f54b3 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java @@ -13,15 +13,12 @@ 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.data.Cursor; 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 jakarta.validation.constraints.Max; -import jakarta.validation.constraints.Min; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; @@ -64,20 +61,17 @@ public ResponseEntity> getSupportedClubDi return ResponseEntity.ok().body(new BaseResponse<>(CLUB_DIVISION_SEARCH_SUCCESS, response)); } - @Operation(summary = "동아리 목록 조회", description = "필터 조건에 맞는 동아리 목록을 커서 페이징으로 조회합니다") + @Operation(summary = "동아리 목록 조회", description = "필터 조건에 맞는 동아리 목록을 조회합니다") @SecurityRequirement(name = JWT_TOKEN_HEADER_KEY) @GetMapping public ResponseEntity> getClubs( @RequestParam(required = false) String category, @RequestParam(required = false) String division, - @RequestParam(required = false) String cursor, - @RequestParam(defaultValue = "20") @Min(1) @Max(30) int size, - @RequestParam(defaultValue = "name") String sortBy, @RequestHeader(value = AuthorizationExtractor.AUTHORIZATION, required = false) String bearerToken ) { String email = resolveLoginEmail(bearerToken); - ClubListCommand command = new ClubListCommand(category, division, Cursor.from(cursor), size, sortBy, email); + ClubListCommand command = new ClubListCommand(category, division, email); ClubListResult result = clubQueryUseCase.getClubs(command); diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubItemResponse.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubItemResponse.java deleted file mode 100644 index 0a4a907d0..000000000 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubItemResponse.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.kustacks.kuring.club.adapter.in.web.dto; - -import com.kustacks.kuring.club.application.port.in.dto.ClubItemResult; - -import java.time.LocalDateTime; - -public record ClubItemResponse( - Long id, - String name, - String summary, - String iconImageUrl, - String category, - String division, - boolean isSubscribed, - int subscriberCount, - LocalDateTime recruitStartDate, - LocalDateTime recruitEndDate -) { - public static ClubItemResponse from(ClubItemResult result) { - return new ClubItemResponse( - result.id(), - result.name(), - result.summary(), - result.iconImageUrl(), - result.category(), - result.division(), - result.isSubscribed(), - result.subscriberCount(), - result.recruitStartDate(), - result.recruitEndDate() - ); - } -} diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubListResponse.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubListResponse.java index 6aafa950e..220a0e42f 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubListResponse.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubListResponse.java @@ -1,26 +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 clubs, - String cursor, - boolean hasNext, - int totalCount + List clubs ) { public static ClubListResponse from(ClubListResult result) { - List clubs = result.clubs().stream() - .map(ClubItemResponse::from) - .toList(); - - return new ClubListResponse( - clubs, - result.cursor(), - result.hasNext(), - result.totalCount() - ); + return new ClubListResponse(result.clubs()); } } diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java index d8c58a65b..8c33a1750 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java @@ -8,7 +8,6 @@ import com.kustacks.kuring.club.domain.Club; import com.kustacks.kuring.club.domain.ClubSubscribe; import com.kustacks.kuring.common.annotation.PersistenceAdapter; -import com.kustacks.kuring.common.data.Cursor; import com.kustacks.kuring.user.domain.RootUser; import lombok.RequiredArgsConstructor; @@ -29,20 +28,9 @@ public class ClubPersistenceAdapter implements ClubQueryPort, ClubSubscriptionCo @Override public List searchClubs( String category, - List divisions, - Cursor cursor, - int size, - String sortBy, - LocalDateTime now + List divisions ) { - return clubRepository.searchClubs( - category, - divisions, - cursor == null ? null : cursor.getStringCursor(), - size, - sortBy, - now - ); + return clubRepository.searchClubs(category, divisions); } @Override diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java index 896137e04..874856c80 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java @@ -10,7 +10,7 @@ public interface ClubQueryRepository { - List searchClubs(String category, List divisions, String cursor, int size, String sortBy, LocalDateTime now); + List searchClubs(String category, List divisions); int countClubsByCategoryAndDivisions(String category, List divisions); diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java index 07b372223..d68d27175 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java @@ -9,10 +9,7 @@ import com.kustacks.kuring.club.domain.ClubRecruitmentStatus; import com.kustacks.kuring.club.domain.ClubSnsType; import com.querydsl.core.Tuple; -import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.core.types.dsl.CaseBuilder; -import com.querydsl.core.types.dsl.NumberExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; @@ -53,13 +50,8 @@ private BooleanExpression recruitEndAtLt(LocalDateTime end) { @Transactional(readOnly = true) public List searchClubs( String category, - List divisions, - String cursor, - int size, - String sortBy, - LocalDateTime now + List divisions ) { - return queryFactory .select(new QClubReadModel( club.id, @@ -74,11 +66,8 @@ public List searchClubs( .from(club) .where( categoryEq(category), - divisionIn(divisions), - cursorCondition(sortBy, cursor, now) + divisionIn(divisions) ) - .orderBy(getOrderSpecifiers(sortBy, now)) - .limit(size) .fetch(); } @@ -203,108 +192,6 @@ private BooleanExpression divisionIn(List divisions) { ); } - //서비스로 - private BooleanExpression cursorCondition(String sortBy, String cursor, LocalDateTime now) { - - if (cursor == null || cursor.equals("0")) return null; - - try { - // 언더바로 - String[] parts = cursor.split("\\|"); - - return switch (sortBy) { - - case "name" -> { - if (parts.length < 2) yield null; - - String lastName = parts[0]; - Long lastId = Long.parseLong(parts[1]); - - yield club.name.gt(lastName) - .or( - club.name.eq(lastName) - .and(club.id.gt(lastId)) - ); - } - - case "recruitEndDate" -> { - if (parts.length < 3) yield null; - //스트링 - int lastGroup = Integer.parseInt(parts[0]); - String lastDateStr = parts[1]; - Long lastId = Long.parseLong(parts[2]); - - NumberExpression currentGroup = recruitmentGroup(now); - - BooleanExpression groupCondition = currentGroup.gt(lastGroup); - - BooleanExpression sameGroupCondition; - - if ("null".equals(lastDateStr)) { - sameGroupCondition = currentGroup.eq(lastGroup) - .and(club.id.gt(lastId)); - } else { - LocalDateTime lastDate = LocalDateTime.parse(lastDateStr); - sameGroupCondition = currentGroup.eq(lastGroup) - .and( - club.recruitEndAt.gt(lastDate) - .or( - club.recruitEndAt.eq(lastDate) - .and(club.id.gt(lastId)) - ) - ); - } - yield groupCondition.or(sameGroupCondition); - - } - - default -> { - Long lastId = Long.parseLong(cursor); - yield club.id.gt(lastId); - } - }; - - } catch (Exception e) { - return null; - } - } - - private OrderSpecifier[] getOrderSpecifiers(String sortBy, LocalDateTime now) { - - return switch (sortBy) { - - case "name" -> new OrderSpecifier[]{ - club.name.asc(), - club.id.asc() - }; - - case "recruitEndDate" -> { - - var statusOrder = new CaseBuilder() - .when(club.recruitEndAt.isNull()).then(2) - .when(club.recruitEndAt.lt(now)).then(1) - .otherwise(0); - - yield new OrderSpecifier[]{ - statusOrder.asc(), - club.recruitEndAt.asc().nullsLast(), - club.id.asc() - }; - } - - default -> new OrderSpecifier[]{ - club.id.asc() - }; - }; - } - - private NumberExpression recruitmentGroup(LocalDateTime now) { - return new CaseBuilder() - .when(club.recruitEndAt.isNull()).then(2) - .when(club.recruitEndAt.lt(now)).then(1) - .otherwise(0); - } - // 얘도 서비스쪽에서 해야될듯 private ClubRecruitmentStatus calculateRecruitmentStatus( LocalDateTime start, diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListCommand.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListCommand.java index da93febc5..69a75f63b 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListCommand.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListCommand.java @@ -1,16 +1,11 @@ package com.kustacks.kuring.club.application.port.in.dto; -import com.kustacks.kuring.common.data.Cursor; - import java.util.Arrays; import java.util.List; public record ClubListCommand( String category, String division, - Cursor cursor, - int size, - String sortBy, String email ) { public List divisionList() { diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListResult.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListResult.java index a10860e82..ab73fb9d4 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListResult.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubListResult.java @@ -3,9 +3,6 @@ import java.util.List; public record ClubListResult( - List clubs, - String cursor, - boolean hasNext, - int totalCount + List clubs ) { } diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java index f063357e0..ddcc4ac8f 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java @@ -3,7 +3,6 @@ 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.common.data.Cursor; import java.time.LocalDateTime; import java.util.List; @@ -16,9 +15,7 @@ public interface ClubQueryPort { Optional findClubDetailById(Long id); - List searchClubs(String category, List divisions, Cursor cursor, int size, String sortBy, LocalDateTime now); - - int countClubsByCategoryAndDivisions(String category, List divisions); + List searchClubs(String category, List divisions); boolean existsSubscription(Long clubId, Long loginUserId); diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index e3d086bb2..ddc60433c 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -12,14 +12,12 @@ import com.kustacks.kuring.club.application.port.out.dto.ClubReadModel; import com.kustacks.kuring.club.domain.ClubDivision; import com.kustacks.kuring.common.annotation.UseCase; -import com.kustacks.kuring.common.data.CursorBasedList; import com.kustacks.kuring.common.exception.NotFoundException; import com.kustacks.kuring.user.application.port.out.RootUserQueryPort; import com.kustacks.kuring.user.domain.RootUser; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -45,28 +43,14 @@ public List getClubDivisions() { @Override public ClubListResult getClubs(ClubListCommand command) { - String email = command.email(); - Optional rootUser = rootUserQueryPort.findRootUserByEmail(email); - - int limit = Math.min(command.size(), 30); + Optional rootUser = rootUserQueryPort.findRootUserByEmail(command.email()); - LocalDateTime now = LocalDateTime.now(); - - CursorBasedList cursorBasedList = CursorBasedList.of( - limit, - club -> generateCursor(club, command.sortBy(), now), - searchSize -> clubQueryPort.searchClubs( - command.category(), - command.divisionList(), - command.cursor(), - searchSize, - command.sortBy(), - now - ) + List clubReadModels = clubQueryPort.searchClubs( + command.category(), + command.divisionList() ); - List clubIds = cursorBasedList.getContents() - .stream() + List clubIds = clubReadModels.stream() .map(ClubReadModel::getId) .toList(); @@ -82,10 +66,8 @@ public ClubListResult getClubs(ClubListCommand command) { Map subscribedMap = subscribedClubIds.stream() .collect(Collectors.toMap(id -> id, id -> true)); - //items -> clubItemResults로 이름 수정? - List items = - cursorBasedList.getContents() - .stream() + List clubItemResults = + clubReadModels.stream() .map(r -> new ClubItemResult( r.getId(), r.getName(), @@ -100,15 +82,7 @@ public ClubListResult getClubs(ClubListCommand command) { )) .toList(); - - int totalCount = clubQueryPort.countClubsByCategoryAndDivisions(command.category(), command.divisionList()); - - return new ClubListResult( - items, - cursorBasedList.getEndCursor(), - cursorBasedList.hasNext(), - totalCount - ); + return new ClubListResult(clubItemResults); } @Override @@ -161,26 +135,4 @@ public ClubDetailResult getClubDetail(ClubDetailCommand command) { ); } - private String generateCursor(ClubReadModel club, String sortBy, LocalDateTime now) { - return switch (sortBy) { - case "name" -> club.getName() + "|" + club.getId(); - case "recruitEndDate" -> { - int group; - if (club.getRecruitEndDate() == null) { - group = 2; - } else if (club.getRecruitEndDate().isBefore(now)) { - group = 1; - } else { - group = 0; - } - - String datePart = club.getRecruitEndDate() == null - ? "null" - : club.getRecruitEndDate().toString(); - - yield group + "|" + datePart + "|" + club.getId(); - } - default -> club.getId().toString(); - }; - } } diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java index 66f57cadc..073a6a663 100644 --- a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java @@ -11,7 +11,6 @@ import com.kustacks.kuring.club.domain.ClubCategory; import com.kustacks.kuring.club.domain.ClubDivision; import com.kustacks.kuring.club.domain.ClubRecruitmentStatus; -import com.kustacks.kuring.common.data.Cursor; import com.kustacks.kuring.user.application.port.out.RootUserQueryPort; import com.kustacks.kuring.user.domain.RootUser; import org.junit.jupiter.api.DisplayName; @@ -106,9 +105,6 @@ void getClubs_success() { // given String category = "academic"; String divisions = "central,engineering"; - Cursor cursor = Cursor.from(null); - int size = 10; - String sortBy = "name"; String email = "test@test.com"; Long loginUserId = 100L; @@ -118,22 +114,15 @@ void getClubs_success() { when(rootUserQueryPort.findRootUserByEmail(email)) .thenReturn(Optional.of(rootUser)); - ClubListCommand command = new ClubListCommand(category, divisions, cursor, size, sortBy, email); + ClubListCommand command = new ClubListCommand(category, divisions, email); List divisionList = List.of("central", "engineering"); when(clubQueryPort.searchClubs( eq(category), - eq(divisionList), - eq(cursor), - eq(size + 1), - eq(sortBy), - any(LocalDateTime.class) + eq(divisionList) )).thenReturn(mockReadModels); - when(clubQueryPort.countClubsByCategoryAndDivisions(category, divisionList)) - .thenReturn(2); - when(clubQueryPort.countSubscribersByClubIds(any())) .thenReturn(Map.of( 1L, 10, @@ -148,16 +137,12 @@ void getClubs_success() { ClubListResult result = clubQueryService.getClubs(command); // then - assertThat(result.totalCount()).isEqualTo(2); assertThat(result.clubs()).hasSize(3); - assertThat(result.hasNext()).isFalse(); - assertThat(result.cursor()).isNull(); assertThat(result.clubs().get(0).subscriberCount()).isEqualTo(10); assertThat(result.clubs().get(0).isSubscribed()).isTrue(); verify(rootUserQueryPort).findRootUserByEmail(email); - verify(clubQueryPort).searchClubs(eq(category), eq(divisionList), eq(cursor), eq(size + 1), eq(sortBy), any(LocalDateTime.class)); - verify(clubQueryPort).countClubsByCategoryAndDivisions(category, divisionList); + verify(clubQueryPort).searchClubs(eq(category), eq(divisionList)); } @Test @@ -166,27 +151,17 @@ void getClubs_withoutLogin() { //given String category = "academic"; String divisions = "central,engineering"; - Cursor cursor = Cursor.from(null); - int size = 10; - String sortBy = "name"; String email = null; - ClubListCommand command = new ClubListCommand(category, divisions, cursor, size, sortBy, email); + ClubListCommand command = new ClubListCommand(category, divisions, email); List divisionList = List.of("central", "engineering"); when(clubQueryPort.searchClubs( eq(category), - eq(divisionList), - eq(cursor), - eq(size + 1), - eq(sortBy), - any(LocalDateTime.class) + eq(divisionList) )).thenReturn(mockReadModels); - when(clubQueryPort.countClubsByCategoryAndDivisions(category, divisionList)) - .thenReturn(2); - when(clubQueryPort.countSubscribersByClubIds(any())) .thenReturn(Map.of( 1L, 5, From 0403b775f42e90cc78ed3ffe0fb85976f042579e Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sat, 28 Feb 2026 11:24:14 +0900 Subject: [PATCH 25/34] =?UTF-8?q?[refactor]=20ClubDivisionListResponse?= =?UTF-8?q?=EC=97=90=EC=84=9C=20Result=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=A7=81=EC=A0=91=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/adapter/in/web/ClubQueryApiV2.java | 9 +++------ .../in/web/dto/ClubDivisionListResponse.java | 7 ++++++- .../adapter/in/web/dto/ClubDivisionResponse.java | 15 --------------- .../out/persistence/ClubPersistenceAdapter.java | 5 ----- 4 files changed, 9 insertions(+), 27 deletions(-) delete mode 100644 src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionResponse.java diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java index a121f54b3..a5877e877 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/ClubQueryApiV2.java @@ -5,11 +5,11 @@ 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.ClubDivisionResponse; 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; @@ -51,12 +51,9 @@ public class ClubQueryApiV2 { @GetMapping("/divisions") public ResponseEntity> getSupportedClubDivisions() { - List divisions = clubQueryUseCase.getClubDivisions() - .stream() - .map(ClubDivisionResponse::from) - .toList(); + List results = clubQueryUseCase.getClubDivisions(); - ClubDivisionListResponse response = new ClubDivisionListResponse(divisions); + ClubDivisionListResponse response = ClubDivisionListResponse.from(results); return ResponseEntity.ok().body(new BaseResponse<>(CLUB_DIVISION_SEARCH_SUCCESS, response)); } diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionListResponse.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionListResponse.java index dc86c9f29..5b32c7c4e 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionListResponse.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionListResponse.java @@ -1,8 +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 divisions + List divisions ) { + public static ClubDivisionListResponse from(List results) { + return new ClubDivisionListResponse(results); + } } diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionResponse.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionResponse.java deleted file mode 100644 index fea4e2ca2..000000000 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDivisionResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.kustacks.kuring.club.adapter.in.web.dto; - -import com.kustacks.kuring.club.application.port.in.dto.ClubDivisionResult; - -public record ClubDivisionResponse( - String code, - String koreanName -) { - public static ClubDivisionResponse from(ClubDivisionResult result) { - return new ClubDivisionResponse( - result.code(), - result.koreanName() - ); - } -} diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java index 8c33a1750..92ec3ba36 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java @@ -33,11 +33,6 @@ public List searchClubs( return clubRepository.searchClubs(category, divisions); } - @Override - public int countClubsByCategoryAndDivisions(String category, List divisions) { - return clubRepository.countClubsByCategoryAndDivisions(category, divisions); - } - @Override public Optional findClubDetailById(Long id) { return clubRepository.findClubDetailById(id); From 59782a7e3fb8ced6b68c1ba634e7e0cac3c663d8 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sat, 28 Feb 2026 11:33:10 +0900 Subject: [PATCH 26/34] =?UTF-8?q?[refactor]=20totalCount=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EA=B4=80=EB=A0=A8=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../out/persistence/ClubQueryRepository.java | 2 -- .../persistence/ClubQueryRepositoryImpl.java | 18 ------------------ 2 files changed, 20 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java index 874856c80..c9ff4ac60 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java @@ -12,8 +12,6 @@ public interface ClubQueryRepository { List searchClubs(String category, List divisions); - int countClubsByCategoryAndDivisions(String category, List divisions); - Optional findClubDetailById(Long id); List findClubsBetweenDates(LocalDateTime start, LocalDateTime end); diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java index d68d27175..1757ba059 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java @@ -71,24 +71,6 @@ public List searchClubs( .fetch(); } - @Override - @Transactional(readOnly = true) - public int countClubsByCategoryAndDivisions(String category, List divisions) { - - //jpa로 해도! - Long count = queryFactory - .select(club.count()) - .from(club) - .where( - categoryEq(category), - divisionIn(divisions) - ) - .fetchOne(); - - //count는 null일수 없음 - return count == null ? 0 : count.intValue(); - } - @Override @Transactional(readOnly = true) public Optional findClubDetailById(Long id) { From 31bc32e76cfa81d8c566a8aef8d28835915f05c0 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sat, 28 Feb 2026 11:50:07 +0900 Subject: [PATCH 27/34] =?UTF-8?q?[refactor]=20=EA=B5=AC=EB=8F=85=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=EC=9D=84=20RootUser=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=EC=9C=BC=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../out/persistence/ClubPersistenceAdapter.java | 9 ++------- .../out/persistence/ClubSubscribeRepository.java | 4 +--- .../club/application/port/out/ClubQueryPort.java | 4 ++-- .../club/application/service/ClubQueryService.java | 14 +++++++------- .../application/service/ClubQueryServiceTest.java | 12 ++++++------ 5 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java index 92ec3ba36..72fe0e4cc 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java @@ -38,11 +38,6 @@ public Optional findClubDetailById(Long id) { return clubRepository.findClubDetailById(id); } -// @Override -// public boolean existsSubscription(Long clubId, Long loginUserId) { -// return clubSubscribeRepository.existsByClubIdAndUser_LoginUserId(clubId, loginUserId); -// } - @Override public Optional findClubById(Long id) { return clubRepository.findById(id); @@ -51,10 +46,10 @@ public Optional findClubById(Long id) { @Override public List findSubscribedClubIds( List clubIds, - Long loginUserId + Long rootUserId ) { return clubSubscribeRepository - .findByClubIdInAndUser_LoginUserId(clubIds, loginUserId) + .findByClubIdInAndRootUserId(clubIds, rootUserId) .stream() .map(sub -> sub.getClub().getId()) .toList(); diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java index 32f24ef8a..92de90b97 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java @@ -17,9 +17,7 @@ interface ClubSubscribeRepository extends JpaRepository { void deleteByRootUserAndClub(RootUser rootUser, Club club); - boolean existsByClubIdAndUser_LoginUserId(Long clubId, Long loginUserId); - List findByClubIdIn(List clubIds); - List findByClubIdInAndUser_LoginUserId(List clubIds, Long loginUserId); + List findByClubIdInAndRootUserId(List clubIds, Long rootUserId); } \ No newline at end of file diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java index ddcc4ac8f..690f4c83d 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java @@ -17,9 +17,9 @@ public interface ClubQueryPort { List searchClubs(String category, List divisions); - boolean existsSubscription(Long clubId, Long loginUserId); + boolean existsSubscription(Long rootUserId, Long clubId); - List findSubscribedClubIds(List clubIds, Long loginUserId); + List findSubscribedClubIds(List clubIds, Long rootUserId); int countSubscribers(Long clubId); diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index ddc60433c..abc5ab981 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -59,8 +59,8 @@ public ClubListResult getClubs(ClubListCommand command) { // 구독 관련 메서드화 하면 좋을듯! List subscribedClubIds = List.of(); if (rootUser.isPresent()) { - Long loginUserId = rootUser.get().getId(); - subscribedClubIds = clubQueryPort.findSubscribedClubIds(clubIds, loginUserId); + Long rootUserId = rootUser.get().getId(); + subscribedClubIds = clubQueryPort.findSubscribedClubIds(clubIds, rootUserId); } Map subscribedMap = subscribedClubIds.stream() @@ -87,21 +87,21 @@ public ClubListResult getClubs(ClubListCommand command) { @Override public ClubDetailResult getClubDetail(ClubDetailCommand command) { - Long id = command.clubId(); + Long clubId = command.clubId(); String email = command.email(); Optional rootUser = rootUserQueryPort.findRootUserByEmail(email); // dto -> readmodel로 이름 수정 - ClubDetailDto dto = clubQueryPort.findClubDetailById(id) + ClubDetailDto dto = clubQueryPort.findClubDetailById(clubId) .orElseThrow(() -> new NotFoundException(CLUB_NOT_FOUND)); - int subscriberCount = clubQueryPort.countSubscribers(id); + int subscriberCount = clubQueryPort.countSubscribers(clubId); boolean isSubscribed = false; if (rootUser.isPresent()) { - Long loginUserId = rootUser.get().getId(); - isSubscribed = clubQueryPort.existsSubscription(id, loginUserId); + Long rootUserId = rootUser.get().getId(); + isSubscribed = clubQueryPort.existsSubscription(rootUserId, clubId); } ClubDetailResult.Location location = dto.hasLocation() ? diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java index 073a6a663..f9446276f 100644 --- a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java @@ -107,10 +107,10 @@ void getClubs_success() { String divisions = "central,engineering"; String email = "test@test.com"; - Long loginUserId = 100L; + Long rootUserId = 100L; RootUser rootUser = mock(RootUser.class); - when(rootUser.getId()).thenReturn(loginUserId); + when(rootUser.getId()).thenReturn(rootUserId); when(rootUserQueryPort.findRootUserByEmail(email)) .thenReturn(Optional.of(rootUser)); @@ -184,12 +184,12 @@ void getClubDetail_success() { Long clubId = 1L; String email = "test@test.com"; - Long loginUserId = 100L; + Long rootUserId = 100L; ClubDetailCommand command = new ClubDetailCommand(clubId, email); RootUser rootUser = mock(RootUser.class); - when(rootUser.getId()).thenReturn(loginUserId); + when(rootUser.getId()).thenReturn(rootUserId); when(rootUserQueryPort.findRootUserByEmail(email)) .thenReturn(Optional.of(rootUser)); @@ -221,7 +221,7 @@ void getClubDetail_success() { when(clubQueryPort.countSubscribers(clubId)) .thenReturn(10); - when(clubQueryPort.existsSubscription(clubId, loginUserId)) + when(clubQueryPort.existsSubscription(rootUserId, clubId)) .thenReturn(true); // when @@ -243,7 +243,7 @@ void getClubDetail_success() { verify(rootUserQueryPort).findRootUserByEmail(email); verify(clubQueryPort).findClubDetailById(clubId); verify(clubQueryPort).countSubscribers(clubId); - verify(clubQueryPort).existsSubscription(clubId, loginUserId); + verify(clubQueryPort).existsSubscription(rootUserId, clubId); } @Test From 2df4cc340c1abf005190a7b5b70b1066ca937fdd Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sat, 28 Feb 2026 12:06:55 +0900 Subject: [PATCH 28/34] =?UTF-8?q?[refactor]=20ClubQueryService=20=EA=B5=AC?= =?UTF-8?q?=EB=8F=85=20=EB=A1=9C=EC=A7=81=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EC=B6=9C=20=EB=B0=8F=20DTO=20from=20=ED=8C=A8?= =?UTF-8?q?=ED=84=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../port/in/dto/ClubDetailResult.java | 41 +++++++++++ .../port/in/dto/ClubItemResult.java | 20 ++++++ .../application/service/ClubQueryService.java | 71 ++++++------------- 3 files changed, 84 insertions(+), 48 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java index 5ecba2b5a..344966ac6 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java @@ -1,5 +1,6 @@ package com.kustacks.kuring.club.application.port.in.dto; +import com.kustacks.kuring.club.application.port.out.dto.ClubDetailDto; import com.kustacks.kuring.club.domain.ClubCategory; import com.kustacks.kuring.club.domain.ClubDivision; import com.kustacks.kuring.club.domain.ClubRecruitmentStatus; @@ -26,11 +27,51 @@ public record ClubDetailResult( String posterImageUrl, Location location ) { + + public static ClubDetailResult from( + ClubDetailDto clubDetailDto, + int subscriberCount, + boolean isSubscribed + ) { + return new ClubDetailResult( + clubDetailDto.getId(), + clubDetailDto.getName(), + clubDetailDto.getSummary(), + clubDetailDto.getCategory(), + clubDetailDto.getDivision(), + subscriberCount, + isSubscribed, + clubDetailDto.getInstagramUrl(), + clubDetailDto.getYoutubeUrl(), + clubDetailDto.getEtcUrl(), + clubDetailDto.getDescription(), + clubDetailDto.getQualifications(), + clubDetailDto.getRecruitmentStatus(), + clubDetailDto.getRecruitStartAt(), + clubDetailDto.getRecruitEndAt(), + clubDetailDto.getApplyUrl(), + clubDetailDto.getPosterImagePath(), + Location.from(clubDetailDto) + ); + } + public record Location( String building, String room, Double lon, Double lat ) { + public static Location from(ClubDetailDto clubDetailDto) { + if (!clubDetailDto.hasLocation()) { + return null; + } + + return new Location( + clubDetailDto.getBuilding(), + clubDetailDto.getRoom(), + clubDetailDto.getLon(), + clubDetailDto.getLat() + ); + } } } diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubItemResult.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubItemResult.java index 85132225e..85ddc2638 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubItemResult.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubItemResult.java @@ -1,5 +1,7 @@ package com.kustacks.kuring.club.application.port.in.dto; +import com.kustacks.kuring.club.application.port.out.dto.ClubReadModel; + import java.time.LocalDateTime; public record ClubItemResult( @@ -14,4 +16,22 @@ public record ClubItemResult( LocalDateTime recruitStartDate, LocalDateTime recruitEndDate ) { + public static ClubItemResult from( + ClubReadModel clubReadModel, + boolean isSubscribed, + int subscriberCount + ) { + return new ClubItemResult( + clubReadModel.getId(), + clubReadModel.getName(), + clubReadModel.getSummary(), + clubReadModel.getIconImageUrl(), + clubReadModel.getCategory().getName(), + clubReadModel.getDivision().getName(), + isSubscribed, + subscriberCount, + clubReadModel.getRecruitStartDate(), + clubReadModel.getRecruitEndDate() + ); + } } diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index abc5ab981..2c3359d21 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -56,29 +56,14 @@ public ClubListResult getClubs(ClubListCommand command) { Map subscriberCountMap = clubQueryPort.countSubscribersByClubIds(clubIds); - // 구독 관련 메서드화 하면 좋을듯! - List subscribedClubIds = List.of(); - if (rootUser.isPresent()) { - Long rootUserId = rootUser.get().getId(); - subscribedClubIds = clubQueryPort.findSubscribedClubIds(clubIds, rootUserId); - } - - Map subscribedMap = subscribedClubIds.stream() - .collect(Collectors.toMap(id -> id, id -> true)); + Map subscribedMap = getSubscribedMap(clubIds, rootUser); List clubItemResults = clubReadModels.stream() - .map(r -> new ClubItemResult( - r.getId(), - r.getName(), - r.getSummary(), - r.getIconImageUrl(), - r.getCategory().getName(), - r.getDivision().getName(), + .map(r -> ClubItemResult.from( + r, subscribedMap.getOrDefault(r.getId(), false), - subscriberCountMap.getOrDefault(r.getId(), 0), - r.getRecruitStartDate(), - r.getRecruitEndDate() + subscriberCountMap.getOrDefault(r.getId(), 0) )) .toList(); @@ -92,8 +77,7 @@ public ClubDetailResult getClubDetail(ClubDetailCommand command) { Optional rootUser = rootUserQueryPort.findRootUserByEmail(email); - // dto -> readmodel로 이름 수정 - ClubDetailDto dto = clubQueryPort.findClubDetailById(clubId) + ClubDetailDto clubDetailDto = clubQueryPort.findClubDetailById(clubId) .orElseThrow(() -> new NotFoundException(CLUB_NOT_FOUND)); int subscriberCount = clubQueryPort.countSubscribers(clubId); @@ -104,35 +88,26 @@ public ClubDetailResult getClubDetail(ClubDetailCommand command) { isSubscribed = clubQueryPort.existsSubscription(rootUserId, clubId); } - ClubDetailResult.Location location = dto.hasLocation() ? - new ClubDetailResult.Location( - dto.getBuilding(), - dto.getRoom(), - dto.getLon(), - dto.getLat() - ) - : null; - - return new ClubDetailResult( - dto.getId(), - dto.getName(), - dto.getSummary(), - dto.getCategory(), - dto.getDivision(), + return ClubDetailResult.from( + clubDetailDto, subscriberCount, - isSubscribed, - dto.getInstagramUrl(), - dto.getYoutubeUrl(), - dto.getEtcUrl(), - dto.getDescription(), - dto.getQualifications(), - dto.getRecruitmentStatus(), - dto.getRecruitStartAt(), - dto.getRecruitEndAt(), - dto.getApplyUrl(), - dto.getPosterImagePath(), - location + isSubscribed ); } + private Map getSubscribedMap(List clubIds, Optional rootUser) { + + if (rootUser.isEmpty()) { + return Map.of(); + } + + Long rootUserId = rootUser.get().getId(); + + List subscribedClubIds = + clubQueryPort.findSubscribedClubIds(clubIds, rootUserId); + + return subscribedClubIds.stream() + .collect(Collectors.toMap(id -> id, id -> true)); + } + } From 6a7d5f8f1fec416c2c27697dc166e71a9539911c Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sat, 28 Feb 2026 12:51:21 +0900 Subject: [PATCH 29/34] =?UTF-8?q?[refactor]=20=EB=AA=A8=EC=A7=91=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EA=B3=84=EC=82=B0=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=84=20Domain=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= =?UTF-8?q?=ED=95=98=EA=B3=A0=20Repository=20=EC=B1=85=EC=9E=84=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/ClubQueryRepositoryImpl.java | 35 +------------------ .../port/in/dto/ClubDetailResult.java | 5 +-- .../port/out/dto/ClubDetailDto.java | 7 ++-- .../application/service/ClubQueryService.java | 13 ++++++- .../club/domain/ClubRecruitmentStatus.java | 23 ++++++++++++ .../service/ClubQueryServiceTest.java | 22 ++++++------ 6 files changed, 53 insertions(+), 52 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java index 1757ba059..b628fd4d0 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java @@ -6,7 +6,6 @@ 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.ClubRecruitmentStatus; import com.kustacks.kuring.club.domain.ClubSnsType; import com.querydsl.core.Tuple; import com.querydsl.core.types.dsl.BooleanExpression; @@ -75,8 +74,6 @@ public List searchClubs( @Transactional(readOnly = true) public Optional findClubDetailById(Long id) { - LocalDateTime now = LocalDateTime.now(); - List tuples = queryFactory .select( club.id, @@ -126,13 +123,6 @@ public Optional findClubDetailById(Long id) { } } - ClubRecruitmentStatus recruitmentStatus = calculateRecruitmentStatus( - first.get(club.recruitStartAt), - first.get(club.recruitEndAt), - first.get(club.isAlways), - now - ); - return Optional.of( new ClubDetailDto( first.get(club.id), @@ -145,9 +135,9 @@ public Optional findClubDetailById(Long id) { etc, first.get(club.description), first.get(club.qualifications), - recruitmentStatus, first.get(club.recruitStartAt), first.get(club.recruitEndAt), + first.get(club.isAlways), first.get(club.applyUrl), first.get(club.posterImagePath), first.get(club.building), @@ -158,7 +148,6 @@ public Optional findClubDetailById(Long id) { ); } - private BooleanExpression categoryEq(String category) { if (category == null) return null; return club.category.eq(ClubCategory.fromName(category)); @@ -174,26 +163,4 @@ private BooleanExpression divisionIn(List divisions) { ); } - // 얘도 서비스쪽에서 해야될듯 - private ClubRecruitmentStatus calculateRecruitmentStatus( - LocalDateTime start, - LocalDateTime end, - Boolean isAlways, - LocalDateTime now - ) { - - if (Boolean.TRUE.equals(isAlways)) { - return ClubRecruitmentStatus.ALWAYS; - } - - if (start != null && now.isBefore(start)) { - return ClubRecruitmentStatus.BEFORE; - } - - if (end != null && now.isAfter(end)) { - return ClubRecruitmentStatus.CLOSED; - } - - return ClubRecruitmentStatus.RECRUITING; - } } diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java index 344966ac6..17e320ec2 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java @@ -31,7 +31,8 @@ public record ClubDetailResult( public static ClubDetailResult from( ClubDetailDto clubDetailDto, int subscriberCount, - boolean isSubscribed + boolean isSubscribed, + ClubRecruitmentStatus recruitmentStatus ) { return new ClubDetailResult( clubDetailDto.getId(), @@ -46,7 +47,7 @@ public static ClubDetailResult from( clubDetailDto.getEtcUrl(), clubDetailDto.getDescription(), clubDetailDto.getQualifications(), - clubDetailDto.getRecruitmentStatus(), + recruitmentStatus, clubDetailDto.getRecruitStartAt(), clubDetailDto.getRecruitEndAt(), clubDetailDto.getApplyUrl(), diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubDetailDto.java b/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubDetailDto.java index d60898ba6..c96b41bc0 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubDetailDto.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/dto/ClubDetailDto.java @@ -2,7 +2,6 @@ import com.kustacks.kuring.club.domain.ClubCategory; import com.kustacks.kuring.club.domain.ClubDivision; -import com.kustacks.kuring.club.domain.ClubRecruitmentStatus; import com.querydsl.core.annotations.QueryProjection; import lombok.AccessLevel; import lombok.Getter; @@ -26,10 +25,10 @@ public class ClubDetailDto { private String description; private String qualifications; - private ClubRecruitmentStatus recruitmentStatus; private LocalDateTime recruitStartAt; private LocalDateTime recruitEndAt; + Boolean isAlways; private String applyUrl; private String posterImagePath; @@ -51,9 +50,9 @@ public ClubDetailDto( String etcUrl, String description, String qualifications, - ClubRecruitmentStatus recruitmentStatus, LocalDateTime recruitStartAt, LocalDateTime recruitEndAt, + Boolean isAlways, String applyUrl, String posterImagePath, String building, @@ -71,9 +70,9 @@ public ClubDetailDto( this.etcUrl = etcUrl; this.description = description; this.qualifications = qualifications; - this.recruitmentStatus = recruitmentStatus; this.recruitStartAt = recruitStartAt; this.recruitEndAt = recruitEndAt; + this.isAlways = isAlways; this.applyUrl = applyUrl; this.posterImagePath = posterImagePath; this.building = building; diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index 2c3359d21..9b2fb8f16 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -11,6 +11,7 @@ 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.ClubDivision; +import com.kustacks.kuring.club.domain.ClubRecruitmentStatus; import com.kustacks.kuring.common.annotation.UseCase; import com.kustacks.kuring.common.exception.NotFoundException; import com.kustacks.kuring.user.application.port.out.RootUserQueryPort; @@ -18,6 +19,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -88,10 +90,19 @@ public ClubDetailResult getClubDetail(ClubDetailCommand command) { isSubscribed = clubQueryPort.existsSubscription(rootUserId, clubId); } + ClubRecruitmentStatus recruitmentStatus = + ClubRecruitmentStatus.from( + clubDetailDto.getRecruitStartAt(), + clubDetailDto.getRecruitEndAt(), + clubDetailDto.getIsAlways(), + LocalDateTime.now() + ); + return ClubDetailResult.from( clubDetailDto, subscriberCount, - isSubscribed + isSubscribed, + recruitmentStatus ); } diff --git a/src/main/java/com/kustacks/kuring/club/domain/ClubRecruitmentStatus.java b/src/main/java/com/kustacks/kuring/club/domain/ClubRecruitmentStatus.java index a38e88f3e..68940f2cb 100644 --- a/src/main/java/com/kustacks/kuring/club/domain/ClubRecruitmentStatus.java +++ b/src/main/java/com/kustacks/kuring/club/domain/ClubRecruitmentStatus.java @@ -2,6 +2,8 @@ import lombok.Getter; +import java.time.LocalDateTime; + @Getter public enum ClubRecruitmentStatus { @@ -15,4 +17,25 @@ public enum ClubRecruitmentStatus { ClubRecruitmentStatus(String value) { this.value = value; } + + public static ClubRecruitmentStatus from( + LocalDateTime start, + LocalDateTime end, + Boolean isAlways, + LocalDateTime now + ) { + if (Boolean.TRUE.equals(isAlways)) { + return ALWAYS; + } + + if (start != null && now.isBefore(start)) { + return BEFORE; + } + + if (end != null && now.isAfter(end)) { + return CLOSED; + } + + return RECRUITING; + } } \ No newline at end of file diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java index f9446276f..1191c7d1b 100644 --- a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java @@ -57,8 +57,8 @@ class ClubQueryServiceTest { "icon-url-1", ClubCategory.ACADEMIC, ClubDivision.CENTRAL, - LocalDateTime.of(2025, 3, 1, 0, 0), - LocalDateTime.of(2025, 3, 31, 23, 59) + LocalDateTime.of(2026, 3, 1, 0, 0), + LocalDateTime.of(2026, 3, 31, 23, 59) ), new ClubReadModel( 2L, @@ -67,8 +67,8 @@ class ClubQueryServiceTest { "icon-url-2", ClubCategory.ACADEMIC, ClubDivision.ENGINEERING, - LocalDateTime.of(2025, 3, 1, 0, 0), - LocalDateTime.of(2025, 3, 31, 23, 59) + LocalDateTime.of(2026, 3, 1, 0, 0), + LocalDateTime.of(2026, 3, 31, 23, 59) ), new ClubReadModel( 3L, @@ -77,8 +77,8 @@ class ClubQueryServiceTest { "icon-url-3", ClubCategory.CULTURE_ART, ClubDivision.ENGINEERING, - LocalDateTime.of(2025, 2, 20, 0, 0), - LocalDateTime.of(2025, 3, 31, 23, 59) + LocalDateTime.of(2026, 2, 10, 0, 0), + LocalDateTime.of(2026, 2, 25, 23, 59) ) ); @@ -204,9 +204,9 @@ void getClubDetail_success() { null, "상세 설명", "지원 자격", - ClubRecruitmentStatus.RECRUITING, - LocalDateTime.of(2025, 3, 1, 0, 0), - LocalDateTime.of(2025, 3, 31, 23, 59), + LocalDateTime.of(2099, 3, 1, 0, 0), + LocalDateTime.of(2099, 3, 31, 23, 59), + false, "apply-url", "poster-path", "공학관", @@ -234,7 +234,7 @@ void getClubDetail_success() { assertThat(result.isSubscribed()).isTrue(); assertThat(result.location().building()).isEqualTo("공학관"); assertThat(result.recruitmentStatus()) - .isEqualTo(ClubRecruitmentStatus.RECRUITING); + .isEqualTo(ClubRecruitmentStatus.BEFORE); assertThat(result.category()) .isEqualTo(ClubCategory.ACADEMIC); assertThat(result.division()) @@ -260,8 +260,8 @@ void getClubDetail_withoutLogin() { ClubCategory.ACADEMIC, ClubDivision.CENTRAL, null, null, null, null, null, - ClubRecruitmentStatus.RECRUITING, null, null, + true, null, null, null, null, null, null From 28e7dd7dc49e55ed8e2ffd51e3d1ae12b933330b Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sat, 28 Feb 2026 15:54:55 +0900 Subject: [PATCH 30/34] =?UTF-8?q?[refactor]=20Club=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=8B=9C=20Enum=20=EB=B3=80=ED=99=98=20=EC=B1=85=EC=9E=84?= =?UTF-8?q?=EC=9D=84=20Service=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/ClubPersistenceAdapter.java | 6 ++++-- .../out/persistence/ClubQueryRepository.java | 4 +++- .../persistence/ClubQueryRepositoryImpl.java | 21 +++++++------------ .../application/port/out/ClubQueryPort.java | 4 +++- .../application/service/ClubQueryService.java | 15 +++++++++++-- .../service/ClubQueryServiceTest.java | 13 +++++++----- 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java index 72fe0e4cc..abd03c2da 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java @@ -6,6 +6,8 @@ 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; @@ -27,8 +29,8 @@ public class ClubPersistenceAdapter implements ClubQueryPort, ClubSubscriptionCo @Override public List searchClubs( - String category, - List divisions + ClubCategory category, + List divisions ) { return clubRepository.searchClubs(category, divisions); } diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java index c9ff4ac60..7cecaaa04 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepository.java @@ -3,6 +3,8 @@ 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; @@ -10,7 +12,7 @@ public interface ClubQueryRepository { - List searchClubs(String category, List divisions); + List searchClubs(ClubCategory category, List divisions); Optional findClubDetailById(Long id); diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java index b628fd4d0..03229032d 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubQueryRepositoryImpl.java @@ -48,8 +48,8 @@ private BooleanExpression recruitEndAtLt(LocalDateTime end) { @Override @Transactional(readOnly = true) public List searchClubs( - String category, - List divisions + ClubCategory category, + List divisions ) { return queryFactory .select(new QClubReadModel( @@ -148,19 +148,14 @@ public Optional findClubDetailById(Long id) { ); } - private BooleanExpression categoryEq(String category) { - if (category == null) return null; - return club.category.eq(ClubCategory.fromName(category)); + private BooleanExpression categoryEq(ClubCategory category) { + return category != null ? club.category.eq(category) : null; } - private BooleanExpression divisionIn(List divisions) { - if (divisions == null || divisions.isEmpty()) return null; - - return club.division.in( - divisions.stream() - .map(ClubDivision::fromName) - .toList() - ); + private BooleanExpression divisionIn(List divisions) { + return divisions != null && !divisions.isEmpty() + ? club.division.in(divisions) + : null; } } diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java index 690f4c83d..5ab75b95c 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java @@ -3,6 +3,8 @@ 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; @@ -15,7 +17,7 @@ public interface ClubQueryPort { Optional findClubDetailById(Long id); - List searchClubs(String category, List divisions); + List searchClubs(ClubCategory category, List divisions); boolean existsSubscription(Long rootUserId, Long clubId); diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index 9b2fb8f16..2e96faf7b 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -10,6 +10,7 @@ import com.kustacks.kuring.club.application.port.out.ClubQueryPort; 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.ClubCategory; import com.kustacks.kuring.club.domain.ClubDivision; import com.kustacks.kuring.club.domain.ClubRecruitmentStatus; import com.kustacks.kuring.common.annotation.UseCase; @@ -47,9 +48,19 @@ public List getClubDivisions() { public ClubListResult getClubs(ClubListCommand command) { Optional rootUser = rootUserQueryPort.findRootUserByEmail(command.email()); + ClubCategory category = command.category() != null + ? ClubCategory.fromName(command.category()) + : null; + + List divisions = command.divisionList() != null + ? command.divisionList().stream() + .map(ClubDivision::fromName) + .toList() + : null; + List clubReadModels = clubQueryPort.searchClubs( - command.category(), - command.divisionList() + category, + divisions ); List clubIds = clubReadModels.stream() diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java index 1191c7d1b..be9a2dca9 100644 --- a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java @@ -116,10 +116,10 @@ void getClubs_success() { ClubListCommand command = new ClubListCommand(category, divisions, email); - List divisionList = List.of("central", "engineering"); + List divisionList = List.of(ClubDivision.CENTRAL, ClubDivision.ENGINEERING); when(clubQueryPort.searchClubs( - eq(category), + eq(ClubCategory.ACADEMIC), eq(divisionList) )).thenReturn(mockReadModels); @@ -142,7 +142,10 @@ void getClubs_success() { assertThat(result.clubs().get(0).isSubscribed()).isTrue(); verify(rootUserQueryPort).findRootUserByEmail(email); - verify(clubQueryPort).searchClubs(eq(category), eq(divisionList)); + verify(clubQueryPort).searchClubs( + eq(ClubCategory.ACADEMIC), + eq(divisionList) + ); } @Test @@ -155,10 +158,10 @@ void getClubs_withoutLogin() { ClubListCommand command = new ClubListCommand(category, divisions, email); - List divisionList = List.of("central", "engineering"); + List divisionList = List.of(ClubDivision.CENTRAL, ClubDivision.ENGINEERING); when(clubQueryPort.searchClubs( - eq(category), + eq(ClubCategory.ACADEMIC), eq(divisionList) )).thenReturn(mockReadModels); From 0d1c6c23fc8b8d9ce8fdd0c95fcef2d1ead7e839 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sat, 28 Feb 2026 16:42:09 +0900 Subject: [PATCH 31/34] =?UTF-8?q?[refactor]=20=EA=B5=AC=EB=8F=85=EC=9E=90?= =?UTF-8?q?=20=EC=88=98=20Long=20=ED=83=80=EC=9E=85=20=ED=86=B5=EC=9D=BC?= =?UTF-8?q?=20=EB=B0=8F=20=EA=B5=AC=EB=8F=85=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20ClubSubscriptionPort?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/web/dto/ClubDetailResponse.java | 2 +- .../persistence/ClubPersistenceAdapter.java | 18 ++++---- .../persistence/ClubSubscribeRepository.java | 13 ++++-- .../port/in/dto/ClubDetailResult.java | 4 +- .../port/in/dto/ClubItemResult.java | 4 +- .../application/port/out/ClubQueryPort.java | 9 ---- .../port/out/ClubSubscriptionQueryPort.java | 12 +++++- .../application/service/ClubQueryService.java | 13 +++--- .../service/ClubQueryServiceTest.java | 42 ++++++++++--------- 9 files changed, 63 insertions(+), 54 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java index 2de6aa35a..6df7591ca 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/in/web/dto/ClubDetailResponse.java @@ -10,7 +10,7 @@ public record ClubDetailResponse( String summary, String category, String division, - int subscriberCount, + Long subscriberCount, boolean isSubscribed, String instagramUrl, String youtubeUrl, diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java index abd03c2da..7a0132ad2 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapter.java @@ -59,27 +59,23 @@ public List findSubscribedClubIds( @Override - public int countSubscribers(Long clubId) { + public Long countSubscribers(Long clubId) { return clubSubscribeRepository.countByClubId(clubId); } @Override - public Map countSubscribersByClubIds(List clubIds) { + public Map countSubscribersByClubIds(List clubIds) { if (clubIds == null || clubIds.isEmpty()) { return Map.of(); } - List subscriptions = clubSubscribeRepository.findByClubIdIn(clubIds); + List subscriptions = clubSubscribeRepository.countSubscribersByClubIds(clubIds); - // groupby로 해도 될듯 return subscriptions.stream() - .collect(Collectors.groupingBy( - sub -> sub.getClub().getId(), - Collectors.collectingAndThen( - Collectors.counting(), - Long::intValue - ) + .collect(Collectors.toMap( + row -> (Long) row[0], + row -> (Long) row[1] )); } @@ -113,7 +109,7 @@ public void deleteSubscription(RootUser rootUser, Club club) { } @Override - public long countSubscriptions(Long rootUserId) { + public Long countSubscriptions(Long rootUserId) { return clubSubscribeRepository.countByRootUserId(rootUserId); } } diff --git a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java index 92de90b97..55758031a 100644 --- a/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java +++ b/src/main/java/com/kustacks/kuring/club/adapter/out/persistence/ClubSubscribeRepository.java @@ -4,20 +4,27 @@ import com.kustacks.kuring.club.domain.ClubSubscribe; import com.kustacks.kuring.user.domain.RootUser; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import java.util.List; interface ClubSubscribeRepository extends JpaRepository { - int countByClubId(Long clubId); + Long countByClubId(Long clubId); - long countByRootUserId(Long rootUserId); + Long countByRootUserId(Long rootUserId); boolean existsByRootUserIdAndClubId(Long rootUserId, Long clubId); void deleteByRootUserAndClub(RootUser rootUser, Club club); - List findByClubIdIn(List clubIds); + @Query(""" + SELECT cs.club.id, COUNT(cs) + FROM ClubSubscribe cs + WHERE cs.club.id IN :clubIds + GROUP BY cs.club.id + """) + List countSubscribersByClubIds(List clubIds); List findByClubIdInAndRootUserId(List clubIds, Long rootUserId); } \ No newline at end of file diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java index 17e320ec2..86db5893d 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java @@ -13,7 +13,7 @@ public record ClubDetailResult( String summary, ClubCategory category, ClubDivision division, - int subscriberCount, + Long subscriberCount, boolean isSubscribed, String instagramUrl, String youtubeUrl, @@ -30,7 +30,7 @@ public record ClubDetailResult( public static ClubDetailResult from( ClubDetailDto clubDetailDto, - int subscriberCount, + Long subscriberCount, boolean isSubscribed, ClubRecruitmentStatus recruitmentStatus ) { diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubItemResult.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubItemResult.java index 85ddc2638..e1a5a6ec5 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubItemResult.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubItemResult.java @@ -12,14 +12,14 @@ public record ClubItemResult( String category, String division, boolean isSubscribed, - int subscriberCount, + Long subscriberCount, LocalDateTime recruitStartDate, LocalDateTime recruitEndDate ) { public static ClubItemResult from( ClubReadModel clubReadModel, boolean isSubscribed, - int subscriberCount + Long subscriberCount ) { return new ClubItemResult( clubReadModel.getId(), diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java index 5ab75b95c..acc71572a 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubQueryPort.java @@ -8,7 +8,6 @@ import java.time.LocalDateTime; import java.util.List; -import java.util.Map; import java.util.Optional; public interface ClubQueryPort { @@ -19,14 +18,6 @@ public interface ClubQueryPort { List searchClubs(ClubCategory category, List divisions); - boolean existsSubscription(Long rootUserId, Long clubId); - - List findSubscribedClubIds(List clubIds, Long rootUserId); - - int countSubscribers(Long clubId); - - Map countSubscribersByClubIds(List clubIds); - List findClubsBetweenDates(LocalDateTime start, LocalDateTime end); List findNextDayRecruitEndClubs(LocalDateTime now); diff --git a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubSubscriptionQueryPort.java b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubSubscriptionQueryPort.java index d912a4783..7d4cdf919 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/out/ClubSubscriptionQueryPort.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/out/ClubSubscriptionQueryPort.java @@ -1,8 +1,18 @@ package com.kustacks.kuring.club.application.port.out; +import java.util.List; +import java.util.Map; + public interface ClubSubscriptionQueryPort { boolean existsSubscription(Long rootUserId, Long clubId); - long countSubscriptions(Long rootUserId); + Long countSubscriptions(Long rootUserId); + + List findSubscribedClubIds(List clubIds, Long rootUserId); + + Long countSubscribers(Long clubId); + + Map countSubscribersByClubIds(List clubIds); + } diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index 2e96faf7b..5dd2e27a0 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -8,6 +8,7 @@ import com.kustacks.kuring.club.application.port.in.dto.ClubListCommand; import com.kustacks.kuring.club.application.port.in.dto.ClubListResult; import com.kustacks.kuring.club.application.port.out.ClubQueryPort; +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.ClubCategory; @@ -35,6 +36,7 @@ public class ClubQueryService implements ClubQueryUseCase { private final ClubQueryPort clubQueryPort; + private final ClubSubscriptionQueryPort clubSubscriptionQueryPort; private final RootUserQueryPort rootUserQueryPort; @Override @@ -67,7 +69,7 @@ public ClubListResult getClubs(ClubListCommand command) { .map(ClubReadModel::getId) .toList(); - Map subscriberCountMap = clubQueryPort.countSubscribersByClubIds(clubIds); + Map subscriberCountMap = clubSubscriptionQueryPort.countSubscribersByClubIds(clubIds); Map subscribedMap = getSubscribedMap(clubIds, rootUser); @@ -76,7 +78,7 @@ public ClubListResult getClubs(ClubListCommand command) { .map(r -> ClubItemResult.from( r, subscribedMap.getOrDefault(r.getId(), false), - subscriberCountMap.getOrDefault(r.getId(), 0) + subscriberCountMap.getOrDefault(r.getId(), 0L) )) .toList(); @@ -93,12 +95,12 @@ public ClubDetailResult getClubDetail(ClubDetailCommand command) { ClubDetailDto clubDetailDto = clubQueryPort.findClubDetailById(clubId) .orElseThrow(() -> new NotFoundException(CLUB_NOT_FOUND)); - int subscriberCount = clubQueryPort.countSubscribers(clubId); + Long subscriberCount = clubSubscriptionQueryPort.countSubscribers(clubId); boolean isSubscribed = false; if (rootUser.isPresent()) { Long rootUserId = rootUser.get().getId(); - isSubscribed = clubQueryPort.existsSubscription(rootUserId, clubId); + isSubscribed = clubSubscriptionQueryPort.existsSubscription(rootUserId, clubId); } ClubRecruitmentStatus recruitmentStatus = @@ -125,8 +127,7 @@ private Map getSubscribedMap(List clubIds, Optional subscribedClubIds = - clubQueryPort.findSubscribedClubIds(clubIds, rootUserId); + List subscribedClubIds = clubSubscriptionQueryPort.findSubscribedClubIds(clubIds, rootUserId); return subscribedClubIds.stream() .collect(Collectors.toMap(id -> id, id -> true)); diff --git a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java index be9a2dca9..5cd551902 100644 --- a/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java +++ b/src/test/java/com/kustacks/kuring/club/application/service/ClubQueryServiceTest.java @@ -6,6 +6,7 @@ import com.kustacks.kuring.club.application.port.in.dto.ClubListCommand; import com.kustacks.kuring.club.application.port.in.dto.ClubListResult; import com.kustacks.kuring.club.application.port.out.ClubQueryPort; +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.ClubCategory; @@ -42,6 +43,9 @@ class ClubQueryServiceTest { @Mock private ClubQueryPort clubQueryPort; + @Mock + private ClubSubscriptionQueryPort clubSubscriptionQueryPort; + @Mock private RootUserQueryPort rootUserQueryPort; @@ -123,14 +127,14 @@ void getClubs_success() { eq(divisionList) )).thenReturn(mockReadModels); - when(clubQueryPort.countSubscribersByClubIds(any())) + when(clubSubscriptionQueryPort.countSubscribersByClubIds(any())) .thenReturn(Map.of( - 1L, 10, - 2L, 10, - 3L, 10 + 1L, 10L, + 2L, 10L, + 3L, 10L )); - when(clubQueryPort.findSubscribedClubIds(any(), anyLong())) + when(clubSubscriptionQueryPort.findSubscribedClubIds(any(), anyLong())) .thenReturn(List.of(1L, 2L, 3L)); // when @@ -165,11 +169,11 @@ void getClubs_withoutLogin() { eq(divisionList) )).thenReturn(mockReadModels); - when(clubQueryPort.countSubscribersByClubIds(any())) + when(clubSubscriptionQueryPort.countSubscribersByClubIds(any())) .thenReturn(Map.of( - 1L, 5, - 2L, 5, - 3L, 5 + 1L, 5L, + 2L, 5L, + 3L, 5L )); //when @@ -177,7 +181,7 @@ void getClubs_withoutLogin() { //then assertThat(result.clubs().get(0).isSubscribed()).isFalse(); - verify(clubQueryPort, never()).findSubscribedClubIds(any(), anyLong()); + verify(clubSubscriptionQueryPort, never()).findSubscribedClubIds(any(), anyLong()); } @Test @@ -221,10 +225,10 @@ void getClubDetail_success() { when(clubQueryPort.findClubDetailById(clubId)) .thenReturn(Optional.of(dto)); - when(clubQueryPort.countSubscribers(clubId)) - .thenReturn(10); + when(clubSubscriptionQueryPort.countSubscribers(clubId)) + .thenReturn(10L); - when(clubQueryPort.existsSubscription(rootUserId, clubId)) + when(clubSubscriptionQueryPort.existsSubscription(rootUserId, clubId)) .thenReturn(true); // when @@ -245,8 +249,8 @@ void getClubDetail_success() { verify(rootUserQueryPort).findRootUserByEmail(email); verify(clubQueryPort).findClubDetailById(clubId); - verify(clubQueryPort).countSubscribers(clubId); - verify(clubQueryPort).existsSubscription(rootUserId, clubId); + verify(clubSubscriptionQueryPort).countSubscribers(clubId); + verify(clubSubscriptionQueryPort).existsSubscription(rootUserId, clubId); } @Test @@ -273,16 +277,16 @@ void getClubDetail_withoutLogin() { when(clubQueryPort.findClubDetailById(clubId)) .thenReturn(Optional.of(dto)); - when(clubQueryPort.countSubscribers(clubId)) - .thenReturn(5); + when(clubSubscriptionQueryPort.countSubscribers(clubId)) + .thenReturn(5L); // when ClubDetailResult result = clubQueryService.getClubDetail(command); // then assertThat(result.isSubscribed()).isFalse(); - verify(clubQueryPort).countSubscribers(clubId); - verify(clubQueryPort, never()).existsSubscription(anyLong(), any()); + verify(clubSubscriptionQueryPort).countSubscribers(clubId); + verify(clubSubscriptionQueryPort, never()).existsSubscription(anyLong(), any()); assertThat(result.subscriberCount()).isEqualTo(5); } } From b5506023a9bc6b20f1ee42665063811f98ae9e31 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sat, 28 Feb 2026 16:55:29 +0900 Subject: [PATCH 32/34] =?UTF-8?q?[test]=20ClubPersistenceAdapter=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ClubPersistenceAdapterTest.java | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/test/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapterTest.java b/src/test/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapterTest.java index 074d63c0f..5ef087e32 100644 --- a/src/test/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapterTest.java +++ b/src/test/java/com/kustacks/kuring/club/adapter/out/persistence/ClubPersistenceAdapterTest.java @@ -1,5 +1,7 @@ package com.kustacks.kuring.club.adapter.out.persistence; +import com.kustacks.kuring.club.domain.Club; +import com.kustacks.kuring.club.domain.ClubSubscribe; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -10,10 +12,13 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -51,4 +56,79 @@ void find_tomorrow_recruit_end_clubs_with_expected_window() { () -> assertThat(endCaptor.getValue()).isEqualTo(LocalDateTime.of(2026, 2, 21, 0, 0, 0)) ); } + + @Test + @DisplayName("countSubscribersByClubIds는 Object[]를 Map으로 변환한다") + void countSubscribersByClubIds_mapping_success() { + //given + List clubIds = List.of(1L, 2L); + + when(clubSubscribeRepository.countSubscribersByClubIds(clubIds)) + .thenReturn(List.of( + new Object[]{1L, 5L}, + new Object[]{2L, 3L} + )); + + //when + Map result = adapter.countSubscribersByClubIds(clubIds); + + //then + assertThat(result).hasSize(2); + assertThat(result.get(1L)).isEqualTo(5L); + assertThat(result.get(2L)).isEqualTo(3L); + + verify(clubSubscribeRepository).countSubscribersByClubIds(clubIds); + } + + @Test + @DisplayName("countSubscribersByClubIds는 null 또는 빈 리스트면 빈 Map을 반환한다") + void countSubscribersByClubIds_empty_input() { + + // given + List emptyList = List.of(); + + // when + Map result_null = adapter.countSubscribersByClubIds(null); + + Map result_empty = adapter.countSubscribersByClubIds(emptyList); + + // then + assertThat(result_null).isEmpty(); + assertThat(result_empty).isEmpty(); + + verify(clubSubscribeRepository, never()).countSubscribersByClubIds(any()); + } + + @Test + @DisplayName("findSubscribedClubIds는 구독된 clubId 목록을 반환한다") + void findSubscribedClubIds_success() { + // given + Long rootUserId = 100L; + List clubIds = List.of(1L, 2L); + + Club club1 = mock(Club.class); + Club club2 = mock(Club.class); + + when(club1.getId()).thenReturn(1L); + when(club2.getId()).thenReturn(2L); + + ClubSubscribe subscribe1 = mock(ClubSubscribe.class); + ClubSubscribe subscribe2 = mock(ClubSubscribe.class); + + when(subscribe1.getClub()).thenReturn(club1); + when(subscribe2.getClub()).thenReturn(club2); + + when(clubSubscribeRepository + .findByClubIdInAndRootUserId(clubIds, rootUserId)) + .thenReturn(List.of(subscribe1, subscribe2)); + + // when + List result = adapter.findSubscribedClubIds(clubIds, rootUserId); + + // then + assertThat(result).containsExactly(1L, 2L); + + verify(clubSubscribeRepository).findByClubIdInAndRootUserId(clubIds, rootUserId); + } + } From 8978c100c12eba1582ea8bea79e97ac7215b8857 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sun, 1 Mar 2026 12:37:08 +0900 Subject: [PATCH 33/34] =?UTF-8?q?[fix]=20=EC=A1=B4=EC=9E=AC=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EA=B5=AC=EB=8F=85=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=8C=93=EA=B0=92=20NOT=5FFOUND=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/kustacks/kuring/acceptance/UserAcceptanceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/kustacks/kuring/acceptance/UserAcceptanceTest.java b/src/test/java/com/kustacks/kuring/acceptance/UserAcceptanceTest.java index 7376a01c4..5e73179aa 100644 --- a/src/test/java/com/kustacks/kuring/acceptance/UserAcceptanceTest.java +++ b/src/test/java/com/kustacks/kuring/acceptance/UserAcceptanceTest.java @@ -520,7 +520,7 @@ void add_club_subscription_fail_when_club_not_found() { var response = 동아리_구독_추가_요청(USER_FCM_TOKEN, accessToken, 99999L); - 실패_응답_확인(response, HttpStatus.BAD_REQUEST); + 실패_응답_확인(response, HttpStatus.NOT_FOUND); } @DisplayName("[v2] 사용자는 동아리 구독을 취소할 수 있다") From 9cad76033077f95f55b3386ae009e713802abaa0 Mon Sep 17 00:00:00 2001 From: jiyun921 Date: Sun, 1 Mar 2026 14:51:55 +0900 Subject: [PATCH 34/34] =?UTF-8?q?[refactor]=20DTO=20from=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20=EB=B0=8F=20Service=EB=A1=9C=20=EB=B3=80=ED=99=98?= =?UTF-8?q?=20=EC=B1=85=EC=9E=84=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../port/in/dto/ClubDetailResult.java | 41 ------------- .../port/in/dto/ClubItemResult.java | 20 ------- .../application/service/ClubQueryService.java | 60 ++++++++++++++++++- 3 files changed, 58 insertions(+), 63 deletions(-) diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java index 86db5893d..2d0de2b72 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubDetailResult.java @@ -1,6 +1,5 @@ package com.kustacks.kuring.club.application.port.in.dto; -import com.kustacks.kuring.club.application.port.out.dto.ClubDetailDto; import com.kustacks.kuring.club.domain.ClubCategory; import com.kustacks.kuring.club.domain.ClubDivision; import com.kustacks.kuring.club.domain.ClubRecruitmentStatus; @@ -28,51 +27,11 @@ public record ClubDetailResult( Location location ) { - public static ClubDetailResult from( - ClubDetailDto clubDetailDto, - Long subscriberCount, - boolean isSubscribed, - ClubRecruitmentStatus recruitmentStatus - ) { - return new ClubDetailResult( - clubDetailDto.getId(), - clubDetailDto.getName(), - clubDetailDto.getSummary(), - clubDetailDto.getCategory(), - clubDetailDto.getDivision(), - subscriberCount, - isSubscribed, - clubDetailDto.getInstagramUrl(), - clubDetailDto.getYoutubeUrl(), - clubDetailDto.getEtcUrl(), - clubDetailDto.getDescription(), - clubDetailDto.getQualifications(), - recruitmentStatus, - clubDetailDto.getRecruitStartAt(), - clubDetailDto.getRecruitEndAt(), - clubDetailDto.getApplyUrl(), - clubDetailDto.getPosterImagePath(), - Location.from(clubDetailDto) - ); - } - public record Location( String building, String room, Double lon, Double lat ) { - public static Location from(ClubDetailDto clubDetailDto) { - if (!clubDetailDto.hasLocation()) { - return null; - } - - return new Location( - clubDetailDto.getBuilding(), - clubDetailDto.getRoom(), - clubDetailDto.getLon(), - clubDetailDto.getLat() - ); - } } } diff --git a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubItemResult.java b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubItemResult.java index e1a5a6ec5..6be3760a8 100644 --- a/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubItemResult.java +++ b/src/main/java/com/kustacks/kuring/club/application/port/in/dto/ClubItemResult.java @@ -1,7 +1,5 @@ package com.kustacks.kuring.club.application.port.in.dto; -import com.kustacks.kuring.club.application.port.out.dto.ClubReadModel; - import java.time.LocalDateTime; public record ClubItemResult( @@ -16,22 +14,4 @@ public record ClubItemResult( LocalDateTime recruitStartDate, LocalDateTime recruitEndDate ) { - public static ClubItemResult from( - ClubReadModel clubReadModel, - boolean isSubscribed, - Long subscriberCount - ) { - return new ClubItemResult( - clubReadModel.getId(), - clubReadModel.getName(), - clubReadModel.getSummary(), - clubReadModel.getIconImageUrl(), - clubReadModel.getCategory().getName(), - clubReadModel.getDivision().getName(), - isSubscribed, - subscriberCount, - clubReadModel.getRecruitStartDate(), - clubReadModel.getRecruitEndDate() - ); - } } diff --git a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java index 5dd2e27a0..1a3b985a9 100644 --- a/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java +++ b/src/main/java/com/kustacks/kuring/club/application/service/ClubQueryService.java @@ -75,7 +75,7 @@ public ClubListResult getClubs(ClubListCommand command) { List clubItemResults = clubReadModels.stream() - .map(r -> ClubItemResult.from( + .map(r -> convertClubItemResult( r, subscribedMap.getOrDefault(r.getId(), false), subscriberCountMap.getOrDefault(r.getId(), 0L) @@ -111,7 +111,7 @@ public ClubDetailResult getClubDetail(ClubDetailCommand command) { LocalDateTime.now() ); - return ClubDetailResult.from( + return convertClubDetailResult( clubDetailDto, subscriberCount, isSubscribed, @@ -133,4 +133,60 @@ private Map getSubscribedMap(List clubIds, Optional id, id -> true)); } + private ClubItemResult convertClubItemResult( + ClubReadModel clubReadModel, + boolean isSubscribed, + Long subscriberCount + ) { + return new ClubItemResult( + clubReadModel.getId(), + clubReadModel.getName(), + clubReadModel.getSummary(), + clubReadModel.getIconImageUrl(), + clubReadModel.getCategory().getName(), + clubReadModel.getDivision().getName(), + isSubscribed, + subscriberCount, + clubReadModel.getRecruitStartDate(), + clubReadModel.getRecruitEndDate() + ); + } + + private ClubDetailResult convertClubDetailResult( + ClubDetailDto clubDetailDto, + Long subscriberCount, + boolean isSubscribed, + ClubRecruitmentStatus recruitmentStatus + ) { + ClubDetailResult.Location location = null; + if (clubDetailDto.hasLocation()) { + location = new ClubDetailResult.Location( + clubDetailDto.getBuilding(), + clubDetailDto.getRoom(), + clubDetailDto.getLon(), + clubDetailDto.getLat() + ); + } + + return new ClubDetailResult( + clubDetailDto.getId(), + clubDetailDto.getName(), + clubDetailDto.getSummary(), + clubDetailDto.getCategory(), + clubDetailDto.getDivision(), + subscriberCount, + isSubscribed, + clubDetailDto.getInstagramUrl(), + clubDetailDto.getYoutubeUrl(), + clubDetailDto.getEtcUrl(), + clubDetailDto.getDescription(), + clubDetailDto.getQualifications(), + recruitmentStatus, + clubDetailDto.getRecruitStartAt(), + clubDetailDto.getRecruitEndAt(), + clubDetailDto.getApplyUrl(), + clubDetailDto.getPosterImagePath(), + location + ); + } }