From daea588b9d7b8c26495dbdc88b1e743bfe0bf4dd Mon Sep 17 00:00:00 2001 From: Wibaek Park <34394229+wibaek@users.noreply.github.com> Date: Sun, 27 Apr 2025 11:31:28 +0900 Subject: [PATCH 01/90] =?UTF-8?q?chore:=20Alloy=EB=A5=BC=20=EC=9D=B4?= =?UTF-8?q?=EC=9A=A9=ED=95=9C=20=EB=A1=9C=EA=B9=85=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#284)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: logback으로 파일 로깅 설정 * chore: alloy 추가 * fix: EOF 개행 * chore: alloy 설정 적용, env 적용 --- .gitignore | 1 + docker-compose.prod.yml | 13 ++++++++++ docker-compose.stage.yml | 13 ++++++++++ docs/config.alloy | 37 +++++++++++++++++++++++++++ src/main/resources/logback-spring.xml | 35 +++++++++++++++++++++++++ 5 files changed, 99 insertions(+) create mode 100644 docs/config.alloy create mode 100644 src/main/resources/logback-spring.xml diff --git a/.gitignore b/.gitignore index 9f59fa8d9..d5df4047a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ +logs/ ### STS ### .apt_generated diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 9517a07aa..30b0c9fc1 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -28,5 +28,18 @@ services: - SPRING_PROFILES_ACTIVE=prod - SPRING_DATA_REDIS_HOST=redis - SPRING_DATA_REDIS_PORT=6379 + volumes: + - ./logs:/var/log/spring depends_on: - redis + + alloy: + image: grafana/alloy:latest + container_name: alloy + ports: + - "12345:12345" + volumes: + - ./logs:/var/log/spring + - ./docs/config.alloy:/etc/alloy/config.alloy:ro + environment: + - ALLOY_ENV=production diff --git a/docker-compose.stage.yml b/docker-compose.stage.yml index 3a97a6411..c4ccd687f 100644 --- a/docker-compose.stage.yml +++ b/docker-compose.stage.yml @@ -28,6 +28,19 @@ services: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=stage + volumes: + - ./logs:/var/log/spring depends_on: - redis network_mode: host + + alloy: + image: grafana/alloy:latest + container_name: alloy + ports: + - "12345:12345" + volumes: + - ./logs:/var/log/spring + - ./docs/config.alloy:/etc/alloy/config.alloy:ro + environment: + - ALLOY_ENV=stage diff --git a/docs/config.alloy b/docs/config.alloy new file mode 100644 index 000000000..bd3aedf9a --- /dev/null +++ b/docs/config.alloy @@ -0,0 +1,37 @@ +livedebugging { + enabled = true +} + +logging { + level = "info" + format = "logfmt" +} + +local.file_match "spring_logs" { + path_targets = [{ __path__ = "/var/log/spring/*.log" }] // 서비스 로그 파일 경로 +} + +loki.source.file "spring_source" { + targets = local.file_match.spring_logs.targets // 위에서 정의한 로그 파일 경로 사용 + forward_to = [loki.process.spring_labels.receiver] // 읽은 로그를 처리 단계로 전달 +} + +loki.process "spring_labels" { + forward_to = [loki.write.grafana_loki.receiver] // 처리된 로그를 Loki로 전송 + + stage.static_labels { + values = { + service = "backend", + env = sys.env("ALLOY_ENV"), + } + } +} + +loki.write "grafana_loki" { + endpoint { + url = "http://monitor.solid-connection.com:3100/loki/api/v1/push" + tenant_id = "fake" // Loki 테넌트 ID (싱글 테넌시이기에 fake로 설정) + batch_wait = "1s" // 로그 배치 전송 대기 시간 + batch_size = "1MB" // 로그 배치 크기 + } +} diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 000000000..390c7c606 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,35 @@ + + + + + + + + + + /var/log/spring/solid-connection-server.log + + + + /var/log/spring/solid-connection-server.%d{yyyy-MM-dd}.log + 30 + + + + + timestamp=%d{yyyy-MM-dd'T'HH:mm:ss.SSS} level=%-5level thread=%thread logger=%logger{36} + message=%msg%n + + + + + + + + + + + + + + From 2ec3edf0c13f313cbd8cd216f7cf2d0e6e2b58db Mon Sep 17 00:00:00 2001 From: Wibaek Park <34394229+wibaek@users.noreply.github.com> Date: Wed, 30 Apr 2025 11:19:55 +0900 Subject: [PATCH 02/90] =?UTF-8?q?chore:=20dev->local,=20stage->dev=20?= =?UTF-8?q?=ED=99=98=EA=B2=BD=EB=AA=85=20=EB=B3=80=EA=B2=BD,=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD=20(#288)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * rename: 도커 파일명 변경 * rename: stage -> dev 환경명 변경 * chore: dev환경이 develop 브랜치를 추적하게 변경 --- .github/workflows/{stage-cd.yml => dev-cd.yml} | 6 +++--- docker-compose.stage.yml => docker-compose.dev.yml | 8 ++++---- src/main/resources/logback-spring.xml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) rename .github/workflows/{stage-cd.yml => dev-cd.yml} (95%) rename docker-compose.stage.yml => docker-compose.dev.yml (85%) diff --git a/.github/workflows/stage-cd.yml b/.github/workflows/dev-cd.yml similarity index 95% rename from .github/workflows/stage-cd.yml rename to .github/workflows/dev-cd.yml index 41ff68b37..793f3dea3 100644 --- a/.github/workflows/stage-cd.yml +++ b/.github/workflows/dev-cd.yml @@ -1,8 +1,8 @@ -name: "[STAGE] Build Gradle and Deploy" +name: "[DEV] Build Gradle and Deploy" on: push: - branches: [ "release" ] + branches: [ "develop" ] workflow_dispatch: jobs: @@ -59,7 +59,7 @@ jobs: host: ${{ secrets.STAGE_HOST }} username: ${{ secrets.STAGE_USERNAME }} key: ${{ secrets.STAGE_PRIVATE_KEY }} - source: "./docker-compose.stage.yml" + source: "./docker-compose.dev.yml" target: "/home/${{ secrets.STAGE_USERNAME }}/solid-connect-stage/" - name: Run docker compose diff --git a/docker-compose.stage.yml b/docker-compose.dev.yml similarity index 85% rename from docker-compose.stage.yml rename to docker-compose.dev.yml index c4ccd687f..a37ef4c55 100644 --- a/docker-compose.stage.yml +++ b/docker-compose.dev.yml @@ -19,15 +19,15 @@ services: - redis network_mode: host - solid-connection-stage: + solid-connection-dev: build: context: . dockerfile: Dockerfile - container_name: solid-connection-stage + container_name: solid-connection-dev ports: - "8080:8080" environment: - - SPRING_PROFILES_ACTIVE=stage + - SPRING_PROFILES_ACTIVE=dev volumes: - ./logs:/var/log/spring depends_on: @@ -43,4 +43,4 @@ services: - ./logs:/var/log/spring - ./docs/config.alloy:/etc/alloy/config.alloy:ro environment: - - ALLOY_ENV=stage + - ALLOY_ENV=dev diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index 390c7c606..e179be0fb 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -27,7 +27,7 @@ - + From ff0125d87f8fd2b82744549e94ef2034ac33b322 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Sat, 3 May 2025 18:01:40 +0900 Subject: [PATCH 03/90] =?UTF-8?q?fix:=20=ED=86=A0=ED=81=B0=20=EC=9E=AC?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#289)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 함수 분리 - subject의 기준이 SiteUser의 id가 아니게 바뀌더라도 변경 부분이 최소화되게 한다. - SiteUser의 subject를 사용하는 곳은 accessToken과 refreshToken를 관리하는 AuthTokenProvider 의 영역이므로 이 위치에 함수를 생성한다. * chore: 사용하지 않는 함수 삭제 * refactor: 가독성을 위해 함수 위치 변경 - parseSubject 가 parseSubjectIgnoringExpiration 보다 먼저 읽히도록 * refactor: 재발급 로직 수정 - subject가 아니라 엑세스 토큰으로 리프레시 토큰을 찾도록 * refactor: 리프레시 토큰 기간 연장 - 참고: discussion #292 * refactor: 액세스 토큰 재발급 로직 수정 * test: authServiceTest 작성 --- .../auth/controller/AuthController.java | 9 +- .../auth/domain/TokenType.java | 10 +- .../auth/dto/ReissueRequest.java | 8 ++ .../auth/service/AuthService.java | 19 ++- .../auth/service/AuthTokenProvider.java | 10 +- .../solidconnection/util/JwtUtils.java | 8 +- .../auth/service/AuthServiceTest.java | 108 ++++++++++++++++++ 7 files changed, 145 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/auth/dto/ReissueRequest.java create mode 100644 src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index 9c84e8d22..7fe543bd7 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -3,6 +3,7 @@ import com.example.solidconnection.auth.dto.EmailSignInRequest; import com.example.solidconnection.auth.dto.EmailSignUpTokenRequest; import com.example.solidconnection.auth.dto.EmailSignUpTokenResponse; +import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.auth.dto.SignUpRequest; @@ -114,13 +115,9 @@ public ResponseEntity quit( @PostMapping("/reissue") public ResponseEntity reissueToken( - Authentication authentication + ReissueRequest reissueRequest ) { - String token = authentication.getCredentials().toString(); - if (token == null) { - throw new CustomException(ErrorCode.AUTHENTICATION_FAILED, "토큰이 없습니다."); - } - ReissueResponse reissueResponse = authService.reissue(token); + ReissueResponse reissueResponse = authService.reissue(reissueRequest); return ResponseEntity.ok(reissueResponse); } } diff --git a/src/main/java/com/example/solidconnection/auth/domain/TokenType.java b/src/main/java/com/example/solidconnection/auth/domain/TokenType.java index caf1c7a9d..560b0e139 100644 --- a/src/main/java/com/example/solidconnection/auth/domain/TokenType.java +++ b/src/main/java/com/example/solidconnection/auth/domain/TokenType.java @@ -5,16 +5,16 @@ @Getter public enum TokenType { - ACCESS("ACCESS:", 1000 * 60 * 60), // 1hour - REFRESH("REFRESH:", 1000 * 60 * 60 * 24 * 7), // 7days + ACCESS("ACCESS:", 1000L * 60 * 60), // 1hour + REFRESH("REFRESH:", 1000L * 60 * 60 * 24 * 90), // 90days BLACKLIST("BLACKLIST:", ACCESS.expireTime), - SIGN_UP("SIGN_UP:", 1000 * 60 * 10), // 10min + SIGN_UP("SIGN_UP:", 1000L * 60 * 10), // 10min ; private final String prefix; - private final int expireTime; + private final long expireTime; - TokenType(String prefix, int expireTime) { + TokenType(String prefix, long expireTime) { this.prefix = prefix; this.expireTime = expireTime; } diff --git a/src/main/java/com/example/solidconnection/auth/dto/ReissueRequest.java b/src/main/java/com/example/solidconnection/auth/dto/ReissueRequest.java new file mode 100644 index 000000000..00443255d --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/dto/ReissueRequest.java @@ -0,0 +1,8 @@ +package com.example.solidconnection.auth.dto; + +import jakarta.validation.constraints.NotBlank; + +public record ReissueRequest( + @NotBlank(message = "리프레시 토큰과 함께 요청해주세요.") + String refreshToken) { +} diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthService.java b/src/main/java/com/example/solidconnection/auth/service/AuthService.java index 04bcadde7..289893dbf 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -1,7 +1,9 @@ package com.example.solidconnection.auth.service; +import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; +import com.example.solidconnection.config.security.JwtProperties; import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; @@ -9,15 +11,18 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; +import java.util.Objects; import java.util.Optional; import static com.example.solidconnection.custom.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; +import static com.example.solidconnection.util.JwtUtils.parseSubject; @RequiredArgsConstructor @Service public class AuthService { private final AuthTokenProvider authTokenProvider; + private final JwtProperties jwtProperties; /* * 로그아웃 한다. @@ -40,13 +45,15 @@ public void quit(SiteUser siteUser) { /* * 액세스 토큰을 재발급한다. - * - 리프레시 토큰이 만료되었거나, 존재하지 않는다면 예외 응답을 반환한다. - * - 리프레시 토큰이 존재한다면, 액세스 토큰을 재발급한다. + * - 요청된 리프레시 토큰과 동일한 subject 의 토큰이 저장되어 있으며 값이 일치할 경우, 액세스 토큰을 재발급한다. + * - 그렇지 않으면 예외를 반환한다. * */ - public ReissueResponse reissue(String subject) { - // 리프레시 토큰 만료 확인 - Optional optionalRefreshToken = authTokenProvider.findRefreshToken(subject); - if (optionalRefreshToken.isEmpty()) { + public ReissueResponse reissue(ReissueRequest reissueRequest) { + // 리프레시 토큰 확인 + String requestedRefreshToken = reissueRequest.refreshToken(); + String subject = parseSubject(requestedRefreshToken, jwtProperties.secret()); + Optional savedRefreshToken = authTokenProvider.findRefreshToken(subject); + if (!Objects.equals(requestedRefreshToken, savedRefreshToken.orElse(null))) { throw new CustomException(REFRESH_TOKEN_EXPIRED); } // 액세스 토큰 재발급 diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java index da040a8d5..b682a4b39 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -8,8 +8,6 @@ import java.util.Optional; -import static com.example.solidconnection.util.JwtUtils.parseSubjectIgnoringExpiration; - @Component public class AuthTokenProvider extends TokenProvider { @@ -18,7 +16,7 @@ public AuthTokenProvider(JwtProperties jwtProperties, RedisTemplate findBlackListToken(String subject) { return Optional.ofNullable(redisTemplate.opsForValue().get(blackListTokenKey)); } - public String getEmail(String token) { - return parseSubjectIgnoringExpiration(token, jwtProperties.secret()); + private String getSubject(SiteUser siteUser) { + return siteUser.getId().toString(); } } diff --git a/src/main/java/com/example/solidconnection/util/JwtUtils.java b/src/main/java/com/example/solidconnection/util/JwtUtils.java index d3ea8fed9..d295f2a3e 100644 --- a/src/main/java/com/example/solidconnection/util/JwtUtils.java +++ b/src/main/java/com/example/solidconnection/util/JwtUtils.java @@ -28,19 +28,19 @@ public static String parseTokenFromRequest(HttpServletRequest request) { return token.substring(TOKEN_PREFIX.length()); } - public static String parseSubjectIgnoringExpiration(String token, String secretKey) { + public static String parseSubject(String token, String secretKey) { try { return parseClaims(token, secretKey).getSubject(); - } catch (ExpiredJwtException e) { - return e.getClaims().getSubject(); } catch (Exception e) { throw new CustomException(INVALID_TOKEN); } } - public static String parseSubject(String token, String secretKey) { + public static String parseSubjectIgnoringExpiration(String token, String secretKey) { try { return parseClaims(token, secretKey).getSubject(); + } catch (ExpiredJwtException e) { + return e.getClaims().getSubject(); } catch (Exception e) { throw new CustomException(INVALID_TOKEN); } diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java new file mode 100644 index 000000000..0030e45e2 --- /dev/null +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -0,0 +1,108 @@ +package com.example.solidconnection.auth.service; + +import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.dto.ReissueRequest; +import com.example.solidconnection.auth.dto.ReissueResponse; +import com.example.solidconnection.config.security.JwtProperties; +import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import com.example.solidconnection.type.PreparationStatus; +import com.example.solidconnection.type.Role; +import com.example.solidconnection.util.JwtUtils; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.time.LocalDate; + +import static com.example.solidconnection.custom.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +@DisplayName("인증 서비스 테스트") +@TestContainerSpringBootTest +class AuthServiceTest { + + @Autowired + private AuthService authService; + + @Autowired + private AuthTokenProvider authTokenProvider; + + @Autowired + private SiteUserRepository siteUserRepository; + + @Autowired + private JwtProperties jwtProperties; + + @Test + void 로그아웃한다() { + // given + String accessToken = "accessToken"; + + // when + authService.signOut(accessToken); + + // then + assertThat(authTokenProvider.findBlackListToken(accessToken)).isNotNull(); + } + + @Test + void 탈퇴한다() { + // given + SiteUser siteUser = createSiteUser(); + + // when + authService.quit(siteUser); + + // then + LocalDate tomorrow = LocalDate.now().plusDays(1); + assertThat(siteUser.getQuitedAt()).isEqualTo(tomorrow); + } + + @Nested + class 토큰을_재발급한다 { + + @Test + void 요청의_리프레시_토큰이_저장되어_있고_값이_일치면_액세스_토큰을_재발급한다() { + // given + SiteUser siteUser = createSiteUser(); + String refreshToken = authTokenProvider.generateAndSaveRefreshToken(siteUser); + ReissueRequest reissueRequest = new ReissueRequest(refreshToken); + + // when + ReissueResponse reissuedAccessToken = authService.reissue(reissueRequest); + + // then + String actualSubject = JwtUtils.parseSubject(reissuedAccessToken.accessToken(), jwtProperties.secret()); + String expectedSubject = JwtUtils.parseSubject(refreshToken, jwtProperties.secret()); + assertThat(actualSubject).isEqualTo(expectedSubject); + } + + @Test + void 요청의_리프레시_토큰이_저장되어있지_않다면_예외_응답을_반환한다() { + // given + String refreshToken = authTokenProvider.generateToken("subject", TokenType.REFRESH); + ReissueRequest reissueRequest = new ReissueRequest(refreshToken); + + // when, then + assertThatCode(() -> authService.reissue(reissueRequest)) + .isInstanceOf(CustomException.class) + .hasMessage(REFRESH_TOKEN_EXPIRED.getMessage()); + } + } + + private SiteUser createSiteUser() { + SiteUser siteUser = new SiteUser( + "test@example.com", + "nickname", + "profileImageUrl", + PreparationStatus.CONSIDERING, + Role.MENTEE + ); + return siteUserRepository.save(siteUser); + } +} From 8a3885b6e15152ca2dd526168f5c5d2ef9849097 Mon Sep 17 00:00:00 2001 From: Wibaek Park <34394229+wibaek@users.noreply.github.com> Date: Mon, 5 May 2025 09:04:54 +0900 Subject: [PATCH 04/90] =?UTF-8?q?chore:=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=9A=B0=20=EA=B2=BD=EB=A1=9C,=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EB=B3=80=EA=B2=BD=20(#291)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 워크플로우 경로, 환경변수 변경 * chore: 환경변수 최신화 stage -> dev 변경 --- .github/workflows/dev-cd.yml | 34 +++++++++++++++++----------------- src/main/resources/secret | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/dev-cd.yml b/.github/workflows/dev-cd.yml index 793f3dea3..1efa6d266 100644 --- a/.github/workflows/dev-cd.yml +++ b/.github/workflows/dev-cd.yml @@ -38,38 +38,38 @@ jobs: - name: Copy jar file to remote uses: appleboy/scp-action@master with: - host: ${{ secrets.STAGE_HOST }} - username: ${{ secrets.STAGE_USERNAME }} - key: ${{ secrets.STAGE_PRIVATE_KEY }} + host: ${{ secrets.DEV_HOST }} + username: ${{ secrets.DEV_USERNAME }} + key: ${{ secrets.DEV_PRIVATE_KEY }} source: "./build/libs/*.jar" - target: "/home/${{ secrets.STAGE_USERNAME }}/solid-connect-stage/" + target: "/home/${{ secrets.DEV_USERNAME }}/solid-connection-dev/" - name: Copy docker file to remote uses: appleboy/scp-action@master with: - host: ${{ secrets.STAGE_HOST }} - username: ${{ secrets.STAGE_USERNAME }} - key: ${{ secrets.STAGE_PRIVATE_KEY }} + host: ${{ secrets.DEV_HOST }} + username: ${{ secrets.DEV_USERNAME }} + key: ${{ secrets.DEV_PRIVATE_KEY }} source: "./Dockerfile" - target: "/home/${{ secrets.STAGE_USERNAME }}/solid-connect-stage/" + target: "/home/${{ secrets.DEV_USERNAME }}/solid-connection-dev/" - name: Copy docker compose file to remote uses: appleboy/scp-action@master with: - host: ${{ secrets.STAGE_HOST }} - username: ${{ secrets.STAGE_USERNAME }} - key: ${{ secrets.STAGE_PRIVATE_KEY }} + host: ${{ secrets.DEV_HOST }} + username: ${{ secrets.DEV_USERNAME }} + key: ${{ secrets.DEV_PRIVATE_KEY }} source: "./docker-compose.dev.yml" - target: "/home/${{ secrets.STAGE_USERNAME }}/solid-connect-stage/" + target: "/home/${{ secrets.DEV_USERNAME }}/solid-connection-dev/" - name: Run docker compose uses: appleboy/ssh-action@master with: - host: ${{ secrets.STAGE_HOST }} - username: ${{ secrets.STAGE_USERNAME }} - key: ${{ secrets.STAGE_PRIVATE_KEY }} + host: ${{ secrets.DEV_HOST }} + username: ${{ secrets.DEV_USERNAME }} + key: ${{ secrets.DEV_PRIVATE_KEY }} script_stop: true script: | - cd /home/${{ secrets.STAGE_USERNAME }}/solid-connect-stage + cd /home/${{ secrets.DEV_USERNAME }}/solid-connection-dev docker compose down - docker compose -f docker-compose.stage.yml up -d --build + docker compose -f docker-compose.dev.yml up -d --build diff --git a/src/main/resources/secret b/src/main/resources/secret index 661dc50f2..cbb94e8df 160000 --- a/src/main/resources/secret +++ b/src/main/resources/secret @@ -1 +1 @@ -Subproject commit 661dc50f2915b20525a2a00b2b7f6473775f3dc1 +Subproject commit cbb94e8dfd95b773decd4193c2821284bdde8adc From fe0c2c05968c21420de73b18dfdba6dda05f7426 Mon Sep 17 00:00:00 2001 From: Wibaek Park <34394229+wibaek@users.noreply.github.com> Date: Tue, 6 May 2025 11:39:08 +0900 Subject: [PATCH 05/90] =?UTF-8?q?fix:=20Alloy=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EC=9D=B4=20=EB=B0=B0=ED=8F=AC=EC=8B=9C?= =?UTF-8?q?=EC=97=90=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EB=90=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8A=94=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#295)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: alloy config 파일을 배포시에 전송하게 변경 * chore: alloy container 실행시 config 파일 위치 변경 --- .github/workflows/dev-cd.yml | 9 +++++++++ .github/workflows/prod-cd.yml | 11 ++++++++++- docker-compose.dev.yml | 2 +- docker-compose.prod.yml | 2 +- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dev-cd.yml b/.github/workflows/dev-cd.yml index 1efa6d266..b86b541e1 100644 --- a/.github/workflows/dev-cd.yml +++ b/.github/workflows/dev-cd.yml @@ -62,6 +62,15 @@ jobs: source: "./docker-compose.dev.yml" target: "/home/${{ secrets.DEV_USERNAME }}/solid-connection-dev/" + - name: Copy alloy config file to remote + uses: appleboy/scp-action@master + with: + host: ${{ secrets.DEV_HOST }} + username: ${{ secrets.DEV_USERNAME }} + key: ${{ secrets.DEV_PRIVATE_KEY }} + source: "./docs/config.alloy" + target: "/home/${{ secrets.DEV_USERNAME }}/solid-connection-dev/" + - name: Run docker compose uses: appleboy/ssh-action@master with: diff --git a/.github/workflows/prod-cd.yml b/.github/workflows/prod-cd.yml index 9030c80f5..354128b77 100644 --- a/.github/workflows/prod-cd.yml +++ b/.github/workflows/prod-cd.yml @@ -61,7 +61,16 @@ jobs: key: ${{ secrets.PRIVATE_KEY }} source: "./docker-compose.prod.yml" target: "/home/${{ secrets.USERNAME }}/solid-connect-server/" - + + - name: Copy alloy config file to remote + uses: appleboy/scp-action@master + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + key: ${{ secrets.PRIVATE_KEY }} + source: "./docs/config.alloy" + target: "/home/${{ secrets.USERNAME }}/solid-connect-server/" + - name: Run docker compose uses: appleboy/ssh-action@master with: diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index a37ef4c55..e66e3a201 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -41,6 +41,6 @@ services: - "12345:12345" volumes: - ./logs:/var/log/spring - - ./docs/config.alloy:/etc/alloy/config.alloy:ro + - ./config.alloy:/etc/alloy/config.alloy:ro environment: - ALLOY_ENV=dev diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 30b0c9fc1..a6952f72c 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -40,6 +40,6 @@ services: - "12345:12345" volumes: - ./logs:/var/log/spring - - ./docs/config.alloy:/etc/alloy/config.alloy:ro + - ./config.alloy:/etc/alloy/config.alloy:ro environment: - ALLOY_ENV=production From 9713f5179075e3827bfb4075d5f1a690d10165f6 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Sat, 10 May 2025 00:29:27 +0900 Subject: [PATCH 06/90] =?UTF-8?q?refactor:=20=EB=AC=B8=EC=9E=90=EC=97=B4?= =?UTF-8?q?=20=ED=86=A0=ED=81=B0=EC=9D=84=20Token=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EB=A1=9C=20(#297)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 토큰 객체 생성 * refactor: authTokenProvider에 토큰 객체 적용 * refactor: authService에 토큰 객체 적용 * refactor: signInService에 토큰 객체 적용 * test: AuthTokenProvider 테스트 보충 * test: AuthService 테스트 보충 * refactor: 인터페이스로 DIP 구현 * test: 리프레시토큰 조회 함수 변경에 따른 테스트 수정 * test: 블랙리스트 저장 방법 변경에 따른 테스트 수정 * test: 깨진 테스트 수정 * refactor: 엑세스 토큰 자체를 블랙리스트로 저장하도록 * refactor: AuthController에서 인증 정보를 String으로 받도록 --- .../auth/controller/AuthController.java | 6 +- .../auth/dto/ReissueResponse.java | 9 +- .../auth/dto/SignInResponse.java | 7 + .../auth/service/AccessToken.java | 11 ++ .../auth/service/AuthService.java | 24 +-- .../auth/service/AuthTokenProvider.java | 63 ++++--- .../auth/service/BlacklistChecker.java | 6 + .../auth/service/RefreshToken.java | 11 ++ .../auth/service/SignInService.java | 7 +- .../solidconnection/auth/service/Subject.java | 6 + .../security/filter/SignOutCheckFilter.java | 6 +- .../auth/service/AuthServiceTest.java | 29 ++- .../auth/service/AuthTokenProviderTest.java | 167 ++++++------------ .../auth/service/SignInServiceTest.java | 10 +- .../filter/SignOutCheckFilterTest.java | 4 +- 15 files changed, 182 insertions(+), 184 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/auth/service/AccessToken.java create mode 100644 src/main/java/com/example/solidconnection/auth/service/BlacklistChecker.java create mode 100644 src/main/java/com/example/solidconnection/auth/service/RefreshToken.java create mode 100644 src/main/java/com/example/solidconnection/auth/service/Subject.java diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index 7fe543bd7..c4b9127d5 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -97,11 +97,11 @@ public ResponseEntity signUp( public ResponseEntity signOut( Authentication authentication ) { - String token = authentication.getCredentials().toString(); - if (token == null) { + String accessToken = (String) authentication.getCredentials(); + if (accessToken == null || accessToken.isBlank()) { throw new CustomException(ErrorCode.AUTHENTICATION_FAILED, "토큰이 없습니다."); } - authService.signOut(token); + authService.signOut(accessToken); return ResponseEntity.ok().build(); } diff --git a/src/main/java/com/example/solidconnection/auth/dto/ReissueResponse.java b/src/main/java/com/example/solidconnection/auth/dto/ReissueResponse.java index 48b55e6cb..972470cca 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/ReissueResponse.java +++ b/src/main/java/com/example/solidconnection/auth/dto/ReissueResponse.java @@ -1,5 +1,12 @@ package com.example.solidconnection.auth.dto; +import com.example.solidconnection.auth.service.AccessToken; + public record ReissueResponse( - String accessToken) { + String accessToken +) { + + public static ReissueResponse from(AccessToken accessToken) { + return new ReissueResponse(accessToken.token()); + } } diff --git a/src/main/java/com/example/solidconnection/auth/dto/SignInResponse.java b/src/main/java/com/example/solidconnection/auth/dto/SignInResponse.java index a4ae442e2..b01fdd369 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/SignInResponse.java +++ b/src/main/java/com/example/solidconnection/auth/dto/SignInResponse.java @@ -1,7 +1,14 @@ package com.example.solidconnection.auth.dto; +import com.example.solidconnection.auth.service.AccessToken; +import com.example.solidconnection.auth.service.RefreshToken; + public record SignInResponse( String accessToken, String refreshToken ) { + + public static SignInResponse of(AccessToken accessToken, RefreshToken refreshToken) { + return new SignInResponse(accessToken.token(), refreshToken.token()); + } } diff --git a/src/main/java/com/example/solidconnection/auth/service/AccessToken.java b/src/main/java/com/example/solidconnection/auth/service/AccessToken.java new file mode 100644 index 000000000..c94e891aa --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/service/AccessToken.java @@ -0,0 +1,11 @@ +package com.example.solidconnection.auth.service; + +public record AccessToken( + Subject subject, + String token +) { + + public AccessToken(String subject, String token) { + this(new Subject(subject), token); + } +} diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthService.java b/src/main/java/com/example/solidconnection/auth/service/AuthService.java index 289893dbf..86220d863 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -1,9 +1,7 @@ package com.example.solidconnection.auth.service; - import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; -import com.example.solidconnection.config.security.JwtProperties; import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; @@ -11,25 +9,22 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; -import java.util.Objects; -import java.util.Optional; import static com.example.solidconnection.custom.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; -import static com.example.solidconnection.util.JwtUtils.parseSubject; @RequiredArgsConstructor @Service public class AuthService { private final AuthTokenProvider authTokenProvider; - private final JwtProperties jwtProperties; /* * 로그아웃 한다. * - 엑세스 토큰을 블랙리스트에 추가한다. * */ - public void signOut(String accessToken) { - authTokenProvider.generateAndSaveBlackListToken(accessToken); + public void signOut(String token) { + AccessToken accessToken = authTokenProvider.toAccessToken(token); + authTokenProvider.addToBlacklist(accessToken); } /* @@ -45,19 +40,18 @@ public void quit(SiteUser siteUser) { /* * 액세스 토큰을 재발급한다. - * - 요청된 리프레시 토큰과 동일한 subject 의 토큰이 저장되어 있으며 값이 일치할 경우, 액세스 토큰을 재발급한다. - * - 그렇지 않으면 예외를 반환한다. + * - 유효한 리프레시토큰이면, 액세스 토큰을 재발급한다. + * - 그렇지 않으면 예외를 발생시킨다. * */ public ReissueResponse reissue(ReissueRequest reissueRequest) { // 리프레시 토큰 확인 String requestedRefreshToken = reissueRequest.refreshToken(); - String subject = parseSubject(requestedRefreshToken, jwtProperties.secret()); - Optional savedRefreshToken = authTokenProvider.findRefreshToken(subject); - if (!Objects.equals(requestedRefreshToken, savedRefreshToken.orElse(null))) { + if (!authTokenProvider.isValidRefreshToken(requestedRefreshToken)) { throw new CustomException(REFRESH_TOKEN_EXPIRED); } // 액세스 토큰 재발급 - String newAccessToken = authTokenProvider.generateAccessToken(subject); - return new ReissueResponse(newAccessToken); + Subject subject = authTokenProvider.parseSubject(requestedRefreshToken); + AccessToken newAccessToken = authTokenProvider.generateAccessToken(subject); + return ReissueResponse.from(newAccessToken); } } diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java index b682a4b39..d44f3bc8f 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -3,49 +3,68 @@ import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.config.security.JwtProperties; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.util.JwtUtils; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; -import java.util.Optional; +import java.util.Objects; @Component -public class AuthTokenProvider extends TokenProvider { +public class AuthTokenProvider extends TokenProvider implements BlacklistChecker { public AuthTokenProvider(JwtProperties jwtProperties, RedisTemplate redisTemplate) { super(jwtProperties, redisTemplate); } - public String generateAccessToken(SiteUser siteUser) { - String subject = getSubject(siteUser); - return generateToken(subject, TokenType.ACCESS); + public AccessToken generateAccessToken(Subject subject) { + String token = generateToken(subject.value(), TokenType.ACCESS); + return new AccessToken(subject, token); } - public String generateAccessToken(String subject) { - return generateToken(subject, TokenType.ACCESS); + public RefreshToken generateAndSaveRefreshToken(Subject subject) { + String token = generateToken(subject.value(), TokenType.REFRESH); + saveToken(token, TokenType.REFRESH); + return new RefreshToken(subject, token); } - public String generateAndSaveRefreshToken(SiteUser siteUser) { - String subject = getSubject(siteUser); - String refreshToken = generateToken(subject, TokenType.REFRESH); - return saveToken(refreshToken, TokenType.REFRESH); + /* + * 액세스 토큰을 블랙리스트에 저장한다. + * - key = BLACKLIST:{accessToken} + * - value = "signOut" -> key 의 존재만 확인하므로, value 에는 무슨 값이 들어가도 상관없다. + * */ + public void addToBlacklist(AccessToken accessToken) { + String blackListKey = TokenType.BLACKLIST.addPrefix(accessToken.token()); + redisTemplate.opsForValue().set(blackListKey, "signOut"); } - public String generateAndSaveBlackListToken(String accessToken) { - String blackListToken = generateToken(accessToken, TokenType.BLACKLIST); - return saveToken(blackListToken, TokenType.BLACKLIST); + /* + * 유효한 리프레시 토큰인지 확인한다. + * - 요청된 토큰과 같은 subject 의 리프레시 토큰을 조회한다. + * - 조회된 리프레시 토큰과 요청된 토큰이 같은지 비교한다. + * */ + public boolean isValidRefreshToken(String requestedRefreshToken) { + String subject = JwtUtils.parseSubject(requestedRefreshToken, jwtProperties.secret()); + String refreshTokenKey = TokenType.REFRESH.addPrefix(subject); + String foundRefreshToken = redisTemplate.opsForValue().get(refreshTokenKey); + return Objects.equals(requestedRefreshToken, foundRefreshToken); } - public Optional findRefreshToken(String subject) { - String refreshTokenKey = TokenType.REFRESH.addPrefix(subject); - return Optional.ofNullable(redisTemplate.opsForValue().get(refreshTokenKey)); + @Override + public boolean isTokenBlacklisted(String accessToken) { + String blackListTokenKey = TokenType.BLACKLIST.addPrefix(accessToken); + return redisTemplate.hasKey(blackListTokenKey); + } + + public Subject parseSubject(String token) { + String subject = JwtUtils.parseSubject(token, jwtProperties.secret()); + return new Subject(subject); } - public Optional findBlackListToken(String subject) { - String blackListTokenKey = TokenType.BLACKLIST.addPrefix(subject); - return Optional.ofNullable(redisTemplate.opsForValue().get(blackListTokenKey)); + public Subject toSubject(SiteUser siteUser) { + return new Subject(siteUser.getId().toString()); } - private String getSubject(SiteUser siteUser) { - return siteUser.getId().toString(); + public AccessToken toAccessToken(String token) { + return new AccessToken(parseSubject(token), token); } } diff --git a/src/main/java/com/example/solidconnection/auth/service/BlacklistChecker.java b/src/main/java/com/example/solidconnection/auth/service/BlacklistChecker.java new file mode 100644 index 000000000..b4e174906 --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/service/BlacklistChecker.java @@ -0,0 +1,6 @@ +package com.example.solidconnection.auth.service; + +public interface BlacklistChecker { + + boolean isTokenBlacklisted(String token); +} diff --git a/src/main/java/com/example/solidconnection/auth/service/RefreshToken.java b/src/main/java/com/example/solidconnection/auth/service/RefreshToken.java new file mode 100644 index 000000000..2aac3ad8c --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/service/RefreshToken.java @@ -0,0 +1,11 @@ +package com.example.solidconnection.auth.service; + +public record RefreshToken( + Subject subject, + String token +) { + + RefreshToken(String subject, String token) { + this(new Subject(subject), token); + } +} diff --git a/src/main/java/com/example/solidconnection/auth/service/SignInService.java b/src/main/java/com/example/solidconnection/auth/service/SignInService.java index 820d2e573..c2b129214 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignInService.java +++ b/src/main/java/com/example/solidconnection/auth/service/SignInService.java @@ -15,9 +15,10 @@ public class SignInService { @Transactional public SignInResponse signIn(SiteUser siteUser) { resetQuitedAt(siteUser); - String accessToken = authTokenProvider.generateAccessToken(siteUser); - String refreshToken = authTokenProvider.generateAndSaveRefreshToken(siteUser); - return new SignInResponse(accessToken, refreshToken); + Subject subject = authTokenProvider.toSubject(siteUser); + AccessToken accessToken = authTokenProvider.generateAccessToken(subject); + RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(subject); + return SignInResponse.of(accessToken, refreshToken); } private void resetQuitedAt(SiteUser siteUser) { diff --git a/src/main/java/com/example/solidconnection/auth/service/Subject.java b/src/main/java/com/example/solidconnection/auth/service/Subject.java new file mode 100644 index 000000000..2c03eb013 --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/service/Subject.java @@ -0,0 +1,6 @@ +package com.example.solidconnection.auth.service; + +public record Subject( + String value +) { +} diff --git a/src/main/java/com/example/solidconnection/custom/security/filter/SignOutCheckFilter.java b/src/main/java/com/example/solidconnection/custom/security/filter/SignOutCheckFilter.java index 2cef8d1ac..a111a7292 100644 --- a/src/main/java/com/example/solidconnection/custom/security/filter/SignOutCheckFilter.java +++ b/src/main/java/com/example/solidconnection/custom/security/filter/SignOutCheckFilter.java @@ -1,6 +1,6 @@ package com.example.solidconnection.custom.security.filter; -import com.example.solidconnection.auth.service.AuthTokenProvider; +import com.example.solidconnection.auth.service.BlacklistChecker; import com.example.solidconnection.custom.exception.CustomException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -20,7 +20,7 @@ @RequiredArgsConstructor public class SignOutCheckFilter extends OncePerRequestFilter { - private final AuthTokenProvider authTokenProvider; + private final BlacklistChecker tokenBlacklistChecker; @Override protected void doFilterInternal(@NonNull HttpServletRequest request, @@ -34,6 +34,6 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, } private boolean hasSignedOut(String accessToken) { - return authTokenProvider.findBlackListToken(accessToken).isPresent(); + return tokenBlacklistChecker.isTokenBlacklisted(accessToken); } } diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java index 0030e45e2..759c90d88 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -1,16 +1,13 @@ package com.example.solidconnection.auth.service; -import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; -import com.example.solidconnection.config.security.JwtProperties; import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.type.PreparationStatus; import com.example.solidconnection.type.Role; -import com.example.solidconnection.util.JwtUtils; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -35,19 +32,16 @@ class AuthServiceTest { @Autowired private SiteUserRepository siteUserRepository; - @Autowired - private JwtProperties jwtProperties; - @Test void 로그아웃한다() { // given - String accessToken = "accessToken"; + AccessToken accessToken = authTokenProvider.generateAccessToken(new Subject("subject")); // todo: #296 // when - authService.signOut(accessToken); + authService.signOut(accessToken.token()); // then - assertThat(authTokenProvider.findBlackListToken(accessToken)).isNotNull(); + assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue(); } @Test @@ -67,26 +61,25 @@ class AuthServiceTest { class 토큰을_재발급한다 { @Test - void 요청의_리프레시_토큰이_저장되어_있고_값이_일치면_액세스_토큰을_재발급한다() { + void 요청의_리프레시_토큰이_저장되어_있으면_액세스_토큰을_재발급한다() { // given - SiteUser siteUser = createSiteUser(); - String refreshToken = authTokenProvider.generateAndSaveRefreshToken(siteUser); - ReissueRequest reissueRequest = new ReissueRequest(refreshToken); + RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(new Subject("subject")); + ReissueRequest reissueRequest = new ReissueRequest(refreshToken.token()); // when ReissueResponse reissuedAccessToken = authService.reissue(reissueRequest); - // then - String actualSubject = JwtUtils.parseSubject(reissuedAccessToken.accessToken(), jwtProperties.secret()); - String expectedSubject = JwtUtils.parseSubject(refreshToken, jwtProperties.secret()); + // then - 요청의 리프레시 토큰과 재발급한 액세스 토큰의 subject 가 동일해야 한다. + Subject expectedSubject = authTokenProvider.parseSubject(refreshToken.token()); + Subject actualSubject = authTokenProvider.parseSubject(reissuedAccessToken.accessToken()); assertThat(actualSubject).isEqualTo(expectedSubject); } @Test void 요청의_리프레시_토큰이_저장되어있지_않다면_예외_응답을_반환한다() { // given - String refreshToken = authTokenProvider.generateToken("subject", TokenType.REFRESH); - ReissueRequest reissueRequest = new ReissueRequest(refreshToken); + String invalidRefreshToken = authTokenProvider.generateAccessToken(new Subject("subject")).token(); + ReissueRequest reissueRequest = new ReissueRequest(invalidRefreshToken); // when, then assertThatCode(() -> authService.reissue(reissueRequest)) diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java index 57a9ea789..2a0bd9b73 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java @@ -1,14 +1,7 @@ package com.example.solidconnection.auth.service; import com.example.solidconnection.auth.domain.TokenType; -import com.example.solidconnection.config.security.JwtProperties; -import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; -import com.example.solidconnection.util.JwtUtils; -import io.jsonwebtoken.Jwts; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -16,8 +9,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; -import java.util.Optional; - import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; @@ -28,157 +19,105 @@ class AuthTokenProviderTest { @Autowired private AuthTokenProvider authTokenProvider; - @Autowired - private SiteUserRepository siteUserRepository; - @Autowired private RedisTemplate redisTemplate; - @Autowired - private JwtProperties jwtProperties; - - private SiteUser siteUser; - private String subject; + private Subject subject; @BeforeEach void setUp() { - siteUser = createSiteUser(); - siteUserRepository.save(siteUser); - subject = siteUser.getId().toString(); + subject = new Subject("subject123"); } - @Nested - class 액세스_토큰을_제공한다 { - - @Test - void SiteUser_로_액세스_토큰을_생성한다() { - // when - String token = authTokenProvider.generateAccessToken(siteUser); - - // then - String actualSubject = JwtUtils.parseSubject(token, jwtProperties.secret()); - assertThat(actualSubject).isEqualTo(subject); - } - - @Test - void subject_로_액세스_토큰을_생성한다() { - // given - String subject = "subject123"; - - // when - String token = authTokenProvider.generateAccessToken(subject); + @Test + void 액세스_토큰을_생성한다() { + // when + AccessToken accessToken = authTokenProvider.generateAccessToken(subject); - // then - String actualSubject = JwtUtils.parseSubject(token, jwtProperties.secret()); - assertThat(actualSubject).isEqualTo(subject); - } + // then + String actualSubject = authTokenProvider.parseSubject(accessToken.token()).value(); + assertThat(actualSubject).isEqualTo(subject.value()); } @Nested class 리프레시_토큰을_제공한다 { @Test - void SiteUser_로_리프레시_토큰을_생성하고_저장한다() { + void 리프레시_토큰을_생성하고_저장한다() { // when - String refreshToken = authTokenProvider.generateAndSaveRefreshToken(siteUser); + RefreshToken actualRefreshToken = authTokenProvider.generateAndSaveRefreshToken(subject); // then - String actualSubject = JwtUtils.parseSubject(refreshToken, jwtProperties.secret()); - String refreshTokenKey = TokenType.REFRESH.addPrefix(subject); + String actualSubject = authTokenProvider.parseSubject(actualRefreshToken.token()).value(); + String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value()); + String expectedRefreshToken = redisTemplate.opsForValue().get(refreshTokenKey); assertAll( - () -> assertThat(actualSubject).isEqualTo(subject), - () -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isEqualTo(refreshToken) + () -> assertThat(actualSubject).isEqualTo(subject.value()), + () -> assertThat(actualRefreshToken.token()).isEqualTo(expectedRefreshToken) ); } @Test - void 저장된_리프레시_토큰을_조회한다() { + void 유효한_리프레시_토큰인지_확인한다() { // given - String refreshToken = "refreshToken"; - redisTemplate.opsForValue().set(TokenType.REFRESH.addPrefix(subject), refreshToken); + RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(subject); + AccessToken fakeRefreshToken = authTokenProvider.generateAccessToken(subject); // todo: issue#296 - // when - Optional optionalRefreshToken = authTokenProvider.findRefreshToken(subject); - - // then - assertThat(optionalRefreshToken.get()).isEqualTo(refreshToken); - } - - @Test - void 저장되지_않은_리프레시_토큰을_조회한다() { - // when - Optional optionalRefreshToken = authTokenProvider.findRefreshToken(subject); - - // then - assertThat(optionalRefreshToken).isEmpty(); + // when, then + assertAll( + () -> assertThat(authTokenProvider.isValidRefreshToken(refreshToken.token())).isTrue(), + () -> assertThat(authTokenProvider.isValidRefreshToken(fakeRefreshToken.token())).isFalse() + ); } } @Nested - class 블랙리스트_토큰을_제공한다 { - - @Test - void 엑세스_토큰으로_블랙리스트_토큰을_생성하고_저장한다() { - // when - String accessToken = "accessToken"; - String blackListToken = authTokenProvider.generateAndSaveBlackListToken(accessToken); - - // then - String actualSubject = JwtUtils.parseSubject(blackListToken, jwtProperties.secret()); - String blackListTokenKey = TokenType.BLACKLIST.addPrefix(accessToken); - assertAll( - () -> assertThat(actualSubject).isEqualTo(accessToken), - () -> assertThat(redisTemplate.opsForValue().get(blackListTokenKey)).isEqualTo(blackListToken) - ); - } + class 블랙리스트를_관리한다 { @Test - void 저장된_블랙리스트_토큰을_조회한다() { + void 액세스_토큰을_블랙리스트에_추가한다() { // given - String accessToken = "accessToken"; - String blackListToken = "token"; - redisTemplate.opsForValue().set(TokenType.BLACKLIST.addPrefix(accessToken), blackListToken); + AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // todo: issue#296 // when - Optional optionalBlackListToken = authTokenProvider.findBlackListToken(accessToken); + authTokenProvider.addToBlacklist(accessToken); // then - assertThat(optionalBlackListToken).hasValue(blackListToken); + String blackListTokenKey = TokenType.BLACKLIST.addPrefix(accessToken.token()); + String foundBlackListToken = redisTemplate.opsForValue().get(blackListTokenKey); + assertThat(foundBlackListToken).isNotNull(); } + /* + * todo: JwtUtils 나 TokenProvider 를 스프링 빈으로 주입받도록 변경한다. (issue#296) + * - 아래 테스트 코드에서는, 내부적으로 JwtUtils.parseSubject() 메서드가 호출될 때 발생하는 예외를 피하기 위해 jwt토큰을 생성한다. + * - 테스트 작성자는 예외 발생을 피하기 위해 "제대로된 jwt 토큰 생성이 필요하다"는 것을 몰라야한다. + * - 따라서, JwtUtils 나 TokenProvider 를 스프링 빈으로 주입받도록 변경하고, 테스트에서 mock 을 사용하여 의존성을 끊을 필요가 있다. + */ @Test - void 저장되지_않은_블랙리스트_토큰을_조회한다() { - // when - Optional optionalBlackListToken = authTokenProvider.findBlackListToken("accessToken"); + void 블랙리스트에_있는_토큰인지_확인한다() { + // given + AccessToken accessToken = authTokenProvider.generateAccessToken(subject); + authTokenProvider.addToBlacklist(accessToken); + AccessToken notRegisteredAccessToken = authTokenProvider.generateAccessToken(new Subject("!")); - // then - assertThat(optionalBlackListToken).isEmpty(); + // when, then + assertAll( + () -> assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue(), + () -> assertThat(authTokenProvider.isTokenBlacklisted(notRegisteredAccessToken.token())).isFalse() + ); } } @Test - void 토큰을_생성한다() { + void 토큰으로부터_Subject_를_추출한다() { + // given + String accessToken = authTokenProvider.generateAccessToken(subject).token(); + // when - String subject = "subject123"; - String token = authTokenProvider.generateToken(subject, TokenType.ACCESS); + Subject actualSubject = authTokenProvider.parseSubject(accessToken); // then - String extractedSubject = Jwts.parser() - .setSigningKey(jwtProperties.secret()) - .parseClaimsJws(token) - .getBody() - .getSubject(); - assertThat(subject).isEqualTo(extractedSubject); - } - - private SiteUser createSiteUser() { - SiteUser siteUser = new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - return siteUserRepository.save(siteUser); + assertThat(actualSubject.value()).isEqualTo(subject.value()); } } diff --git a/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java index 6136bbee2..e40f20b7a 100644 --- a/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java @@ -1,5 +1,6 @@ package com.example.solidconnection.auth.service; +import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.config.security.JwtProperties; import com.example.solidconnection.siteuser.domain.SiteUser; @@ -12,9 +13,9 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; import java.time.LocalDate; -import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; @@ -35,6 +36,9 @@ class SignInServiceTest { @Autowired private SiteUserRepository siteUserRepository; + @Autowired + private RedisTemplate redisTemplate; + private SiteUser siteUser; private String subject; @@ -53,11 +57,11 @@ void setUp() { // then String accessTokenSubject = JwtUtils.parseSubject(signInResponse.accessToken(), jwtProperties.secret()); String refreshTokenSubject = JwtUtils.parseSubject(signInResponse.refreshToken(), jwtProperties.secret()); - Optional savedRefreshToken = authTokenProvider.findRefreshToken(subject); + String savedRefreshToken = redisTemplate.opsForValue().get(TokenType.REFRESH.addPrefix(refreshTokenSubject)); assertAll( () -> assertThat(accessTokenSubject).isEqualTo(subject), () -> assertThat(refreshTokenSubject).isEqualTo(subject), - () -> assertThat(savedRefreshToken).hasValue(signInResponse.refreshToken())); + () -> assertThat(savedRefreshToken).isEqualTo(signInResponse.refreshToken())); } @Test diff --git a/src/test/java/com/example/solidconnection/custom/security/filter/SignOutCheckFilterTest.java b/src/test/java/com/example/solidconnection/custom/security/filter/SignOutCheckFilterTest.java index a11d8d28a..e76a01c75 100644 --- a/src/test/java/com/example/solidconnection/custom/security/filter/SignOutCheckFilterTest.java +++ b/src/test/java/com/example/solidconnection/custom/security/filter/SignOutCheckFilterTest.java @@ -55,12 +55,12 @@ void setUp() { } @Test - void 로그아웃한_토큰이면_예외를_응답한다() throws Exception { + void 로그아웃한_토큰이면_예외를_응답한다() { // given String token = createToken(subject); request = createRequest(token); String refreshTokenKey = BLACKLIST.addPrefix(token); - redisTemplate.opsForValue().set(refreshTokenKey, "signOut"); + redisTemplate.opsForValue().set(refreshTokenKey, token); // when & then assertThatCode(() -> signOutCheckFilter.doFilterInternal(request, response, filterChain)) From 3b16bda82a54b7cae6a8dc6c7c8f18a3677b1a60 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Sun, 11 May 2025 01:44:38 +0900 Subject: [PATCH 07/90] =?UTF-8?q?refactor:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83,=20=ED=83=88=ED=87=B4=20=EC=8B=9C=20refresh=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=EC=9D=84=20=EC=82=AD=EC=A0=9C=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20(#303)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 액세스 토큰에 해당하는 리프레시 토큰 삭제 함수 구현 * refactor: 로그아웃 시, 리프레시 토큰 삭제하도록 * refactor: 탈퇴 시, 로그아웃하도록 * refactor: 엑세스 토큰 가져오는 함수 분리 - Authentication 자체가 null 인 경우도 예외처리 --- .../auth/controller/AuthController.java | 18 ++++++++----- .../auth/service/AuthService.java | 8 ++++-- .../auth/service/AuthTokenProvider.java | 6 +++++ .../auth/service/AuthServiceTest.java | 26 ++++++++++++++++--- .../auth/service/AuthTokenProviderTest.java | 14 ++++++++++ 5 files changed, 60 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index c4b9127d5..0df89426e 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -97,19 +97,18 @@ public ResponseEntity signUp( public ResponseEntity signOut( Authentication authentication ) { - String accessToken = (String) authentication.getCredentials(); - if (accessToken == null || accessToken.isBlank()) { - throw new CustomException(ErrorCode.AUTHENTICATION_FAILED, "토큰이 없습니다."); - } + String accessToken = getAccessToken(authentication); authService.signOut(accessToken); return ResponseEntity.ok().build(); } @PatchMapping("/quit") public ResponseEntity quit( - @AuthorizedUser SiteUser siteUser + @AuthorizedUser SiteUser siteUser, + Authentication authentication // todo: #299를 작업하며 인자를 (Authentication authentication)만 받도록 수정해야 함 ) { - authService.quit(siteUser); + String accessToken = getAccessToken(authentication); + authService.quit(siteUser, accessToken); return ResponseEntity.ok().build(); } @@ -120,4 +119,11 @@ public ResponseEntity reissueToken( ReissueResponse reissueResponse = authService.reissue(reissueRequest); return ResponseEntity.ok(reissueResponse); } + + private String getAccessToken(Authentication authentication) { + if (authentication == null || !(authentication.getCredentials() instanceof String accessToken)) { + throw new CustomException(ErrorCode.AUTHENTICATION_FAILED, "엑세스 토큰이 없습니다."); + } + return accessToken; + } } diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthService.java b/src/main/java/com/example/solidconnection/auth/service/AuthService.java index 86220d863..50926ca93 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -19,11 +19,13 @@ public class AuthService { private final AuthTokenProvider authTokenProvider; /* - * 로그아웃 한다. + * 로그아웃한다. * - 엑세스 토큰을 블랙리스트에 추가한다. + * - 리프레시 토큰을 삭제한다. * */ public void signOut(String token) { AccessToken accessToken = authTokenProvider.toAccessToken(token); + authTokenProvider.deleteRefreshTokenByAccessToken(accessToken); authTokenProvider.addToBlacklist(accessToken); } @@ -31,11 +33,13 @@ public void signOut(String token) { * 탈퇴한다. * - 탈퇴한 시점의 다음날을 탈퇴일로 잡는다. * - e.g. 2024-01-01 18:00 탈퇴 시, 2024-01-02 00:00 가 탈퇴일이 된다. + * - 로그아웃한다. * */ @Transactional - public void quit(SiteUser siteUser) { + public void quit(SiteUser siteUser, String token) { // todo: #299를 작업하며 인자를 (String token)만 받도록 수정해야 함 LocalDate tomorrow = LocalDate.now().plusDays(1); siteUser.setQuitedAt(tomorrow); + signOut(token); } /* diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java index d44f3bc8f..2e43e8be9 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -49,6 +49,12 @@ public boolean isValidRefreshToken(String requestedRefreshToken) { return Objects.equals(requestedRefreshToken, foundRefreshToken); } + public void deleteRefreshTokenByAccessToken(AccessToken accessToken) { + String subject = accessToken.subject().value(); + String refreshTokenKey = TokenType.REFRESH.addPrefix(subject); + redisTemplate.delete(refreshTokenKey); + } + @Override public boolean isTokenBlacklisted(String accessToken) { String blackListTokenKey = TokenType.BLACKLIST.addPrefix(accessToken); diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java index 759c90d88..18c7c4ef0 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -1,5 +1,6 @@ package com.example.solidconnection.auth.service; +import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; import com.example.solidconnection.custom.exception.CustomException; @@ -12,12 +13,14 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; import java.time.LocalDate; import static com.example.solidconnection.custom.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; @DisplayName("인증 서비스 테스트") @TestContainerSpringBootTest @@ -32,29 +35,44 @@ class AuthServiceTest { @Autowired private SiteUserRepository siteUserRepository; + @Autowired + private RedisTemplate redisTemplate; + @Test void 로그아웃한다() { // given - AccessToken accessToken = authTokenProvider.generateAccessToken(new Subject("subject")); // todo: #296 + Subject subject = new Subject("subject"); + AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // todo: #296 // when authService.signOut(accessToken.token()); // then - assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue(); + String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value()); + assertAll( + () -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(), + () -> assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue() + ); } @Test void 탈퇴한다() { // given SiteUser siteUser = createSiteUser(); + Subject subject = authTokenProvider.toSubject(siteUser); + AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // todo: #296 // when - authService.quit(siteUser); + authService.quit(siteUser, accessToken.token()); // then LocalDate tomorrow = LocalDate.now().plusDays(1); - assertThat(siteUser.getQuitedAt()).isEqualTo(tomorrow); + String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value()); + assertAll( + () -> assertThat(siteUser.getQuitedAt()).isEqualTo(tomorrow), + () -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(), + () -> assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue() + ); } @Nested diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java index 2a0bd9b73..dc35ab3a7 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java @@ -69,6 +69,20 @@ class 리프레시_토큰을_제공한다 { () -> assertThat(authTokenProvider.isValidRefreshToken(fakeRefreshToken.token())).isFalse() ); } + + @Test + void 액세서_토큰에_해당하는_리프레시_토큰을_삭제한다() { + // given + authTokenProvider.generateAndSaveRefreshToken(subject); + AccessToken accessToken = authTokenProvider.generateAccessToken(subject); + + // when + authTokenProvider.deleteRefreshTokenByAccessToken(accessToken); + + // then + String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value()); + assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(); + } } @Nested From 75d5d24a357b1f94d8b86a5d16ec3ff3ba839d2d Mon Sep 17 00:00:00 2001 From: Wibaek Park <34394229+wibaek@users.noreply.github.com> Date: Sun, 11 May 2025 12:59:29 +0900 Subject: [PATCH 08/90] =?UTF-8?q?fix:=20Alloy=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#305)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.dev.yml | 2 +- docker-compose.prod.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index e66e3a201..a37ef4c55 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -41,6 +41,6 @@ services: - "12345:12345" volumes: - ./logs:/var/log/spring - - ./config.alloy:/etc/alloy/config.alloy:ro + - ./docs/config.alloy:/etc/alloy/config.alloy:ro environment: - ALLOY_ENV=dev diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index a6952f72c..30b0c9fc1 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -40,6 +40,6 @@ services: - "12345:12345" volumes: - ./logs:/var/log/spring - - ./config.alloy:/etc/alloy/config.alloy:ro + - ./docs/config.alloy:/etc/alloy/config.alloy:ro environment: - ALLOY_ENV=production From bf97db45e44475dd1df8f48f9171be21d358c08c Mon Sep 17 00:00:00 2001 From: Wibaek Park <34394229+wibaek@users.noreply.github.com> Date: Sun, 11 May 2025 13:01:49 +0900 Subject: [PATCH 09/90] =?UTF-8?q?feat:=20News=20=EC=86=8C=EC=8B=9D?= =?UTF-8?q?=EC=A7=80=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#302)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: News 소식지 도메인 추가 BaseEntity를 상속한 이유는 이후에 유저도 소식지를 쓸 수 있게 되면 필요할 것이라 추정되기에 * chore: import 수정 * feat: 뉴스 테이블 생성 Flyway 스키마 추가 * chore: Flyway 파일 포매팅 --- README.md | Bin 1154 -> 1114 bytes .../solidconnection/news/domain/News.java | 32 ++++++++++++++++++ .../db/migration/V12__create_news.sql | 10 ++++++ 3 files changed, 42 insertions(+) create mode 100644 src/main/java/com/example/solidconnection/news/domain/News.java create mode 100644 src/main/resources/db/migration/V12__create_news.sql diff --git a/README.md b/README.md index a07f6d77b95a903a0b2ef7d7e620278d8a540a83..5f4bcca59e9278564cbc50a3068a7f104325fce2 100644 GIT binary patch delta 73 zcmZqTyu~rWXQD@lsRDxngDwy!Fz7MpGvqMjGbA%40_k`lozIZVPyi&08B&4#QXpFo T$j)F;*v!Zn$+S6)S%(n-MHUYX delta 93 zcmcb`(Zo5yXJSAAt1g2AL&C&Tc}HD_G$1pDA)g_cA)6tUp@=~j$jWEPWhelW#Xu1~ jh8(bLB9PW&s07O70CkiC Date: Sun, 11 May 2025 20:47:34 +0900 Subject: [PATCH 10/90] =?UTF-8?q?fix:=20=EB=88=84=EB=9D=BD=ED=95=9C=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#309)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/solidconnection/auth/controller/AuthController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index 0df89426e..553ef4063 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -114,7 +114,7 @@ public ResponseEntity quit( @PostMapping("/reissue") public ResponseEntity reissueToken( - ReissueRequest reissueRequest + @Valid @RequestBody ReissueRequest reissueRequest ) { ReissueResponse reissueResponse = authService.reissue(reissueRequest); return ResponseEntity.ok(reissueResponse); From c0f79cc4a4d12db4cfe9121650ec0040d0408ed6 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Sun, 11 May 2025 22:38:45 +0900 Subject: [PATCH 11/90] =?UTF-8?q?refactor:=20=EB=A7=8C=EB=A3=8C=EB=90=9C?= =?UTF-8?q?=20=ED=86=A0=ED=81=B0=20=ED=97=88=EC=9A=A9=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20(#308)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 만료된 토큰도 허용하는 코드 삭제 * test: 만료된 토큰과 관련된 테스트 코드 삭제 * test: 삭제된 테스트와의 연관성 제거 - 삭제된 테스트 코드와 연관된 테스트 함수명, 구조 수정 --- .../security/AuthenticationManagerConfig.java | 5 +- .../config/web/WebMvcConfig.java | 3 - .../exception/JwtExpiredTokenException.java | 10 --- .../custom/resolver/ExpiredToken.java | 12 --- .../custom/resolver/ExpiredTokenResolver.java | 35 -------- .../ExpiredTokenAuthentication.java | 19 ----- .../filter/JwtAuthenticationFilter.java | 7 -- .../ExpiredTokenAuthenticationProvider.java | 35 -------- .../solidconnection/util/JwtUtils.java | 26 ------ .../resolver/ExpiredTokenResolverTest.java | 43 ---------- .../ExpiredTokenAuthenticationTest.java | 64 --------------- .../filter/JwtAuthenticationFilterTest.java | 52 ++++-------- ...xpiredTokenAuthenticationProviderTest.java | 80 ------------------ .../solidconnection/util/JwtUtilsTest.java | 81 +------------------ 14 files changed, 17 insertions(+), 455 deletions(-) delete mode 100644 src/main/java/com/example/solidconnection/custom/exception/JwtExpiredTokenException.java delete mode 100644 src/main/java/com/example/solidconnection/custom/resolver/ExpiredToken.java delete mode 100644 src/main/java/com/example/solidconnection/custom/resolver/ExpiredTokenResolver.java delete mode 100644 src/main/java/com/example/solidconnection/custom/security/authentication/ExpiredTokenAuthentication.java delete mode 100644 src/main/java/com/example/solidconnection/custom/security/provider/ExpiredTokenAuthenticationProvider.java delete mode 100644 src/test/java/com/example/solidconnection/custom/resolver/ExpiredTokenResolverTest.java delete mode 100644 src/test/java/com/example/solidconnection/custom/security/authentication/ExpiredTokenAuthenticationTest.java delete mode 100644 src/test/java/com/example/solidconnection/custom/security/provider/ExpiredTokenAuthenticationProviderTest.java diff --git a/src/main/java/com/example/solidconnection/config/security/AuthenticationManagerConfig.java b/src/main/java/com/example/solidconnection/config/security/AuthenticationManagerConfig.java index 785283d7d..e4af023b7 100644 --- a/src/main/java/com/example/solidconnection/config/security/AuthenticationManagerConfig.java +++ b/src/main/java/com/example/solidconnection/config/security/AuthenticationManagerConfig.java @@ -1,6 +1,5 @@ package com.example.solidconnection.config.security; -import com.example.solidconnection.custom.security.provider.ExpiredTokenAuthenticationProvider; import com.example.solidconnection.custom.security.provider.SiteUserAuthenticationProvider; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; @@ -13,13 +12,11 @@ public class AuthenticationManagerConfig { private final SiteUserAuthenticationProvider siteUserAuthenticationProvider; - private final ExpiredTokenAuthenticationProvider expiredTokenAuthenticationProvider; @Bean public AuthenticationManager authenticationManager() { return new ProviderManager( - siteUserAuthenticationProvider, - expiredTokenAuthenticationProvider + siteUserAuthenticationProvider ); } } diff --git a/src/main/java/com/example/solidconnection/config/web/WebMvcConfig.java b/src/main/java/com/example/solidconnection/config/web/WebMvcConfig.java index 6d16694cc..7f56e320f 100644 --- a/src/main/java/com/example/solidconnection/config/web/WebMvcConfig.java +++ b/src/main/java/com/example/solidconnection/config/web/WebMvcConfig.java @@ -2,7 +2,6 @@ import com.example.solidconnection.custom.resolver.AuthorizedUserResolver; import com.example.solidconnection.custom.resolver.CustomPageableHandlerMethodArgumentResolver; -import com.example.solidconnection.custom.resolver.ExpiredTokenResolver; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -15,14 +14,12 @@ public class WebMvcConfig implements WebMvcConfigurer { private final AuthorizedUserResolver authorizedUserResolver; - private final ExpiredTokenResolver expiredTokenResolver; private final CustomPageableHandlerMethodArgumentResolver customPageableHandlerMethodArgumentResolver; @Override public void addArgumentResolvers(List resolvers) { resolvers.addAll(List.of( authorizedUserResolver, - expiredTokenResolver, customPageableHandlerMethodArgumentResolver )); } diff --git a/src/main/java/com/example/solidconnection/custom/exception/JwtExpiredTokenException.java b/src/main/java/com/example/solidconnection/custom/exception/JwtExpiredTokenException.java deleted file mode 100644 index b0a52e9fa..000000000 --- a/src/main/java/com/example/solidconnection/custom/exception/JwtExpiredTokenException.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.solidconnection.custom.exception; - -import org.springframework.security.core.AuthenticationException; - -public class JwtExpiredTokenException extends AuthenticationException { - - public JwtExpiredTokenException(String msg) { - super(msg); - } -} diff --git a/src/main/java/com/example/solidconnection/custom/resolver/ExpiredToken.java b/src/main/java/com/example/solidconnection/custom/resolver/ExpiredToken.java deleted file mode 100644 index 5de4ad95a..000000000 --- a/src/main/java/com/example/solidconnection/custom/resolver/ExpiredToken.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.solidconnection.custom.resolver; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -// todo: 사용되지 않음, 다른 PR에서 삭제하고 더 효율적인 구조를 고민해봐야 함 -@Target({ElementType.PARAMETER}) -@Retention(RetentionPolicy.RUNTIME) -public @interface ExpiredToken { -} diff --git a/src/main/java/com/example/solidconnection/custom/resolver/ExpiredTokenResolver.java b/src/main/java/com/example/solidconnection/custom/resolver/ExpiredTokenResolver.java deleted file mode 100644 index 7547a1d61..000000000 --- a/src/main/java/com/example/solidconnection/custom/resolver/ExpiredTokenResolver.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.example.solidconnection.custom.resolver; - -import com.example.solidconnection.custom.security.authentication.ExpiredTokenAuthentication; -import lombok.RequiredArgsConstructor; -import org.springframework.core.MethodParameter; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; -import org.springframework.web.bind.support.WebDataBinderFactory; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.method.support.ModelAndViewContainer; - -// todo: 사용되지 않음, 다른 PR에서 삭제하고 더 효율적인 구조를 고민해봐야 함 -@Component -@RequiredArgsConstructor -public class ExpiredTokenResolver implements HandlerMethodArgumentResolver { - - @Override - public boolean supportsParameter(MethodParameter parameter) { - return parameter.hasParameterAnnotation(ExpiredToken.class) - && parameter.getParameterType().equals(ExpiredTokenAuthentication.class); - } - - @Override - public Object resolveArgument(MethodParameter parameter, - ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, - WebDataBinderFactory binderFactory) throws Exception { - try { - return SecurityContextHolder.getContext().getAuthentication(); - } catch (Exception e) { - return null; - } - } -} diff --git a/src/main/java/com/example/solidconnection/custom/security/authentication/ExpiredTokenAuthentication.java b/src/main/java/com/example/solidconnection/custom/security/authentication/ExpiredTokenAuthentication.java deleted file mode 100644 index 061484674..000000000 --- a/src/main/java/com/example/solidconnection/custom/security/authentication/ExpiredTokenAuthentication.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.solidconnection.custom.security.authentication; - -// todo: 사용되지 않음, 다른 PR에서 삭제하고 더 효율적인 구조를 고민해봐야 함 -public class ExpiredTokenAuthentication extends JwtAuthentication { - - public ExpiredTokenAuthentication(String token) { - super(token, null); - setAuthenticated(false); - } - - public ExpiredTokenAuthentication(String token, String subject) { - super(token, subject); - setAuthenticated(false); - } - - public String getSubject() { - return (String) getPrincipal(); - } -} diff --git a/src/main/java/com/example/solidconnection/custom/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/example/solidconnection/custom/security/filter/JwtAuthenticationFilter.java index 3f5bce556..d9d8efd65 100644 --- a/src/main/java/com/example/solidconnection/custom/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/solidconnection/custom/security/filter/JwtAuthenticationFilter.java @@ -1,7 +1,5 @@ package com.example.solidconnection.custom.security.filter; -import com.example.solidconnection.config.security.JwtProperties; -import com.example.solidconnection.custom.security.authentication.ExpiredTokenAuthentication; import com.example.solidconnection.custom.security.authentication.JwtAuthentication; import com.example.solidconnection.custom.security.authentication.SiteUserAuthentication; import jakarta.servlet.FilterChain; @@ -18,7 +16,6 @@ import java.io.IOException; -import static com.example.solidconnection.util.JwtUtils.isExpired; import static com.example.solidconnection.util.JwtUtils.parseTokenFromRequest; @@ -26,7 +23,6 @@ @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { - private final JwtProperties jwtProperties; private final AuthenticationManager authenticationManager; @Override @@ -47,9 +43,6 @@ public void doFilterInternal(@NonNull HttpServletRequest request, } private JwtAuthentication createAuthentication(String token) { - if (isExpired(token, jwtProperties.secret())) { - return new ExpiredTokenAuthentication(token); - } return new SiteUserAuthentication(token); } } diff --git a/src/main/java/com/example/solidconnection/custom/security/provider/ExpiredTokenAuthenticationProvider.java b/src/main/java/com/example/solidconnection/custom/security/provider/ExpiredTokenAuthenticationProvider.java deleted file mode 100644 index 01b065a19..000000000 --- a/src/main/java/com/example/solidconnection/custom/security/provider/ExpiredTokenAuthenticationProvider.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.example.solidconnection.custom.security.provider; - - -import com.example.solidconnection.config.security.JwtProperties; -import com.example.solidconnection.custom.security.authentication.ExpiredTokenAuthentication; -import com.example.solidconnection.custom.security.authentication.JwtAuthentication; -import lombok.RequiredArgsConstructor; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.stereotype.Component; - -import static com.example.solidconnection.util.JwtUtils.parseSubjectIgnoringExpiration; - -// todo: 사용되지 않음, 다른 PR에서 삭제하고 더 효율적인 구조를 고민해봐야 함 -@Component -@RequiredArgsConstructor -public class ExpiredTokenAuthenticationProvider implements AuthenticationProvider { - - private final JwtProperties jwtProperties; - - @Override - public Authentication authenticate(Authentication auth) throws AuthenticationException { - JwtAuthentication jwtAuth = (JwtAuthentication) auth; - String token = jwtAuth.getToken(); - String subject = parseSubjectIgnoringExpiration(token, jwtProperties.secret()); - - return new ExpiredTokenAuthentication(token, subject); - } - - @Override - public boolean supports(Class authentication) { - return ExpiredTokenAuthentication.class.isAssignableFrom(authentication); - } -} diff --git a/src/main/java/com/example/solidconnection/util/JwtUtils.java b/src/main/java/com/example/solidconnection/util/JwtUtils.java index d295f2a3e..a5c96d092 100644 --- a/src/main/java/com/example/solidconnection/util/JwtUtils.java +++ b/src/main/java/com/example/solidconnection/util/JwtUtils.java @@ -7,8 +7,6 @@ import jakarta.servlet.http.HttpServletRequest; import org.springframework.stereotype.Component; -import java.util.Date; - import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_TOKEN; @Component @@ -35,30 +33,6 @@ public static String parseSubject(String token, String secretKey) { throw new CustomException(INVALID_TOKEN); } } - - public static String parseSubjectIgnoringExpiration(String token, String secretKey) { - try { - return parseClaims(token, secretKey).getSubject(); - } catch (ExpiredJwtException e) { - return e.getClaims().getSubject(); - } catch (Exception e) { - throw new CustomException(INVALID_TOKEN); - } - } - - public static boolean isExpired(String token, String secretKey) { - try { - Date expiration = Jwts.parser() - .setSigningKey(secretKey) - .parseClaimsJws(token) - .getBody() - .getExpiration(); - return expiration.before(new Date()); - } catch (Exception e) { - return true; - } - } - public static Claims parseClaims(String token, String secretKey) throws ExpiredJwtException { return Jwts.parser() .setSigningKey(secretKey) diff --git a/src/test/java/com/example/solidconnection/custom/resolver/ExpiredTokenResolverTest.java b/src/test/java/com/example/solidconnection/custom/resolver/ExpiredTokenResolverTest.java deleted file mode 100644 index a0393dbc7..000000000 --- a/src/test/java/com/example/solidconnection/custom/resolver/ExpiredTokenResolverTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.example.solidconnection.custom.resolver; - -import com.example.solidconnection.custom.security.authentication.ExpiredTokenAuthentication; -import com.example.solidconnection.support.TestContainerSpringBootTest; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.context.SecurityContextHolder; - -import static org.assertj.core.api.Assertions.assertThat; - -@TestContainerSpringBootTest -@DisplayName("만료된 토큰 argument resolver 테스트") -class ExpiredTokenResolverTest { - - @BeforeEach - void setUp() { - SecurityContextHolder.clearContext(); - } - - @Autowired - private ExpiredTokenResolver expiredTokenResolver; - - @Test - void security_context_에_저장된_만료시간을_검증하지_않는_토큰을_반환한다() throws Exception { - // given - ExpiredTokenAuthentication authentication = new ExpiredTokenAuthentication("token"); - SecurityContextHolder.getContext().setAuthentication(authentication); - - // when - ExpiredTokenAuthentication expiredTokenAuthentication = (ExpiredTokenAuthentication) expiredTokenResolver.resolveArgument(null, null, null, null); - - // then - assertThat(expiredTokenAuthentication.getToken()).isEqualTo("token"); - } - - @Test - void security_context_에_저장된_만료시간을_검증하지_않는_토큰이_없으면_null_을_반환한다() throws Exception { - // when, then - assertThat(expiredTokenResolver.resolveArgument(null, null, null, null)).isNull(); - } -} diff --git a/src/test/java/com/example/solidconnection/custom/security/authentication/ExpiredTokenAuthenticationTest.java b/src/test/java/com/example/solidconnection/custom/security/authentication/ExpiredTokenAuthenticationTest.java deleted file mode 100644 index 9ef78d0c7..000000000 --- a/src/test/java/com/example/solidconnection/custom/security/authentication/ExpiredTokenAuthenticationTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.example.solidconnection.custom.security.authentication; - -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.util.Date; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; - -@DisplayName("만료된 토큰 인증 정보 테스트") -class ExpiredTokenAuthenticationTest { - - @Test - void 인증_정보에_저장된_토큰을_반환한다() { - // given - String token = "token123"; - ExpiredTokenAuthentication auth = new ExpiredTokenAuthentication(token); - - // when - String result = auth.getToken(); - - // then - assertThat(result).isEqualTo(token); - } - - @Test - void 인증_정보에_저장된_토큰의_subject_를_반환한다() { - // given - String subject = "subject321"; - String token = createToken(subject); - ExpiredTokenAuthentication auth = new ExpiredTokenAuthentication(token, subject); - - // when - String result = auth.getSubject(); - - // then - assertThat(result).isEqualTo(subject); - } - - @Test - void 항상_isAuthenticated_는_false_를_반환한다() { - // given - ExpiredTokenAuthentication auth1 = new ExpiredTokenAuthentication("token"); - ExpiredTokenAuthentication auth2 = new ExpiredTokenAuthentication("token", "subject"); - - // when & then - assertAll( - () -> assertThat(auth1.isAuthenticated()).isFalse(), - () -> assertThat(auth2.isAuthenticated()).isFalse() - ); - } - - private String createToken(String subject) { - return Jwts.builder() - .setSubject(subject) - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() + 1000)) - .signWith(SignatureAlgorithm.HS256, "secret") - .compact(); - } -} diff --git a/src/test/java/com/example/solidconnection/custom/security/filter/JwtAuthenticationFilterTest.java b/src/test/java/com/example/solidconnection/custom/security/filter/JwtAuthenticationFilterTest.java index cbca9c5f2..61e4c9170 100644 --- a/src/test/java/com/example/solidconnection/custom/security/filter/JwtAuthenticationFilterTest.java +++ b/src/test/java/com/example/solidconnection/custom/security/filter/JwtAuthenticationFilterTest.java @@ -1,7 +1,6 @@ package com.example.solidconnection.custom.security.filter; import com.example.solidconnection.config.security.JwtProperties; -import com.example.solidconnection.custom.security.authentication.ExpiredTokenAuthentication; import com.example.solidconnection.custom.security.authentication.SiteUserAuthentication; import com.example.solidconnection.custom.security.userdetails.SiteUserDetailsService; import com.example.solidconnection.support.TestContainerSpringBootTest; @@ -12,7 +11,6 @@ import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; @@ -36,7 +34,7 @@ class JwtAuthenticationFilterTest { @Autowired private JwtProperties jwtProperties; - @MockBean + @MockBean // 이 테스트코드에서 사용자를 조회할 필요는 없으므로 MockBean 으로 대체 private SiteUserDetailsService siteUserDetailsService; private HttpServletRequest request; @@ -63,40 +61,20 @@ void setUp() { then(filterChain).should().doFilter(request, response); } - @Nested - class 토큰이_있으면_컨텍스트에_저장한다 { - - @Test - void 유효한_토큰을_컨텍스트에_저장한다() throws Exception { - // given - Date validExpiration = new Date(System.currentTimeMillis() + 1000); - String token = createTokenWithExpiration(validExpiration); - request = createRequestWithToken(token); - - // when - jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); - - // then - assertThat(SecurityContextHolder.getContext().getAuthentication()) - .isExactlyInstanceOf(SiteUserAuthentication.class); - then(filterChain).should().doFilter(request, response); - } - - @Test - void 만료된_토큰을_컨텍스트에_저장한다() throws Exception { - // given - Date invalidExpiration = new Date(System.currentTimeMillis() - 1000); - String token = createTokenWithExpiration(invalidExpiration); - request = createRequestWithToken(token); - - // when - jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); - - // then - assertThat(SecurityContextHolder.getContext().getAuthentication()) - .isExactlyInstanceOf(ExpiredTokenAuthentication.class); - then(filterChain).should().doFilter(request, response); - } + @Test + void 토큰이_있으면_컨텍스트에_저장한다() throws Exception { + // given + Date validExpiration = new Date(System.currentTimeMillis() + 1000); + String token = createTokenWithExpiration(validExpiration); + request = createRequestWithToken(token); + + // when + jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); + + // then + assertThat(SecurityContextHolder.getContext().getAuthentication()) + .isExactlyInstanceOf(SiteUserAuthentication.class); + then(filterChain).should().doFilter(request, response); } private String createTokenWithExpiration(Date expiration) { diff --git a/src/test/java/com/example/solidconnection/custom/security/provider/ExpiredTokenAuthenticationProviderTest.java b/src/test/java/com/example/solidconnection/custom/security/provider/ExpiredTokenAuthenticationProviderTest.java deleted file mode 100644 index ad6053359..000000000 --- a/src/test/java/com/example/solidconnection/custom/security/provider/ExpiredTokenAuthenticationProviderTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.example.solidconnection.custom.security.provider; - -import com.example.solidconnection.config.security.JwtProperties; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.custom.security.authentication.ExpiredTokenAuthentication; -import com.example.solidconnection.support.TestContainerSpringBootTest; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.Authentication; - -import java.net.PasswordAuthentication; -import java.util.Date; - -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_TOKEN; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.*; - -@TestContainerSpringBootTest -@DisplayName("만료된 토큰 provider 테스트") -class ExpiredTokenAuthenticationProviderTest { - - @Autowired - private ExpiredTokenAuthenticationProvider expiredTokenAuthenticationProvider; - - @Autowired - private JwtProperties jwtProperties; - - @Test - void 처리할_수_있는_타입인지를_반환한다() { - // given - Class supportedType = ExpiredTokenAuthentication.class; - Class notSupportedType = PasswordAuthentication.class; - - // when & then - assertAll( - () -> assertTrue(expiredTokenAuthenticationProvider.supports(supportedType)), - () -> assertFalse(expiredTokenAuthenticationProvider.supports(notSupportedType)) - ); - } - - @Test - void 만료된_토큰의_인증_정보를_반환한다() { - // given - String expiredToken = createExpiredToken(); - ExpiredTokenAuthentication ExpiredTokenAuthentication = new ExpiredTokenAuthentication(expiredToken); - - // when - Authentication result = expiredTokenAuthenticationProvider.authenticate(ExpiredTokenAuthentication); - - // then - assertAll( - () -> assertThat(result).isInstanceOf(ExpiredTokenAuthentication.class), - () -> assertThat(result.isAuthenticated()).isFalse() - ); - } - - @Test - void 유효하지_않은_토큰이면_예외_응답을_반환한다() { - // given - ExpiredTokenAuthentication ExpiredTokenAuthentication = new ExpiredTokenAuthentication("invalid token"); - - // when & then - assertThatCode(() -> expiredTokenAuthenticationProvider.authenticate(ExpiredTokenAuthentication)) - .isInstanceOf(CustomException.class) - .hasMessageContaining(INVALID_TOKEN.getMessage()); - } - - private String createExpiredToken() { - return Jwts.builder() - .setSubject("1") - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() - 1000)) - .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) - .compact(); - } -} diff --git a/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java b/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java index 95bdd5a52..0c16de671 100644 --- a/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java +++ b/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java @@ -12,7 +12,6 @@ import java.util.Date; import static com.example.solidconnection.util.JwtUtils.parseSubject; -import static com.example.solidconnection.util.JwtUtils.parseSubjectIgnoringExpiration; import static com.example.solidconnection.util.JwtUtils.parseTokenFromRequest; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; @@ -59,7 +58,7 @@ class 요청으로부터_토큰을_추출한다 { } @Nested - class 유효한_토큰으로부터_subject_를_추출한다 { + class 토큰으로부터_subject_를_추출한다 { @Test void 유효한_토큰의_subject_를_추출한다() { @@ -87,75 +86,6 @@ class 유효한_토큰으로부터_subject_를_추출한다 { } } - @Nested - class 만료된_토큰으로부터_subject_를_추출한다 { - - @Test - void 만료된_토큰의_subject_를_예외를_발생시키지_않고_추출한다() { - // given - String subject = "subject999"; - String token = createExpiredToken(subject); - - // when - String extractedSubject = parseSubjectIgnoringExpiration(token, jwtSecretKey); - - // then - assertThat(extractedSubject).isEqualTo(subject); - } - - @Test - void 유효하지_않은_토큰의_subject_를_추출하면_예외_응답을_반환한다() { - // given - String token = createExpiredUnsignedToken("hackers secret key"); - - // when & then - assertThatCode(() -> parseSubjectIgnoringExpiration(token, jwtSecretKey)) - .isInstanceOf(CustomException.class) - .hasMessage(ErrorCode.INVALID_TOKEN.getMessage()); - } - } - - - @Nested - class 토큰이_만료되었는지_확인한다 { - - @Test - void 서명된_토큰의_만료_여부를_반환한다() { - // given - String subject = "subject123"; - String validToken = createValidToken(subject); - String expiredToken = createExpiredToken(subject); - - // when - boolean isExpired1 = JwtUtils.isExpired(validToken, jwtSecretKey); - boolean isExpired2 = JwtUtils.isExpired(expiredToken, jwtSecretKey); - - // then - assertAll( - () -> assertThat(isExpired1).isFalse(), - () -> assertThat(isExpired2).isTrue() - ); - } - - @Test - void 서명되지_않은_토큰의_만료_여부를_반환한다() { - // given - String subject = "subject123"; - String validToken = createValidToken(subject); - String expiredToken = createExpiredToken(subject); - - // when - boolean isExpired1 = JwtUtils.isExpired(validToken, "wrong-secret-key"); - boolean isExpired2 = JwtUtils.isExpired(expiredToken, "wrong-secret-key"); - - // then - assertAll( - () -> assertThat(isExpired1).isTrue(), - () -> assertThat(isExpired2).isTrue() - ); - } - } - private String createValidToken(String subject) { return Jwts.builder() .setSubject(subject) @@ -173,13 +103,4 @@ private String createExpiredToken(String subject) { .signWith(SignatureAlgorithm.HS256, jwtSecretKey) .compact(); } - - private String createExpiredUnsignedToken(String jwtSecretKey) { - return Jwts.builder() - .setSubject("subject") - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() - 1000)) - .signWith(SignatureAlgorithm.HS256, jwtSecretKey) - .compact(); - } } From f9e05b0a383db28c67cfefb6e2e3592be1aaf7cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Sun, 11 May 2025 23:55:28 +0900 Subject: [PATCH 12/90] =?UTF-8?q?refactor:=20=EB=8C=80=ED=95=99=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A0=95=EC=9D=98=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EA=B0=9C=EC=84=A0=20(#286)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 메서드 체이닝으로 대학 test fixture 생성 * refactor: UniversityQueryServiceTest에 임시 반영 * chore: 테스트 환경에서 lombok 사용을 위한 의존성 추가 * test: 메서드 체이닝을 활용하여 대학 관련 Fixture 클래스 추가 * test: 대학 생성을 위한 Fixture Helper 클래스 추가 * test: Fixture Helper 클래스 실제 대학 테스트에 적용 * chore: 사용하지 않는 BuilderSupporter 클래스 제거 * refactor: 내가 생각한 방향 구현 * refactor: 테스트 코드 컴포넌트 @TestComponent로 변경 * refactor: 패키지 구조를 도메인 중심으로 개선 * feat: RegionFixture에 지역 생성 메서드 추가 * feat: CountryFixture에 국가 생성 메서드 추가 * feat: UniversityFixture에 대학 생성 메서드 추가 * feat: UniversityInfoForApplyFixture에 대학 지원 생성 메서드 추가 * feat: LanguageRequirementFixture에 언어 요구사항 생성 메서드 추가 * test: 추가한 Fixture 함수 테스트에 반영 * chore: 대학 정보 이름 실제 데이터와 일치하게 반영 * refactor: 언어 요구사항 데이터 세팅 방식 변경 --------- Co-authored-by: nayonsoso --- build.gradle | 2 + .../country/fixture/CountryFixture.java | 54 ++++++++ .../fixture/CountryFixtureBuilder.java | 42 ++++++ .../repository/CountryRepositoryForTest.java | 11 ++ .../region/fixture/RegionFixture.java | 33 +++++ .../region/fixture/RegionFixtureBuilder.java | 35 +++++ .../repository/RegionRepositoryForTest.java | 11 ++ .../support/TestContainerSpringBootTest.java | 2 + .../fixture/LanguageRequirementFixture.java | 54 ++++++++ .../LanguageRequirementFixtureBuilder.java | 45 +++++++ .../university/fixture/UniversityFixture.java | 97 ++++++++++++++ .../fixture/UniversityFixtureBuilder.java | 58 ++++++++ .../UniversityInfoForApplyFixture.java | 97 ++++++++++++++ .../UniversityInfoForApplyFixtureBuilder.java | 53 ++++++++ .../service/UniversityQueryServiceTest.java | 124 +++++++++--------- 15 files changed, 656 insertions(+), 62 deletions(-) create mode 100644 src/test/java/com/example/solidconnection/country/fixture/CountryFixture.java create mode 100644 src/test/java/com/example/solidconnection/country/fixture/CountryFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/country/repository/CountryRepositoryForTest.java create mode 100644 src/test/java/com/example/solidconnection/region/fixture/RegionFixture.java create mode 100644 src/test/java/com/example/solidconnection/region/fixture/RegionFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/region/repository/RegionRepositoryForTest.java create mode 100644 src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixture.java create mode 100644 src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/university/fixture/UniversityFixture.java create mode 100644 src/test/java/com/example/solidconnection/university/fixture/UniversityFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixture.java create mode 100644 src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixtureBuilder.java diff --git a/build.gradle b/build.gradle index b4267f41e..6f6a01f61 100644 --- a/build.gradle +++ b/build.gradle @@ -58,6 +58,8 @@ dependencies { testImplementation 'org.testcontainers:testcontainers' testImplementation 'org.testcontainers:junit-jupiter' testImplementation 'org.testcontainers:mysql' + testImplementation 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' // Etc implementation 'org.hibernate.validator:hibernate-validator' diff --git a/src/test/java/com/example/solidconnection/country/fixture/CountryFixture.java b/src/test/java/com/example/solidconnection/country/fixture/CountryFixture.java new file mode 100644 index 000000000..53f394eae --- /dev/null +++ b/src/test/java/com/example/solidconnection/country/fixture/CountryFixture.java @@ -0,0 +1,54 @@ +package com.example.solidconnection.country.fixture; + +import com.example.solidconnection.entity.Country; +import com.example.solidconnection.region.fixture.RegionFixture; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class CountryFixture { + + private final RegionFixture regionFixture; + private final CountryFixtureBuilder countryFixtureBuilder; + + public Country 미국() { + return countryFixtureBuilder.country() + .code("US") + .koreanName("미국") + .region(regionFixture.영미권()) + .findOrCreate(); + } + + public Country 캐나다() { + return countryFixtureBuilder.country() + .code("CA") + .koreanName("캐나다") + .region(regionFixture.영미권()) + .findOrCreate(); + } + + public Country 덴마크() { + return countryFixtureBuilder.country() + .code("DK") + .koreanName("덴마크") + .region(regionFixture.유럽()) + .findOrCreate(); + } + + public Country 오스트리아() { + return countryFixtureBuilder.country() + .code("AT") + .koreanName("오스트리아") + .region(regionFixture.유럽()) + .findOrCreate(); + } + + public Country 일본() { + return countryFixtureBuilder.country() + .code("JP") + .koreanName("일본") + .region(regionFixture.아시아()) + .findOrCreate(); + } +} diff --git a/src/test/java/com/example/solidconnection/country/fixture/CountryFixtureBuilder.java b/src/test/java/com/example/solidconnection/country/fixture/CountryFixtureBuilder.java new file mode 100644 index 000000000..e3ea004c5 --- /dev/null +++ b/src/test/java/com/example/solidconnection/country/fixture/CountryFixtureBuilder.java @@ -0,0 +1,42 @@ +package com.example.solidconnection.country.fixture; + +import com.example.solidconnection.entity.Country; +import com.example.solidconnection.entity.Region; +import com.example.solidconnection.country.repository.CountryRepositoryForTest; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class CountryFixtureBuilder { + + private final CountryRepositoryForTest countryRepositoryForTest; + + private String code; + private String koreanName; + private Region region; + + public CountryFixtureBuilder country() { + return new CountryFixtureBuilder(countryRepositoryForTest); + } + + public CountryFixtureBuilder code(String code) { + this.code = code; + return this; + } + + public CountryFixtureBuilder koreanName(String koreanName) { + this.koreanName = koreanName; + return this; + } + + public CountryFixtureBuilder region(Region region) { + this.region = region; + return this; + } + + public Country findOrCreate() { + return countryRepositoryForTest.findByCode(code) + .orElseGet(() -> countryRepositoryForTest.save(new Country(code, koreanName, region))); + } +} diff --git a/src/test/java/com/example/solidconnection/country/repository/CountryRepositoryForTest.java b/src/test/java/com/example/solidconnection/country/repository/CountryRepositoryForTest.java new file mode 100644 index 000000000..fc5dab0f9 --- /dev/null +++ b/src/test/java/com/example/solidconnection/country/repository/CountryRepositoryForTest.java @@ -0,0 +1,11 @@ +package com.example.solidconnection.country.repository; + +import com.example.solidconnection.entity.Country; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface CountryRepositoryForTest extends JpaRepository { + + Optional findByCode(String code); +} diff --git a/src/test/java/com/example/solidconnection/region/fixture/RegionFixture.java b/src/test/java/com/example/solidconnection/region/fixture/RegionFixture.java new file mode 100644 index 000000000..a1cd97e56 --- /dev/null +++ b/src/test/java/com/example/solidconnection/region/fixture/RegionFixture.java @@ -0,0 +1,33 @@ +package com.example.solidconnection.region.fixture; + +import com.example.solidconnection.entity.Region; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class RegionFixture { + + private final RegionFixtureBuilder regionFixtureBuilder; + + public Region 영미권() { + return regionFixtureBuilder.region() + .code("AMERICAS") + .koreanName("영미권") + .findOrCreate(); + } + + public Region 유럽() { + return regionFixtureBuilder.region() + .code("EUROPE") + .koreanName("유럽") + .findOrCreate(); + } + + public Region 아시아() { + return regionFixtureBuilder.region() + .code("ASIA") + .koreanName("아시아") + .findOrCreate(); + } +} diff --git a/src/test/java/com/example/solidconnection/region/fixture/RegionFixtureBuilder.java b/src/test/java/com/example/solidconnection/region/fixture/RegionFixtureBuilder.java new file mode 100644 index 000000000..a385a53f8 --- /dev/null +++ b/src/test/java/com/example/solidconnection/region/fixture/RegionFixtureBuilder.java @@ -0,0 +1,35 @@ +package com.example.solidconnection.region.fixture; + +import com.example.solidconnection.entity.Region; +import com.example.solidconnection.region.repository.RegionRepositoryForTest; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class RegionFixtureBuilder { + + private final RegionRepositoryForTest regionRepositoryForTest; + + private String code; + private String koreanName; + + public RegionFixtureBuilder region() { + return new RegionFixtureBuilder(regionRepositoryForTest); + } + + public RegionFixtureBuilder code(String code) { + this.code = code; + return this; + } + + public RegionFixtureBuilder koreanName(String koreanName) { + this.koreanName = koreanName; + return this; + } + + public Region findOrCreate() { + return regionRepositoryForTest.findByCode(code) + .orElseGet(() -> regionRepositoryForTest.save(new Region(code, koreanName))); + } +} diff --git a/src/test/java/com/example/solidconnection/region/repository/RegionRepositoryForTest.java b/src/test/java/com/example/solidconnection/region/repository/RegionRepositoryForTest.java new file mode 100644 index 000000000..00c35c02d --- /dev/null +++ b/src/test/java/com/example/solidconnection/region/repository/RegionRepositoryForTest.java @@ -0,0 +1,11 @@ +package com.example.solidconnection.region.repository; + +import com.example.solidconnection.entity.Region; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface RegionRepositoryForTest extends JpaRepository { + + Optional findByCode(String code); +} diff --git a/src/test/java/com/example/solidconnection/support/TestContainerSpringBootTest.java b/src/test/java/com/example/solidconnection/support/TestContainerSpringBootTest.java index 5c5c93742..462400400 100644 --- a/src/test/java/com/example/solidconnection/support/TestContainerSpringBootTest.java +++ b/src/test/java/com/example/solidconnection/support/TestContainerSpringBootTest.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.ComponentScan; import org.springframework.test.context.ContextConfiguration; import org.testcontainers.junit.jupiter.Testcontainers; @@ -11,6 +12,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +@ComponentScan(basePackages = "com.example.solidconnection") @ExtendWith({DatabaseClearExtension.class}) @ContextConfiguration(initializers = {RedisTestContainer.class, MySQLTestContainer.class}) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) diff --git a/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixture.java b/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixture.java new file mode 100644 index 000000000..ba32c1b27 --- /dev/null +++ b/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixture.java @@ -0,0 +1,54 @@ +package com.example.solidconnection.university.fixture; + +import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.university.domain.LanguageRequirement; +import com.example.solidconnection.university.domain.UniversityInfoForApply; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class LanguageRequirementFixture { + + private final LanguageRequirementFixtureBuilder languageRequirementFixtureBuilder; + + public LanguageRequirement 토플_80(UniversityInfoForApply universityInfo) { + return languageRequirementFixtureBuilder + .languageTestType(LanguageTestType.TOEFL_IBT) + .minScore("80") + .universityInfoForApply(universityInfo) + .create(); + } + + public LanguageRequirement 토플_70(UniversityInfoForApply universityInfo) { + return languageRequirementFixtureBuilder + .languageTestType(LanguageTestType.TOEFL_IBT) + .minScore("70") + .universityInfoForApply(universityInfo) + .create(); + } + + public LanguageRequirement 토익_800(UniversityInfoForApply universityInfo) { + return languageRequirementFixtureBuilder + .languageTestType(LanguageTestType.TOEIC) + .minScore("800") + .universityInfoForApply(universityInfo) + .create(); + } + + public LanguageRequirement 토익_900(UniversityInfoForApply universityInfo) { + return languageRequirementFixtureBuilder + .languageTestType(LanguageTestType.TOEIC) + .minScore("900") + .universityInfoForApply(universityInfo) + .create(); + } + + public LanguageRequirement JLPT_N2(UniversityInfoForApply universityInfo) { + return languageRequirementFixtureBuilder + .languageTestType(LanguageTestType.JLPT) + .minScore("N2") + .universityInfoForApply(universityInfo) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixtureBuilder.java b/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixtureBuilder.java new file mode 100644 index 000000000..e6f3fd4d5 --- /dev/null +++ b/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixtureBuilder.java @@ -0,0 +1,45 @@ +package com.example.solidconnection.university.fixture; + +import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.university.domain.LanguageRequirement; +import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.repository.LanguageRequirementRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class LanguageRequirementFixtureBuilder { + + private final LanguageRequirementRepository languageRequirementRepository; + + private LanguageTestType languageTestType; + private String minScore; + private UniversityInfoForApply universityInfoForApply; + + public LanguageRequirementFixtureBuilder languageTestType(LanguageTestType languageTestType) { + this.languageTestType = languageTestType; + return this; + } + + public LanguageRequirementFixtureBuilder minScore(String minScore) { + this.minScore = minScore; + return this; + } + + public LanguageRequirementFixtureBuilder universityInfoForApply(UniversityInfoForApply universityInfoForApply) { + this.universityInfoForApply = universityInfoForApply; + return this; + } + + public LanguageRequirement create() { + LanguageRequirement languageRequirement = new LanguageRequirement( + null, + languageTestType, + minScore, + universityInfoForApply + ); + universityInfoForApply.addLanguageRequirements(languageRequirement); + return languageRequirementRepository.save(languageRequirement); + } +} diff --git a/src/test/java/com/example/solidconnection/university/fixture/UniversityFixture.java b/src/test/java/com/example/solidconnection/university/fixture/UniversityFixture.java new file mode 100644 index 000000000..f3a41515f --- /dev/null +++ b/src/test/java/com/example/solidconnection/university/fixture/UniversityFixture.java @@ -0,0 +1,97 @@ +package com.example.solidconnection.university.fixture; + +import com.example.solidconnection.country.fixture.CountryFixture; +import com.example.solidconnection.region.fixture.RegionFixture; +import com.example.solidconnection.university.domain.University; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public final class UniversityFixture { + + private final RegionFixture regionFixture; + private final CountryFixture countryFixture; + private final UniversityFixtureBuilder universityFixtureBuilder; + + public University 괌_대학() { + return universityFixtureBuilder.university() + .koreanName("괌 대학") + .englishName("University of Guam") + .country(countryFixture.미국()) + .region(regionFixture.영미권()) + .create(); + } + + public University 네바다주립_대학_라스베이거스() { + return universityFixtureBuilder.university() + .koreanName("네바다주립 대학 라스베이거스") + .englishName("University of Nevada, Las Vegas") + .country(countryFixture.미국()) + .region(regionFixture.영미권()) + .create(); + } + + public University 메모리얼_대학_세인트존스() { + return universityFixtureBuilder.university() + .koreanName("메모리얼 대학 세인트존스") + .englishName("Memorial University of Newfoundland St. John's") + .country(countryFixture.캐나다()) + .region(regionFixture.영미권()) + .create(); + } + + public University 서던덴마크_대학() { + return universityFixtureBuilder.university() + .koreanName("서던덴마크 대학") + .englishName("University of Southern Denmark") + .country(countryFixture.덴마크()) + .region(regionFixture.유럽()) + .create(); + } + + public University 코펜하겐IT_대학() { + return universityFixtureBuilder.university() + .koreanName("코펜하겐IT 대학") + .englishName("IT University of Copenhagen") + .country(countryFixture.덴마크()) + .region(regionFixture.유럽()) + .create(); + } + + public University 그라츠_대학() { + return universityFixtureBuilder.university() + .koreanName("그라츠 대학") + .englishName("University of Graz") + .country(countryFixture.오스트리아()) + .region(regionFixture.유럽()) + .create(); + } + + public University 그라츠공과_대학() { + return universityFixtureBuilder.university() + .koreanName("그라츠공과 대학") + .englishName("Graz University of Technology") + .country(countryFixture.오스트리아()) + .region(regionFixture.유럽()) + .create(); + } + + public University 린츠_카톨릭_대학() { + return universityFixtureBuilder.university() + .koreanName("린츠 카톨릭 대학") + .englishName("Catholic Private University Linz") + .country(countryFixture.오스트리아()) + .region(regionFixture.유럽()) + .create(); + } + + public University 메이지_대학() { + return universityFixtureBuilder.university() + .koreanName("메이지 대학") + .englishName("Meiji University") + .country(countryFixture.일본()) + .region(regionFixture.아시아()) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/university/fixture/UniversityFixtureBuilder.java b/src/test/java/com/example/solidconnection/university/fixture/UniversityFixtureBuilder.java new file mode 100644 index 000000000..f51ea9677 --- /dev/null +++ b/src/test/java/com/example/solidconnection/university/fixture/UniversityFixtureBuilder.java @@ -0,0 +1,58 @@ +package com.example.solidconnection.university.fixture; + +import com.example.solidconnection.entity.Country; +import com.example.solidconnection.entity.Region; +import com.example.solidconnection.university.domain.University; +import com.example.solidconnection.university.repository.UniversityRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class UniversityFixtureBuilder { + + private final UniversityRepository universityRepository; + + private String koreanName; + private String englishName; + private Country country; + private Region region; + + public UniversityFixtureBuilder university() { + return new UniversityFixtureBuilder(universityRepository); + } + + public UniversityFixtureBuilder koreanName(String koreanName) { + this.koreanName = koreanName; + return this; + } + + public UniversityFixtureBuilder englishName(String englishName) { + this.englishName = englishName; + return this; + } + + public UniversityFixtureBuilder country(Country country) { + this.country = country; + return this; + } + + public UniversityFixtureBuilder region(Region region) { + this.region = region; + return this; + } + + public University create() { + University university = new University( + null, koreanName, englishName, + "formatName", + "https://homepage-url", + "https://english-course-url", + "https://accommodation-url", + "https://logo-image-url", + "https://background-image-url", + null, country, region + ); + return universityRepository.save(university); + } +} diff --git a/src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixture.java b/src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixture.java new file mode 100644 index 000000000..32a32cf6d --- /dev/null +++ b/src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixture.java @@ -0,0 +1,97 @@ +package com.example.solidconnection.university.fixture; + +import com.example.solidconnection.university.domain.UniversityInfoForApply; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class UniversityInfoForApplyFixture { + + private final UniversityInfoForApplyFixtureBuilder universityInfoForApplyFixtureBuilder; + private final UniversityFixture universityFixture; + + @Value("${university.term}") + public String term; + + public UniversityInfoForApply 괌대학_A_지원_정보() { + return universityInfoForApplyFixtureBuilder.universityInfoForApply() + .term(term) + .koreanName("괌대학(A형)") + .university(universityFixture.괌_대학()) + .create(); + } + + public UniversityInfoForApply 괌대학_B_지원_정보() { + return universityInfoForApplyFixtureBuilder.universityInfoForApply() + .term(term) + .koreanName("괌대학(B형)") + .university(universityFixture.괌_대학()) + .create(); + } + + public UniversityInfoForApply 네바다주립대학_라스베이거스_지원_정보() { + return universityInfoForApplyFixtureBuilder.universityInfoForApply() + .term(term) + .koreanName("네바다주립대학 라스베이거스(B형)") + .university(universityFixture.네바다주립_대학_라스베이거스()) + .create(); + } + + public UniversityInfoForApply 메모리얼대학_세인트존스_A_지원_정보() { + return universityInfoForApplyFixtureBuilder.universityInfoForApply() + .term(term) + .koreanName("메모리얼 대학 세인트존스(A형)") + .university(universityFixture.메모리얼_대학_세인트존스()) + .create(); + } + + public UniversityInfoForApply 서던덴마크대학교_지원_정보() { + return universityInfoForApplyFixtureBuilder.universityInfoForApply() + .term(term) + .koreanName("서던덴마크대학교") + .university(universityFixture.서던덴마크_대학()) + .create(); + } + + public UniversityInfoForApply 코펜하겐IT대학_지원_정보() { + return universityInfoForApplyFixtureBuilder.universityInfoForApply() + .term(term) + .koreanName("코펜하겐 IT대학") + .university(universityFixture.코펜하겐IT_대학()) + .create(); + } + + public UniversityInfoForApply 그라츠대학_지원_정보() { + return universityInfoForApplyFixtureBuilder.universityInfoForApply() + .term(term) + .koreanName("그라츠 대학") + .university(universityFixture.그라츠_대학()) + .create(); + } + + public UniversityInfoForApply 그라츠공과대학_지원_정보() { + return universityInfoForApplyFixtureBuilder.universityInfoForApply() + .term(term) + .koreanName("그라츠공과대학") + .university(universityFixture.그라츠공과_대학()) + .create(); + } + + public UniversityInfoForApply 린츠_카톨릭대학_지원_정보() { + return universityInfoForApplyFixtureBuilder.universityInfoForApply() + .term(term) + .koreanName("린츠 카톨릭 대학교") + .university(universityFixture.린츠_카톨릭_대학()) + .create(); + } + + public UniversityInfoForApply 메이지대학_지원_정보() { + return universityInfoForApplyFixtureBuilder.universityInfoForApply() + .term(term) + .koreanName("메이지대학") + .university(universityFixture.메이지_대학()) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixtureBuilder.java b/src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixtureBuilder.java new file mode 100644 index 000000000..8041bda1e --- /dev/null +++ b/src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixtureBuilder.java @@ -0,0 +1,53 @@ +package com.example.solidconnection.university.fixture; + +import com.example.solidconnection.university.domain.University; +import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +import java.util.HashSet; + +import static com.example.solidconnection.type.SemesterAvailableForDispatch.ONE_SEMESTER; +import static com.example.solidconnection.type.TuitionFeeType.HOME_UNIVERSITY_PAYMENT; + +@TestComponent +@RequiredArgsConstructor +public class UniversityInfoForApplyFixtureBuilder { + + private final UniversityInfoForApplyRepository universityInfoForApplyRepository; + + private String term; + private String koreanName; + private University university; + + public UniversityInfoForApplyFixtureBuilder universityInfoForApply() { + return new UniversityInfoForApplyFixtureBuilder(universityInfoForApplyRepository); + } + + public UniversityInfoForApplyFixtureBuilder term(String term) { + this.term = term; + return this; + } + + public UniversityInfoForApplyFixtureBuilder koreanName(String koreanName) { + this.koreanName = koreanName; + return this; + } + + public UniversityInfoForApplyFixtureBuilder university(University university) { + this.university = university; + return this; + } + + public UniversityInfoForApply create() { + UniversityInfoForApply universityInfoForApply = new UniversityInfoForApply( + null, term, koreanName, 1, HOME_UNIVERSITY_PAYMENT, ONE_SEMESTER, + "1", "detailsForLanguage", "gpaRequirement", + "gpaRequirementCriteria", "detailsForApply", "detailsForMajor", + "detailsForAccommodation", "detailsForEnglishCourse", "details", + new HashSet<>(), university + ); + return universityInfoForApplyRepository.save(universityInfoForApply); + } +} diff --git a/src/test/java/com/example/solidconnection/university/service/UniversityQueryServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UniversityQueryServiceTest.java index 1cd0d755f..731ccec5b 100644 --- a/src/test/java/com/example/solidconnection/university/service/UniversityQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UniversityQueryServiceTest.java @@ -1,15 +1,16 @@ package com.example.solidconnection.university.service; import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.support.integration.BaseIntegrationTest; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import com.example.solidconnection.university.fixture.LanguageRequirementFixture; +import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.dto.UniversityDetailResponse; -import com.example.solidconnection.university.dto.LanguageRequirementResponse; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponses; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; import com.example.solidconnection.university.repository.custom.UniversityFilterRepository; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -17,14 +18,15 @@ import java.util.List; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; import static com.example.solidconnection.custom.exception.ErrorCode.UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.times; +@TestContainerSpringBootTest @DisplayName("대학교 조회 서비스 테스트") -class UniversityQueryServiceTest extends BaseIntegrationTest { +class UniversityQueryServiceTest { @Autowired private UniversityQueryService universityQueryService; @@ -35,60 +37,36 @@ class UniversityQueryServiceTest extends BaseIntegrationTest { @SpyBean private UniversityInfoForApplyRepository universityInfoForApplyRepository; + @Autowired + private UniversityInfoForApplyFixture universityInfoForApplyFixture; + + @Autowired + private LanguageRequirementFixture languageRequirementFixture; + @Test - void 대학_상세정보를_정상_조회한다() { + void 대학_상세정보를_정상_조회한다() { // given - Long universityId = 괌대학_A_지원_정보.getId(); + UniversityInfoForApply 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); // when - UniversityDetailResponse response = universityQueryService.getUniversityDetail(universityId); + UniversityDetailResponse response = universityQueryService.getUniversityDetail(괌대학_A_지원_정보.getId()); // then - Assertions.assertAll( - () -> assertThat(response.id()).isEqualTo(괌대학_A_지원_정보.getId()), - () -> assertThat(response.term()).isEqualTo(괌대학_A_지원_정보.getTerm()), - () -> assertThat(response.koreanName()).isEqualTo(괌대학_A_지원_정보.getKoreanName()), - () -> assertThat(response.englishName()).isEqualTo(영미권_미국_괌대학.getEnglishName()), - () -> assertThat(response.formatName()).isEqualTo(영미권_미국_괌대학.getFormatName()), - () -> assertThat(response.region()).isEqualTo(영미권.getKoreanName()), - () -> assertThat(response.country()).isEqualTo(미국.getKoreanName()), - () -> assertThat(response.homepageUrl()).isEqualTo(영미권_미국_괌대학.getHomepageUrl()), - () -> assertThat(response.logoImageUrl()).isEqualTo(영미권_미국_괌대학.getLogoImageUrl()), - () -> assertThat(response.backgroundImageUrl()).isEqualTo(영미권_미국_괌대학.getBackgroundImageUrl()), - () -> assertThat(response.detailsForLocal()).isEqualTo(영미권_미국_괌대학.getDetailsForLocal()), - () -> assertThat(response.studentCapacity()).isEqualTo(괌대학_A_지원_정보.getStudentCapacity()), - () -> assertThat(response.tuitionFeeType()).isEqualTo(괌대학_A_지원_정보.getTuitionFeeType().getKoreanName()), - () -> assertThat(response.semesterAvailableForDispatch()).isEqualTo(괌대학_A_지원_정보.getSemesterAvailableForDispatch().getKoreanName()), - () -> assertThat(response.languageRequirements()).containsOnlyOnceElementsOf( - 괌대학_A_지원_정보.getLanguageRequirements().stream() - .map(LanguageRequirementResponse::from) - .toList()), - () -> assertThat(response.detailsForLanguage()).isEqualTo(괌대학_A_지원_정보.getDetailsForLanguage()), - () -> assertThat(response.gpaRequirement()).isEqualTo(괌대학_A_지원_정보.getGpaRequirement()), - () -> assertThat(response.gpaRequirementCriteria()).isEqualTo(괌대학_A_지원_정보.getGpaRequirementCriteria()), - () -> assertThat(response.semesterRequirement()).isEqualTo(괌대학_A_지원_정보.getSemesterRequirement()), - () -> assertThat(response.detailsForApply()).isEqualTo(괌대학_A_지원_정보.getDetailsForApply()), - () -> assertThat(response.detailsForMajor()).isEqualTo(괌대학_A_지원_정보.getDetailsForMajor()), - () -> assertThat(response.detailsForAccommodation()).isEqualTo(괌대학_A_지원_정보.getDetailsForAccommodation()), - () -> assertThat(response.detailsForEnglishCourse()).isEqualTo(괌대학_A_지원_정보.getDetailsForEnglishCourse()), - () -> assertThat(response.details()).isEqualTo(괌대학_A_지원_정보.getDetails()), - () -> assertThat(response.accommodationUrl()).isEqualTo(괌대학_A_지원_정보.getUniversity().getAccommodationUrl()), - () -> assertThat(response.englishCourseUrl()).isEqualTo(괌대학_A_지원_정보.getUniversity().getEnglishCourseUrl()) - ); + assertThat(response.id()).isEqualTo(괌대학_A_지원_정보.getId()); } @Test void 대학_상세정보_조회시_캐시가_적용된다() { // given - Long universityId = 괌대학_A_지원_정보.getId(); + UniversityInfoForApply 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); // when - UniversityDetailResponse firstResponse = universityQueryService.getUniversityDetail(universityId); - UniversityDetailResponse secondResponse = universityQueryService.getUniversityDetail(universityId); + UniversityDetailResponse firstResponse = universityQueryService.getUniversityDetail(괌대학_A_지원_정보.getId()); + UniversityDetailResponse secondResponse = universityQueryService.getUniversityDetail(괌대학_A_지원_정보.getId()); // then assertThat(firstResponse).isEqualTo(secondResponse); - then(universityInfoForApplyRepository).should(times(1)).getUniversityInfoForApplyById(universityId); + then(universityInfoForApplyRepository).should(times(1)).getUniversityInfoForApplyById(괌대학_A_지원_정보.getId()); } @Test @@ -106,6 +84,14 @@ class UniversityQueryServiceTest extends BaseIntegrationTest { @Test void 전체_대학을_조회한다() { + // given + UniversityInfoForApply 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); + UniversityInfoForApply 괌대학_B_지원_정보 = universityInfoForApplyFixture.괌대학_B_지원_정보(); + UniversityInfoForApply 네바다주립대학_라스베이거스_지원_정보 = universityInfoForApplyFixture.네바다주립대학_라스베이거스_지원_정보(); + UniversityInfoForApply 서던덴마크대학교_지원_정보 = universityInfoForApplyFixture.서던덴마크대학교_지원_정보(); + UniversityInfoForApply 그라츠대학_지원_정보 = universityInfoForApplyFixture.그라츠대학_지원_정보(); + UniversityInfoForApply 메이지대학_지원_정보 = universityInfoForApplyFixture.메이지대학_지원_정보(); + // when UniversityInfoForApplyPreviewResponses response = universityQueryService.searchUniversity( null, List.of(), null, null); @@ -116,12 +102,8 @@ class UniversityQueryServiceTest extends BaseIntegrationTest { UniversityInfoForApplyPreviewResponse.from(괌대학_A_지원_정보), UniversityInfoForApplyPreviewResponse.from(괌대학_B_지원_정보), UniversityInfoForApplyPreviewResponse.from(네바다주립대학_라스베이거스_지원_정보), - UniversityInfoForApplyPreviewResponse.from(메모리얼대학_세인트존스_A_지원_정보), UniversityInfoForApplyPreviewResponse.from(서던덴마크대학교_지원_정보), - UniversityInfoForApplyPreviewResponse.from(코펜하겐IT대학_지원_정보), UniversityInfoForApplyPreviewResponse.from(그라츠대학_지원_정보), - UniversityInfoForApplyPreviewResponse.from(그라츠공과대학_지원_정보), - UniversityInfoForApplyPreviewResponse.from(린츠_카톨릭대학_지원_정보), UniversityInfoForApplyPreviewResponse.from(메이지대학_지원_정보) ); } @@ -129,7 +111,8 @@ class UniversityQueryServiceTest extends BaseIntegrationTest { @Test void 대학_조회시_캐시가_적용된다() { // given - String regionCode = 영미권.getCode(); + universityInfoForApplyFixture.괌대학_A_지원_정보(); + String regionCode = "AMERICAS"; List keywords = List.of("괌"); LanguageTestType testType = LanguageTestType.TOEFL_IBT; String testScore = "70"; @@ -150,22 +133,28 @@ class UniversityQueryServiceTest extends BaseIntegrationTest { @Test void 지역으로_대학을_필터링한다() { + // given + UniversityInfoForApply 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); + universityInfoForApplyFixture.코펜하겐IT대학_지원_정보(); + universityInfoForApplyFixture.그라츠공과대학_지원_정보(); + universityInfoForApplyFixture.메이지대학_지원_정보(); + // when UniversityInfoForApplyPreviewResponses response = universityQueryService.searchUniversity( - 영미권.getCode(), List.of(), null, null); + "AMERICAS", List.of(), null, null); // then assertThat(response.universityInfoForApplyPreviewResponses()) - .containsExactlyInAnyOrder( - UniversityInfoForApplyPreviewResponse.from(괌대학_A_지원_정보), - UniversityInfoForApplyPreviewResponse.from(괌대학_B_지원_정보), - UniversityInfoForApplyPreviewResponse.from(네바다주립대학_라스베이거스_지원_정보), - UniversityInfoForApplyPreviewResponse.from(메모리얼대학_세인트존스_A_지원_정보) - ); + .containsExactlyInAnyOrder(UniversityInfoForApplyPreviewResponse.from(괌대학_A_지원_정보)); } @Test void 키워드로_대학을_필터링한다() { + // given + universityInfoForApplyFixture.괌대학_A_지원_정보(); + UniversityInfoForApply 그라츠대학_지원_정보 = universityInfoForApplyFixture.그라츠대학_지원_정보(); + UniversityInfoForApply 메이지대학_지원_정보 = universityInfoForApplyFixture.메이지대학_지원_정보(); + // when UniversityInfoForApplyPreviewResponses response = universityQueryService.searchUniversity( null, List.of("라", "일본"), null, null); @@ -173,34 +162,45 @@ class UniversityQueryServiceTest extends BaseIntegrationTest { // then assertThat(response.universityInfoForApplyPreviewResponses()) .containsExactlyInAnyOrder( - UniversityInfoForApplyPreviewResponse.from(네바다주립대학_라스베이거스_지원_정보), UniversityInfoForApplyPreviewResponse.from(그라츠대학_지원_정보), - UniversityInfoForApplyPreviewResponse.from(그라츠공과대학_지원_정보), UniversityInfoForApplyPreviewResponse.from(메이지대학_지원_정보) ); } @Test void 어학시험_조건으로_대학을_필터링한다() { + // given + UniversityInfoForApply 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); + languageRequirementFixture.토플_80(괌대학_A_지원_정보); + languageRequirementFixture.토익_800(괌대학_A_지원_정보); + UniversityInfoForApply 괌대학_B_지원_정보 = universityInfoForApplyFixture.괌대학_B_지원_정보(); + languageRequirementFixture.토플_70(괌대학_B_지원_정보); + languageRequirementFixture.토익_900(괌대학_B_지원_정보); + // when UniversityInfoForApplyPreviewResponses response = universityQueryService.searchUniversity( null, List.of(), LanguageTestType.TOEFL_IBT, "70"); // then assertThat(response.universityInfoForApplyPreviewResponses()) - .containsExactlyInAnyOrder( - UniversityInfoForApplyPreviewResponse.from(괌대학_B_지원_정보), - UniversityInfoForApplyPreviewResponse.from(서던덴마크대학교_지원_정보) - ); + .containsExactlyInAnyOrder(UniversityInfoForApplyPreviewResponse.from(괌대학_B_지원_정보)); } @Test void 모든_조건으로_대학을_필터링한다() { + // given + UniversityInfoForApply 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); + languageRequirementFixture.토플_80(괌대학_A_지원_정보); + languageRequirementFixture.토익_800(괌대학_A_지원_정보); + UniversityInfoForApply 서던덴마크대학교_지원_정보 = universityInfoForApplyFixture.서던덴마크대학교_지원_정보(); + languageRequirementFixture.토플_70(서던덴마크대학교_지원_정보); + // when UniversityInfoForApplyPreviewResponses response = universityQueryService.searchUniversity( "EUROPE", List.of(), LanguageTestType.TOEFL_IBT, "70"); // then - assertThat(response.universityInfoForApplyPreviewResponses()).containsExactly(UniversityInfoForApplyPreviewResponse.from(서던덴마크대학교_지원_정보)); + assertThat(response.universityInfoForApplyPreviewResponses()) + .containsExactly(UniversityInfoForApplyPreviewResponse.from(서던덴마크대학교_지원_정보)); } } From 5f1474a53f4dd7a63b72325108b19dd038b926c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Mon, 12 May 2025 00:01:05 +0900 Subject: [PATCH 13/90] =?UTF-8?q?refactor:=20=ED=9A=8C=EC=9B=90=20?= =?UTF-8?q?=ED=83=88=ED=87=B4=20API=20HTTP=20Method=EB=A5=BC=20PATCH?= =?UTF-8?q?=EC=97=90=EC=84=9C=20DELETE=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#3?= =?UTF-8?q?11)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 회원 탈퇴 API HTTP Method를 PATCH에서 DELETE로 변경 * chore: 불필요한 import 제거 --- .../solidconnection/auth/controller/AuthController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index 553ef4063..064aa88aa 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -26,7 +26,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; -import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -102,7 +102,7 @@ public ResponseEntity signOut( return ResponseEntity.ok().build(); } - @PatchMapping("/quit") + @DeleteMapping("/quit") public ResponseEntity quit( @AuthorizedUser SiteUser siteUser, Authentication authentication // todo: #299를 작업하며 인자를 (Authentication authentication)만 받도록 수정해야 함 From 8a845356fb4e21141a1f4b58de09b7e4fbfc17e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Tue, 13 May 2025 00:56:23 +0900 Subject: [PATCH 14/90] =?UTF-8?q?refactor:=20=EB=8C=80=ED=95=99(=EC=B6=94?= =?UTF-8?q?=EC=B2=9C,=20=EC=A2=8B=EC=95=84=EC=9A=94)=20=ED=86=B5=ED=95=A9?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?Fixture=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20(#313)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 대학 좋아요 통합 테스트 데이터 Fixture 메서드로 변경 * refactor: 대학 추천 통합 테스트 데이터 Fixture 메서드로 변경 --- ...GeneralUniversityRecommendServiceTest.java | 26 +++++++-- .../service/UniversityLikeServiceTest.java | 34 +++++------ .../UniversityRecommendServiceTest.java | 56 +++++++++++++++---- 3 files changed, 84 insertions(+), 32 deletions(-) diff --git a/src/test/java/com/example/solidconnection/university/service/GeneralUniversityRecommendServiceTest.java b/src/test/java/com/example/solidconnection/university/service/GeneralUniversityRecommendServiceTest.java index d93765a44..c431bca6c 100644 --- a/src/test/java/com/example/solidconnection/university/service/GeneralUniversityRecommendServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/GeneralUniversityRecommendServiceTest.java @@ -1,8 +1,9 @@ package com.example.solidconnection.university.service; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.support.integration.BaseIntegrationTest; import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -14,20 +15,37 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; -@DisplayName("공통 추천 대학 서비스 테스트") @TestContainerSpringBootTest -class GeneralUniversityRecommendServiceTest extends BaseIntegrationTest { +@DisplayName("공통 추천 대학 서비스 테스트") +class GeneralUniversityRecommendServiceTest { @Autowired private GeneralUniversityRecommendService generalUniversityRecommendService; + @Autowired + private UniversityInfoForApplyFixture universityInfoForApplyFixture; + @Value("${university.term}") private String term; + @BeforeEach + void setUp() { + universityInfoForApplyFixture.괌대학_A_지원_정보(); + universityInfoForApplyFixture.괌대학_B_지원_정보(); + universityInfoForApplyFixture.네바다주립대학_라스베이거스_지원_정보(); + universityInfoForApplyFixture.메모리얼대학_세인트존스_A_지원_정보(); + universityInfoForApplyFixture.서던덴마크대학교_지원_정보(); + universityInfoForApplyFixture.코펜하겐IT대학_지원_정보(); + universityInfoForApplyFixture.그라츠대학_지원_정보(); + universityInfoForApplyFixture.그라츠공과대학_지원_정보(); + universityInfoForApplyFixture.린츠_카톨릭대학_지원_정보(); + universityInfoForApplyFixture.메이지대학_지원_정보(); + generalUniversityRecommendService.init(); + } + @Test void 모집_시기의_대학들_중에서_랜덤하게_N개를_추천_목록으로_구성한다() { // given - generalUniversityRecommendService.init(); List universities = generalUniversityRecommendService.getRecommendUniversities(); // when & then diff --git a/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java index 6bc3c13e3..3b2ab8c22 100644 --- a/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java @@ -4,13 +4,15 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.support.integration.BaseIntegrationTest; +import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.type.PreparationStatus; import com.example.solidconnection.type.Role; import com.example.solidconnection.university.domain.LikedUniversity; import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.dto.IsLikeResponse; import com.example.solidconnection.university.dto.LikeResultResponse; +import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -25,8 +27,9 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; +@TestContainerSpringBootTest @DisplayName("대학교 좋아요 서비스 테스트") -class UniversityLikeServiceTest extends BaseIntegrationTest { +class UniversityLikeServiceTest { @Autowired private UniversityLikeService universityLikeService; @@ -37,14 +40,23 @@ class UniversityLikeServiceTest extends BaseIntegrationTest { @Autowired private SiteUserRepository siteUserRepository; + @Autowired + private UniversityInfoForApplyFixture universityInfoForApplyFixture; + + private SiteUser testUser; + private UniversityInfoForApply 괌대학_A_지원_정보; + + @BeforeEach + void setUp() { + testUser = createSiteUser(); + 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); + } + @Nested class 대학_좋아요를_등록한다 { @Test void 성공적으로_좋아요를_등록한다() { - // given - SiteUser testUser = createSiteUser(); - // when LikeResultResponse response = universityLikeService.likeUniversity(testUser, 괌대학_A_지원_정보.getId()); @@ -60,7 +72,6 @@ class 대학_좋아요를_등록한다 { @Test void 이미_좋아요한_대학이면_예외_응답을_반환한다() { // given - SiteUser testUser = createSiteUser(); saveLikedUniversity(testUser, 괌대학_A_지원_정보); // when & then @@ -76,7 +87,6 @@ class 대학_좋아요를_취소한다 { @Test void 성공적으로_좋아요를_취소한다() { // given - SiteUser testUser = createSiteUser(); saveLikedUniversity(testUser, 괌대학_A_지원_정보); // when @@ -93,9 +103,6 @@ class 대학_좋아요를_취소한다 { @Test void 좋아요하지_않은_대학이면_예외_응답을_반환한다() { - // given - SiteUser testUser = createSiteUser(); - // when & then assertThatCode(() -> universityLikeService.cancelLikeUniversity(testUser, 괌대학_A_지원_정보.getId())) .isInstanceOf(CustomException.class) @@ -106,7 +113,6 @@ class 대학_좋아요를_취소한다 { @Test void 존재하지_않는_대학_좋아요_시도하면_예외_응답을_반환한다() { // given - SiteUser testUser = createSiteUser(); Long invalidUniversityId = 9999L; // when & then @@ -118,7 +124,6 @@ class 대학_좋아요를_취소한다 { @Test void 좋아요한_대학인지_확인한다() { // given - SiteUser testUser = createSiteUser(); saveLikedUniversity(testUser, 괌대학_A_지원_정보); // when @@ -130,9 +135,6 @@ class 대학_좋아요를_취소한다 { @Test void 좋아요하지_않은_대학인지_확인한다() { - // given - SiteUser testUser = createSiteUser(); - // when IsLikeResponse response = universityLikeService.getIsLiked(testUser, 괌대학_A_지원_정보.getId()); @@ -143,7 +145,6 @@ class 대학_좋아요를_취소한다 { @Test void 존재하지_않는_대학의_좋아요_여부를_조회하면_예외_응답을_반환한다() { // given - SiteUser testUser = createSiteUser(); Long invalidUniversityId = 9999L; // when & then @@ -152,6 +153,7 @@ class 대학_좋아요를_취소한다 { .hasMessage(UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND.getMessage()); } + // todo : 추후 Fixture로 대체 필요 private SiteUser createSiteUser() { SiteUser siteUser = new SiteUser( "test@example.com", diff --git a/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java index 11591d11f..7645794bf 100644 --- a/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java @@ -1,16 +1,20 @@ package com.example.solidconnection.university.service; +import com.example.solidconnection.country.fixture.CountryFixture; import com.example.solidconnection.entity.InterestedCountry; import com.example.solidconnection.entity.InterestedRegion; +import com.example.solidconnection.region.fixture.RegionFixture; import com.example.solidconnection.repositories.InterestedCountyRepository; import com.example.solidconnection.repositories.InterestedRegionRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.support.integration.BaseIntegrationTest; +import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.type.PreparationStatus; import com.example.solidconnection.type.Role; +import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; import com.example.solidconnection.university.dto.UniversityRecommendsResponse; +import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -21,8 +25,9 @@ import static com.example.solidconnection.university.service.UniversityRecommendService.RECOMMEND_UNIVERSITY_NUM; import static org.assertj.core.api.Assertions.assertThat; +@TestContainerSpringBootTest @DisplayName("대학교 추천 서비스 테스트") -class UniversityRecommendServiceTest extends BaseIntegrationTest { +class UniversityRecommendServiceTest { @Autowired private UniversityRecommendService universityRecommendService; @@ -39,16 +44,47 @@ class UniversityRecommendServiceTest extends BaseIntegrationTest { @Autowired private GeneralUniversityRecommendService generalUniversityRecommendService; + @Autowired + private RegionFixture regionFixture; + + @Autowired + private CountryFixture countryFixture; + + @Autowired + private UniversityInfoForApplyFixture universityInfoForApplyFixture; + + private SiteUser testUser; + private UniversityInfoForApply 괌대학_A_지원_정보; + private UniversityInfoForApply 괌대학_B_지원_정보; + private UniversityInfoForApply 네바다주립대학_라스베이거스_지원_정보; + private UniversityInfoForApply 메모리얼대학_세인트존스_A_지원_정보; + private UniversityInfoForApply 서던덴마크대학교_지원_정보; + private UniversityInfoForApply 코펜하겐IT대학_지원_정보; + private UniversityInfoForApply 그라츠대학_지원_정보; + private UniversityInfoForApply 그라츠공과대학_지원_정보; + private UniversityInfoForApply 린츠_카톨릭대학_지원_정보; + private UniversityInfoForApply 메이지대학_지원_정보; + @BeforeEach void setUp() { + testUser = createSiteUser(); + 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); + 괌대학_B_지원_정보 = universityInfoForApplyFixture.괌대학_B_지원_정보(); + 네바다주립대학_라스베이거스_지원_정보 = universityInfoForApplyFixture.네바다주립대학_라스베이거스_지원_정보(); + 메모리얼대학_세인트존스_A_지원_정보 = universityInfoForApplyFixture.메모리얼대학_세인트존스_A_지원_정보(); + 서던덴마크대학교_지원_정보 = universityInfoForApplyFixture.서던덴마크대학교_지원_정보(); + 코펜하겐IT대학_지원_정보 = universityInfoForApplyFixture.코펜하겐IT대학_지원_정보(); + 그라츠대학_지원_정보 = universityInfoForApplyFixture.그라츠대학_지원_정보(); + 그라츠공과대학_지원_정보 = universityInfoForApplyFixture.그라츠공과대학_지원_정보(); + 린츠_카톨릭대학_지원_정보 = universityInfoForApplyFixture.린츠_카톨릭대학_지원_정보(); + 메이지대학_지원_정보 = universityInfoForApplyFixture.메이지대학_지원_정보(); generalUniversityRecommendService.init(); } @Test void 관심_지역_설정한_사용자의_맞춤_추천_대학을_조회한다() { // given - SiteUser testUser = createSiteUser(); - interestedRegionRepository.save(new InterestedRegion(testUser, 영미권)); + interestedRegionRepository.save(new InterestedRegion(testUser, regionFixture.영미권())); // when UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(testUser); @@ -67,8 +103,7 @@ void setUp() { @Test void 관심_국가_설정한_사용자의_맞춤_추천_대학을_조회한다() { // given - SiteUser testUser = createSiteUser(); - interestedCountyRepository.save(new InterestedCountry(testUser, 덴마크)); + interestedCountyRepository.save(new InterestedCountry(testUser, countryFixture.덴마크())); // when UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(testUser); @@ -85,9 +120,8 @@ void setUp() { @Test void 관심_지역과_국가_모두_설정한_사용자의_맞춤_추천_대학을_조회한다() { // given - SiteUser testUser = createSiteUser(); - interestedRegionRepository.save(new InterestedRegion(testUser, 영미권)); - interestedCountyRepository.save(new InterestedCountry(testUser, 덴마크)); + interestedRegionRepository.save(new InterestedRegion(testUser, regionFixture.영미권())); + interestedCountyRepository.save(new InterestedCountry(testUser, countryFixture.덴마크())); // when UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(testUser); @@ -107,9 +141,6 @@ void setUp() { @Test void 관심사_미설정_사용자는_일반_추천_대학을_조회한다() { - // given - SiteUser testUser = createSiteUser(); - // when UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(testUser); @@ -138,6 +169,7 @@ void setUp() { ); } + // todo : 추후 Fixture로 대체 필요 private SiteUser createSiteUser() { SiteUser siteUser = new SiteUser( "test@example.com", From c4626fdb1a239e026b776cf35c9991c701f11f92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Mon, 19 May 2025 12:16:29 +0900 Subject: [PATCH 15/90] =?UTF-8?q?refactor:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20fixture=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#319)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: SiteUserFixture에 유저 생성 메서드 추가 * refactor: 대학 관련 테스트 유저 데이터 Fixture 메서드로 변경 * refactor: 유저 관련 테스트 유저 데이터 Fixture 메서드로 변경 * refactor: 점수 관련 테스트 유저 데이터 Fixture 메서드로 변경 * refactor: 동시성 관련 테스트 유저 데이터 Fixture 메서드로 변경 * refactor: 인증 관련 테스트 유저 데이터 Fixture 메서드로 변경 * refactor: 어드민 관련 테스트 유저 데이터 Fixture 메서드로 변경 * refactor: 커스텀 관련 테스트 유저 데이터 Fixture 메서드로 변경 * refactor: SiteUserFixtureBuilder에서 비밀번호 인코딩 처리 통일 * refactor: 재사용성 없는 fixture 제거 * refactor: 테스트 유저 메서드명 사용자()로 통일 * refactor: 테스트 어드민 메서드명 관리자()로 통일 * refactor: Repository 테스트는 fixture 대신 entity 사용하도록 변경 * refactor: 커스텀 프로필 사용자로 이름 변경 * refactor: 커스텀 프로필 생성 함수 private로 변경 --- .../service/AdminGpaScoreServiceTest.java | 34 ++--- .../AdminLanguageTestScoreServiceTest.java | 34 ++--- .../auth/service/AuthServiceTest.java | 27 +--- .../auth/service/EmailSignInServiceTest.java | 32 +--- .../auth/service/SignInServiceTest.java | 37 ++--- .../post/service/PostCommandServiceTest.java | 50 ++++--- .../post/service/PostLikeServiceTest.java | 36 +++-- .../post/service/PostQueryServiceTest.java | 26 +++- .../PostLikeCountConcurrencyTest.java | 27 ++-- .../PostViewCountConcurrencyTest.java | 48 +++--- .../concurrency/ThunderingHerdTest.java | 33 ++--- .../resolver/AuthorizedUserResolverTest.java | 24 +-- .../aspect/AdminAuthorizationAspectTest.java | 32 ++-- .../SiteUserAuthenticationProviderTest.java | 27 +--- .../SiteUserDetailsServiceTest.java | 30 ++-- .../userdetails/SiteUserDetailsTest.java | 22 +-- .../score/service/ScoreServiceTest.java | 57 +++---- .../siteuser/fixture/SiteUserFixture.java | 69 +++++++++ .../fixture/SiteUserFixtureBuilder.java | 72 +++++++++ .../siteuser/service/MyPageServiceTest.java | 139 ++++++++---------- .../siteuser/service/SiteUserServiceTest.java | 28 +--- .../service/UniversityLikeServiceTest.java | 48 +++--- .../UniversityRecommendServiceTest.java | 54 +++---- 23 files changed, 464 insertions(+), 522 deletions(-) create mode 100644 src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixture.java create mode 100644 src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixtureBuilder.java diff --git a/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java b/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java index add4115ef..fc7735d86 100644 --- a/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java @@ -9,10 +9,8 @@ import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.repository.GpaScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.integration.BaseIntegrationTest; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import com.example.solidconnection.type.VerifyStatus; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -38,26 +36,23 @@ class AdminGpaScoreServiceTest extends BaseIntegrationTest { private AdminGpaScoreService adminGpaScoreService; @Autowired - private SiteUserRepository siteUserRepository; + private GpaScoreRepository gpaScoreRepository; @Autowired - private GpaScoreRepository gpaScoreRepository; + private SiteUserFixture siteUserFixture; - private SiteUser siteUser1; - private SiteUser siteUser2; - private SiteUser siteUser3; private GpaScore gpaScore1; private GpaScore gpaScore2; private GpaScore gpaScore3; @BeforeEach void setUp() { - siteUser1 = createSiteUser(1, "test1"); - siteUser2 = createSiteUser(2, "test2"); - siteUser3 = createSiteUser(3, "test3"); - gpaScore3 = createGpaScore(siteUser3, VerifyStatus.REJECTED); - gpaScore2 = createGpaScore(siteUser2, VerifyStatus.PENDING); - gpaScore1 = createGpaScore(siteUser1, VerifyStatus.PENDING); + SiteUser user1 = siteUserFixture.사용자(1, "test1"); + SiteUser user2 = siteUserFixture.사용자(2, "test2"); + SiteUser user3 = siteUserFixture.사용자(3, "test3"); + gpaScore3 = createGpaScore(user3, VerifyStatus.REJECTED); + gpaScore2 = createGpaScore(user2, VerifyStatus.PENDING); + gpaScore1 = createGpaScore(user1, VerifyStatus.PENDING); } @Nested @@ -209,17 +204,6 @@ class GPA_점수_검증_및_수정 { } } - private SiteUser createSiteUser(int index, String nickname) { - SiteUser siteUser = new SiteUser( - "test" + index + " @example.com", - nickname, - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - return siteUserRepository.save(siteUser); - } - private GpaScore createGpaScore(SiteUser siteUser, VerifyStatus status) { GpaScore gpaScore = new GpaScore( new Gpa(4.0, 4.5, "/gpa-report.pdf"), diff --git a/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java b/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java index a3d00457e..edf34670b 100644 --- a/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java @@ -9,10 +9,8 @@ import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.integration.BaseIntegrationTest; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import com.example.solidconnection.type.VerifyStatus; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -39,26 +37,23 @@ class AdminLanguageTestScoreServiceTest extends BaseIntegrationTest { private AdminLanguageTestScoreService adminLanguageTestScoreService; @Autowired - private SiteUserRepository siteUserRepository; + private LanguageTestScoreRepository languageTestScoreRepository; @Autowired - private LanguageTestScoreRepository languageTestScoreRepository; + private SiteUserFixture siteUserFixture; - private SiteUser siteUser1; - private SiteUser siteUser2; - private SiteUser siteUser3; private LanguageTestScore languageTestScore1; private LanguageTestScore languageTestScore2; private LanguageTestScore languageTestScore3; @BeforeEach void setUp() { - siteUser1 = createSiteUser(1, "test1"); - siteUser2 = createSiteUser(2, "test2"); - siteUser3 = createSiteUser(3, "test3"); - languageTestScore3 = createLanguageTestScore(siteUser3, VerifyStatus.REJECTED); - languageTestScore2 = createLanguageTestScore(siteUser2, VerifyStatus.PENDING); - languageTestScore1 = createLanguageTestScore(siteUser1, VerifyStatus.PENDING); + SiteUser user1 = siteUserFixture.사용자(1, "test1"); + SiteUser user2 = siteUserFixture.사용자(2, "test2"); + SiteUser user3 = siteUserFixture.사용자(3, "test3"); + languageTestScore3 = createLanguageTestScore(user3, VerifyStatus.REJECTED); + languageTestScore2 = createLanguageTestScore(user2, VerifyStatus.PENDING); + languageTestScore1 = createLanguageTestScore(user1, VerifyStatus.PENDING); } @Nested @@ -219,17 +214,6 @@ class 어학점수_검증_및_수정 { } } - private SiteUser createSiteUser(int index, String nickname) { - SiteUser siteUser = new SiteUser( - "test" + index + " @example.com", - nickname, - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - return siteUserRepository.save(siteUser); - } - private LanguageTestScore createLanguageTestScore(SiteUser siteUser, VerifyStatus status) { LanguageTestScore languageTestScore = new LanguageTestScore( new LanguageTest(TOEIC, "500", "/toeic-report.pdf"), diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java index 18c7c4ef0..cb0ffbb96 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -5,10 +5,8 @@ import com.example.solidconnection.auth.dto.ReissueResponse; import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -33,10 +31,10 @@ class AuthServiceTest { private AuthTokenProvider authTokenProvider; @Autowired - private SiteUserRepository siteUserRepository; + private RedisTemplate redisTemplate; @Autowired - private RedisTemplate redisTemplate; + private SiteUserFixture siteUserFixture; @Test void 로그아웃한다() { @@ -58,18 +56,18 @@ class AuthServiceTest { @Test void 탈퇴한다() { // given - SiteUser siteUser = createSiteUser(); - Subject subject = authTokenProvider.toSubject(siteUser); + SiteUser user = siteUserFixture.사용자(); + Subject subject = authTokenProvider.toSubject(user); AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // todo: #296 // when - authService.quit(siteUser, accessToken.token()); + authService.quit(user, accessToken.token()); // then LocalDate tomorrow = LocalDate.now().plusDays(1); String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value()); assertAll( - () -> assertThat(siteUser.getQuitedAt()).isEqualTo(tomorrow), + () -> assertThat(user.getQuitedAt()).isEqualTo(tomorrow), () -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(), () -> assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue() ); @@ -105,15 +103,4 @@ class 토큰을_재발급한다 { .hasMessage(REFRESH_TOKEN_EXPIRED.getMessage()); } } - - private SiteUser createSiteUser() { - SiteUser siteUser = new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - return siteUserRepository.save(siteUser); - } } diff --git a/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java index 5a32ef362..4a98de6ce 100644 --- a/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java @@ -4,18 +4,14 @@ import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.custom.exception.ErrorCode; -import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.crypto.password.PasswordEncoder; import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; @@ -28,19 +24,15 @@ class EmailSignInServiceTest { private EmailSignInService emailSignInService; @Autowired - private SiteUserRepository siteUserRepository; - - @Autowired - private PasswordEncoder passwordEncoder; + private SiteUserFixture siteUserFixture; @Test void 로그인에_성공한다() { // given String email = "testEmail"; String rawPassword = "testPassword"; - SiteUser siteUser = createSiteUser(email, rawPassword); - siteUserRepository.save(siteUser); - EmailSignInRequest signInRequest = new EmailSignInRequest(siteUser.getEmail(), rawPassword); + SiteUser user = siteUserFixture.사용자(email, rawPassword); + EmailSignInRequest signInRequest = new EmailSignInRequest(user.getEmail(), rawPassword); // when SignInResponse signInResponse = emailSignInService.signIn(signInRequest); @@ -70,8 +62,7 @@ class 로그인에_실패한다 { void 비밀번호가_일치하지_않으면_예외_응답을_반환한다() { // given String email = "testEmail"; - SiteUser siteUser = createSiteUser(email, "testPassword"); - siteUserRepository.save(siteUser); + siteUserFixture.사용자(email, "testPassword"); EmailSignInRequest signInRequest = new EmailSignInRequest(email, "틀린비밀번호"); // when & then @@ -80,17 +71,4 @@ class 로그인에_실패한다 { .hasMessageContaining(ErrorCode.USER_NOT_FOUND.getMessage()); } } - - private SiteUser createSiteUser(String email, String rawPassword) { - String encodedPassword = passwordEncoder.encode(rawPassword); - return new SiteUser( - email, - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE, - AuthType.EMAIL, - encodedPassword - ); - } } diff --git a/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java index e40f20b7a..51eee236c 100644 --- a/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java @@ -4,10 +4,8 @@ import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.config.security.JwtProperties; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import com.example.solidconnection.util.JwtUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -31,28 +29,24 @@ class SignInServiceTest { private JwtProperties jwtProperties; @Autowired - private AuthTokenProvider authTokenProvider; - - @Autowired - private SiteUserRepository siteUserRepository; + private RedisTemplate redisTemplate; @Autowired - private RedisTemplate redisTemplate; + private SiteUserFixture siteUserFixture; - private SiteUser siteUser; + private SiteUser user; private String subject; @BeforeEach void setUp() { - siteUser = createSiteUser(); - siteUserRepository.save(siteUser); - subject = siteUser.getId().toString(); + user = siteUserFixture.사용자(); + subject = user.getId().toString(); } @Test void 성공적으로_로그인한다() { // when - SignInResponse signInResponse = signInService.signIn(siteUser); + SignInResponse signInResponse = signInService.signIn(user); // then String accessTokenSubject = JwtUtils.parseSubject(signInResponse.accessToken(), jwtProperties.secret()); @@ -67,23 +61,12 @@ void setUp() { @Test void 탈퇴한_이력이_있으면_초기화한다() { // given - siteUser.setQuitedAt(LocalDate.now().minusDays(1)); - siteUserRepository.save(siteUser); + user.setQuitedAt(LocalDate.now().minusDays(1)); // when - signInService.signIn(siteUser); + signInService.signIn(user); // then - assertThat(siteUser.getQuitedAt()).isNull(); - } - - private SiteUser createSiteUser() { - return new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); + assertThat(user.getQuitedAt()).isNull(); } } diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java index 328a1dc41..d3931008a 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java @@ -15,11 +15,13 @@ import com.example.solidconnection.s3.UploadedFileUrlResponse; import com.example.solidconnection.service.RedisService; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.integration.BaseIntegrationTest; import com.example.solidconnection.type.ImgType; import com.example.solidconnection.type.PostCategory; import com.example.solidconnection.util.RedisUtils; import jakarta.transaction.Transactional; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -63,6 +65,16 @@ class PostCommandServiceTest extends BaseIntegrationTest { @Autowired private PostImageRepository postImageRepository; + @Autowired + private SiteUserFixture siteUserFixture; + + private SiteUser user1; + + @BeforeEach + void setUp() { + user1 = siteUserFixture.사용자(1, "test1"); + } + @Nested class 게시글_생성_테스트 { @@ -78,7 +90,7 @@ class 게시글_생성_테스트 { // when PostCreateResponse response = postCommandService.createPost( - 테스트유저_1, + user1, request, imageFiles ); @@ -107,7 +119,7 @@ class 게시글_생성_테스트 { // when & then assertThatThrownBy(() -> - postCommandService.createPost(테스트유저_1, request, imageFiles)) + postCommandService.createPost(user1, request, imageFiles)) .isInstanceOf(CustomException.class) .hasMessage(INVALID_POST_CATEGORY.getMessage()); } @@ -120,7 +132,7 @@ class 게시글_생성_테스트 { // when & then assertThatThrownBy(() -> - postCommandService.createPost(테스트유저_1, request, imageFiles)) + postCommandService.createPost(user1, request, imageFiles)) .isInstanceOf(CustomException.class) .hasMessage(INVALID_POST_CATEGORY.getMessage()); } @@ -133,7 +145,7 @@ class 게시글_생성_테스트 { // when & then assertThatThrownBy(() -> - postCommandService.createPost(테스트유저_1, request, imageFiles)) + postCommandService.createPost(user1, request, imageFiles)) .isInstanceOf(CustomException.class) .hasMessage(CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES.getMessage()); } @@ -148,7 +160,7 @@ class 게시글_수정_테스트 { // given String originImageUrl = "origin-image-url"; String expectedImageUrl = "update-image-url"; - Post testPost = createPost(자유게시판, 테스트유저_1, originImageUrl); + Post testPost = createPost(자유게시판, user1, originImageUrl); PostUpdateRequest request = createPostUpdateRequest(); List imageFiles = List.of(createImageFile()); @@ -157,7 +169,7 @@ class 게시글_수정_테스트 { // when PostUpdateResponse response = postCommandService.updatePost( - 테스트유저_1, + user1, testPost.getId(), request, imageFiles @@ -180,14 +192,15 @@ class 게시글_수정_테스트 { @Test void 다른_사용자의_게시글을_수정하면_예외_응답을_반환한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1, "origin-image-url"); + SiteUser user2 = siteUserFixture.사용자(2, "test2"); + Post testPost = createPost(자유게시판, user1, "origin-image-url"); PostUpdateRequest request = createPostUpdateRequest(); List imageFiles = List.of(); // when & then assertThatThrownBy(() -> postCommandService.updatePost( - 테스트유저_2, + user2, testPost.getId(), request, imageFiles @@ -199,14 +212,14 @@ class 게시글_수정_테스트 { @Test void 질문_게시글을_수정하면_예외_응답을_반환한다() { // given - Post testPost = createQuestionPost(자유게시판, 테스트유저_1, "origin-image-url"); + Post testPost = createQuestionPost(자유게시판, user1, "origin-image-url"); PostUpdateRequest request = createPostUpdateRequest(); List imageFiles = List.of(); // when & then assertThatThrownBy(() -> postCommandService.updatePost( - 테스트유저_1, + user1, testPost.getId(), request, imageFiles @@ -218,14 +231,14 @@ class 게시글_수정_테스트 { @Test void 이미지를_5개_초과하여_수정하면_예외_응답을_반환한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1, "origin-image-url"); + Post testPost = createPost(자유게시판, user1, "origin-image-url"); PostUpdateRequest request = createPostUpdateRequest(); List imageFiles = createSixImageFiles(); // when & then assertThatThrownBy(() -> postCommandService.updatePost( - 테스트유저_1, + user1, testPost.getId(), request, imageFiles @@ -242,13 +255,13 @@ class 게시글_삭제_테스트 { void 게시글을_성공적으로_삭제한다() { // given String originImageUrl = "origin-image-url"; - Post testPost = createPost(자유게시판, 테스트유저_1, originImageUrl); + Post testPost = createPost(자유게시판, user1, originImageUrl); String viewCountKey = redisUtils.getPostViewCountRedisKey(testPost.getId()); redisService.increaseViewCount(viewCountKey); // when PostDeleteResponse response = postCommandService.deletePostById( - 테스트유저_1, + user1, testPost.getId() ); @@ -264,12 +277,13 @@ class 게시글_삭제_테스트 { @Test void 다른_사용자의_게시글을_삭제하면_예외_응답을_반환한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1, "origin-image-url"); + SiteUser user2 = siteUserFixture.사용자(2, "test2"); + Post testPost = createPost(자유게시판, user1, "origin-image-url"); // when & then assertThatThrownBy(() -> postCommandService.deletePostById( - 테스트유저_2, + user2, testPost.getId() )) .isInstanceOf(CustomException.class) @@ -279,12 +293,12 @@ class 게시글_삭제_테스트 { @Test void 질문_게시글을_삭제하면_예외_응답을_반환한다() { // given - Post testPost = createQuestionPost(자유게시판, 테스트유저_1, "origin-image-url"); + Post testPost = createQuestionPost(자유게시판, user1, "origin-image-url"); // when & then assertThatThrownBy(() -> postCommandService.deletePostById( - 테스트유저_1, + user1, testPost.getId() )) .isInstanceOf(CustomException.class) diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java index 23fa6bf50..30026e46a 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java @@ -8,8 +8,10 @@ import com.example.solidconnection.community.post.repository.PostLikeRepository; import com.example.solidconnection.community.post.repository.PostRepository; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.integration.BaseIntegrationTest; import com.example.solidconnection.type.PostCategory; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -33,18 +35,28 @@ class PostLikeServiceTest extends BaseIntegrationTest { @Autowired private PostLikeRepository postLikeRepository; + @Autowired + private SiteUserFixture siteUserFixture; + + private SiteUser user; + + @BeforeEach + void setUp() { + user = siteUserFixture.사용자(1, "test1"); + } + @Nested class 게시글_좋아요_테스트 { @Test void 게시글을_성공적으로_좋아요한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); + Post testPost = createPost(자유게시판, user); long beforeLikeCount = testPost.getLikeCount(); // when PostLikeResponse response = postLikeService.likePost( - 테스트유저_1, + user, testPost.getId() ); @@ -54,20 +66,20 @@ class 게시글_좋아요_테스트 { () -> assertThat(response.likeCount()).isEqualTo(beforeLikeCount + 1), () -> assertThat(response.isLiked()).isTrue(), () -> assertThat(likedPost.getLikeCount()).isEqualTo(beforeLikeCount + 1), - () -> assertThat(postLikeRepository.findPostLikeByPostAndSiteUser(likedPost, 테스트유저_1)).isPresent() + () -> assertThat(postLikeRepository.findPostLikeByPostAndSiteUser(likedPost, user)).isPresent() ); } @Test void 이미_좋아요한_게시글을_다시_좋아요하면_예외_응답을_반환한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); - postLikeService.likePost(테스트유저_1, testPost.getId()); + Post testPost = createPost(자유게시판, user); + postLikeService.likePost(user, testPost.getId()); // when & then assertThatThrownBy(() -> postLikeService.likePost( - 테스트유저_1, + user, testPost.getId() )) .isInstanceOf(CustomException.class) @@ -81,13 +93,13 @@ class 게시글_좋아요_취소_테스트 { @Test void 게시글_좋아요를_성공적으로_취소한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); - PostLikeResponse beforeResponse = postLikeService.likePost(테스트유저_1, testPost.getId()); + Post testPost = createPost(자유게시판, user); + PostLikeResponse beforeResponse = postLikeService.likePost(user, testPost.getId()); long beforeLikeCount = beforeResponse.likeCount(); // when PostDislikeResponse response = postLikeService.dislikePost( - 테스트유저_1, + user, testPost.getId() ); @@ -97,19 +109,19 @@ class 게시글_좋아요_취소_테스트 { () -> assertThat(response.likeCount()).isEqualTo(beforeLikeCount - 1), () -> assertThat(response.isLiked()).isFalse(), () -> assertThat(unlikedPost.getLikeCount()).isEqualTo(beforeLikeCount - 1), - () -> assertThat(postLikeRepository.findPostLikeByPostAndSiteUser(unlikedPost, 테스트유저_1)).isEmpty() + () -> assertThat(postLikeRepository.findPostLikeByPostAndSiteUser(unlikedPost, user)).isEmpty() ); } @Test void 좋아요하지_않은_게시글을_좋아요_취소하면_예외_응답을_반환한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); + Post testPost = createPost(자유게시판, user); // when & then assertThatThrownBy(() -> postLikeService.dislikePost( - 테스트유저_1, + user, testPost.getId() )) .isInstanceOf(CustomException.class) diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java index fc7926698..67212e014 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java @@ -13,10 +13,12 @@ import com.example.solidconnection.community.post.repository.PostImageRepository; import com.example.solidconnection.service.RedisService; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.integration.BaseIntegrationTest; import com.example.solidconnection.type.BoardCode; import com.example.solidconnection.type.PostCategory; import com.example.solidconnection.util.RedisUtils; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -48,6 +50,16 @@ class PostQueryServiceTest extends BaseIntegrationTest { @Autowired private PostImageRepository postImageRepository; + @Autowired + private SiteUserFixture siteUserFixture; + + private SiteUser user; + + @BeforeEach + void setUp() { + user = siteUserFixture.사용자(1, "test1"); + } + @Test void 게시판_코드와_카테고리로_게시글_목록을_조회한다() { // given @@ -103,15 +115,15 @@ class PostQueryServiceTest extends BaseIntegrationTest { // given String expectedImageUrl = "test-image-url"; List imageUrls = List.of(expectedImageUrl); - Post testPost = createPost(자유게시판, 테스트유저_1, expectedImageUrl); - List comments = createComments(testPost, 테스트유저_1, List.of("첫번째 댓글", "두번째 댓글")); + Post testPost = createPost(자유게시판, user, expectedImageUrl); + List comments = createComments(testPost, user, List.of("첫번째 댓글", "두번째 댓글")); - String validateKey = redisUtils.getValidatePostViewCountRedisKey(테스트유저_1.getId(), testPost.getId()); + String validateKey = redisUtils.getValidatePostViewCountRedisKey(user.getId(), testPost.getId()); String viewCountKey = redisUtils.getPostViewCountRedisKey(testPost.getId()); // when PostFindResponse response = postQueryService.findPostById( - 테스트유저_1, + user, testPost.getId() ); @@ -128,9 +140,9 @@ class PostQueryServiceTest extends BaseIntegrationTest { () -> assertThat(response.postFindBoardResponse().code()).isEqualTo(자유게시판.getCode()), () -> assertThat(response.postFindBoardResponse().koreanName()).isEqualTo(자유게시판.getKoreanName()), - () -> assertThat(response.postFindSiteUserResponse().id()).isEqualTo(테스트유저_1.getId()), - () -> assertThat(response.postFindSiteUserResponse().nickname()).isEqualTo(테스트유저_1.getNickname()), - () -> assertThat(response.postFindSiteUserResponse().profileImageUrl()).isEqualTo(테스트유저_1.getProfileImageUrl()), + () -> assertThat(response.postFindSiteUserResponse().id()).isEqualTo(user.getId()), + () -> assertThat(response.postFindSiteUserResponse().nickname()).isEqualTo(user.getNickname()), + () -> assertThat(response.postFindSiteUserResponse().profileImageUrl()).isEqualTo(user.getProfileImageUrl()), () -> assertThat(response.postFindPostImageResponses()) .hasSize(imageUrls.size()) diff --git a/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java b/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java index fdcf0ec8d..4f2ddf687 100644 --- a/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java +++ b/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java @@ -6,11 +6,10 @@ import com.example.solidconnection.community.post.repository.PostRepository; import com.example.solidconnection.community.post.service.PostLikeService; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.type.PostCategory; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -31,43 +30,39 @@ class PostLikeCountConcurrencyTest { @Autowired private PostLikeService postLikeService; + @Autowired private PostRepository postRepository; + @Autowired private BoardRepository boardRepository; + @Autowired private SiteUserRepository siteUserRepository; + @Autowired + private SiteUserFixture siteUserFixture; + @Value("${view.count.scheduling.delay}") private int SCHEDULING_DELAY_MS; + private int THREAD_NUMS = 1000; private int THREAD_POOL_SIZE = 200; private int TIMEOUT_SECONDS = 10; private Post post; private Board board; - private SiteUser siteUser; + private SiteUser user; @BeforeEach void setUp() { board = createBoard(); boardRepository.save(board); - siteUser = createSiteUser(); - siteUserRepository.save(siteUser); - post = createPost(board, siteUser); + user = siteUserFixture.사용자(); + post = createPost(board, user); postRepository.save(post); } - private SiteUser createSiteUser() { - return new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - } - private Board createBoard() { return new Board( "FREE", "자유게시판"); diff --git a/src/test/java/com/example/solidconnection/concurrency/PostViewCountConcurrencyTest.java b/src/test/java/com/example/solidconnection/concurrency/PostViewCountConcurrencyTest.java index beeb8b046..6c1a0e18f 100644 --- a/src/test/java/com/example/solidconnection/concurrency/PostViewCountConcurrencyTest.java +++ b/src/test/java/com/example/solidconnection/concurrency/PostViewCountConcurrencyTest.java @@ -6,11 +6,9 @@ import com.example.solidconnection.community.post.repository.PostRepository; import com.example.solidconnection.service.RedisService; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.type.PostCategory; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import com.example.solidconnection.util.RedisUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -23,54 +21,48 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import static com.example.solidconnection.type.RedisConstants.*; +import static com.example.solidconnection.type.RedisConstants.VALIDATE_VIEW_COUNT_TTL; import static org.junit.jupiter.api.Assertions.assertEquals; @TestContainerSpringBootTest @DisplayName("게시글 조회수 동시성 테스트") -public class PostViewCountConcurrencyTest { +class PostViewCountConcurrencyTest { @Autowired private RedisService redisService; + @Autowired private PostRepository postRepository; + @Autowired private BoardRepository boardRepository; - @Autowired - private SiteUserRepository siteUserRepository; + @Autowired private RedisUtils redisUtils; + @Autowired + private SiteUserFixture siteUserFixture; + @Value("${view.count.scheduling.delay}") private int SCHEDULING_DELAY_MS; + private int THREAD_NUMS = 1000; private int THREAD_POOL_SIZE = 200; private int TIMEOUT_SECONDS = 10; private Post post; private Board board; - private SiteUser siteUser; + private SiteUser user; @BeforeEach - public void setUp() { + void setUp() { board = createBoard(); boardRepository.save(board); - siteUser = createSiteUser(); - siteUserRepository.save(siteUser); - post = createPost(board, siteUser); + user = siteUserFixture.사용자(); + post = createPost(board, user); postRepository.save(post); } - private SiteUser createSiteUser() { - return new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - } - private Board createBoard() { return new Board( "FREE", "자유게시판"); @@ -91,9 +83,9 @@ private Post createPost(Board board, SiteUser siteUser) { } @Test - public void 게시글을_조회할_때_조회수_동시성_문제를_해결한다() throws InterruptedException { + void 게시글을_조회할_때_조회수_동시성_문제를_해결한다() throws InterruptedException { - redisService.deleteKey(redisUtils.getValidatePostViewCountRedisKey(siteUser.getId(), post.getId())); + redisService.deleteKey(redisUtils.getValidatePostViewCountRedisKey(user.getId(), post.getId())); ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE); CountDownLatch doneSignal = new CountDownLatch(THREAD_NUMS); @@ -121,9 +113,9 @@ private Post createPost(Board board, SiteUser siteUser) { } @Test - public void 게시글을_조회할_때_조회수_조작_문제를_해결한다() throws InterruptedException { + void 게시글을_조회할_때_조회수_조작_문제를_해결한다() throws InterruptedException { - redisService.deleteKey(redisUtils.getValidatePostViewCountRedisKey(siteUser.getId(), post.getId())); + redisService.deleteKey(redisUtils.getValidatePostViewCountRedisKey(user.getId(), post.getId())); ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE); CountDownLatch doneSignal = new CountDownLatch(THREAD_NUMS); @@ -131,7 +123,7 @@ private Post createPost(Board board, SiteUser siteUser) { for (int i = 0; i < THREAD_NUMS; i++) { executorService.submit(() -> { try { - boolean isFirstTime = redisService.isPresent(redisUtils.getValidatePostViewCountRedisKey(siteUser.getId(), post.getId())); + boolean isFirstTime = redisService.isPresent(redisUtils.getValidatePostViewCountRedisKey(user.getId(), post.getId())); if (isFirstTime) { redisService.increaseViewCount(redisUtils.getPostViewCountRedisKey(post.getId())); } @@ -144,7 +136,7 @@ private Post createPost(Board board, SiteUser siteUser) { for (int i = 0; i < THREAD_NUMS; i++) { executorService.submit(() -> { try { - boolean isFirstTime = redisService.isPresent(redisUtils.getValidatePostViewCountRedisKey(siteUser.getId(), post.getId())); + boolean isFirstTime = redisService.isPresent(redisUtils.getValidatePostViewCountRedisKey(user.getId(), post.getId())); if (isFirstTime) { redisService.increaseViewCount(redisUtils.getPostViewCountRedisKey(post.getId())); } diff --git a/src/test/java/com/example/solidconnection/concurrency/ThunderingHerdTest.java b/src/test/java/com/example/solidconnection/concurrency/ThunderingHerdTest.java index 4800a0153..d2cadca65 100644 --- a/src/test/java/com/example/solidconnection/concurrency/ThunderingHerdTest.java +++ b/src/test/java/com/example/solidconnection/concurrency/ThunderingHerdTest.java @@ -2,10 +2,8 @@ import com.example.solidconnection.application.service.ApplicationQueryService; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -22,32 +20,25 @@ @TestContainerSpringBootTest @DisplayName("ThunderingHerd 테스트") -public class ThunderingHerdTest { +class ThunderingHerdTest { + @Autowired private ApplicationQueryService applicationQueryService; + @Autowired private RedisTemplate redisTemplate; + @Autowired - private SiteUserRepository siteUserRepository; + private SiteUserFixture siteUserFixture; + private int THREAD_NUMS = 1000; private int THREAD_POOL_SIZE = 200; private int TIMEOUT_SECONDS = 10; - private SiteUser siteUser; + private SiteUser user; @BeforeEach public void setUp() { - siteUser = createSiteUser(); - siteUserRepository.save(siteUser); - } - - private SiteUser createSiteUser() { - return new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); + user = siteUserFixture.사용자(); } @Test @@ -63,9 +54,9 @@ private SiteUser createSiteUser() { executorService.submit(() -> { try { List tasks = Arrays.asList( - () -> applicationQueryService.getApplicants(siteUser, "", ""), - () -> applicationQueryService.getApplicants(siteUser, "ASIA", ""), - () -> applicationQueryService.getApplicants(siteUser, "", "추오") + () -> applicationQueryService.getApplicants(user, "", ""), + () -> applicationQueryService.getApplicants(user, "ASIA", ""), + () -> applicationQueryService.getApplicants(user, "", "추오") ); Collections.shuffle(tasks); tasks.forEach(Runnable::run); diff --git a/src/test/java/com/example/solidconnection/custom/resolver/AuthorizedUserResolverTest.java b/src/test/java/com/example/solidconnection/custom/resolver/AuthorizedUserResolverTest.java index b517681aa..e0c1a006f 100644 --- a/src/test/java/com/example/solidconnection/custom/resolver/AuthorizedUserResolverTest.java +++ b/src/test/java/com/example/solidconnection/custom/resolver/AuthorizedUserResolverTest.java @@ -1,14 +1,11 @@ package com.example.solidconnection.custom.resolver; - import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.custom.security.authentication.SiteUserAuthentication; import com.example.solidconnection.custom.security.userdetails.SiteUserDetails; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -32,7 +29,7 @@ class AuthorizedUserResolverTest { private AuthorizedUserResolver authorizedUserResolver; @Autowired - private SiteUserRepository siteUserRepository; + private SiteUserFixture siteUserFixture; @BeforeEach void setUp() { @@ -42,8 +39,8 @@ void setUp() { @Test void security_context_에_저장된_인증된_사용자를_반환한다() { // given - SiteUser siteUser = createAndSaveSiteUser(); - Authentication authentication = createAuthenticationWithUser(siteUser); + SiteUser user = siteUserFixture.사용자(); + Authentication authentication = createAuthenticationWithUser(user); SecurityContextHolder.getContext().setAuthentication(authentication); MethodParameter parameter = mock(MethodParameter.class); @@ -55,7 +52,7 @@ void setUp() { SiteUser resolveSiteUser = (SiteUser) authorizedUserResolver.resolveArgument(parameter, null, null, null); // then - assertThat(resolveSiteUser).isEqualTo(siteUser); + assertThat(resolveSiteUser).isEqualTo(user); } @Nested @@ -90,17 +87,6 @@ class security_context_에_저장된_사용자가_없는_경우 { } } - private SiteUser createAndSaveSiteUser() { - SiteUser siteUser = new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - return siteUserRepository.save(siteUser); - } - private SiteUserAuthentication createAuthenticationWithUser(SiteUser siteUser) { SiteUserDetails userDetails = new SiteUserDetails(siteUser); return new SiteUserAuthentication("token", userDetails); diff --git a/src/test/java/com/example/solidconnection/custom/security/aspect/AdminAuthorizationAspectTest.java b/src/test/java/com/example/solidconnection/custom/security/aspect/AdminAuthorizationAspectTest.java index 0104c9ccd..b733acceb 100644 --- a/src/test/java/com/example/solidconnection/custom/security/aspect/AdminAuthorizationAspectTest.java +++ b/src/test/java/com/example/solidconnection/custom/security/aspect/AdminAuthorizationAspectTest.java @@ -3,9 +3,8 @@ import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.custom.security.annotation.RequireAdminAccess; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -24,13 +23,16 @@ class AdminAuthorizationAspectTest { @Autowired private TestService testService; + @Autowired + private SiteUserFixture siteUserFixture; + @Test void 어드민_사용자는_어드민_전용_메소드에_접근할_수_있다() { // given - SiteUser adminUser = createSiteUser(Role.ADMIN); + SiteUser admin = siteUserFixture.관리자(); // when - boolean response = testService.adminOnlyMethod(adminUser); + boolean response = testService.adminOnlyMethod(admin); // then assertThat(response).isTrue(); @@ -39,10 +41,10 @@ class AdminAuthorizationAspectTest { @Test void 일반_사용자가_어드민_전용_메소드에_접근하면_예외_응답을_반환한다() { // given - SiteUser mentorUser = createSiteUser(Role.MENTOR); + SiteUser user = siteUserFixture.사용자(); // when & then - assertThatCode(() -> testService.adminOnlyMethod(mentorUser)) + assertThatCode(() -> testService.adminOnlyMethod(user)) .isInstanceOf(CustomException.class) .hasMessage(ACCESS_DENIED.getMessage()); } @@ -50,28 +52,18 @@ class AdminAuthorizationAspectTest { @Test void 어드민_어노테이션이_없는_메소드는_모두_접근_가능하다() { // given - SiteUser menteeUser = createSiteUser(Role.MENTEE); - SiteUser adminUser = createSiteUser(Role.ADMIN); + SiteUser user = siteUserFixture.사용자(); + SiteUser admin = siteUserFixture.관리자(); // when - boolean menteeResponse = testService.publicMethod(menteeUser); - boolean adminResponse = testService.publicMethod(adminUser); + boolean menteeResponse = testService.publicMethod(user); + boolean adminResponse = testService.publicMethod(admin); // then assertThat(menteeResponse).isTrue(); assertThat(adminResponse).isTrue(); } - private SiteUser createSiteUser(Role role) { - return new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - role - ); - } - @TestConfiguration static class TestConfig { diff --git a/src/test/java/com/example/solidconnection/custom/security/provider/SiteUserAuthenticationProviderTest.java b/src/test/java/com/example/solidconnection/custom/security/provider/SiteUserAuthenticationProviderTest.java index 423bec415..b8999e3f5 100644 --- a/src/test/java/com/example/solidconnection/custom/security/provider/SiteUserAuthenticationProviderTest.java +++ b/src/test/java/com/example/solidconnection/custom/security/provider/SiteUserAuthenticationProviderTest.java @@ -5,10 +5,8 @@ import com.example.solidconnection.custom.security.authentication.SiteUserAuthentication; import com.example.solidconnection.custom.security.userdetails.SiteUserDetails; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.junit.jupiter.api.BeforeEach; @@ -38,14 +36,13 @@ class SiteUserAuthenticationProviderTest { private JwtProperties jwtProperties; @Autowired - private SiteUserRepository siteUserRepository; + private SiteUserFixture siteUserFixture; - private SiteUser siteUser; + private SiteUser user; @BeforeEach void setUp() { - siteUser = createSiteUser(); - siteUserRepository.save(siteUser); + user = siteUserFixture.사용자(); } @Test @@ -64,7 +61,7 @@ void setUp() { @Test void 유효한_토큰이면_정상적으로_인증_정보를_반환한다() { // given - String token = createValidToken(siteUser.getId()); + String token = createValidToken(user.getId()); SiteUserAuthentication auth = new SiteUserAuthentication(token); // when @@ -106,7 +103,7 @@ class 예외_응답을_반환하다 { @Test void 유효한_토큰이지만_해당되는_사용자가_없으면_예외_응답을_반환한다() { // given - long notExistingUserId = siteUser.getId() + 100; + long notExistingUserId = user.getId() + 100; String token = createValidToken(notExistingUserId); SiteUserAuthentication auth = new SiteUserAuthentication(token); @@ -128,7 +125,7 @@ private String createValidToken(long id) { private String createExpiredToken() { return Jwts.builder() - .setSubject(String.valueOf(siteUser.getId())) + .setSubject(String.valueOf(user.getId())) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() - 1000)) .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) @@ -143,14 +140,4 @@ private String createWrongSubjectTypeToken() { .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) .compact(); } - - private SiteUser createSiteUser() { - return new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - } } diff --git a/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsServiceTest.java b/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsServiceTest.java index 0b2a0db73..718732a44 100644 --- a/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsServiceTest.java +++ b/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsServiceTest.java @@ -2,10 +2,9 @@ import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -26,14 +25,17 @@ class SiteUserDetailsServiceTest { @Autowired private SiteUserDetailsService userDetailsService; + @Autowired + private SiteUserFixture siteUserFixture; + @Autowired private SiteUserRepository siteUserRepository; @Test void 사용자_인증_정보를_반환한다() { // given - SiteUser siteUser = siteUserRepository.save(createSiteUser()); - String username = getUserName(siteUser); + SiteUser user = siteUserFixture.사용자(); + String username = getUserName(user); // when SiteUserDetails userDetails = (SiteUserDetails) userDetailsService.loadUserByUsername(username); @@ -41,7 +43,7 @@ class SiteUserDetailsServiceTest { // then assertAll( () -> assertThat(userDetails.getUsername()).isEqualTo(username), - () -> assertThat(userDetails.getSiteUser()).extracting("id").isEqualTo(siteUser.getId()) + () -> assertThat(userDetails.getSiteUser()).extracting("id").isEqualTo(user.getId()) ); } @@ -73,10 +75,10 @@ class 예외_응답을_반환한다 { @Test void 탈퇴한_사용자이면_예외_응답을_반환한다() { // given - SiteUser siteUser = createSiteUser(); - siteUser.setQuitedAt(LocalDate.now()); - siteUserRepository.save(siteUser); - String username = getUserName(siteUser); + SiteUser user = siteUserFixture.사용자(); + user.setQuitedAt(LocalDate.now()); + siteUserRepository.save(user); + String username = getUserName(user); // when & then assertThatCode(() -> userDetailsService.loadUserByUsername(username)) @@ -85,16 +87,6 @@ class 예외_응답을_반환한다 { } } - private SiteUser createSiteUser() { - return new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - } - private String getUserName(SiteUser siteUser) { return siteUser.getId().toString(); } diff --git a/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsTest.java b/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsTest.java index b49b9cf27..6fe9b6d69 100644 --- a/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsTest.java +++ b/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsTest.java @@ -1,10 +1,8 @@ package com.example.solidconnection.custom.security.userdetails; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -19,13 +17,13 @@ class SiteUserDetailsTest { @Autowired - private SiteUserRepository siteUserRepository; + private SiteUserFixture siteUserFixture; @Test void 사용자_권한을_정상적으로_반환한다() { // given - SiteUser siteUser = siteUserRepository.save(createSiteUser()); - SiteUserDetails siteUserDetails = new SiteUserDetails(siteUser); + SiteUser user = siteUserFixture.사용자(); + SiteUserDetails siteUserDetails = new SiteUserDetails(user); // when Collection authorities = siteUserDetails.getAuthorities(); @@ -33,16 +31,6 @@ class SiteUserDetailsTest { // then assertThat(authorities) .extracting("authority") - .containsExactly("ROLE_" + siteUser.getRole().name()); - } - - private SiteUser createSiteUser() { - return new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); + .containsExactly("ROLE_" + user.getRole().name()); } } diff --git a/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java b/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java index a8b1ac3d8..27ed7a617 100644 --- a/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java @@ -15,13 +15,13 @@ import com.example.solidconnection.score.repository.GpaScoreRepository; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.integration.BaseIntegrationTest; import com.example.solidconnection.type.ImgType; import com.example.solidconnection.type.LanguageTestType; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import com.example.solidconnection.type.VerifyStatus; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -52,17 +52,26 @@ class ScoreServiceTest extends BaseIntegrationTest { @MockBean private S3Service s3Service; + @Autowired + private SiteUserFixture siteUserFixture; + + private SiteUser user; + + @BeforeEach + void setUp() { + user = siteUserFixture.사용자(); + } + @Test void GPA_점수_상태를_조회한다() { // given - SiteUser testUser = createSiteUser(); List scores = List.of( - createGpaScore(testUser, 3.5, 4.5), - createGpaScore(testUser, 3.8, 4.5) + createGpaScore(user, 3.5, 4.5), + createGpaScore(user, 3.8, 4.5) ); // when - GpaScoreStatusesResponse response = scoreService.getGpaScoreStatus(testUser); + GpaScoreStatusesResponse response = scoreService.getGpaScoreStatus(user); // then assertThat(response.gpaScoreStatusResponseList()) @@ -76,11 +85,8 @@ class ScoreServiceTest extends BaseIntegrationTest { @Test void GPA_점수가_없는_경우_빈_리스트를_반환한다() { - // given - SiteUser testUser = createSiteUser(); - // when - GpaScoreStatusesResponse response = scoreService.getGpaScoreStatus(testUser); + GpaScoreStatusesResponse response = scoreService.getGpaScoreStatus(user); // then assertThat(response.gpaScoreStatusResponseList()).isEmpty(); @@ -89,15 +95,14 @@ class ScoreServiceTest extends BaseIntegrationTest { @Test void 어학_시험_점수_상태를_조회한다() { // given - SiteUser testUser = createSiteUser(); List scores = List.of( - createLanguageTestScore(testUser, LanguageTestType.TOEIC, "100"), - createLanguageTestScore(testUser, LanguageTestType.TOEFL_IBT, "7.5") + createLanguageTestScore(user, LanguageTestType.TOEIC, "100"), + createLanguageTestScore(user, LanguageTestType.TOEFL_IBT, "7.5") ); - siteUserRepository.save(testUser); + siteUserRepository.save(user); // when - LanguageTestScoreStatusesResponse response = scoreService.getLanguageTestScoreStatus(testUser); + LanguageTestScoreStatusesResponse response = scoreService.getLanguageTestScoreStatus(user); // then assertThat(response.languageTestScoreStatusResponseList()) @@ -111,11 +116,8 @@ class ScoreServiceTest extends BaseIntegrationTest { @Test void 어학_시험_점수가_없는_경우_빈_리스트를_반환한다() { - // given - SiteUser testUser = createSiteUser(); - // when - LanguageTestScoreStatusesResponse response = scoreService.getLanguageTestScoreStatus(testUser); + LanguageTestScoreStatusesResponse response = scoreService.getLanguageTestScoreStatus(user); // then assertThat(response.languageTestScoreStatusResponseList()).isEmpty(); @@ -124,14 +126,13 @@ class ScoreServiceTest extends BaseIntegrationTest { @Test void GPA_점수를_등록한다() { // given - SiteUser testUser = createSiteUser(); GpaScoreRequest request = createGpaScoreRequest(); MockMultipartFile file = createFile(); String fileUrl = "/gpa-report.pdf"; given(s3Service.uploadFile(file, ImgType.GPA)).willReturn(new UploadedFileUrlResponse(fileUrl)); // when - long scoreId = scoreService.submitGpaScore(testUser, request, file); + long scoreId = scoreService.submitGpaScore(user, request, file); GpaScore savedScore = gpaScoreRepository.findById(scoreId).orElseThrow(); // then @@ -147,14 +148,13 @@ class ScoreServiceTest extends BaseIntegrationTest { @Test void 어학_시험_점수를_등록한다() { // given - SiteUser testUser = createSiteUser(); LanguageTestScoreRequest request = createLanguageTestScoreRequest(); MockMultipartFile file = createFile(); String fileUrl = "/gpa-report.pdf"; given(s3Service.uploadFile(file, ImgType.LANGUAGE_TEST)).willReturn(new UploadedFileUrlResponse(fileUrl)); // when - long scoreId = scoreService.submitLanguageTestScore(testUser, request, file); + long scoreId = scoreService.submitLanguageTestScore(user, request, file); LanguageTestScore savedScore = languageTestScoreRepository.findById(scoreId).orElseThrow(); // then @@ -167,17 +167,6 @@ class ScoreServiceTest extends BaseIntegrationTest { ); } - private SiteUser createSiteUser() { - SiteUser siteUser = new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - return siteUserRepository.save(siteUser); - } - private GpaScore createGpaScore(SiteUser siteUser, double gpa, double gpaCriteria) { GpaScore gpaScore = new GpaScore( new Gpa(gpa, gpaCriteria, "/gpa-report.pdf"), diff --git a/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixture.java b/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixture.java new file mode 100644 index 000000000..000b7bc48 --- /dev/null +++ b/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixture.java @@ -0,0 +1,69 @@ +package com.example.solidconnection.siteuser.fixture; + +import com.example.solidconnection.siteuser.domain.AuthType; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.type.Role; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class SiteUserFixture { + + private final SiteUserFixtureBuilder siteUserFixtureBuilder; + + public SiteUser 사용자() { + return siteUserFixtureBuilder.siteUser() + .email("test@example.com") + .authType(AuthType.EMAIL) + .nickname("사용자") + .profileImageUrl("profileImageUrl") + .role(Role.MENTEE) + .password("password123") + .create(); + } + + public SiteUser 사용자(int index, String nickname) { + return siteUserFixtureBuilder.siteUser() + .email("test" + index + "@example.com") + .authType(AuthType.EMAIL) + .nickname(nickname) + .profileImageUrl("profileImageUrl") + .role(Role.MENTEE) + .password("password123") + .create(); + } + + public SiteUser 사용자(String email, AuthType authType) { + return siteUserFixtureBuilder.siteUser() + .email(email) + .authType(authType) + .nickname("사용자") + .profileImageUrl("profileImageUrl") + .role(Role.MENTEE) + .password("password123") + .create(); + } + + public SiteUser 사용자(String email, String password) { + return siteUserFixtureBuilder.siteUser() + .email(email) + .authType(AuthType.EMAIL) + .nickname("사용자") + .profileImageUrl("profileImageUrl") + .role(Role.MENTEE) + .password(password) + .create(); + } + + public SiteUser 관리자() { + return siteUserFixtureBuilder.siteUser() + .email("admin@example.com") + .authType(AuthType.EMAIL) + .nickname("관리자") + .profileImageUrl("profileImageUrl") + .role(Role.ADMIN) + .password("admin123") + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixtureBuilder.java b/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixtureBuilder.java new file mode 100644 index 000000000..f0d8bd406 --- /dev/null +++ b/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixtureBuilder.java @@ -0,0 +1,72 @@ +package com.example.solidconnection.siteuser.fixture; + +import com.example.solidconnection.siteuser.domain.AuthType; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.type.PreparationStatus; +import com.example.solidconnection.type.Role; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; +import org.springframework.security.crypto.password.PasswordEncoder; + +@TestComponent +@RequiredArgsConstructor +public class SiteUserFixtureBuilder { + + private final SiteUserRepository siteUserRepository; + private final PasswordEncoder passwordEncoder; + + private String email; + private AuthType authType; + private String nickname; + private String profileImageUrl; + private Role role; + private String password; + + public SiteUserFixtureBuilder siteUser() { + return new SiteUserFixtureBuilder(siteUserRepository, passwordEncoder); + } + + public SiteUserFixtureBuilder email(String email) { + this.email = email; + return this; + } + + public SiteUserFixtureBuilder authType(AuthType authType) { + this.authType = authType; + return this; + } + + public SiteUserFixtureBuilder nickname(String nickname) { + this.nickname = nickname; + return this; + } + + public SiteUserFixtureBuilder profileImageUrl(String profileImageUrl) { + this.profileImageUrl = profileImageUrl; + return this; + } + + public SiteUserFixtureBuilder role(Role role) { + this.role = role; + return this; + } + + public SiteUserFixtureBuilder password(String password) { + this.password = password; + return this; + } + + public SiteUser create() { + SiteUser siteUser = new SiteUser( + email, + nickname, + profileImageUrl, + PreparationStatus.CONSIDERING, + role, + authType, + passwordEncoder.encode(password) + ); + return siteUserRepository.save(siteUser); + } +} diff --git a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java index 95a887736..1c5680d33 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java @@ -3,17 +3,19 @@ import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.s3.S3Service; import com.example.solidconnection.s3.UploadedFileUrlResponse; +import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.MyPageResponse; -import com.example.solidconnection.siteuser.dto.NicknameUpdateRequest; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.siteuser.fixture.SiteUserFixtureBuilder; import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.support.integration.BaseIntegrationTest; +import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.type.ImgType; -import com.example.solidconnection.type.PreparationStatus; import com.example.solidconnection.type.Role; import com.example.solidconnection.university.domain.LikedUniversity; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; +import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -38,8 +40,9 @@ import static org.mockito.BDDMockito.never; import static org.mockito.BDDMockito.then; +@TestContainerSpringBootTest @DisplayName("마이페이지 서비스 테스트") -class MyPageServiceTest extends BaseIntegrationTest { +class MyPageServiceTest { @Autowired private MyPageService myPageService; @@ -53,22 +56,37 @@ class MyPageServiceTest extends BaseIntegrationTest { @Autowired private LikedUniversityRepository likedUniversityRepository; + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private UniversityInfoForApplyFixture universityInfoForApplyFixture; + + @Autowired + private SiteUserFixtureBuilder siteUserFixtureBuilder; + + private SiteUser user; + + @BeforeEach + void setUp() { + user = siteUserFixture.사용자(); + } + @Test void 마이페이지_정보를_조회한다() { // given - SiteUser testUser = createSiteUser(); - int likedUniversityCount = createLikedUniversities(testUser); + int likedUniversityCount = createLikedUniversities(user); // when - MyPageResponse response = myPageService.getMyPageInfo(testUser); + MyPageResponse response = myPageService.getMyPageInfo(user); // then Assertions.assertAll( - () -> assertThat(response.nickname()).isEqualTo(testUser.getNickname()), - () -> assertThat(response.profileImageUrl()).isEqualTo(testUser.getProfileImageUrl()), - () -> assertThat(response.role()).isEqualTo(testUser.getRole()), - () -> assertThat(response.email()).isEqualTo(testUser.getEmail()), - () -> assertThat(response.likedPostCount()).isEqualTo(testUser.getPostLikeList().size()), + () -> assertThat(response.nickname()).isEqualTo(user.getNickname()), + () -> assertThat(response.profileImageUrl()).isEqualTo(user.getProfileImageUrl()), + () -> assertThat(response.role()).isEqualTo(user.getRole()), + () -> assertThat(response.email()).isEqualTo(user.getEmail()), + () -> assertThat(response.likedPostCount()).isEqualTo(user.getPostLikeList().size()), () -> assertThat(response.likedUniversityCount()).isEqualTo(likedUniversityCount) ); } @@ -76,21 +94,13 @@ class MyPageServiceTest extends BaseIntegrationTest { @Test void 관심_대학교_목록을_조회한다() { // given - SiteUser testUser = createSiteUser(); - int likedUniversityCount = createLikedUniversities(testUser); + int likedUniversityCount = createLikedUniversities(user); // when - List response = myPageService.getWishUniversity(testUser); + List response = myPageService.getWishUniversity(user); // then - assertThat(response) - .hasSize(likedUniversityCount) - .usingRecursiveFieldByFieldElementComparatorIgnoringFields("id") - .containsAll(List.of( - UniversityInfoForApplyPreviewResponse.from(괌대학_A_지원_정보), - UniversityInfoForApplyPreviewResponse.from(메이지대학_지원_정보), - UniversityInfoForApplyPreviewResponse.from(코펜하겐IT대학_지원_정보) - )); + assertThat(response).hasSize(likedUniversityCount); } @Nested @@ -99,29 +109,27 @@ class 프로필_이미지_수정_테스트 { @Test void 새로운_이미지로_성공적으로_업데이트한다() { // given - SiteUser testUser = createSiteUser(); String expectedUrl = "newProfileImageUrl"; MockMultipartFile imageFile = createValidImageFile(); given(s3Service.uploadFile(any(), eq(ImgType.PROFILE))) .willReturn(new UploadedFileUrlResponse(expectedUrl)); // when - myPageService.updateMyPageInfo(testUser, imageFile, "newNickname"); + myPageService.updateMyPageInfo(user, imageFile, "newNickname"); // then - assertThat(testUser.getProfileImageUrl()).isEqualTo(expectedUrl); + assertThat(user.getProfileImageUrl()).isEqualTo(expectedUrl); } @Test void 프로필을_처음_수정하는_것이면_이전_이미지를_삭제하지_않는다() { // given - SiteUser testUser = createSiteUser(); MockMultipartFile imageFile = createValidImageFile(); given(s3Service.uploadFile(any(), eq(ImgType.PROFILE))) .willReturn(new UploadedFileUrlResponse("newProfileImageUrl")); // when - myPageService.updateMyPageInfo(testUser, imageFile, "newNickname"); + myPageService.updateMyPageInfo(user, imageFile, "newNickname"); // then then(s3Service).should(never()).deleteExProfile(any()); @@ -130,16 +138,16 @@ class 프로필_이미지_수정_테스트 { @Test void 프로필을_처음_수정하는_것이_아니라면_이전_이미지를_삭제한다() { // given - SiteUser testUser = createSiteUserWithCustomProfile(); + SiteUser 커스텀_프로필_사용자 = createSiteUserWithCustomProfile(); MockMultipartFile imageFile = createValidImageFile(); given(s3Service.uploadFile(any(), eq(ImgType.PROFILE))) .willReturn(new UploadedFileUrlResponse("newProfileImageUrl")); // when - myPageService.updateMyPageInfo(testUser, imageFile, "newNickname"); + myPageService.updateMyPageInfo(커스텀_프로필_사용자, imageFile, "newNickname"); // then - then(s3Service).should().deleteExProfile(testUser); + then(s3Service).should().deleteExProfile(커스텀_프로필_사용자); } } @@ -155,15 +163,14 @@ void setUp() { @Test void 닉네임을_성공적으로_수정한다() { // given - SiteUser testUser = createSiteUser(); MockMultipartFile imageFile = createValidImageFile(); String newNickname = "newNickname"; // when - myPageService.updateMyPageInfo(testUser, imageFile, newNickname); + myPageService.updateMyPageInfo(user, imageFile, newNickname); // then - SiteUser updatedUser = siteUserRepository.findById(testUser.getId()).get(); + SiteUser updatedUser = siteUserRepository.findById(user.getId()).get(); assertThat(updatedUser.getNicknameModifiedAt()).isNotNull(); assertThat(updatedUser.getNickname()).isEqualTo(newNickname); } @@ -171,12 +178,10 @@ void setUp() { @Test void 중복된_닉네임으로_변경하면_예외_응답을_반환한다() { // given - createDuplicatedSiteUser(); - SiteUser testUser = createSiteUser(); - MockMultipartFile imageFile = createValidImageFile(); + SiteUser existingUser = siteUserFixture.사용자(1, "existing nickname"); // when & then - assertThatCode(() -> myPageService.updateMyPageInfo(testUser, imageFile, "duplicatedNickname")) + assertThatCode(() -> myPageService.updateMyPageInfo(user, null, existingUser.getNickname())) .isInstanceOf(CustomException.class) .hasMessage(NICKNAME_ALREADY_EXISTED.getMessage()); } @@ -184,58 +189,21 @@ void setUp() { @Test void 최소_대기기간이_지나지_않은_상태에서_변경하면_예외_응답을_반환한다() { // given - SiteUser testUser = createSiteUser(); MockMultipartFile imageFile = createValidImageFile(); LocalDateTime modifiedAt = LocalDateTime.now().minusDays(MIN_DAYS_BETWEEN_NICKNAME_CHANGES - 1); - testUser.setNicknameModifiedAt(modifiedAt); - siteUserRepository.save(testUser); - - NicknameUpdateRequest request = new NicknameUpdateRequest("newNickname"); + user.setNicknameModifiedAt(modifiedAt); // when & then - assertThatCode(() -> myPageService.updateMyPageInfo(testUser, imageFile, "nickname12")) + assertThatCode(() -> myPageService.updateMyPageInfo(user, imageFile, "nickname12")) .isInstanceOf(CustomException.class) .hasMessage(createExpectedErrorMessage(modifiedAt)); } } - private SiteUser createSiteUser() { - SiteUser siteUser = new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - return siteUserRepository.save(siteUser); - } - - private SiteUser createSiteUserWithCustomProfile() { - SiteUser siteUser = new SiteUser( - "test@example.com", - "nickname", - "profile/profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - return siteUserRepository.save(siteUser); - } - - private void createDuplicatedSiteUser() { - SiteUser siteUser = new SiteUser( - "duplicated@example.com", - "duplicatedNickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - siteUserRepository.save(siteUser); - } - private int createLikedUniversities(SiteUser testUser) { - LikedUniversity likedUniversity1 = new LikedUniversity(null, 괌대학_A_지원_정보, testUser); - LikedUniversity likedUniversity2 = new LikedUniversity(null, 메이지대학_지원_정보, testUser); - LikedUniversity likedUniversity3 = new LikedUniversity(null, 코펜하겐IT대학_지원_정보, testUser); + LikedUniversity likedUniversity1 = new LikedUniversity(null, universityInfoForApplyFixture.괌대학_A_지원_정보(), testUser); + LikedUniversity likedUniversity2 = new LikedUniversity(null, universityInfoForApplyFixture.메이지대학_지원_정보(), testUser); + LikedUniversity likedUniversity3 = new LikedUniversity(null, universityInfoForApplyFixture.코펜하겐IT대학_지원_정보(), testUser); likedUniversityRepository.save(likedUniversity1); likedUniversityRepository.save(likedUniversity2); @@ -259,4 +227,15 @@ private String createExpectedErrorMessage(LocalDateTime modifiedAt) { ); return CAN_NOT_CHANGE_NICKNAME_YET.getMessage() + " : " + formatLastModifiedAt; } + + private SiteUser createSiteUserWithCustomProfile() { + return siteUserFixtureBuilder.siteUser() + .email("customProfile@example.com") + .authType(AuthType.EMAIL) + .nickname("커스텀프로필") + .profileImageUrl("profile/profileImageUrl") + .role(Role.MENTEE) + .password("customPassword123") + .create(); + } } diff --git a/src/test/java/com/example/solidconnection/siteuser/service/SiteUserServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/SiteUserServiceTest.java index 6c9736198..665fb11da 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/SiteUserServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/SiteUserServiceTest.java @@ -2,10 +2,8 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.NicknameExistsResponse; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.support.integration.BaseIntegrationTest; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -14,20 +12,21 @@ import static org.assertj.core.api.Assertions.assertThat; +@TestContainerSpringBootTest @DisplayName("유저 서비스 테스트") -class SiteUserServiceTest extends BaseIntegrationTest { +class SiteUserServiceTest { @Autowired private SiteUserService siteUserService; @Autowired - private SiteUserRepository siteUserRepository; + private SiteUserFixture siteUserFixture; - private SiteUser siteUser; + private SiteUser user; @BeforeEach void setUp() { - siteUser = createSiteUser(); + user = siteUserFixture.사용자(); } @Nested @@ -36,7 +35,7 @@ class 닉네임_중복_검사 { @Test void 존재하는_닉네임이면_true를_반환한다() { // when - NicknameExistsResponse response = siteUserService.checkNicknameExists(siteUser.getNickname()); + NicknameExistsResponse response = siteUserService.checkNicknameExists(user.getNickname()); // then assertThat(response.exists()).isTrue(); @@ -51,15 +50,4 @@ class 닉네임_중복_검사 { assertThat(response.exists()).isFalse(); } } - - private SiteUser createSiteUser() { - SiteUser siteUser = new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - return siteUserRepository.save(siteUser); - } } diff --git a/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java index 3b2ab8c22..7f970108a 100644 --- a/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java @@ -2,11 +2,9 @@ import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import com.example.solidconnection.university.domain.LikedUniversity; import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.dto.IsLikeResponse; @@ -38,17 +36,17 @@ class UniversityLikeServiceTest { private LikedUniversityRepository likedUniversityRepository; @Autowired - private SiteUserRepository siteUserRepository; + private SiteUserFixture siteUserFixture; @Autowired private UniversityInfoForApplyFixture universityInfoForApplyFixture; - private SiteUser testUser; + private SiteUser user; private UniversityInfoForApply 괌대학_A_지원_정보; @BeforeEach void setUp() { - testUser = createSiteUser(); + user = siteUserFixture.사용자(); 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); } @@ -58,13 +56,13 @@ class 대학_좋아요를_등록한다 { @Test void 성공적으로_좋아요를_등록한다() { // when - LikeResultResponse response = universityLikeService.likeUniversity(testUser, 괌대학_A_지원_정보.getId()); + LikeResultResponse response = universityLikeService.likeUniversity(user, 괌대학_A_지원_정보.getId()); // then assertAll( () -> assertThat(response.result()).isEqualTo(LIKE_SUCCESS_MESSAGE), () -> assertThat(likedUniversityRepository.findBySiteUserAndUniversityInfoForApply( - testUser, 괌대학_A_지원_정보 + user, 괌대학_A_지원_정보 )).isPresent() ); } @@ -72,10 +70,10 @@ class 대학_좋아요를_등록한다 { @Test void 이미_좋아요한_대학이면_예외_응답을_반환한다() { // given - saveLikedUniversity(testUser, 괌대학_A_지원_정보); + saveLikedUniversity(user, 괌대학_A_지원_정보); // when & then - assertThatCode(() -> universityLikeService.likeUniversity(testUser, 괌대학_A_지원_정보.getId())) + assertThatCode(() -> universityLikeService.likeUniversity(user, 괌대학_A_지원_정보.getId())) .isInstanceOf(CustomException.class) .hasMessage(ALREADY_LIKED_UNIVERSITY.getMessage()); } @@ -87,16 +85,16 @@ class 대학_좋아요를_취소한다 { @Test void 성공적으로_좋아요를_취소한다() { // given - saveLikedUniversity(testUser, 괌대학_A_지원_정보); + saveLikedUniversity(user, 괌대학_A_지원_정보); // when - LikeResultResponse response = universityLikeService.cancelLikeUniversity(testUser, 괌대학_A_지원_정보.getId()); + LikeResultResponse response = universityLikeService.cancelLikeUniversity(user, 괌대학_A_지원_정보.getId()); // then assertAll( () -> assertThat(response.result()).isEqualTo(LIKE_CANCELED_MESSAGE), () -> assertThat(likedUniversityRepository.findBySiteUserAndUniversityInfoForApply( - testUser, 괌대학_A_지원_정보 + user, 괌대학_A_지원_정보 )).isEmpty() ); } @@ -104,7 +102,7 @@ class 대학_좋아요를_취소한다 { @Test void 좋아요하지_않은_대학이면_예외_응답을_반환한다() { // when & then - assertThatCode(() -> universityLikeService.cancelLikeUniversity(testUser, 괌대학_A_지원_정보.getId())) + assertThatCode(() -> universityLikeService.cancelLikeUniversity(user, 괌대학_A_지원_정보.getId())) .isInstanceOf(CustomException.class) .hasMessage(NOT_LIKED_UNIVERSITY.getMessage()); } @@ -116,7 +114,7 @@ class 대학_좋아요를_취소한다 { Long invalidUniversityId = 9999L; // when & then - assertThatCode(() -> universityLikeService.likeUniversity(testUser, invalidUniversityId)) + assertThatCode(() -> universityLikeService.likeUniversity(user, invalidUniversityId)) .isInstanceOf(CustomException.class) .hasMessage(UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND.getMessage()); } @@ -124,10 +122,10 @@ class 대학_좋아요를_취소한다 { @Test void 좋아요한_대학인지_확인한다() { // given - saveLikedUniversity(testUser, 괌대학_A_지원_정보); + saveLikedUniversity(user, 괌대학_A_지원_정보); // when - IsLikeResponse response = universityLikeService.getIsLiked(testUser, 괌대학_A_지원_정보.getId()); + IsLikeResponse response = universityLikeService.getIsLiked(user, 괌대학_A_지원_정보.getId()); // then assertThat(response.isLike()).isTrue(); @@ -136,7 +134,7 @@ class 대학_좋아요를_취소한다 { @Test void 좋아요하지_않은_대학인지_확인한다() { // when - IsLikeResponse response = universityLikeService.getIsLiked(testUser, 괌대학_A_지원_정보.getId()); + IsLikeResponse response = universityLikeService.getIsLiked(user, 괌대학_A_지원_정보.getId()); // then assertThat(response.isLike()).isFalse(); @@ -148,23 +146,11 @@ class 대학_좋아요를_취소한다 { Long invalidUniversityId = 9999L; // when & then - assertThatCode(() -> universityLikeService.getIsLiked(testUser, invalidUniversityId)) + assertThatCode(() -> universityLikeService.getIsLiked(user, invalidUniversityId)) .isInstanceOf(CustomException.class) .hasMessage(UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND.getMessage()); } - // todo : 추후 Fixture로 대체 필요 - private SiteUser createSiteUser() { - SiteUser siteUser = new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - return siteUserRepository.save(siteUser); - } - private void saveLikedUniversity(SiteUser siteUser, UniversityInfoForApply universityInfoForApply) { LikedUniversity likedUniversity = LikedUniversity.builder() .siteUser(siteUser) diff --git a/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java index 7645794bf..ffacab33f 100644 --- a/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java @@ -7,10 +7,8 @@ import com.example.solidconnection.repositories.InterestedCountyRepository; import com.example.solidconnection.repositories.InterestedRegionRepository; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; import com.example.solidconnection.university.dto.UniversityRecommendsResponse; @@ -32,9 +30,6 @@ class UniversityRecommendServiceTest { @Autowired private UniversityRecommendService universityRecommendService; - @Autowired - private SiteUserRepository siteUserRepository; - @Autowired private InterestedRegionRepository interestedRegionRepository; @@ -44,6 +39,9 @@ class UniversityRecommendServiceTest { @Autowired private GeneralUniversityRecommendService generalUniversityRecommendService; + @Autowired + private SiteUserFixture siteUserFixture; + @Autowired private RegionFixture regionFixture; @@ -53,41 +51,37 @@ class UniversityRecommendServiceTest { @Autowired private UniversityInfoForApplyFixture universityInfoForApplyFixture; - private SiteUser testUser; + private SiteUser user; private UniversityInfoForApply 괌대학_A_지원_정보; private UniversityInfoForApply 괌대학_B_지원_정보; private UniversityInfoForApply 네바다주립대학_라스베이거스_지원_정보; private UniversityInfoForApply 메모리얼대학_세인트존스_A_지원_정보; private UniversityInfoForApply 서던덴마크대학교_지원_정보; private UniversityInfoForApply 코펜하겐IT대학_지원_정보; - private UniversityInfoForApply 그라츠대학_지원_정보; - private UniversityInfoForApply 그라츠공과대학_지원_정보; - private UniversityInfoForApply 린츠_카톨릭대학_지원_정보; - private UniversityInfoForApply 메이지대학_지원_정보; @BeforeEach void setUp() { - testUser = createSiteUser(); + user = siteUserFixture.사용자(); 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); 괌대학_B_지원_정보 = universityInfoForApplyFixture.괌대학_B_지원_정보(); 네바다주립대학_라스베이거스_지원_정보 = universityInfoForApplyFixture.네바다주립대학_라스베이거스_지원_정보(); 메모리얼대학_세인트존스_A_지원_정보 = universityInfoForApplyFixture.메모리얼대학_세인트존스_A_지원_정보(); 서던덴마크대학교_지원_정보 = universityInfoForApplyFixture.서던덴마크대학교_지원_정보(); 코펜하겐IT대학_지원_정보 = universityInfoForApplyFixture.코펜하겐IT대학_지원_정보(); - 그라츠대학_지원_정보 = universityInfoForApplyFixture.그라츠대학_지원_정보(); - 그라츠공과대학_지원_정보 = universityInfoForApplyFixture.그라츠공과대학_지원_정보(); - 린츠_카톨릭대학_지원_정보 = universityInfoForApplyFixture.린츠_카톨릭대학_지원_정보(); - 메이지대학_지원_정보 = universityInfoForApplyFixture.메이지대학_지원_정보(); + universityInfoForApplyFixture.그라츠대학_지원_정보(); + universityInfoForApplyFixture.그라츠공과대학_지원_정보(); + universityInfoForApplyFixture.린츠_카톨릭대학_지원_정보(); + universityInfoForApplyFixture.메이지대학_지원_정보(); generalUniversityRecommendService.init(); } @Test void 관심_지역_설정한_사용자의_맞춤_추천_대학을_조회한다() { // given - interestedRegionRepository.save(new InterestedRegion(testUser, regionFixture.영미권())); + interestedRegionRepository.save(new InterestedRegion(user, regionFixture.영미권())); // when - UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(testUser); + UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(user); // then assertThat(response.recommendedUniversities()) @@ -103,10 +97,10 @@ void setUp() { @Test void 관심_국가_설정한_사용자의_맞춤_추천_대학을_조회한다() { // given - interestedCountyRepository.save(new InterestedCountry(testUser, countryFixture.덴마크())); + interestedCountyRepository.save(new InterestedCountry(user, countryFixture.덴마크())); // when - UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(testUser); + UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(user); // then assertThat(response.recommendedUniversities()) @@ -120,11 +114,11 @@ void setUp() { @Test void 관심_지역과_국가_모두_설정한_사용자의_맞춤_추천_대학을_조회한다() { // given - interestedRegionRepository.save(new InterestedRegion(testUser, regionFixture.영미권())); - interestedCountyRepository.save(new InterestedCountry(testUser, countryFixture.덴마크())); + interestedRegionRepository.save(new InterestedRegion(user, regionFixture.영미권())); + interestedCountyRepository.save(new InterestedCountry(user, countryFixture.덴마크())); // when - UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(testUser); + UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(user); // then assertThat(response.recommendedUniversities()) @@ -142,7 +136,7 @@ void setUp() { @Test void 관심사_미설정_사용자는_일반_추천_대학을_조회한다() { // when - UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(testUser); + UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(user); // then assertThat(response.recommendedUniversities()) @@ -168,16 +162,4 @@ void setUp() { .toList() ); } - - // todo : 추후 Fixture로 대체 필요 - private SiteUser createSiteUser() { - SiteUser siteUser = new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - return siteUserRepository.save(siteUser); - } } From 0518a772ba8e16c4830ca045f4de4c4d8701faa7 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Mon, 19 May 2025 21:58:42 +0900 Subject: [PATCH 16/90] =?UTF-8?q?refactor:=20=EB=8F=84=EB=A9=94=EC=9D=B8?= =?UTF-8?q?=EB=B3=84=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=EB=A1=9C=20=EC=9E=AC=EA=B5=AC=EC=84=B1=20(#322)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: community 관련 Type 클래스 패키지 이동 - BoardCode를 community.board.domain 으로 - PostCategory를 community.post.domain 으로 - RedisConstants 를 community.post.service 로 * refactor: s3 관련 Type 클래스 패키지 이동 - ImgType을 s3로 * refactor: university 관련 Type 클래스 패키지 이동 - LanguageTestType, SemesterAvailableForDispatch, TuitionFeeType을 university.domain 으로 * refactor: siteUser 관련 Type 클래스 패키지 이동 - PreparationStatus, Role을 siteuser.domain 로 * refactor: application 관련 Type 클래스 패키지 이동 - VerifyStatus 을 application.domain 로 * refactor: 게시글 관련 redis 클래스 패키지 이동 - RedisService, UpdateViewCountService 를 post.service 로 * refactor: 지역 관련 클래스 패키지 이동 - location 패키지 하위에 region 과 country 를 생성 - Region, InterestedRegion 을 location.region.domain 으로 - Country, InterestedCountry 을 location.country.domain 으로 - RegionRepository, InterestedRegionRepository를 location.region.repository 으로 - CountryRepository, InterestedCountyRepository를 location.country.repository 으로 - 테스트 코드는 프로덕션 코드와 대응하는 위치로 * refactor: security 관련 클래스 패키지 이동 - custom.security에 있던 security를 루트 경로로 - 스프링 시큐리티는 서블릿 밖에 존재하므로, 분리된 하나의 관심사라 볼 수 있다. * refactor: 공용 코드의 패키지 이름 변경 - custom -> common * refactor: dto 입력 검증 관련 클래스 패키지 이동 - 특정 dto 에서만 사용하므로, 그 하위로 옮긴다. - RejectedReasonRequired, RejectedReasonValidator를 admin.dto.validation 으로 - ValidUniversityChoice, ValidUniversityChoiceValidator를 university.dto.validation 으로 * refactor: BaseEntity 패키지 이동 - 여러 도메인에서 쓰이므로 common 하위로 * refactor: 특정 도메인에만 관련된 config 패키지 이동 - 관련 도메인 하위에서만 필요한 설정은 그 도메인 하위로 옮긴다. - AppleOAuthClientProperties, KakaoOAuthClientProperties 를 auth.client.config 로 - CorsProperties, JwtProperties, SecurityConfiguration, AuthenticationManagerConfig 를 security.config 로 * refactor: config 클래스 패키지 이동 config -> common.config * refactor: S3 클래스 패키지 이동 - 다른 도메인 패키지들과 마찬가지로, 레이어별로 패키지를 나눈다. * chore: conflict 해소와 import문 정리 --- .../controller/AdminScoreController.java | 2 +- .../admin/dto/GpaScoreResponse.java | 2 +- .../admin/dto/GpaScoreStatusResponse.java | 2 +- .../admin/dto/GpaScoreUpdateRequest.java | 4 +-- .../admin/dto/LanguageTestResponse.java | 2 +- .../admin/dto/LanguageTestScoreResponse.java | 4 +-- .../dto/LanguageTestScoreStatusResponse.java | 2 +- .../dto/LanguageTestScoreUpdateRequest.java | 6 ++-- .../admin/dto/ScoreSearchCondition.java | 2 +- .../admin/dto/ScoreUpdateRequest.java | 2 +- .../admin/service/AdminGpaScoreService.java | 6 ++-- .../AdminLanguageTestScoreService.java | 6 ++-- .../controller/ApplicationController.java | 4 +-- .../application/domain/Application.java | 3 +- .../application/domain/LanguageTest.java | 2 +- .../domain}/VerifyStatus.java | 2 +- .../application/dto/ApplicantResponse.java | 2 +- .../dto/UniversityChoiceRequest.java | 2 +- .../validation}/RejectedReasonRequired.java | 3 +- .../validation}/RejectedReasonValidator.java | 7 ++-- .../repository/ApplicationRepository.java | 6 ++-- .../service/ApplicationQueryService.java | 6 ++-- .../service/ApplicationSubmissionService.java | 14 ++++---- .../auth/client/AppleOAuthClient.java | 8 ++--- .../AppleOAuthClientSecretProvider.java | 6 ++-- .../auth/client/ApplePublicKeyProvider.java | 10 +++--- .../auth/client/KakaoOAuthClient.java | 10 +++--- .../config}/AppleOAuthClientProperties.java | 2 +- .../config}/KakaoOAuthClientProperties.java | 2 +- .../auth/controller/AuthController.java | 6 ++-- .../auth/dto/SignUpRequest.java | 4 +-- .../auth/service/AuthService.java | 4 +-- .../auth/service/AuthTokenProvider.java | 2 +- .../service/CommonSignUpTokenProvider.java | 6 ++-- .../auth/service/EmailSignInService.java | 4 +-- .../auth/service/EmailSignUpService.java | 12 +++---- .../service/EmailSignUpTokenProvider.java | 8 ++--- .../auth/service/SignUpService.java | 18 +++++----- .../auth/service/TokenProvider.java | 2 +- .../service/oauth/OAuthSignUpService.java | 12 +++---- .../oauth/OAuthSignUpTokenProvider.java | 8 ++--- .../cache/ThunderingHerdCachingAspect.java | 8 ++--- .../{entity => }/common/BaseEntity.java | 2 +- .../config/client/RestTemplateConfig.java | 2 +- .../config/redis/RedisConfig.java | 4 +-- .../config/scheduler/SchedulerConfig.java | 2 +- .../{ => common}/config/sync/AsyncConfig.java | 2 +- .../{ => common}/config/web/WebMvcConfig.java | 6 ++-- .../exception/CustomAccessDeniedHandler.java | 4 +-- .../CustomAuthenticationEntryPoint.java | 4 +-- .../exception/CustomException.java | 2 +- .../exception/CustomExceptionHandler.java | 12 +++---- .../exception/ErrorCode.java | 2 +- .../resolver/AuthorizedUser.java | 2 +- .../resolver/AuthorizedUserResolver.java | 8 ++--- ...PageableHandlerMethodArgumentResolver.java | 2 +- .../response/ErrorResponse.java | 6 ++-- .../response/PageResponse.java | 2 +- .../board/controller/BoardController.java | 4 +-- .../board/domain}/BoardCode.java | 2 +- .../board/repository/BoardRepository.java | 6 ++-- .../comment/controller/CommentController.java | 2 +- .../community/comment/domain/Comment.java | 2 +- .../comment/repository/CommentRepository.java | 4 +-- .../comment/service/CommentService.java | 10 +++--- .../post/controller/PostController.java | 2 +- .../community/post/domain/Post.java | 3 +- .../community/post/domain/PostCategory.java | 5 +++ .../community/post/dto/PostCreateRequest.java | 2 +- .../community/post/dto/PostListResponse.java | 2 +- .../post/repository/PostLikeRepository.java | 4 +-- .../post/repository/PostRepository.java | 4 +-- .../post/service/PostCommandService.java | 21 ++++++------ .../post/service/PostLikeService.java | 6 ++-- .../post/service/PostQueryService.java | 15 ++++---- .../post/service}/RedisConstants.java | 2 +- .../post}/service/RedisService.java | 8 ++--- .../post}/service/UpdateViewCountService.java | 2 +- .../country/domain}/Country.java | 3 +- .../country/domain}/InterestedCountry.java | 2 +- .../repository}/CountryRepository.java | 4 +-- .../InterestedCountyRepository.java | 4 +-- .../region/domain}/InterestedRegion.java | 2 +- .../region/domain}/Region.java | 2 +- .../InterestedRegionRepository.java | 4 +-- .../region/repository}/RegionRepository.java | 4 +-- .../solidconnection/news/domain/News.java | 2 +- .../s3/{ => config}/AmazonS3Config.java | 2 +- .../s3/{ => controller}/S3Controller.java | 9 +++-- .../{type => s3/domain}/ImgType.java | 2 +- .../s3/{ => dto}/UploadedFileUrlResponse.java | 2 +- .../s3/{ => dto}/urlPrefixResponse.java | 2 +- .../s3/{ => service}/FileUploadService.java | 8 ++--- .../s3/{ => service}/S3Service.java | 17 +++++----- .../scheduler/UpdateViewCountScheduler.java | 4 +-- .../score/controller/ScoreController.java | 2 +- .../score/domain/GpaScore.java | 4 +-- .../score/domain/LanguageTestScore.java | 4 +-- .../score/dto/GpaScoreStatusResponse.java | 2 +- .../score/dto/LanguageTestResponse.java | 2 +- .../score/dto/LanguageTestScoreRequest.java | 2 +- .../dto/LanguageTestScoreStatusResponse.java | 2 +- .../LanguageTestScoreRepository.java | 2 +- .../custom/GpaScoreFilterRepositoryImpl.java | 2 +- ...LanguageTestScoreFilterRepositoryImpl.java | 2 +- .../score/service/ScoreService.java | 10 +++--- .../annotation/RequireAdminAccess.java | 2 +- .../aspect/AdminAuthorizationAspect.java | 10 +++--- .../authentication/JwtAuthentication.java | 2 +- .../SiteUserAuthentication.java | 4 +-- .../config}/AuthenticationManagerConfig.java | 4 +-- .../config}/CorsProperties.java | 2 +- .../config}/JwtProperties.java | 2 +- .../config}/SecurityConfiguration.java | 14 ++++---- .../filter/ExceptionHandlerFilter.java | 10 +++--- .../filter/JwtAuthenticationFilter.java | 6 ++-- .../security/filter/SignOutCheckFilter.java | 6 ++-- .../SiteUserAuthenticationProvider.java | 12 +++---- .../userdetails/SecurityRoleMapper.java | 4 +-- .../security/userdetails/SiteUserDetails.java | 2 +- .../userdetails/SiteUserDetailsService.java | 8 ++--- .../siteuser/controller/MyPageController.java | 2 +- .../domain}/PreparationStatus.java | 2 +- .../{type => siteuser/domain}/Role.java | 2 +- .../siteuser/domain/SiteUser.java | 2 -- .../siteuser/dto/MyPageResponse.java | 2 +- .../siteuser/service/MyPageService.java | 12 +++---- .../solidconnection/type/PostCategory.java | 5 --- .../controller/UniversityController.java | 4 +-- .../domain/LanguageRequirement.java | 1 - .../domain}/LanguageTestType.java | 2 +- .../domain}/SemesterAvailableForDispatch.java | 2 +- .../domain}/TuitionFeeType.java | 2 +- .../university/domain/University.java | 4 +-- .../domain/UniversityInfoForApply.java | 2 -- .../dto/LanguageRequirementResponse.java | 2 +- .../validation}/ValidUniversityChoice.java | 3 +- .../ValidUniversityChoiceValidator.java | 9 +++-- .../LanguageRequirementRepository.java | 2 +- .../UniversityInfoForApplyRepository.java | 6 ++-- .../repository/UniversityRepository.java | 4 +-- .../custom/UniversityFilterRepository.java | 2 +- .../UniversityFilterRepositoryImpl.java | 6 ++-- .../service/UniversityLikeService.java | 6 ++-- .../service/UniversityQueryService.java | 2 +- .../solidconnection/util/JwtUtils.java | 4 +-- .../solidconnection/util/RedisUtils.java | 8 ++--- .../RejectedReasonValidatorTest.java | 8 ++--- .../service/AdminGpaScoreServiceTest.java | 6 ++-- .../AdminLanguageTestScoreServiceTest.java | 8 ++--- .../service/ApplicationQueryServiceTest.java | 4 +-- .../ApplicationSubmissionServiceTest.java | 14 ++++---- .../auth/service/AuthServiceTest.java | 4 +-- .../auth/service/EmailSignInServiceTest.java | 4 +-- .../auth/service/SignInServiceTest.java | 2 +- .../oauth/OAuthSignUpTokenProviderTest.java | 10 +++--- .../CustomAccessDeniedHandlerTest.java | 6 ++-- .../CustomAuthenticationEntryPointTest.java | 6 ++-- .../resolver/AuthorizedUserResolverTest.java | 10 +++--- ...ableHandlerMethodArgumentResolverTest.java | 2 +- .../comment/service/CommentServiceTest.java | 12 +++---- .../post/service/PostCommandServiceTest.java | 19 +++++------ .../post/service/PostLikeServiceTest.java | 8 ++--- .../post/service/PostQueryServiceTest.java | 11 +++--- .../PostLikeCountConcurrencyTest.java | 2 +- .../PostViewCountConcurrencyTest.java | 6 ++-- .../solidconnection/e2e/DynamicFixture.java | 4 +-- .../country/fixture/CountryFixture.java | 6 ++-- .../fixture/CountryFixtureBuilder.java | 8 ++--- .../repository/CountryRepositoryForTest.java | 4 +-- .../region/fixture/RegionFixture.java | 4 +-- .../region/fixture/RegionFixtureBuilder.java | 6 ++-- .../repository/RegionRepositoryForTest.java | 4 +-- .../score/service/ScoreServiceTest.java | 10 +++--- .../aspect/AdminAuthorizationAspectTest.java | 8 ++--- .../SiteUserAuthenticationTest.java | 8 ++--- .../filter/ExceptionHandlerFilterTest.java | 7 ++-- .../filter/JwtAuthenticationFilterTest.java | 8 ++--- .../filter/SignOutCheckFilterTest.java | 20 +++++------ .../SiteUserAuthenticationProviderTest.java | 14 ++++---- .../SiteUserDetailsServiceTest.java | 8 ++--- .../userdetails/SiteUserDetailsTest.java | 2 +- .../siteuser/fixture/SiteUserFixture.java | 2 +- .../fixture/SiteUserFixtureBuilder.java | 4 +-- .../repository/SiteUserRepositoryTest.java | 4 +-- .../siteuser/service/MyPageServiceTest.java | 14 ++++---- .../integration/BaseIntegrationTest.java | 34 +++++++++---------- .../ValidUniversityChoiceValidatorTest.java | 8 ++--- .../fixture/LanguageRequirementFixture.java | 2 +- .../LanguageRequirementFixtureBuilder.java | 2 +- .../university/fixture/UniversityFixture.java | 4 +-- .../fixture/UniversityFixtureBuilder.java | 4 +-- .../UniversityInfoForApplyFixtureBuilder.java | 4 +-- .../service/UniversityLikeServiceTest.java | 8 ++--- .../service/UniversityQueryServiceTest.java | 10 +++--- .../UniversityRecommendServiceTest.java | 12 +++---- .../solidconnection/util/JwtUtilsTest.java | 4 +-- 197 files changed, 527 insertions(+), 536 deletions(-) rename src/main/java/com/example/solidconnection/{type => application/domain}/VerifyStatus.java (52%) rename src/main/java/com/example/solidconnection/{custom/validation/annotation => application/dto/validation}/RejectedReasonRequired.java (78%) rename src/main/java/com/example/solidconnection/{custom/validation/validator => application/dto/validation}/RejectedReasonValidator.java (82%) rename src/main/java/com/example/solidconnection/{config/client => auth/client/config}/AppleOAuthClientProperties.java (87%) rename src/main/java/com/example/solidconnection/{config/client => auth/client/config}/KakaoOAuthClientProperties.java (83%) rename src/main/java/com/example/solidconnection/{entity => }/common/BaseEntity.java (95%) rename src/main/java/com/example/solidconnection/{ => common}/config/client/RestTemplateConfig.java (90%) rename src/main/java/com/example/solidconnection/{ => common}/config/redis/RedisConfig.java (95%) rename src/main/java/com/example/solidconnection/{ => common}/config/scheduler/SchedulerConfig.java (94%) rename src/main/java/com/example/solidconnection/{ => common}/config/sync/AsyncConfig.java (92%) rename src/main/java/com/example/solidconnection/{ => common}/config/web/WebMvcConfig.java (82%) rename src/main/java/com/example/solidconnection/{custom => common}/exception/CustomAccessDeniedHandler.java (90%) rename src/main/java/com/example/solidconnection/{custom => common}/exception/CustomAuthenticationEntryPoint.java (90%) rename src/main/java/com/example/solidconnection/{custom => common}/exception/CustomException.java (89%) rename src/main/java/com/example/solidconnection/{custom => common}/exception/CustomExceptionHandler.java (89%) rename src/main/java/com/example/solidconnection/{custom => common}/exception/ErrorCode.java (99%) rename src/main/java/com/example/solidconnection/{custom => common}/resolver/AuthorizedUser.java (85%) rename src/main/java/com/example/solidconnection/{custom => common}/resolver/AuthorizedUserResolver.java (88%) rename src/main/java/com/example/solidconnection/{custom => common}/resolver/CustomPageableHandlerMethodArgumentResolver.java (92%) rename src/main/java/com/example/solidconnection/{custom => common}/response/ErrorResponse.java (64%) rename src/main/java/com/example/solidconnection/{custom => common}/response/PageResponse.java (92%) rename src/main/java/com/example/solidconnection/{type => community/board/domain}/BoardCode.java (50%) create mode 100644 src/main/java/com/example/solidconnection/community/post/domain/PostCategory.java rename src/main/java/com/example/solidconnection/{type => community/post/service}/RedisConstants.java (90%) rename src/main/java/com/example/solidconnection/{ => community/post}/service/RedisService.java (82%) rename src/main/java/com/example/solidconnection/{ => community/post}/service/UpdateViewCountService.java (95%) rename src/main/java/com/example/solidconnection/{entity => location/country/domain}/Country.java (85%) rename src/main/java/com/example/solidconnection/{entity => location/country/domain}/InterestedCountry.java (92%) rename src/main/java/com/example/solidconnection/{repositories => location/country/repository}/CountryRepository.java (78%) rename src/main/java/com/example/solidconnection/{repositories => location/country/repository}/InterestedCountyRepository.java (72%) rename src/main/java/com/example/solidconnection/{entity => location/region/domain}/InterestedRegion.java (92%) rename src/main/java/com/example/solidconnection/{entity => location/region/domain}/Region.java (91%) rename src/main/java/com/example/solidconnection/{repositories => location/region/repository}/InterestedRegionRepository.java (72%) rename src/main/java/com/example/solidconnection/{repositories => location/region/repository}/RegionRepository.java (78%) rename src/main/java/com/example/solidconnection/s3/{ => config}/AmazonS3Config.java (95%) rename src/main/java/com/example/solidconnection/s3/{ => controller}/S3Controller.java (88%) rename src/main/java/com/example/solidconnection/{type => s3/domain}/ImgType.java (83%) rename src/main/java/com/example/solidconnection/s3/{ => dto}/UploadedFileUrlResponse.java (60%) rename src/main/java/com/example/solidconnection/s3/{ => dto}/urlPrefixResponse.java (78%) rename src/main/java/com/example/solidconnection/s3/{ => service}/FileUploadService.java (89%) rename src/main/java/com/example/solidconnection/s3/{ => service}/S3Service.java (90%) rename src/main/java/com/example/solidconnection/{custom => }/security/annotation/RequireAdminAccess.java (80%) rename src/main/java/com/example/solidconnection/{custom => }/security/aspect/AdminAuthorizationAspect.java (75%) rename src/main/java/com/example/solidconnection/{custom => }/security/authentication/JwtAuthentication.java (92%) rename src/main/java/com/example/solidconnection/{custom => }/security/authentication/SiteUserAuthentication.java (69%) rename src/main/java/com/example/solidconnection/{config/security => security/config}/AuthenticationManagerConfig.java (81%) rename src/main/java/com/example/solidconnection/{config/security => security/config}/CorsProperties.java (79%) rename src/main/java/com/example/solidconnection/{config/security => security/config}/JwtProperties.java (75%) rename src/main/java/com/example/solidconnection/{config/security => security/config}/SecurityConfiguration.java (87%) rename src/main/java/com/example/solidconnection/{custom => }/security/filter/ExceptionHandlerFilter.java (87%) rename src/main/java/com/example/solidconnection/{custom => }/security/filter/JwtAuthenticationFilter.java (87%) rename src/main/java/com/example/solidconnection/{custom => }/security/filter/SignOutCheckFilter.java (87%) rename src/main/java/com/example/solidconnection/{custom => }/security/provider/SiteUserAuthenticationProvider.java (72%) rename src/main/java/com/example/solidconnection/{custom => }/security/userdetails/SecurityRoleMapper.java (77%) rename src/main/java/com/example/solidconnection/{custom => }/security/userdetails/SiteUserDetails.java (95%) rename src/main/java/com/example/solidconnection/{custom => }/security/userdetails/SiteUserDetailsService.java (87%) rename src/main/java/com/example/solidconnection/{type => siteuser/domain}/PreparationStatus.java (83%) rename src/main/java/com/example/solidconnection/{type => siteuser/domain}/Role.java (52%) delete mode 100644 src/main/java/com/example/solidconnection/type/PostCategory.java rename src/main/java/com/example/solidconnection/{type => university/domain}/LanguageTestType.java (96%) rename src/main/java/com/example/solidconnection/{type => university/domain}/SemesterAvailableForDispatch.java (90%) rename src/main/java/com/example/solidconnection/{type => university/domain}/TuitionFeeType.java (87%) rename src/main/java/com/example/solidconnection/{custom/validation/annotation => university/dto/validation}/ValidUniversityChoice.java (78%) rename src/main/java/com/example/solidconnection/{custom/validation/validator => university/dto/validation}/ValidUniversityChoiceValidator.java (86%) rename src/test/java/com/example/solidconnection/{custom/validation/validator => admin/dto/validation}/RejectedReasonValidatorTest.java (93%) rename src/test/java/com/example/solidconnection/{custom => common}/exception/CustomAccessDeniedHandlerTest.java (90%) rename src/test/java/com/example/solidconnection/{custom => common}/exception/CustomAuthenticationEntryPointTest.java (91%) rename src/test/java/com/example/solidconnection/{custom => common}/resolver/AuthorizedUserResolverTest.java (91%) rename src/test/java/com/example/solidconnection/{custom => common}/resolver/CustomPageableHandlerMethodArgumentResolverTest.java (98%) rename src/test/java/com/example/solidconnection/{ => location}/country/fixture/CountryFixture.java (87%) rename src/test/java/com/example/solidconnection/{ => location}/country/fixture/CountryFixtureBuilder.java (78%) rename src/test/java/com/example/solidconnection/{ => location}/country/repository/CountryRepositoryForTest.java (62%) rename src/test/java/com/example/solidconnection/{ => location}/region/fixture/RegionFixture.java (86%) rename src/test/java/com/example/solidconnection/{ => location}/region/fixture/RegionFixtureBuilder.java (79%) rename src/test/java/com/example/solidconnection/{ => location}/region/repository/RegionRepositoryForTest.java (62%) rename src/test/java/com/example/solidconnection/{custom => }/security/aspect/AdminAuthorizationAspectTest.java (90%) rename src/test/java/com/example/solidconnection/{custom => }/security/authentication/SiteUserAuthenticationTest.java (88%) rename src/test/java/com/example/solidconnection/{custom => }/security/filter/ExceptionHandlerFilterTest.java (93%) rename src/test/java/com/example/solidconnection/{custom => }/security/filter/JwtAuthenticationFilterTest.java (91%) rename src/test/java/com/example/solidconnection/{custom => }/security/filter/SignOutCheckFilterTest.java (85%) rename src/test/java/com/example/solidconnection/{custom => }/security/provider/SiteUserAuthenticationProviderTest.java (91%) rename src/test/java/com/example/solidconnection/{custom => }/security/userdetails/SiteUserDetailsServiceTest.java (92%) rename src/test/java/com/example/solidconnection/{custom => }/security/userdetails/SiteUserDetailsTest.java (94%) rename src/test/java/com/example/solidconnection/{custom/validation/validator => university/dto/validation}/ValidUniversityChoiceValidatorTest.java (92%) diff --git a/src/main/java/com/example/solidconnection/admin/controller/AdminScoreController.java b/src/main/java/com/example/solidconnection/admin/controller/AdminScoreController.java index 47bac37e1..d2869e4d3 100644 --- a/src/main/java/com/example/solidconnection/admin/controller/AdminScoreController.java +++ b/src/main/java/com/example/solidconnection/admin/controller/AdminScoreController.java @@ -9,7 +9,7 @@ import com.example.solidconnection.admin.dto.ScoreSearchCondition; import com.example.solidconnection.admin.service.AdminGpaScoreService; import com.example.solidconnection.admin.service.AdminLanguageTestScoreService; -import com.example.solidconnection.custom.response.PageResponse; +import com.example.solidconnection.common.response.PageResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; diff --git a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreResponse.java b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreResponse.java index 5f37e823b..c90ba6a40 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreResponse.java @@ -1,7 +1,7 @@ package com.example.solidconnection.admin.dto; +import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.score.domain.GpaScore; -import com.example.solidconnection.type.VerifyStatus; public record GpaScoreResponse( long id, diff --git a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreStatusResponse.java b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreStatusResponse.java index 49afbd4ed..a9d1d0668 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreStatusResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreStatusResponse.java @@ -1,6 +1,6 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.type.VerifyStatus; +import com.example.solidconnection.application.domain.VerifyStatus; import java.time.ZonedDateTime; diff --git a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreUpdateRequest.java b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreUpdateRequest.java index b7fe58c71..c22979ea6 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreUpdateRequest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.custom.validation.annotation.RejectedReasonRequired; -import com.example.solidconnection.type.VerifyStatus; +import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.application.dto.validation.RejectedReasonRequired; import jakarta.validation.constraints.NotNull; @RejectedReasonRequired diff --git a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestResponse.java b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestResponse.java index c91fc68c3..257b8fd6f 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestResponse.java @@ -1,6 +1,6 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.university.domain.LanguageTestType; public record LanguageTestResponse( LanguageTestType languageTestType, diff --git a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreResponse.java b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreResponse.java index aee435c9c..978dcae82 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreResponse.java @@ -1,8 +1,8 @@ package com.example.solidconnection.admin.dto; +import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.score.domain.LanguageTestScore; -import com.example.solidconnection.type.LanguageTestType; -import com.example.solidconnection.type.VerifyStatus; +import com.example.solidconnection.university.domain.LanguageTestType; public record LanguageTestScoreResponse( long id, diff --git a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreStatusResponse.java b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreStatusResponse.java index c852b5b2a..3094ea18f 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreStatusResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreStatusResponse.java @@ -1,6 +1,6 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.type.VerifyStatus; +import com.example.solidconnection.application.domain.VerifyStatus; import java.time.ZonedDateTime; diff --git a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreUpdateRequest.java b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreUpdateRequest.java index 3e76e0c93..c072111ec 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreUpdateRequest.java @@ -1,8 +1,8 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.custom.validation.annotation.RejectedReasonRequired; -import com.example.solidconnection.type.LanguageTestType; -import com.example.solidconnection.type.VerifyStatus; +import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.application.dto.validation.RejectedReasonRequired; +import com.example.solidconnection.university.domain.LanguageTestType; import jakarta.validation.constraints.NotNull; @RejectedReasonRequired diff --git a/src/main/java/com/example/solidconnection/admin/dto/ScoreSearchCondition.java b/src/main/java/com/example/solidconnection/admin/dto/ScoreSearchCondition.java index 2e94628e6..1dc284be8 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/ScoreSearchCondition.java +++ b/src/main/java/com/example/solidconnection/admin/dto/ScoreSearchCondition.java @@ -1,6 +1,6 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.type.VerifyStatus; +import com.example.solidconnection.application.domain.VerifyStatus; import java.time.LocalDate; diff --git a/src/main/java/com/example/solidconnection/admin/dto/ScoreUpdateRequest.java b/src/main/java/com/example/solidconnection/admin/dto/ScoreUpdateRequest.java index 184f76100..7223b48e4 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/ScoreUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/admin/dto/ScoreUpdateRequest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.type.VerifyStatus; +import com.example.solidconnection.application.domain.VerifyStatus; public interface ScoreUpdateRequest { VerifyStatus verifyStatus(); diff --git a/src/main/java/com/example/solidconnection/admin/service/AdminGpaScoreService.java b/src/main/java/com/example/solidconnection/admin/service/AdminGpaScoreService.java index c761ff485..eae555406 100644 --- a/src/main/java/com/example/solidconnection/admin/service/AdminGpaScoreService.java +++ b/src/main/java/com/example/solidconnection/admin/service/AdminGpaScoreService.java @@ -5,17 +5,17 @@ import com.example.solidconnection.admin.dto.GpaScoreUpdateRequest; import com.example.solidconnection.admin.dto.ScoreSearchCondition; import com.example.solidconnection.application.domain.Gpa; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.repository.GpaScoreRepository; -import com.example.solidconnection.type.VerifyStatus; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import static com.example.solidconnection.custom.exception.ErrorCode.GPA_SCORE_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.GPA_SCORE_NOT_FOUND; @RequiredArgsConstructor @Service diff --git a/src/main/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreService.java b/src/main/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreService.java index 380ef02c6..db89ae91c 100644 --- a/src/main/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreService.java +++ b/src/main/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreService.java @@ -5,17 +5,17 @@ import com.example.solidconnection.admin.dto.LanguageTestScoreUpdateRequest; import com.example.solidconnection.admin.dto.ScoreSearchCondition; import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; -import com.example.solidconnection.type.VerifyStatus; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import static com.example.solidconnection.custom.exception.ErrorCode.LANGUAGE_TEST_SCORE_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.LANGUAGE_TEST_SCORE_NOT_FOUND; @RequiredArgsConstructor @Service diff --git a/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java b/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java index 36c7d6af2..228c436ba 100644 --- a/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java +++ b/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java @@ -5,8 +5,8 @@ import com.example.solidconnection.application.dto.ApplyRequest; import com.example.solidconnection.application.service.ApplicationQueryService; import com.example.solidconnection.application.service.ApplicationSubmissionService; -import com.example.solidconnection.custom.resolver.AuthorizedUser; -import com.example.solidconnection.custom.security.annotation.RequireAdminAccess; +import com.example.solidconnection.common.resolver.AuthorizedUser; +import com.example.solidconnection.security.annotation.RequireAdminAccess; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/example/solidconnection/application/domain/Application.java b/src/main/java/com/example/solidconnection/application/domain/Application.java index 6caa75331..98342ca88 100644 --- a/src/main/java/com/example/solidconnection/application/domain/Application.java +++ b/src/main/java/com/example/solidconnection/application/domain/Application.java @@ -1,7 +1,6 @@ package com.example.solidconnection.application.domain; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.type.VerifyStatus; import com.example.solidconnection.university.domain.UniversityInfoForApply; import jakarta.persistence.Column; import jakarta.persistence.Embedded; @@ -19,7 +18,7 @@ import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; -import static com.example.solidconnection.type.VerifyStatus.PENDING; +import static com.example.solidconnection.application.domain.VerifyStatus.PENDING; @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) diff --git a/src/main/java/com/example/solidconnection/application/domain/LanguageTest.java b/src/main/java/com/example/solidconnection/application/domain/LanguageTest.java index 4295372d4..4a0544ea7 100644 --- a/src/main/java/com/example/solidconnection/application/domain/LanguageTest.java +++ b/src/main/java/com/example/solidconnection/application/domain/LanguageTest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.application.domain; -import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.university.domain.LanguageTestType; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import jakarta.persistence.EnumType; diff --git a/src/main/java/com/example/solidconnection/type/VerifyStatus.java b/src/main/java/com/example/solidconnection/application/domain/VerifyStatus.java similarity index 52% rename from src/main/java/com/example/solidconnection/type/VerifyStatus.java rename to src/main/java/com/example/solidconnection/application/domain/VerifyStatus.java index 95f122715..e6f1afbe6 100644 --- a/src/main/java/com/example/solidconnection/type/VerifyStatus.java +++ b/src/main/java/com/example/solidconnection/application/domain/VerifyStatus.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.type; +package com.example.solidconnection.application.domain; public enum VerifyStatus { PENDING, REJECTED, APPROVED diff --git a/src/main/java/com/example/solidconnection/application/dto/ApplicantResponse.java b/src/main/java/com/example/solidconnection/application/dto/ApplicantResponse.java index 9835491b1..c6161a134 100644 --- a/src/main/java/com/example/solidconnection/application/dto/ApplicantResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/ApplicantResponse.java @@ -1,7 +1,7 @@ package com.example.solidconnection.application.dto; import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.university.domain.LanguageTestType; public record ApplicantResponse( String nicknameForApply, diff --git a/src/main/java/com/example/solidconnection/application/dto/UniversityChoiceRequest.java b/src/main/java/com/example/solidconnection/application/dto/UniversityChoiceRequest.java index d219dbc2e..220b803b3 100644 --- a/src/main/java/com/example/solidconnection/application/dto/UniversityChoiceRequest.java +++ b/src/main/java/com/example/solidconnection/application/dto/UniversityChoiceRequest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.application.dto; -import com.example.solidconnection.custom.validation.annotation.ValidUniversityChoice; +import com.example.solidconnection.university.dto.validation.ValidUniversityChoice; @ValidUniversityChoice public record UniversityChoiceRequest( diff --git a/src/main/java/com/example/solidconnection/custom/validation/annotation/RejectedReasonRequired.java b/src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonRequired.java similarity index 78% rename from src/main/java/com/example/solidconnection/custom/validation/annotation/RejectedReasonRequired.java rename to src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonRequired.java index 4ae4a6618..281be235b 100644 --- a/src/main/java/com/example/solidconnection/custom/validation/annotation/RejectedReasonRequired.java +++ b/src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonRequired.java @@ -1,6 +1,5 @@ -package com.example.solidconnection.custom.validation.annotation; +package com.example.solidconnection.application.dto.validation; -import com.example.solidconnection.custom.validation.validator.RejectedReasonValidator; import jakarta.validation.Constraint; import jakarta.validation.Payload; diff --git a/src/main/java/com/example/solidconnection/custom/validation/validator/RejectedReasonValidator.java b/src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonValidator.java similarity index 82% rename from src/main/java/com/example/solidconnection/custom/validation/validator/RejectedReasonValidator.java rename to src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonValidator.java index c975a0a0a..0f1837e0e 100644 --- a/src/main/java/com/example/solidconnection/custom/validation/validator/RejectedReasonValidator.java +++ b/src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonValidator.java @@ -1,13 +1,12 @@ -package com.example.solidconnection.custom.validation.validator; +package com.example.solidconnection.application.dto.validation; import com.example.solidconnection.admin.dto.ScoreUpdateRequest; -import com.example.solidconnection.custom.validation.annotation.RejectedReasonRequired; -import com.example.solidconnection.type.VerifyStatus; +import com.example.solidconnection.application.domain.VerifyStatus; import io.micrometer.common.util.StringUtils; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; -import static com.example.solidconnection.custom.exception.ErrorCode.REJECTED_REASON_REQUIRED; +import static com.example.solidconnection.common.exception.ErrorCode.REJECTED_REASON_REQUIRED; public class RejectedReasonValidator implements ConstraintValidator { diff --git a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java index 5aeb972bf..7df53eae6 100644 --- a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java +++ b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java @@ -1,9 +1,9 @@ package com.example.solidconnection.application.repository; import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.type.VerifyStatus; import com.example.solidconnection.university.domain.UniversityInfoForApply; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -13,7 +13,7 @@ import java.util.List; import java.util.Optional; -import static com.example.solidconnection.custom.exception.ErrorCode.APPLICATION_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.APPLICATION_NOT_FOUND; @Repository public interface ApplicationRepository extends JpaRepository { diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java index 157c6adcd..e13ca7e3f 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java @@ -1,14 +1,14 @@ package com.example.solidconnection.application.service; import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.application.dto.ApplicantResponse; import com.example.solidconnection.application.dto.ApplicationsResponse; import com.example.solidconnection.application.dto.UniversityApplicantsResponse; import com.example.solidconnection.application.repository.ApplicationRepository; import com.example.solidconnection.cache.annotation.ThunderingHerdCaching; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.type.VerifyStatus; import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; @@ -25,7 +25,7 @@ import java.util.function.Function; import java.util.stream.Collectors; -import static com.example.solidconnection.custom.exception.ErrorCode.APPLICATION_NOT_APPROVED; +import static com.example.solidconnection.common.exception.ErrorCode.APPLICATION_NOT_APPROVED; @RequiredArgsConstructor @Service diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java index 432e93aff..af1678a16 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java @@ -1,18 +1,18 @@ package com.example.solidconnection.application.service; import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.application.dto.ApplicationSubmissionResponse; import com.example.solidconnection.application.dto.ApplyRequest; import com.example.solidconnection.application.dto.UniversityChoiceRequest; import com.example.solidconnection.application.repository.ApplicationRepository; import com.example.solidconnection.cache.annotation.DefaultCacheOut; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.repository.GpaScoreRepository; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.type.VerifyStatus; import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; import lombok.RequiredArgsConstructor; @@ -22,11 +22,11 @@ import java.util.Optional; -import static com.example.solidconnection.custom.exception.ErrorCode.APPLY_UPDATE_LIMIT_EXCEED; -import static com.example.solidconnection.custom.exception.ErrorCode.GPA_SCORE_NOT_FOUND; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_GPA_SCORE_STATUS; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE_STATUS; +import static com.example.solidconnection.common.exception.ErrorCode.APPLY_UPDATE_LIMIT_EXCEED; +import static com.example.solidconnection.common.exception.ErrorCode.GPA_SCORE_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_GPA_SCORE_STATUS; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE_STATUS; @RequiredArgsConstructor @Service diff --git a/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClient.java b/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClient.java index aef1309af..733eb5700 100644 --- a/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClient.java +++ b/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClient.java @@ -1,9 +1,9 @@ package com.example.solidconnection.auth.client; +import com.example.solidconnection.auth.client.config.AppleOAuthClientProperties; import com.example.solidconnection.auth.dto.oauth.AppleTokenDto; import com.example.solidconnection.auth.dto.oauth.AppleUserInfoDto; -import com.example.solidconnection.config.client.AppleOAuthClientProperties; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import io.jsonwebtoken.Jwts; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpEntity; @@ -19,8 +19,8 @@ import java.security.PublicKey; import java.util.Objects; -import static com.example.solidconnection.custom.exception.ErrorCode.APPLE_AUTHORIZATION_FAILED; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_APPLE_ID_TOKEN; +import static com.example.solidconnection.common.exception.ErrorCode.APPLE_AUTHORIZATION_FAILED; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_APPLE_ID_TOKEN; /* * 애플 인증을 위한 OAuth2 클라이언트 diff --git a/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClientSecretProvider.java b/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClientSecretProvider.java index 31228e5d3..6bdd6a5fb 100644 --- a/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClientSecretProvider.java +++ b/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClientSecretProvider.java @@ -1,7 +1,7 @@ package com.example.solidconnection.auth.client; -import com.example.solidconnection.config.client.AppleOAuthClientProperties; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.auth.client.config.AppleOAuthClientProperties; +import com.example.solidconnection.common.exception.CustomException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import jakarta.annotation.PostConstruct; @@ -16,7 +16,7 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.util.Date; -import static com.example.solidconnection.custom.exception.ErrorCode.FAILED_TO_READ_APPLE_PRIVATE_KEY; +import static com.example.solidconnection.common.exception.ErrorCode.FAILED_TO_READ_APPLE_PRIVATE_KEY; /* * 애플 OAuth 에 필요한 클라이언트 시크릿은 매번 동적으로 생성해야 한다. diff --git a/src/main/java/com/example/solidconnection/auth/client/ApplePublicKeyProvider.java b/src/main/java/com/example/solidconnection/auth/client/ApplePublicKeyProvider.java index 1cc708cc7..0fe6b9700 100644 --- a/src/main/java/com/example/solidconnection/auth/client/ApplePublicKeyProvider.java +++ b/src/main/java/com/example/solidconnection/auth/client/ApplePublicKeyProvider.java @@ -1,7 +1,7 @@ package com.example.solidconnection.auth.client; -import com.example.solidconnection.config.client.AppleOAuthClientProperties; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.auth.client.config.AppleOAuthClientProperties; +import com.example.solidconnection.common.exception.CustomException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -20,9 +20,9 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import static com.example.solidconnection.custom.exception.ErrorCode.APPLE_ID_TOKEN_EXPIRED; -import static com.example.solidconnection.custom.exception.ErrorCode.APPLE_PUBLIC_KEY_NOT_FOUND; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_APPLE_ID_TOKEN; +import static com.example.solidconnection.common.exception.ErrorCode.APPLE_ID_TOKEN_EXPIRED; +import static com.example.solidconnection.common.exception.ErrorCode.APPLE_PUBLIC_KEY_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_APPLE_ID_TOKEN; import static org.apache.tomcat.util.codec.binary.Base64.decodeBase64URLSafe; /* diff --git a/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java b/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java index 5d625cb7c..9539116aa 100644 --- a/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java +++ b/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java @@ -1,9 +1,9 @@ package com.example.solidconnection.auth.client; +import com.example.solidconnection.auth.client.config.KakaoOAuthClientProperties; import com.example.solidconnection.auth.dto.oauth.KakaoTokenDto; import com.example.solidconnection.auth.dto.oauth.KakaoUserInfoDto; -import com.example.solidconnection.config.client.KakaoOAuthClientProperties; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -16,9 +16,9 @@ import java.util.Objects; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_OR_EXPIRED_KAKAO_AUTH_CODE; -import static com.example.solidconnection.custom.exception.ErrorCode.KAKAO_REDIRECT_URI_MISMATCH; -import static com.example.solidconnection.custom.exception.ErrorCode.KAKAO_USER_INFO_FAIL; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_OR_EXPIRED_KAKAO_AUTH_CODE; +import static com.example.solidconnection.common.exception.ErrorCode.KAKAO_REDIRECT_URI_MISMATCH; +import static com.example.solidconnection.common.exception.ErrorCode.KAKAO_USER_INFO_FAIL; /* * 카카오 인증을 위한 OAuth2 클라이언트 diff --git a/src/main/java/com/example/solidconnection/config/client/AppleOAuthClientProperties.java b/src/main/java/com/example/solidconnection/auth/client/config/AppleOAuthClientProperties.java similarity index 87% rename from src/main/java/com/example/solidconnection/config/client/AppleOAuthClientProperties.java rename to src/main/java/com/example/solidconnection/auth/client/config/AppleOAuthClientProperties.java index c04908583..ae62d3561 100644 --- a/src/main/java/com/example/solidconnection/config/client/AppleOAuthClientProperties.java +++ b/src/main/java/com/example/solidconnection/auth/client/config/AppleOAuthClientProperties.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.config.client; +package com.example.solidconnection.auth.client.config; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/java/com/example/solidconnection/config/client/KakaoOAuthClientProperties.java b/src/main/java/com/example/solidconnection/auth/client/config/KakaoOAuthClientProperties.java similarity index 83% rename from src/main/java/com/example/solidconnection/config/client/KakaoOAuthClientProperties.java rename to src/main/java/com/example/solidconnection/auth/client/config/KakaoOAuthClientProperties.java index 73b196d76..f9a476e6a 100644 --- a/src/main/java/com/example/solidconnection/config/client/KakaoOAuthClientProperties.java +++ b/src/main/java/com/example/solidconnection/auth/client/config/KakaoOAuthClientProperties.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.config.client; +package com.example.solidconnection.auth.client.config; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index 064aa88aa..da3ea12c1 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -17,9 +17,9 @@ import com.example.solidconnection.auth.service.oauth.AppleOAuthService; import com.example.solidconnection.auth.service.oauth.KakaoOAuthService; import com.example.solidconnection.auth.service.oauth.OAuthSignUpService; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.custom.exception.ErrorCode; -import com.example.solidconnection.custom.resolver.AuthorizedUser; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; +import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; diff --git a/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java b/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java index 9bf92a295..b43671e82 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java +++ b/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java @@ -1,9 +1,9 @@ package com.example.solidconnection.auth.dto; import com.example.solidconnection.siteuser.domain.AuthType; +import com.example.solidconnection.siteuser.domain.PreparationStatus; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import jakarta.validation.constraints.NotBlank; import java.util.List; diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthService.java b/src/main/java/com/example/solidconnection/auth/service/AuthService.java index 50926ca93..496e48724 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -2,7 +2,7 @@ import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -10,7 +10,7 @@ import java.time.LocalDate; -import static com.example.solidconnection.custom.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; +import static com.example.solidconnection.common.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; @RequiredArgsConstructor @Service diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java index 2e43e8be9..fa2a064cc 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -1,7 +1,7 @@ package com.example.solidconnection.auth.service; import com.example.solidconnection.auth.domain.TokenType; -import com.example.solidconnection.config.security.JwtProperties; +import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.util.JwtUtils; import org.springframework.data.redis.core.RedisTemplate; diff --git a/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java index 3d0eda53b..d16cb5134 100644 --- a/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java @@ -1,14 +1,14 @@ package com.example.solidconnection.auth.service; -import com.example.solidconnection.config.security.JwtProperties; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.util.JwtUtils; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import static com.example.solidconnection.auth.service.EmailSignUpTokenProvider.AUTH_TYPE_CLAIM_KEY; -import static com.example.solidconnection.custom.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; +import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; @Component @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java index 3e26309a5..d80465791 100644 --- a/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java +++ b/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java @@ -2,7 +2,7 @@ import com.example.solidconnection.auth.dto.EmailSignInRequest; import com.example.solidconnection.auth.dto.SignInResponse; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; @@ -12,7 +12,7 @@ import java.util.Optional; -import static com.example.solidconnection.custom.exception.ErrorCode.USER_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; /* * 보안을 위해 이메일과 비밀번호 중 무엇이 틀렸는지 구체적으로 응답하지 않는다. diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java index 37f6681ea..01e117ea0 100644 --- a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java +++ b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java @@ -1,17 +1,17 @@ package com.example.solidconnection.auth.service; import com.example.solidconnection.auth.dto.SignUpRequest; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.repositories.CountryRepository; -import com.example.solidconnection.repositories.InterestedCountyRepository; -import com.example.solidconnection.repositories.InterestedRegionRepository; -import com.example.solidconnection.repositories.RegionRepository; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.location.country.repository.CountryRepository; +import com.example.solidconnection.location.country.repository.InterestedCountyRepository; +import com.example.solidconnection.location.region.repository.InterestedRegionRepository; +import com.example.solidconnection.location.region.repository.RegionRepository; import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import org.springframework.stereotype.Service; -import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_EXISTED; +import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_EXISTED; @Service public class EmailSignUpService extends SignUpService { diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java index 1c27a87bd..c6bee80ab 100644 --- a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java @@ -2,8 +2,8 @@ import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.EmailSignUpTokenRequest; -import com.example.solidconnection.config.security.JwtProperties; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.AuthType; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; @@ -17,8 +17,8 @@ import java.util.Map; import java.util.Objects; -import static com.example.solidconnection.custom.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; -import static com.example.solidconnection.custom.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; +import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; +import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; import static com.example.solidconnection.util.JwtUtils.parseClaims; import static com.example.solidconnection.util.JwtUtils.parseSubject; diff --git a/src/main/java/com/example/solidconnection/auth/service/SignUpService.java b/src/main/java/com/example/solidconnection/auth/service/SignUpService.java index 319083658..95bd4ecac 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignUpService.java +++ b/src/main/java/com/example/solidconnection/auth/service/SignUpService.java @@ -2,20 +2,20 @@ import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.auth.dto.SignUpRequest; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.entity.InterestedCountry; -import com.example.solidconnection.entity.InterestedRegion; -import com.example.solidconnection.repositories.CountryRepository; -import com.example.solidconnection.repositories.InterestedCountyRepository; -import com.example.solidconnection.repositories.InterestedRegionRepository; -import com.example.solidconnection.repositories.RegionRepository; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.location.country.domain.InterestedCountry; +import com.example.solidconnection.location.country.repository.CountryRepository; +import com.example.solidconnection.location.country.repository.InterestedCountyRepository; +import com.example.solidconnection.location.region.domain.InterestedRegion; +import com.example.solidconnection.location.region.repository.InterestedRegionRepository; +import com.example.solidconnection.location.region.repository.RegionRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import org.springframework.transaction.annotation.Transactional; import java.util.List; -import static com.example.solidconnection.custom.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; +import static com.example.solidconnection.common.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; /* * 우리 서버에서 인증되었음을 확인하기 위한 signUpToken 을 검증한다. @@ -85,6 +85,8 @@ private void saveInterestedCountry(SignUpRequest signUpRequest, SiteUser savedSi } protected abstract void validateSignUpToken(SignUpRequest signUpRequest); + protected abstract void validateUserNotDuplicated(SignUpRequest signUpRequest); + protected abstract SiteUser createSiteUser(SignUpRequest signUpRequest); } diff --git a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java index f5f638ab3..0f3552db2 100644 --- a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java @@ -1,7 +1,7 @@ package com.example.solidconnection.auth.service; import com.example.solidconnection.auth.domain.TokenType; -import com.example.solidconnection.config.security.JwtProperties; +import com.example.solidconnection.security.config.JwtProperties; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java index a46728bb2..5676f1c7d 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java @@ -3,17 +3,17 @@ import com.example.solidconnection.auth.dto.SignUpRequest; import com.example.solidconnection.auth.service.SignInService; import com.example.solidconnection.auth.service.SignUpService; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.repositories.CountryRepository; -import com.example.solidconnection.repositories.InterestedCountyRepository; -import com.example.solidconnection.repositories.InterestedRegionRepository; -import com.example.solidconnection.repositories.RegionRepository; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.location.country.repository.CountryRepository; +import com.example.solidconnection.location.country.repository.InterestedCountyRepository; +import com.example.solidconnection.location.region.repository.InterestedRegionRepository; +import com.example.solidconnection.location.region.repository.RegionRepository; import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import org.springframework.stereotype.Service; -import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_EXISTED; +import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_EXISTED; @Service public class OAuthSignUpService extends SignUpService { diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java index c3a95dbe9..8fa290d30 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java @@ -2,8 +2,8 @@ import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.service.TokenProvider; -import com.example.solidconnection.config.security.JwtProperties; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.AuthType; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; @@ -16,8 +16,8 @@ import java.util.Map; import java.util.Objects; -import static com.example.solidconnection.custom.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; -import static com.example.solidconnection.custom.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; +import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; +import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; import static com.example.solidconnection.util.JwtUtils.parseClaims; import static com.example.solidconnection.util.JwtUtils.parseSubject; diff --git a/src/main/java/com/example/solidconnection/cache/ThunderingHerdCachingAspect.java b/src/main/java/com/example/solidconnection/cache/ThunderingHerdCachingAspect.java index a37e80f51..5527c2523 100644 --- a/src/main/java/com/example/solidconnection/cache/ThunderingHerdCachingAspect.java +++ b/src/main/java/com/example/solidconnection/cache/ThunderingHerdCachingAspect.java @@ -22,10 +22,10 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import static com.example.solidconnection.type.RedisConstants.CREATE_CHANNEL; -import static com.example.solidconnection.type.RedisConstants.LOCK_TIMEOUT_MS; -import static com.example.solidconnection.type.RedisConstants.MAX_WAIT_TIME_MS; -import static com.example.solidconnection.type.RedisConstants.REFRESH_LIMIT_PERCENT; +import static com.example.solidconnection.community.post.service.RedisConstants.CREATE_CHANNEL; +import static com.example.solidconnection.community.post.service.RedisConstants.LOCK_TIMEOUT_MS; +import static com.example.solidconnection.community.post.service.RedisConstants.MAX_WAIT_TIME_MS; +import static com.example.solidconnection.community.post.service.RedisConstants.REFRESH_LIMIT_PERCENT; @Aspect @Component diff --git a/src/main/java/com/example/solidconnection/entity/common/BaseEntity.java b/src/main/java/com/example/solidconnection/common/BaseEntity.java similarity index 95% rename from src/main/java/com/example/solidconnection/entity/common/BaseEntity.java rename to src/main/java/com/example/solidconnection/common/BaseEntity.java index 508953f88..febf5e77c 100644 --- a/src/main/java/com/example/solidconnection/entity/common/BaseEntity.java +++ b/src/main/java/com/example/solidconnection/common/BaseEntity.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.entity.common; +package com.example.solidconnection.common; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; diff --git a/src/main/java/com/example/solidconnection/config/client/RestTemplateConfig.java b/src/main/java/com/example/solidconnection/common/config/client/RestTemplateConfig.java similarity index 90% rename from src/main/java/com/example/solidconnection/config/client/RestTemplateConfig.java rename to src/main/java/com/example/solidconnection/common/config/client/RestTemplateConfig.java index 36ce3f67b..88afde086 100644 --- a/src/main/java/com/example/solidconnection/config/client/RestTemplateConfig.java +++ b/src/main/java/com/example/solidconnection/common/config/client/RestTemplateConfig.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.config.client; +package com.example.solidconnection.common.config.client; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; diff --git a/src/main/java/com/example/solidconnection/config/redis/RedisConfig.java b/src/main/java/com/example/solidconnection/common/config/redis/RedisConfig.java similarity index 95% rename from src/main/java/com/example/solidconnection/config/redis/RedisConfig.java rename to src/main/java/com/example/solidconnection/common/config/redis/RedisConfig.java index 22847dc6d..99c98581a 100644 --- a/src/main/java/com/example/solidconnection/config/redis/RedisConfig.java +++ b/src/main/java/com/example/solidconnection/common/config/redis/RedisConfig.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.config.redis; +package com.example.solidconnection.common.config.redis; import com.example.solidconnection.cache.CacheUpdateListener; import org.springframework.beans.factory.annotation.Value; @@ -16,7 +16,7 @@ import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; -import static com.example.solidconnection.type.RedisConstants.CREATE_CHANNEL; +import static com.example.solidconnection.community.post.service.RedisConstants.CREATE_CHANNEL; @Configuration @EnableRedisRepositories diff --git a/src/main/java/com/example/solidconnection/config/scheduler/SchedulerConfig.java b/src/main/java/com/example/solidconnection/common/config/scheduler/SchedulerConfig.java similarity index 94% rename from src/main/java/com/example/solidconnection/config/scheduler/SchedulerConfig.java rename to src/main/java/com/example/solidconnection/common/config/scheduler/SchedulerConfig.java index 2a2cfa6a5..227b35372 100644 --- a/src/main/java/com/example/solidconnection/config/scheduler/SchedulerConfig.java +++ b/src/main/java/com/example/solidconnection/common/config/scheduler/SchedulerConfig.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.config.scheduler; +package com.example.solidconnection.common.config.scheduler; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.SchedulingConfigurer; diff --git a/src/main/java/com/example/solidconnection/config/sync/AsyncConfig.java b/src/main/java/com/example/solidconnection/common/config/sync/AsyncConfig.java similarity index 92% rename from src/main/java/com/example/solidconnection/config/sync/AsyncConfig.java rename to src/main/java/com/example/solidconnection/common/config/sync/AsyncConfig.java index 417b040b3..852e3de02 100644 --- a/src/main/java/com/example/solidconnection/config/sync/AsyncConfig.java +++ b/src/main/java/com/example/solidconnection/common/config/sync/AsyncConfig.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.config.sync; +package com.example.solidconnection.common.config.sync; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/example/solidconnection/config/web/WebMvcConfig.java b/src/main/java/com/example/solidconnection/common/config/web/WebMvcConfig.java similarity index 82% rename from src/main/java/com/example/solidconnection/config/web/WebMvcConfig.java rename to src/main/java/com/example/solidconnection/common/config/web/WebMvcConfig.java index 7f56e320f..a6ba76f7b 100644 --- a/src/main/java/com/example/solidconnection/config/web/WebMvcConfig.java +++ b/src/main/java/com/example/solidconnection/common/config/web/WebMvcConfig.java @@ -1,7 +1,7 @@ -package com.example.solidconnection.config.web; +package com.example.solidconnection.common.config.web; -import com.example.solidconnection.custom.resolver.AuthorizedUserResolver; -import com.example.solidconnection.custom.resolver.CustomPageableHandlerMethodArgumentResolver; +import com.example.solidconnection.common.resolver.AuthorizedUserResolver; +import com.example.solidconnection.common.resolver.CustomPageableHandlerMethodArgumentResolver; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; diff --git a/src/main/java/com/example/solidconnection/custom/exception/CustomAccessDeniedHandler.java b/src/main/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandler.java similarity index 90% rename from src/main/java/com/example/solidconnection/custom/exception/CustomAccessDeniedHandler.java rename to src/main/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandler.java index 52b1725fc..2c7e0fd19 100644 --- a/src/main/java/com/example/solidconnection/custom/exception/CustomAccessDeniedHandler.java +++ b/src/main/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandler.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.custom.exception; +package com.example.solidconnection.common.exception; -import com.example.solidconnection.custom.response.ErrorResponse; +import com.example.solidconnection.common.response.ErrorResponse; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/src/main/java/com/example/solidconnection/custom/exception/CustomAuthenticationEntryPoint.java b/src/main/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPoint.java similarity index 90% rename from src/main/java/com/example/solidconnection/custom/exception/CustomAuthenticationEntryPoint.java rename to src/main/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPoint.java index 20f0786b7..d5eb87705 100644 --- a/src/main/java/com/example/solidconnection/custom/exception/CustomAuthenticationEntryPoint.java +++ b/src/main/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPoint.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.custom.exception; +package com.example.solidconnection.common.exception; -import com.example.solidconnection.custom.response.ErrorResponse; +import com.example.solidconnection.common.response.ErrorResponse; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; diff --git a/src/main/java/com/example/solidconnection/custom/exception/CustomException.java b/src/main/java/com/example/solidconnection/common/exception/CustomException.java similarity index 89% rename from src/main/java/com/example/solidconnection/custom/exception/CustomException.java rename to src/main/java/com/example/solidconnection/common/exception/CustomException.java index 2f1962fbf..fab42924c 100644 --- a/src/main/java/com/example/solidconnection/custom/exception/CustomException.java +++ b/src/main/java/com/example/solidconnection/common/exception/CustomException.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.custom.exception; +package com.example.solidconnection.common.exception; import lombok.Getter; diff --git a/src/main/java/com/example/solidconnection/custom/exception/CustomExceptionHandler.java b/src/main/java/com/example/solidconnection/common/exception/CustomExceptionHandler.java similarity index 89% rename from src/main/java/com/example/solidconnection/custom/exception/CustomExceptionHandler.java rename to src/main/java/com/example/solidconnection/common/exception/CustomExceptionHandler.java index c0c610bce..bc03ca98a 100644 --- a/src/main/java/com/example/solidconnection/custom/exception/CustomExceptionHandler.java +++ b/src/main/java/com/example/solidconnection/common/exception/CustomExceptionHandler.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.custom.exception; +package com.example.solidconnection.common.exception; -import com.example.solidconnection.custom.response.ErrorResponse; +import com.example.solidconnection.common.response.ErrorResponse; import com.fasterxml.jackson.databind.exc.InvalidFormatException; import io.jsonwebtoken.JwtException; import lombok.extern.slf4j.Slf4j; @@ -13,10 +13,10 @@ import java.util.ArrayList; import java.util.List; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_INPUT; -import static com.example.solidconnection.custom.exception.ErrorCode.JSON_PARSING_FAILED; -import static com.example.solidconnection.custom.exception.ErrorCode.JWT_EXCEPTION; -import static com.example.solidconnection.custom.exception.ErrorCode.NOT_DEFINED_ERROR; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_INPUT; +import static com.example.solidconnection.common.exception.ErrorCode.JSON_PARSING_FAILED; +import static com.example.solidconnection.common.exception.ErrorCode.JWT_EXCEPTION; +import static com.example.solidconnection.common.exception.ErrorCode.NOT_DEFINED_ERROR; @Slf4j @ControllerAdvice diff --git a/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java similarity index 99% rename from src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java rename to src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 1a4e46b72..6e932a159 100644 --- a/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.custom.exception; +package com.example.solidconnection.common.exception; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/com/example/solidconnection/custom/resolver/AuthorizedUser.java b/src/main/java/com/example/solidconnection/common/resolver/AuthorizedUser.java similarity index 85% rename from src/main/java/com/example/solidconnection/custom/resolver/AuthorizedUser.java rename to src/main/java/com/example/solidconnection/common/resolver/AuthorizedUser.java index fa1db7f74..0f4aa0954 100644 --- a/src/main/java/com/example/solidconnection/custom/resolver/AuthorizedUser.java +++ b/src/main/java/com/example/solidconnection/common/resolver/AuthorizedUser.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.custom.resolver; +package com.example.solidconnection.common.resolver; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/com/example/solidconnection/custom/resolver/AuthorizedUserResolver.java b/src/main/java/com/example/solidconnection/common/resolver/AuthorizedUserResolver.java similarity index 88% rename from src/main/java/com/example/solidconnection/custom/resolver/AuthorizedUserResolver.java rename to src/main/java/com/example/solidconnection/common/resolver/AuthorizedUserResolver.java index f4ba9fe7f..f729eb073 100644 --- a/src/main/java/com/example/solidconnection/custom/resolver/AuthorizedUserResolver.java +++ b/src/main/java/com/example/solidconnection/common/resolver/AuthorizedUserResolver.java @@ -1,7 +1,7 @@ -package com.example.solidconnection.custom.resolver; +package com.example.solidconnection.common.resolver; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.custom.security.userdetails.SiteUserDetails; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.security.userdetails.SiteUserDetails; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; import org.springframework.core.MethodParameter; @@ -13,7 +13,7 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; -import static com.example.solidconnection.custom.exception.ErrorCode.AUTHENTICATION_FAILED; +import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; @Component @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/custom/resolver/CustomPageableHandlerMethodArgumentResolver.java b/src/main/java/com/example/solidconnection/common/resolver/CustomPageableHandlerMethodArgumentResolver.java similarity index 92% rename from src/main/java/com/example/solidconnection/custom/resolver/CustomPageableHandlerMethodArgumentResolver.java rename to src/main/java/com/example/solidconnection/common/resolver/CustomPageableHandlerMethodArgumentResolver.java index 418c6867f..ecb8bc75b 100644 --- a/src/main/java/com/example/solidconnection/custom/resolver/CustomPageableHandlerMethodArgumentResolver.java +++ b/src/main/java/com/example/solidconnection/common/resolver/CustomPageableHandlerMethodArgumentResolver.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.custom.resolver; +package com.example.solidconnection.common.resolver; import org.springframework.data.domain.PageRequest; import org.springframework.data.web.PageableHandlerMethodArgumentResolver; diff --git a/src/main/java/com/example/solidconnection/custom/response/ErrorResponse.java b/src/main/java/com/example/solidconnection/common/response/ErrorResponse.java similarity index 64% rename from src/main/java/com/example/solidconnection/custom/response/ErrorResponse.java rename to src/main/java/com/example/solidconnection/common/response/ErrorResponse.java index 83cc02622..68b9c259f 100644 --- a/src/main/java/com/example/solidconnection/custom/response/ErrorResponse.java +++ b/src/main/java/com/example/solidconnection/common/response/ErrorResponse.java @@ -1,7 +1,7 @@ -package com.example.solidconnection.custom.response; +package com.example.solidconnection.common.response; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.custom.exception.ErrorCode; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; public record ErrorResponse(String message) { diff --git a/src/main/java/com/example/solidconnection/custom/response/PageResponse.java b/src/main/java/com/example/solidconnection/common/response/PageResponse.java similarity index 92% rename from src/main/java/com/example/solidconnection/custom/response/PageResponse.java rename to src/main/java/com/example/solidconnection/common/response/PageResponse.java index d1e3479d6..90790d271 100644 --- a/src/main/java/com/example/solidconnection/custom/response/PageResponse.java +++ b/src/main/java/com/example/solidconnection/common/response/PageResponse.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.custom.response; +package com.example.solidconnection.common.response; import org.springframework.data.domain.Page; diff --git a/src/main/java/com/example/solidconnection/community/board/controller/BoardController.java b/src/main/java/com/example/solidconnection/community/board/controller/BoardController.java index a87552796..b46c93256 100644 --- a/src/main/java/com/example/solidconnection/community/board/controller/BoardController.java +++ b/src/main/java/com/example/solidconnection/community/board/controller/BoardController.java @@ -1,10 +1,10 @@ package com.example.solidconnection.community.board.controller; +import com.example.solidconnection.common.resolver.AuthorizedUser; +import com.example.solidconnection.community.board.domain.BoardCode; import com.example.solidconnection.community.post.dto.PostListResponse; import com.example.solidconnection.community.post.service.PostQueryService; -import com.example.solidconnection.custom.resolver.AuthorizedUser; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.type.BoardCode; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; diff --git a/src/main/java/com/example/solidconnection/type/BoardCode.java b/src/main/java/com/example/solidconnection/community/board/domain/BoardCode.java similarity index 50% rename from src/main/java/com/example/solidconnection/type/BoardCode.java rename to src/main/java/com/example/solidconnection/community/board/domain/BoardCode.java index 0d161e941..33266e8ef 100644 --- a/src/main/java/com/example/solidconnection/type/BoardCode.java +++ b/src/main/java/com/example/solidconnection/community/board/domain/BoardCode.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.type; +package com.example.solidconnection.community.board.domain; public enum BoardCode { EUROPE, AMERICAS, ASIA, FREE; diff --git a/src/main/java/com/example/solidconnection/community/board/repository/BoardRepository.java b/src/main/java/com/example/solidconnection/community/board/repository/BoardRepository.java index 06dd01161..4c6f799d3 100644 --- a/src/main/java/com/example/solidconnection/community/board/repository/BoardRepository.java +++ b/src/main/java/com/example/solidconnection/community/board/repository/BoardRepository.java @@ -1,8 +1,8 @@ package com.example.solidconnection.community.board.repository; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.community.board.domain.Board; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.custom.exception.ErrorCode; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.query.Param; @@ -10,7 +10,7 @@ import java.util.Optional; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_BOARD_CODE; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_BOARD_CODE; @Repository public interface BoardRepository extends JpaRepository { diff --git a/src/main/java/com/example/solidconnection/community/comment/controller/CommentController.java b/src/main/java/com/example/solidconnection/community/comment/controller/CommentController.java index d096f6cc9..f17792a75 100644 --- a/src/main/java/com/example/solidconnection/community/comment/controller/CommentController.java +++ b/src/main/java/com/example/solidconnection/community/comment/controller/CommentController.java @@ -1,12 +1,12 @@ package com.example.solidconnection.community.comment.controller; +import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.community.comment.dto.CommentCreateRequest; import com.example.solidconnection.community.comment.dto.CommentCreateResponse; import com.example.solidconnection.community.comment.dto.CommentDeleteResponse; import com.example.solidconnection.community.comment.dto.CommentUpdateRequest; import com.example.solidconnection.community.comment.dto.CommentUpdateResponse; import com.example.solidconnection.community.comment.service.CommentService; -import com.example.solidconnection.custom.resolver.AuthorizedUser; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/example/solidconnection/community/comment/domain/Comment.java b/src/main/java/com/example/solidconnection/community/comment/domain/Comment.java index abed4b8f0..9bb2dccd9 100644 --- a/src/main/java/com/example/solidconnection/community/comment/domain/Comment.java +++ b/src/main/java/com/example/solidconnection/community/comment/domain/Comment.java @@ -1,6 +1,6 @@ package com.example.solidconnection.community.comment.domain; -import com.example.solidconnection.entity.common.BaseEntity; +import com.example.solidconnection.common.BaseEntity; import com.example.solidconnection.community.post.domain.Post; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.persistence.CascadeType; diff --git a/src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java b/src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java index e5feb3f04..cd2ae72ae 100644 --- a/src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java +++ b/src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java @@ -1,14 +1,14 @@ package com.example.solidconnection.community.comment.repository; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.comment.domain.Comment; -import com.example.solidconnection.custom.exception.CustomException; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_COMMENT_ID; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_COMMENT_ID; public interface CommentRepository extends JpaRepository { diff --git a/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java b/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java index 76138b356..6a2760cf0 100644 --- a/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java +++ b/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java @@ -1,5 +1,6 @@ package com.example.solidconnection.community.comment.service; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.comment.domain.Comment; import com.example.solidconnection.community.comment.dto.CommentCreateRequest; import com.example.solidconnection.community.comment.dto.CommentCreateResponse; @@ -10,7 +11,6 @@ import com.example.solidconnection.community.comment.repository.CommentRepository; import com.example.solidconnection.community.post.domain.Post; import com.example.solidconnection.community.post.repository.PostRepository; -import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import lombok.RequiredArgsConstructor; @@ -20,10 +20,10 @@ import java.util.List; import java.util.stream.Collectors; -import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_UPDATE_DEPRECATED_COMMENT; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_COMMENT_LEVEL; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_ACCESS; -import static com.example.solidconnection.custom.exception.ErrorCode.USER_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPDATE_DEPRECATED_COMMENT; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_COMMENT_LEVEL; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ACCESS; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; @Service @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/community/post/controller/PostController.java b/src/main/java/com/example/solidconnection/community/post/controller/PostController.java index ee422930a..23b34568e 100644 --- a/src/main/java/com/example/solidconnection/community/post/controller/PostController.java +++ b/src/main/java/com/example/solidconnection/community/post/controller/PostController.java @@ -1,5 +1,6 @@ package com.example.solidconnection.community.post.controller; +import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.community.post.dto.PostCreateRequest; import com.example.solidconnection.community.post.dto.PostCreateResponse; import com.example.solidconnection.community.post.dto.PostDeleteResponse; @@ -11,7 +12,6 @@ import com.example.solidconnection.community.post.service.PostCommandService; import com.example.solidconnection.community.post.service.PostLikeService; import com.example.solidconnection.community.post.service.PostQueryService; -import com.example.solidconnection.custom.resolver.AuthorizedUser; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/example/solidconnection/community/post/domain/Post.java b/src/main/java/com/example/solidconnection/community/post/domain/Post.java index 4d96b9b22..613abbb4b 100644 --- a/src/main/java/com/example/solidconnection/community/post/domain/Post.java +++ b/src/main/java/com/example/solidconnection/community/post/domain/Post.java @@ -1,11 +1,10 @@ package com.example.solidconnection.community.post.domain; +import com.example.solidconnection.common.BaseEntity; import com.example.solidconnection.community.board.domain.Board; import com.example.solidconnection.community.comment.domain.Comment; -import com.example.solidconnection.entity.common.BaseEntity; import com.example.solidconnection.community.post.dto.PostUpdateRequest; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.type.PostCategory; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/com/example/solidconnection/community/post/domain/PostCategory.java b/src/main/java/com/example/solidconnection/community/post/domain/PostCategory.java new file mode 100644 index 000000000..3f8819749 --- /dev/null +++ b/src/main/java/com/example/solidconnection/community/post/domain/PostCategory.java @@ -0,0 +1,5 @@ +package com.example.solidconnection.community.post.domain; + +public enum PostCategory { + 전체, 자유, 질문 +} diff --git a/src/main/java/com/example/solidconnection/community/post/dto/PostCreateRequest.java b/src/main/java/com/example/solidconnection/community/post/dto/PostCreateRequest.java index 5e6590b20..0cc3afc1f 100644 --- a/src/main/java/com/example/solidconnection/community/post/dto/PostCreateRequest.java +++ b/src/main/java/com/example/solidconnection/community/post/dto/PostCreateRequest.java @@ -2,8 +2,8 @@ import com.example.solidconnection.community.board.domain.Board; import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.community.post.domain.PostCategory; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.type.PostCategory; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; diff --git a/src/main/java/com/example/solidconnection/community/post/dto/PostListResponse.java b/src/main/java/com/example/solidconnection/community/post/dto/PostListResponse.java index f02af017e..15f5ec7a5 100644 --- a/src/main/java/com/example/solidconnection/community/post/dto/PostListResponse.java +++ b/src/main/java/com/example/solidconnection/community/post/dto/PostListResponse.java @@ -1,7 +1,7 @@ package com.example.solidconnection.community.post.dto; -import com.example.solidconnection.community.post.domain.PostImage; import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.community.post.domain.PostImage; import java.time.ZonedDateTime; import java.util.List; diff --git a/src/main/java/com/example/solidconnection/community/post/repository/PostLikeRepository.java b/src/main/java/com/example/solidconnection/community/post/repository/PostLikeRepository.java index 417e97310..fba377899 100644 --- a/src/main/java/com/example/solidconnection/community/post/repository/PostLikeRepository.java +++ b/src/main/java/com/example/solidconnection/community/post/repository/PostLikeRepository.java @@ -1,6 +1,6 @@ package com.example.solidconnection.community.post.repository; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.post.domain.Post; import com.example.solidconnection.community.post.domain.PostLike; import com.example.solidconnection.siteuser.domain.SiteUser; @@ -9,7 +9,7 @@ import java.util.Optional; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_LIKE; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_LIKE; @Repository public interface PostLikeRepository extends JpaRepository { diff --git a/src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java b/src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java index 336189b05..8c1d256a6 100644 --- a/src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java +++ b/src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java @@ -1,6 +1,6 @@ package com.example.solidconnection.community.post.repository; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.post.domain.Post; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; @@ -11,7 +11,7 @@ import java.util.Optional; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_ID; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ID; @Repository public interface PostRepository extends JpaRepository { diff --git a/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java b/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java index b95cbcf1b..cb553fd0c 100644 --- a/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java +++ b/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java @@ -1,8 +1,10 @@ package com.example.solidconnection.community.post.service; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.board.domain.Board; import com.example.solidconnection.community.board.repository.BoardRepository; import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.community.post.domain.PostCategory; import com.example.solidconnection.community.post.domain.PostImage; import com.example.solidconnection.community.post.dto.PostCreateRequest; import com.example.solidconnection.community.post.dto.PostCreateResponse; @@ -10,14 +12,11 @@ import com.example.solidconnection.community.post.dto.PostUpdateRequest; import com.example.solidconnection.community.post.dto.PostUpdateResponse; import com.example.solidconnection.community.post.repository.PostRepository; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.s3.S3Service; -import com.example.solidconnection.s3.UploadedFileUrlResponse; -import com.example.solidconnection.service.RedisService; +import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; +import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.type.ImgType; -import com.example.solidconnection.type.PostCategory; import com.example.solidconnection.util.RedisUtils; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.EnumUtils; @@ -27,11 +26,11 @@ import java.util.List; -import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_DELETE_OR_UPDATE_QUESTION; -import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_ACCESS; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_CATEGORY; -import static com.example.solidconnection.custom.exception.ErrorCode.USER_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_DELETE_OR_UPDATE_QUESTION; +import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ACCESS; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_CATEGORY; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; @Service @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/community/post/service/PostLikeService.java b/src/main/java/com/example/solidconnection/community/post/service/PostLikeService.java index 98d1a239f..e0921e295 100644 --- a/src/main/java/com/example/solidconnection/community/post/service/PostLikeService.java +++ b/src/main/java/com/example/solidconnection/community/post/service/PostLikeService.java @@ -1,12 +1,12 @@ package com.example.solidconnection.community.post.service; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.post.domain.Post; import com.example.solidconnection.community.post.domain.PostLike; import com.example.solidconnection.community.post.dto.PostDislikeResponse; import com.example.solidconnection.community.post.dto.PostLikeResponse; import com.example.solidconnection.community.post.repository.PostLikeRepository; import com.example.solidconnection.community.post.repository.PostRepository; -import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import lombok.RequiredArgsConstructor; @@ -14,8 +14,8 @@ import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; -import static com.example.solidconnection.custom.exception.ErrorCode.DUPLICATE_POST_LIKE; -import static com.example.solidconnection.custom.exception.ErrorCode.USER_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_POST_LIKE; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; @Service @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java b/src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java index 66cbb5faa..b8d07b54f 100644 --- a/src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java +++ b/src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java @@ -1,22 +1,21 @@ package com.example.solidconnection.community.post.service; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.board.domain.Board; +import com.example.solidconnection.community.board.domain.BoardCode; import com.example.solidconnection.community.board.dto.PostFindBoardResponse; -import com.example.solidconnection.community.comment.dto.PostFindCommentResponse; -import com.example.solidconnection.community.post.dto.PostListResponse; import com.example.solidconnection.community.board.repository.BoardRepository; +import com.example.solidconnection.community.comment.dto.PostFindCommentResponse; import com.example.solidconnection.community.comment.service.CommentService; -import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.community.post.domain.PostCategory; import com.example.solidconnection.community.post.dto.PostFindPostImageResponse; import com.example.solidconnection.community.post.dto.PostFindResponse; +import com.example.solidconnection.community.post.dto.PostListResponse; import com.example.solidconnection.community.post.repository.PostLikeRepository; import com.example.solidconnection.community.post.repository.PostRepository; -import com.example.solidconnection.service.RedisService; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.PostFindSiteUserResponse; -import com.example.solidconnection.type.BoardCode; -import com.example.solidconnection.type.PostCategory; import com.example.solidconnection.util.RedisUtils; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.EnumUtils; @@ -26,8 +25,8 @@ import java.util.List; import java.util.stream.Collectors; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_BOARD_CODE; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_CATEGORY; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_BOARD_CODE; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_CATEGORY; @Service @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/type/RedisConstants.java b/src/main/java/com/example/solidconnection/community/post/service/RedisConstants.java similarity index 90% rename from src/main/java/com/example/solidconnection/type/RedisConstants.java rename to src/main/java/com/example/solidconnection/community/post/service/RedisConstants.java index 7d4c7f2c9..46260596c 100644 --- a/src/main/java/com/example/solidconnection/type/RedisConstants.java +++ b/src/main/java/com/example/solidconnection/community/post/service/RedisConstants.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.type; +package com.example.solidconnection.community.post.service; import lombok.Getter; diff --git a/src/main/java/com/example/solidconnection/service/RedisService.java b/src/main/java/com/example/solidconnection/community/post/service/RedisService.java similarity index 82% rename from src/main/java/com/example/solidconnection/service/RedisService.java rename to src/main/java/com/example/solidconnection/community/post/service/RedisService.java index 36be7b66f..38d573d94 100644 --- a/src/main/java/com/example/solidconnection/service/RedisService.java +++ b/src/main/java/com/example/solidconnection/community/post/service/RedisService.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.service; +package com.example.solidconnection.community.post.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -9,11 +9,11 @@ import java.util.Collections; import java.util.concurrent.TimeUnit; -import static com.example.solidconnection.type.RedisConstants.VALIDATE_VIEW_COUNT_TTL; -import static com.example.solidconnection.type.RedisConstants.VIEW_COUNT_TTL; +import static com.example.solidconnection.community.post.service.RedisConstants.VALIDATE_VIEW_COUNT_TTL; +import static com.example.solidconnection.community.post.service.RedisConstants.VIEW_COUNT_TTL; @Service -public class RedisService { +public class RedisService { // todo: 정말 필요한지 고민 필요 private final RedisTemplate redisTemplate; private final RedisScript incrViewCountLuaScript; diff --git a/src/main/java/com/example/solidconnection/service/UpdateViewCountService.java b/src/main/java/com/example/solidconnection/community/post/service/UpdateViewCountService.java similarity index 95% rename from src/main/java/com/example/solidconnection/service/UpdateViewCountService.java rename to src/main/java/com/example/solidconnection/community/post/service/UpdateViewCountService.java index 2b67e25ec..89a6f341a 100644 --- a/src/main/java/com/example/solidconnection/service/UpdateViewCountService.java +++ b/src/main/java/com/example/solidconnection/community/post/service/UpdateViewCountService.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.service; +package com.example.solidconnection.community.post.service; import com.example.solidconnection.community.post.domain.Post; import com.example.solidconnection.community.post.repository.PostRepository; diff --git a/src/main/java/com/example/solidconnection/entity/Country.java b/src/main/java/com/example/solidconnection/location/country/domain/Country.java similarity index 85% rename from src/main/java/com/example/solidconnection/entity/Country.java rename to src/main/java/com/example/solidconnection/location/country/domain/Country.java index 0a5d974d7..5a14fef86 100644 --- a/src/main/java/com/example/solidconnection/entity/Country.java +++ b/src/main/java/com/example/solidconnection/location/country/domain/Country.java @@ -1,5 +1,6 @@ -package com.example.solidconnection.entity; +package com.example.solidconnection.location.country.domain; +import com.example.solidconnection.location.region.domain.Region; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; diff --git a/src/main/java/com/example/solidconnection/entity/InterestedCountry.java b/src/main/java/com/example/solidconnection/location/country/domain/InterestedCountry.java similarity index 92% rename from src/main/java/com/example/solidconnection/entity/InterestedCountry.java rename to src/main/java/com/example/solidconnection/location/country/domain/InterestedCountry.java index 8b8b4e735..9d75c51ed 100644 --- a/src/main/java/com/example/solidconnection/entity/InterestedCountry.java +++ b/src/main/java/com/example/solidconnection/location/country/domain/InterestedCountry.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.entity; +package com.example.solidconnection.location.country.domain; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.persistence.Entity; diff --git a/src/main/java/com/example/solidconnection/repositories/CountryRepository.java b/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java similarity index 78% rename from src/main/java/com/example/solidconnection/repositories/CountryRepository.java rename to src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java index d9ba75555..4860e6015 100644 --- a/src/main/java/com/example/solidconnection/repositories/CountryRepository.java +++ b/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.repositories; +package com.example.solidconnection.location.country.repository; -import com.example.solidconnection.entity.Country; +import com.example.solidconnection.location.country.domain.Country; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; diff --git a/src/main/java/com/example/solidconnection/repositories/InterestedCountyRepository.java b/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountyRepository.java similarity index 72% rename from src/main/java/com/example/solidconnection/repositories/InterestedCountyRepository.java rename to src/main/java/com/example/solidconnection/location/country/repository/InterestedCountyRepository.java index 68e10b320..879323e28 100644 --- a/src/main/java/com/example/solidconnection/repositories/InterestedCountyRepository.java +++ b/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountyRepository.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.repositories; +package com.example.solidconnection.location.country.repository; -import com.example.solidconnection.entity.InterestedCountry; +import com.example.solidconnection.location.country.domain.InterestedCountry; import com.example.solidconnection.siteuser.domain.SiteUser; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/example/solidconnection/entity/InterestedRegion.java b/src/main/java/com/example/solidconnection/location/region/domain/InterestedRegion.java similarity index 92% rename from src/main/java/com/example/solidconnection/entity/InterestedRegion.java rename to src/main/java/com/example/solidconnection/location/region/domain/InterestedRegion.java index 7ec8fa50c..457ee4eb6 100644 --- a/src/main/java/com/example/solidconnection/entity/InterestedRegion.java +++ b/src/main/java/com/example/solidconnection/location/region/domain/InterestedRegion.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.entity; +package com.example.solidconnection.location.region.domain; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.persistence.Entity; diff --git a/src/main/java/com/example/solidconnection/entity/Region.java b/src/main/java/com/example/solidconnection/location/region/domain/Region.java similarity index 91% rename from src/main/java/com/example/solidconnection/entity/Region.java rename to src/main/java/com/example/solidconnection/location/region/domain/Region.java index 6bd64c5cc..cb0d5ab7a 100644 --- a/src/main/java/com/example/solidconnection/entity/Region.java +++ b/src/main/java/com/example/solidconnection/location/region/domain/Region.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.entity; +package com.example.solidconnection.location.region.domain; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/com/example/solidconnection/repositories/InterestedRegionRepository.java b/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java similarity index 72% rename from src/main/java/com/example/solidconnection/repositories/InterestedRegionRepository.java rename to src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java index df5acd696..90d860b3c 100644 --- a/src/main/java/com/example/solidconnection/repositories/InterestedRegionRepository.java +++ b/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.repositories; +package com.example.solidconnection.location.region.repository; -import com.example.solidconnection.entity.InterestedRegion; +import com.example.solidconnection.location.region.domain.InterestedRegion; import com.example.solidconnection.siteuser.domain.SiteUser; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/example/solidconnection/repositories/RegionRepository.java b/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java similarity index 78% rename from src/main/java/com/example/solidconnection/repositories/RegionRepository.java rename to src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java index 0dc99fb08..094f19f5f 100644 --- a/src/main/java/com/example/solidconnection/repositories/RegionRepository.java +++ b/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.repositories; +package com.example.solidconnection.location.region.repository; -import com.example.solidconnection.entity.Region; +import com.example.solidconnection.location.region.domain.Region; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; diff --git a/src/main/java/com/example/solidconnection/news/domain/News.java b/src/main/java/com/example/solidconnection/news/domain/News.java index 1a9f22a4a..6a3bbdf1f 100644 --- a/src/main/java/com/example/solidconnection/news/domain/News.java +++ b/src/main/java/com/example/solidconnection/news/domain/News.java @@ -1,6 +1,6 @@ package com.example.solidconnection.news.domain; -import com.example.solidconnection.entity.common.BaseEntity; +import com.example.solidconnection.common.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; diff --git a/src/main/java/com/example/solidconnection/s3/AmazonS3Config.java b/src/main/java/com/example/solidconnection/s3/config/AmazonS3Config.java similarity index 95% rename from src/main/java/com/example/solidconnection/s3/AmazonS3Config.java rename to src/main/java/com/example/solidconnection/s3/config/AmazonS3Config.java index c12f067dd..3b19cecfa 100644 --- a/src/main/java/com/example/solidconnection/s3/AmazonS3Config.java +++ b/src/main/java/com/example/solidconnection/s3/config/AmazonS3Config.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.s3; +package com.example.solidconnection.s3.config; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; diff --git a/src/main/java/com/example/solidconnection/s3/S3Controller.java b/src/main/java/com/example/solidconnection/s3/controller/S3Controller.java similarity index 88% rename from src/main/java/com/example/solidconnection/s3/S3Controller.java rename to src/main/java/com/example/solidconnection/s3/controller/S3Controller.java index 26f9160c0..20013fa6a 100644 --- a/src/main/java/com/example/solidconnection/s3/S3Controller.java +++ b/src/main/java/com/example/solidconnection/s3/controller/S3Controller.java @@ -1,8 +1,11 @@ -package com.example.solidconnection.s3; +package com.example.solidconnection.s3.controller; -import com.example.solidconnection.custom.resolver.AuthorizedUser; +import com.example.solidconnection.common.resolver.AuthorizedUser; +import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; +import com.example.solidconnection.s3.dto.urlPrefixResponse; +import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.type.ImgType; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/example/solidconnection/type/ImgType.java b/src/main/java/com/example/solidconnection/s3/domain/ImgType.java similarity index 83% rename from src/main/java/com/example/solidconnection/type/ImgType.java rename to src/main/java/com/example/solidconnection/s3/domain/ImgType.java index 45eb516bb..df881fe4b 100644 --- a/src/main/java/com/example/solidconnection/type/ImgType.java +++ b/src/main/java/com/example/solidconnection/s3/domain/ImgType.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.type; +package com.example.solidconnection.s3.domain; import lombok.Getter; diff --git a/src/main/java/com/example/solidconnection/s3/UploadedFileUrlResponse.java b/src/main/java/com/example/solidconnection/s3/dto/UploadedFileUrlResponse.java similarity index 60% rename from src/main/java/com/example/solidconnection/s3/UploadedFileUrlResponse.java rename to src/main/java/com/example/solidconnection/s3/dto/UploadedFileUrlResponse.java index 6d9b690fa..ca6a08b46 100644 --- a/src/main/java/com/example/solidconnection/s3/UploadedFileUrlResponse.java +++ b/src/main/java/com/example/solidconnection/s3/dto/UploadedFileUrlResponse.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.s3; +package com.example.solidconnection.s3.dto; public record UploadedFileUrlResponse( String fileUrl) { diff --git a/src/main/java/com/example/solidconnection/s3/urlPrefixResponse.java b/src/main/java/com/example/solidconnection/s3/dto/urlPrefixResponse.java similarity index 78% rename from src/main/java/com/example/solidconnection/s3/urlPrefixResponse.java rename to src/main/java/com/example/solidconnection/s3/dto/urlPrefixResponse.java index 59eac23ca..f36838060 100644 --- a/src/main/java/com/example/solidconnection/s3/urlPrefixResponse.java +++ b/src/main/java/com/example/solidconnection/s3/dto/urlPrefixResponse.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.s3; +package com.example.solidconnection.s3.dto; public record urlPrefixResponse( String s3Default, diff --git a/src/main/java/com/example/solidconnection/s3/FileUploadService.java b/src/main/java/com/example/solidconnection/s3/service/FileUploadService.java similarity index 89% rename from src/main/java/com/example/solidconnection/s3/FileUploadService.java rename to src/main/java/com/example/solidconnection/s3/service/FileUploadService.java index 71d9f9c7a..61bc6f89c 100644 --- a/src/main/java/com/example/solidconnection/s3/FileUploadService.java +++ b/src/main/java/com/example/solidconnection/s3/service/FileUploadService.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.s3; +package com.example.solidconnection.s3.service; import com.amazonaws.AmazonServiceException; import com.amazonaws.SdkClientException; @@ -6,7 +6,7 @@ import com.amazonaws.services.s3.model.CannedAccessControlList; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; @@ -15,8 +15,8 @@ import java.io.IOException; -import static com.example.solidconnection.custom.exception.ErrorCode.S3_CLIENT_EXCEPTION; -import static com.example.solidconnection.custom.exception.ErrorCode.S3_SERVICE_EXCEPTION; +import static com.example.solidconnection.common.exception.ErrorCode.S3_CLIENT_EXCEPTION; +import static com.example.solidconnection.common.exception.ErrorCode.S3_SERVICE_EXCEPTION; @Component @EnableAsync diff --git a/src/main/java/com/example/solidconnection/s3/S3Service.java b/src/main/java/com/example/solidconnection/s3/service/S3Service.java similarity index 90% rename from src/main/java/com/example/solidconnection/s3/S3Service.java rename to src/main/java/com/example/solidconnection/s3/service/S3Service.java index 2f3c633dd..1b7daf78f 100644 --- a/src/main/java/com/example/solidconnection/s3/S3Service.java +++ b/src/main/java/com/example/solidconnection/s3/service/S3Service.java @@ -1,13 +1,14 @@ -package com.example.solidconnection.s3; +package com.example.solidconnection.s3.service; import com.amazonaws.AmazonServiceException; import com.amazonaws.SdkClientException; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.DeleteObjectRequest; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.type.ImgType; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,11 +23,11 @@ import java.util.Objects; import java.util.UUID; -import static com.example.solidconnection.custom.exception.ErrorCode.FILE_NOT_EXIST; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_FILE_EXTENSIONS; -import static com.example.solidconnection.custom.exception.ErrorCode.NOT_ALLOWED_FILE_EXTENSIONS; -import static com.example.solidconnection.custom.exception.ErrorCode.S3_CLIENT_EXCEPTION; -import static com.example.solidconnection.custom.exception.ErrorCode.S3_SERVICE_EXCEPTION; +import static com.example.solidconnection.common.exception.ErrorCode.FILE_NOT_EXIST; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_FILE_EXTENSIONS; +import static com.example.solidconnection.common.exception.ErrorCode.NOT_ALLOWED_FILE_EXTENSIONS; +import static com.example.solidconnection.common.exception.ErrorCode.S3_CLIENT_EXCEPTION; +import static com.example.solidconnection.common.exception.ErrorCode.S3_SERVICE_EXCEPTION; @Service @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/scheduler/UpdateViewCountScheduler.java b/src/main/java/com/example/solidconnection/scheduler/UpdateViewCountScheduler.java index 8da1fe1ca..97a546c05 100644 --- a/src/main/java/com/example/solidconnection/scheduler/UpdateViewCountScheduler.java +++ b/src/main/java/com/example/solidconnection/scheduler/UpdateViewCountScheduler.java @@ -1,6 +1,6 @@ package com.example.solidconnection.scheduler; -import com.example.solidconnection.service.UpdateViewCountService; +import com.example.solidconnection.community.post.service.UpdateViewCountService; import com.example.solidconnection.util.RedisUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -13,7 +13,7 @@ import java.util.List; -import static com.example.solidconnection.type.RedisConstants.VIEW_COUNT_KEY_PATTERN; +import static com.example.solidconnection.community.post.service.RedisConstants.VIEW_COUNT_KEY_PATTERN; @RequiredArgsConstructor @Component diff --git a/src/main/java/com/example/solidconnection/score/controller/ScoreController.java b/src/main/java/com/example/solidconnection/score/controller/ScoreController.java index e67639274..257c3ef4c 100644 --- a/src/main/java/com/example/solidconnection/score/controller/ScoreController.java +++ b/src/main/java/com/example/solidconnection/score/controller/ScoreController.java @@ -1,6 +1,6 @@ package com.example.solidconnection.score.controller; -import com.example.solidconnection.custom.resolver.AuthorizedUser; +import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.score.dto.GpaScoreRequest; import com.example.solidconnection.score.dto.GpaScoreStatusesResponse; import com.example.solidconnection.score.dto.LanguageTestScoreRequest; diff --git a/src/main/java/com/example/solidconnection/score/domain/GpaScore.java b/src/main/java/com/example/solidconnection/score/domain/GpaScore.java index ddc583aa7..0156dae9a 100644 --- a/src/main/java/com/example/solidconnection/score/domain/GpaScore.java +++ b/src/main/java/com/example/solidconnection/score/domain/GpaScore.java @@ -1,9 +1,9 @@ package com.example.solidconnection.score.domain; import com.example.solidconnection.application.domain.Gpa; -import com.example.solidconnection.entity.common.BaseEntity; +import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.BaseEntity; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.type.VerifyStatus; import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; diff --git a/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java b/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java index ec791373e..f2257561d 100644 --- a/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java +++ b/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java @@ -1,9 +1,9 @@ package com.example.solidconnection.score.domain; import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.entity.common.BaseEntity; +import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.BaseEntity; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.type.VerifyStatus; import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; diff --git a/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusResponse.java b/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusResponse.java index df161d358..4804d6874 100644 --- a/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusResponse.java +++ b/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusResponse.java @@ -1,7 +1,7 @@ package com.example.solidconnection.score.dto; +import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.score.domain.GpaScore; -import com.example.solidconnection.type.VerifyStatus; public record GpaScoreStatusResponse( long id, diff --git a/src/main/java/com/example/solidconnection/score/dto/LanguageTestResponse.java b/src/main/java/com/example/solidconnection/score/dto/LanguageTestResponse.java index 060574f46..2368e18c7 100644 --- a/src/main/java/com/example/solidconnection/score/dto/LanguageTestResponse.java +++ b/src/main/java/com/example/solidconnection/score/dto/LanguageTestResponse.java @@ -1,7 +1,7 @@ package com.example.solidconnection.score.dto; import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.university.domain.LanguageTestType; public record LanguageTestResponse( LanguageTestType languageTestType, diff --git a/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreRequest.java b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreRequest.java index e49af4369..6173e8f00 100644 --- a/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreRequest.java +++ b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreRequest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.score.dto; -import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.university.domain.LanguageTestType; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusResponse.java b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusResponse.java index 3ee96906e..4aa033d72 100644 --- a/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusResponse.java +++ b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusResponse.java @@ -1,7 +1,7 @@ package com.example.solidconnection.score.dto; +import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.score.domain.LanguageTestScore; -import com.example.solidconnection.type.VerifyStatus; public record LanguageTestScoreStatusResponse( long id, diff --git a/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java b/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java index 5bef377cf..1934a0612 100644 --- a/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java +++ b/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java @@ -3,7 +3,7 @@ import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.repository.custom.LanguageTestScoreFilterRepository; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.university.domain.LanguageTestType; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/example/solidconnection/score/repository/custom/GpaScoreFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/score/repository/custom/GpaScoreFilterRepositoryImpl.java index a02e62b49..a90961c3d 100644 --- a/src/main/java/com/example/solidconnection/score/repository/custom/GpaScoreFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/score/repository/custom/GpaScoreFilterRepositoryImpl.java @@ -5,7 +5,7 @@ import com.example.solidconnection.admin.dto.GpaScoreStatusResponse; import com.example.solidconnection.admin.dto.ScoreSearchCondition; import com.example.solidconnection.admin.dto.SiteUserResponse; -import com.example.solidconnection.type.VerifyStatus; +import com.example.solidconnection.application.domain.VerifyStatus; import com.querydsl.core.types.ConstructorExpression; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; diff --git a/src/main/java/com/example/solidconnection/score/repository/custom/LanguageTestScoreFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/score/repository/custom/LanguageTestScoreFilterRepositoryImpl.java index 5d88c1451..28baaf521 100644 --- a/src/main/java/com/example/solidconnection/score/repository/custom/LanguageTestScoreFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/score/repository/custom/LanguageTestScoreFilterRepositoryImpl.java @@ -5,7 +5,7 @@ import com.example.solidconnection.admin.dto.LanguageTestScoreStatusResponse; import com.example.solidconnection.admin.dto.ScoreSearchCondition; import com.example.solidconnection.admin.dto.SiteUserResponse; -import com.example.solidconnection.type.VerifyStatus; +import com.example.solidconnection.application.domain.VerifyStatus; import com.querydsl.core.types.ConstructorExpression; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; diff --git a/src/main/java/com/example/solidconnection/score/service/ScoreService.java b/src/main/java/com/example/solidconnection/score/service/ScoreService.java index eb00a14e9..5a6cf9801 100644 --- a/src/main/java/com/example/solidconnection/score/service/ScoreService.java +++ b/src/main/java/com/example/solidconnection/score/service/ScoreService.java @@ -2,9 +2,10 @@ import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.s3.S3Service; -import com.example.solidconnection.s3.UploadedFileUrlResponse; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; +import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.dto.GpaScoreRequest; @@ -17,7 +18,6 @@ import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.type.ImgType; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -28,7 +28,7 @@ import java.util.Optional; import java.util.stream.Collectors; -import static com.example.solidconnection.custom.exception.ErrorCode.USER_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; @Service @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/custom/security/annotation/RequireAdminAccess.java b/src/main/java/com/example/solidconnection/security/annotation/RequireAdminAccess.java similarity index 80% rename from src/main/java/com/example/solidconnection/custom/security/annotation/RequireAdminAccess.java rename to src/main/java/com/example/solidconnection/security/annotation/RequireAdminAccess.java index 559664e25..682d5bdf8 100644 --- a/src/main/java/com/example/solidconnection/custom/security/annotation/RequireAdminAccess.java +++ b/src/main/java/com/example/solidconnection/security/annotation/RequireAdminAccess.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.custom.security.annotation; +package com.example.solidconnection.security.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/com/example/solidconnection/custom/security/aspect/AdminAuthorizationAspect.java b/src/main/java/com/example/solidconnection/security/aspect/AdminAuthorizationAspect.java similarity index 75% rename from src/main/java/com/example/solidconnection/custom/security/aspect/AdminAuthorizationAspect.java rename to src/main/java/com/example/solidconnection/security/aspect/AdminAuthorizationAspect.java index 20e8c27c8..5ebba881f 100644 --- a/src/main/java/com/example/solidconnection/custom/security/aspect/AdminAuthorizationAspect.java +++ b/src/main/java/com/example/solidconnection/security/aspect/AdminAuthorizationAspect.java @@ -1,7 +1,7 @@ -package com.example.solidconnection.custom.security.aspect; +package com.example.solidconnection.security.aspect; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.custom.security.annotation.RequireAdminAccess; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.security.annotation.RequireAdminAccess; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; import org.aspectj.lang.ProceedingJoinPoint; @@ -9,8 +9,8 @@ import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; -import static com.example.solidconnection.custom.exception.ErrorCode.ACCESS_DENIED; -import static com.example.solidconnection.type.Role.ADMIN; +import static com.example.solidconnection.common.exception.ErrorCode.ACCESS_DENIED; +import static com.example.solidconnection.siteuser.domain.Role.ADMIN; @Aspect @Component diff --git a/src/main/java/com/example/solidconnection/custom/security/authentication/JwtAuthentication.java b/src/main/java/com/example/solidconnection/security/authentication/JwtAuthentication.java similarity index 92% rename from src/main/java/com/example/solidconnection/custom/security/authentication/JwtAuthentication.java rename to src/main/java/com/example/solidconnection/security/authentication/JwtAuthentication.java index 6c9f2fa21..64e89e891 100644 --- a/src/main/java/com/example/solidconnection/custom/security/authentication/JwtAuthentication.java +++ b/src/main/java/com/example/solidconnection/security/authentication/JwtAuthentication.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.custom.security.authentication; +package com.example.solidconnection.security.authentication; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; diff --git a/src/main/java/com/example/solidconnection/custom/security/authentication/SiteUserAuthentication.java b/src/main/java/com/example/solidconnection/security/authentication/SiteUserAuthentication.java similarity index 69% rename from src/main/java/com/example/solidconnection/custom/security/authentication/SiteUserAuthentication.java rename to src/main/java/com/example/solidconnection/security/authentication/SiteUserAuthentication.java index 3387cee55..60a71b032 100644 --- a/src/main/java/com/example/solidconnection/custom/security/authentication/SiteUserAuthentication.java +++ b/src/main/java/com/example/solidconnection/security/authentication/SiteUserAuthentication.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.custom.security.authentication; +package com.example.solidconnection.security.authentication; -import com.example.solidconnection.custom.security.userdetails.SiteUserDetails; +import com.example.solidconnection.security.userdetails.SiteUserDetails; public class SiteUserAuthentication extends JwtAuthentication { diff --git a/src/main/java/com/example/solidconnection/config/security/AuthenticationManagerConfig.java b/src/main/java/com/example/solidconnection/security/config/AuthenticationManagerConfig.java similarity index 81% rename from src/main/java/com/example/solidconnection/config/security/AuthenticationManagerConfig.java rename to src/main/java/com/example/solidconnection/security/config/AuthenticationManagerConfig.java index e4af023b7..72601237b 100644 --- a/src/main/java/com/example/solidconnection/config/security/AuthenticationManagerConfig.java +++ b/src/main/java/com/example/solidconnection/security/config/AuthenticationManagerConfig.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.config.security; +package com.example.solidconnection.security.config; -import com.example.solidconnection.custom.security.provider.SiteUserAuthenticationProvider; +import com.example.solidconnection.security.provider.SiteUserAuthenticationProvider; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/example/solidconnection/config/security/CorsProperties.java b/src/main/java/com/example/solidconnection/security/config/CorsProperties.java similarity index 79% rename from src/main/java/com/example/solidconnection/config/security/CorsProperties.java rename to src/main/java/com/example/solidconnection/security/config/CorsProperties.java index f851692c6..a179a7563 100644 --- a/src/main/java/com/example/solidconnection/config/security/CorsProperties.java +++ b/src/main/java/com/example/solidconnection/security/config/CorsProperties.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.config.security; +package com.example.solidconnection.security.config; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/java/com/example/solidconnection/config/security/JwtProperties.java b/src/main/java/com/example/solidconnection/security/config/JwtProperties.java similarity index 75% rename from src/main/java/com/example/solidconnection/config/security/JwtProperties.java rename to src/main/java/com/example/solidconnection/security/config/JwtProperties.java index e0c63da46..f4afa5245 100644 --- a/src/main/java/com/example/solidconnection/config/security/JwtProperties.java +++ b/src/main/java/com/example/solidconnection/security/config/JwtProperties.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.config.security; +package com.example.solidconnection.security.config; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java b/src/main/java/com/example/solidconnection/security/config/SecurityConfiguration.java similarity index 87% rename from src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java rename to src/main/java/com/example/solidconnection/security/config/SecurityConfiguration.java index 1d0b110bb..46d1793df 100644 --- a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java +++ b/src/main/java/com/example/solidconnection/security/config/SecurityConfiguration.java @@ -1,10 +1,10 @@ -package com.example.solidconnection.config.security; +package com.example.solidconnection.security.config; -import com.example.solidconnection.custom.exception.CustomAccessDeniedHandler; -import com.example.solidconnection.custom.exception.CustomAuthenticationEntryPoint; -import com.example.solidconnection.custom.security.filter.ExceptionHandlerFilter; -import com.example.solidconnection.custom.security.filter.JwtAuthenticationFilter; -import com.example.solidconnection.custom.security.filter.SignOutCheckFilter; +import com.example.solidconnection.common.exception.CustomAccessDeniedHandler; +import com.example.solidconnection.common.exception.CustomAuthenticationEntryPoint; +import com.example.solidconnection.security.filter.ExceptionHandlerFilter; +import com.example.solidconnection.security.filter.JwtAuthenticationFilter; +import com.example.solidconnection.security.filter.SignOutCheckFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -20,7 +20,7 @@ import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import static com.example.solidconnection.type.Role.ADMIN; +import static com.example.solidconnection.siteuser.domain.Role.ADMIN; @Configuration @EnableWebSecurity diff --git a/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java b/src/main/java/com/example/solidconnection/security/filter/ExceptionHandlerFilter.java similarity index 87% rename from src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java rename to src/main/java/com/example/solidconnection/security/filter/ExceptionHandlerFilter.java index 2db133b8f..84602a5f8 100644 --- a/src/main/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilter.java +++ b/src/main/java/com/example/solidconnection/security/filter/ExceptionHandlerFilter.java @@ -1,8 +1,8 @@ -package com.example.solidconnection.custom.security.filter; +package com.example.solidconnection.security.filter; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.custom.exception.ErrorCode; -import com.example.solidconnection.custom.response.ErrorResponse; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; +import com.example.solidconnection.common.response.ErrorResponse; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -16,7 +16,7 @@ import java.io.IOException; -import static com.example.solidconnection.custom.exception.ErrorCode.AUTHENTICATION_FAILED; +import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; @Component @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/custom/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java similarity index 87% rename from src/main/java/com/example/solidconnection/custom/security/filter/JwtAuthenticationFilter.java rename to src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java index d9d8efd65..39917d42e 100644 --- a/src/main/java/com/example/solidconnection/custom/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java @@ -1,7 +1,7 @@ -package com.example.solidconnection.custom.security.filter; +package com.example.solidconnection.security.filter; -import com.example.solidconnection.custom.security.authentication.JwtAuthentication; -import com.example.solidconnection.custom.security.authentication.SiteUserAuthentication; +import com.example.solidconnection.security.authentication.JwtAuthentication; +import com.example.solidconnection.security.authentication.SiteUserAuthentication; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/com/example/solidconnection/custom/security/filter/SignOutCheckFilter.java b/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java similarity index 87% rename from src/main/java/com/example/solidconnection/custom/security/filter/SignOutCheckFilter.java rename to src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java index a111a7292..5c51c53cd 100644 --- a/src/main/java/com/example/solidconnection/custom/security/filter/SignOutCheckFilter.java +++ b/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java @@ -1,7 +1,7 @@ -package com.example.solidconnection.custom.security.filter; +package com.example.solidconnection.security.filter; import com.example.solidconnection.auth.service.BlacklistChecker; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -13,7 +13,7 @@ import java.io.IOException; -import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_SIGN_OUT; +import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_SIGN_OUT; import static com.example.solidconnection.util.JwtUtils.parseTokenFromRequest; @Component diff --git a/src/main/java/com/example/solidconnection/custom/security/provider/SiteUserAuthenticationProvider.java b/src/main/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProvider.java similarity index 72% rename from src/main/java/com/example/solidconnection/custom/security/provider/SiteUserAuthenticationProvider.java rename to src/main/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProvider.java index 25f211710..6ce43b97c 100644 --- a/src/main/java/com/example/solidconnection/custom/security/provider/SiteUserAuthenticationProvider.java +++ b/src/main/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProvider.java @@ -1,10 +1,10 @@ -package com.example.solidconnection.custom.security.provider; +package com.example.solidconnection.security.provider; -import com.example.solidconnection.config.security.JwtProperties; -import com.example.solidconnection.custom.security.userdetails.SiteUserDetails; -import com.example.solidconnection.custom.security.userdetails.SiteUserDetailsService; -import com.example.solidconnection.custom.security.authentication.JwtAuthentication; -import com.example.solidconnection.custom.security.authentication.SiteUserAuthentication; +import com.example.solidconnection.security.authentication.JwtAuthentication; +import com.example.solidconnection.security.authentication.SiteUserAuthentication; +import com.example.solidconnection.security.config.JwtProperties; +import com.example.solidconnection.security.userdetails.SiteUserDetails; +import com.example.solidconnection.security.userdetails.SiteUserDetailsService; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; diff --git a/src/main/java/com/example/solidconnection/custom/security/userdetails/SecurityRoleMapper.java b/src/main/java/com/example/solidconnection/security/userdetails/SecurityRoleMapper.java similarity index 77% rename from src/main/java/com/example/solidconnection/custom/security/userdetails/SecurityRoleMapper.java rename to src/main/java/com/example/solidconnection/security/userdetails/SecurityRoleMapper.java index 3af238f13..802229e46 100644 --- a/src/main/java/com/example/solidconnection/custom/security/userdetails/SecurityRoleMapper.java +++ b/src/main/java/com/example/solidconnection/security/userdetails/SecurityRoleMapper.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.custom.security.userdetails; +package com.example.solidconnection.security.userdetails; -import com.example.solidconnection.type.Role; +import com.example.solidconnection.siteuser.domain.Role; import org.springframework.security.core.authority.SimpleGrantedAuthority; import java.util.List; diff --git a/src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetails.java b/src/main/java/com/example/solidconnection/security/userdetails/SiteUserDetails.java similarity index 95% rename from src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetails.java rename to src/main/java/com/example/solidconnection/security/userdetails/SiteUserDetails.java index 008f77ef5..d92d5cc32 100644 --- a/src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetails.java +++ b/src/main/java/com/example/solidconnection/security/userdetails/SiteUserDetails.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.custom.security.userdetails; +package com.example.solidconnection.security.userdetails; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.Getter; diff --git a/src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsService.java b/src/main/java/com/example/solidconnection/security/userdetails/SiteUserDetailsService.java similarity index 87% rename from src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsService.java rename to src/main/java/com/example/solidconnection/security/userdetails/SiteUserDetailsService.java index fd23fa899..e13dc1089 100644 --- a/src/main/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsService.java +++ b/src/main/java/com/example/solidconnection/security/userdetails/SiteUserDetailsService.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.custom.security.userdetails; +package com.example.solidconnection.security.userdetails; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import lombok.RequiredArgsConstructor; @@ -9,8 +9,8 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; -import static com.example.solidconnection.custom.exception.ErrorCode.AUTHENTICATION_FAILED; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_TOKEN; +import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; @Service @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java b/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java index 41862bf8b..ff36d7baa 100644 --- a/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java +++ b/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java @@ -1,6 +1,6 @@ package com.example.solidconnection.siteuser.controller; -import com.example.solidconnection.custom.resolver.AuthorizedUser; +import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.MyPageResponse; import com.example.solidconnection.siteuser.service.MyPageService; diff --git a/src/main/java/com/example/solidconnection/type/PreparationStatus.java b/src/main/java/com/example/solidconnection/siteuser/domain/PreparationStatus.java similarity index 83% rename from src/main/java/com/example/solidconnection/type/PreparationStatus.java rename to src/main/java/com/example/solidconnection/siteuser/domain/PreparationStatus.java index c4f1650e9..6f86853c0 100644 --- a/src/main/java/com/example/solidconnection/type/PreparationStatus.java +++ b/src/main/java/com/example/solidconnection/siteuser/domain/PreparationStatus.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.type; +package com.example.solidconnection.siteuser.domain; public enum PreparationStatus { CONSIDERING, // 교환학생 지원 고민 상태 diff --git a/src/main/java/com/example/solidconnection/type/Role.java b/src/main/java/com/example/solidconnection/siteuser/domain/Role.java similarity index 52% rename from src/main/java/com/example/solidconnection/type/Role.java rename to src/main/java/com/example/solidconnection/siteuser/domain/Role.java index 8223e8de0..4ea5bf151 100644 --- a/src/main/java/com/example/solidconnection/type/Role.java +++ b/src/main/java/com/example/solidconnection/siteuser/domain/Role.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.type; +package com.example.solidconnection.siteuser.domain; public enum Role { diff --git a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java index 21dfbcc13..bcfb1f9ac 100644 --- a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java +++ b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java @@ -5,8 +5,6 @@ import com.example.solidconnection.community.post.domain.PostLike; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.domain.LanguageTestScore; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/MyPageResponse.java b/src/main/java/com/example/solidconnection/siteuser/dto/MyPageResponse.java index 981866632..9185362cf 100644 --- a/src/main/java/com/example/solidconnection/siteuser/dto/MyPageResponse.java +++ b/src/main/java/com/example/solidconnection/siteuser/dto/MyPageResponse.java @@ -1,8 +1,8 @@ package com.example.solidconnection.siteuser.dto; import com.example.solidconnection.siteuser.domain.AuthType; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.type.Role; public record MyPageResponse( String nickname, diff --git a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java index e89c6cdfa..2c84f0518 100644 --- a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java +++ b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java @@ -1,13 +1,13 @@ package com.example.solidconnection.siteuser.service; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.s3.S3Service; -import com.example.solidconnection.s3.UploadedFileUrlResponse; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; +import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.MyPageResponse; import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.type.ImgType; import com.example.solidconnection.university.domain.LikedUniversity; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; import lombok.RequiredArgsConstructor; @@ -19,8 +19,8 @@ import java.time.format.DateTimeFormatter; import java.util.List; -import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; -import static com.example.solidconnection.custom.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; +import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; +import static com.example.solidconnection.common.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; @RequiredArgsConstructor @Service diff --git a/src/main/java/com/example/solidconnection/type/PostCategory.java b/src/main/java/com/example/solidconnection/type/PostCategory.java deleted file mode 100644 index b42b94f95..000000000 --- a/src/main/java/com/example/solidconnection/type/PostCategory.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.example.solidconnection.type; - -public enum PostCategory { - 전체, 자유, 질문 -} diff --git a/src/main/java/com/example/solidconnection/university/controller/UniversityController.java b/src/main/java/com/example/solidconnection/university/controller/UniversityController.java index 635693d4c..6345876a9 100644 --- a/src/main/java/com/example/solidconnection/university/controller/UniversityController.java +++ b/src/main/java/com/example/solidconnection/university/controller/UniversityController.java @@ -1,9 +1,9 @@ package com.example.solidconnection.university.controller; -import com.example.solidconnection.custom.resolver.AuthorizedUser; +import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.service.MyPageService; -import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.dto.IsLikeResponse; import com.example.solidconnection.university.dto.LikeResultResponse; import com.example.solidconnection.university.dto.UniversityDetailResponse; diff --git a/src/main/java/com/example/solidconnection/university/domain/LanguageRequirement.java b/src/main/java/com/example/solidconnection/university/domain/LanguageRequirement.java index 508c7531e..55377131b 100644 --- a/src/main/java/com/example/solidconnection/university/domain/LanguageRequirement.java +++ b/src/main/java/com/example/solidconnection/university/domain/LanguageRequirement.java @@ -1,6 +1,5 @@ package com.example.solidconnection.university.domain; -import com.example.solidconnection.type.LanguageTestType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; diff --git a/src/main/java/com/example/solidconnection/type/LanguageTestType.java b/src/main/java/com/example/solidconnection/university/domain/LanguageTestType.java similarity index 96% rename from src/main/java/com/example/solidconnection/type/LanguageTestType.java rename to src/main/java/com/example/solidconnection/university/domain/LanguageTestType.java index 29082c98e..f220ac3d3 100644 --- a/src/main/java/com/example/solidconnection/type/LanguageTestType.java +++ b/src/main/java/com/example/solidconnection/university/domain/LanguageTestType.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.type; +package com.example.solidconnection.university.domain; import java.util.Comparator; diff --git a/src/main/java/com/example/solidconnection/type/SemesterAvailableForDispatch.java b/src/main/java/com/example/solidconnection/university/domain/SemesterAvailableForDispatch.java similarity index 90% rename from src/main/java/com/example/solidconnection/type/SemesterAvailableForDispatch.java rename to src/main/java/com/example/solidconnection/university/domain/SemesterAvailableForDispatch.java index 2a04805d6..9d44ecd8c 100644 --- a/src/main/java/com/example/solidconnection/type/SemesterAvailableForDispatch.java +++ b/src/main/java/com/example/solidconnection/university/domain/SemesterAvailableForDispatch.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.type; +package com.example.solidconnection.university.domain; public enum SemesterAvailableForDispatch { ONE_SEMESTER("1개학기"), diff --git a/src/main/java/com/example/solidconnection/type/TuitionFeeType.java b/src/main/java/com/example/solidconnection/university/domain/TuitionFeeType.java similarity index 87% rename from src/main/java/com/example/solidconnection/type/TuitionFeeType.java rename to src/main/java/com/example/solidconnection/university/domain/TuitionFeeType.java index 21ab6700e..c7abc09e1 100644 --- a/src/main/java/com/example/solidconnection/type/TuitionFeeType.java +++ b/src/main/java/com/example/solidconnection/university/domain/TuitionFeeType.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.type; +package com.example.solidconnection.university.domain; public enum TuitionFeeType { HOME_UNIVERSITY_PAYMENT("본교등록금납부형"), diff --git a/src/main/java/com/example/solidconnection/university/domain/University.java b/src/main/java/com/example/solidconnection/university/domain/University.java index c3021385e..d010861d4 100644 --- a/src/main/java/com/example/solidconnection/university/domain/University.java +++ b/src/main/java/com/example/solidconnection/university/domain/University.java @@ -1,7 +1,7 @@ package com.example.solidconnection.university.domain; -import com.example.solidconnection.entity.Country; -import com.example.solidconnection.entity.Region; +import com.example.solidconnection.location.country.domain.Country; +import com.example.solidconnection.location.region.domain.Region; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; diff --git a/src/main/java/com/example/solidconnection/university/domain/UniversityInfoForApply.java b/src/main/java/com/example/solidconnection/university/domain/UniversityInfoForApply.java index e1a87fe83..c968a3a1a 100644 --- a/src/main/java/com/example/solidconnection/university/domain/UniversityInfoForApply.java +++ b/src/main/java/com/example/solidconnection/university/domain/UniversityInfoForApply.java @@ -1,7 +1,5 @@ package com.example.solidconnection.university.domain; -import com.example.solidconnection.type.SemesterAvailableForDispatch; -import com.example.solidconnection.type.TuitionFeeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; diff --git a/src/main/java/com/example/solidconnection/university/dto/LanguageRequirementResponse.java b/src/main/java/com/example/solidconnection/university/dto/LanguageRequirementResponse.java index 8cc7b9733..292bd837d 100644 --- a/src/main/java/com/example/solidconnection/university/dto/LanguageRequirementResponse.java +++ b/src/main/java/com/example/solidconnection/university/dto/LanguageRequirementResponse.java @@ -1,7 +1,7 @@ package com.example.solidconnection.university.dto; -import com.example.solidconnection.type.LanguageTestType; import com.example.solidconnection.university.domain.LanguageRequirement; +import com.example.solidconnection.university.domain.LanguageTestType; public record LanguageRequirementResponse( LanguageTestType languageTestType, diff --git a/src/main/java/com/example/solidconnection/custom/validation/annotation/ValidUniversityChoice.java b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoice.java similarity index 78% rename from src/main/java/com/example/solidconnection/custom/validation/annotation/ValidUniversityChoice.java rename to src/main/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoice.java index 7e5827113..658de067c 100644 --- a/src/main/java/com/example/solidconnection/custom/validation/annotation/ValidUniversityChoice.java +++ b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoice.java @@ -1,6 +1,5 @@ -package com.example.solidconnection.custom.validation.annotation; +package com.example.solidconnection.university.dto.validation; -import com.example.solidconnection.custom.validation.validator.ValidUniversityChoiceValidator; import jakarta.validation.Constraint; import jakarta.validation.Payload; diff --git a/src/main/java/com/example/solidconnection/custom/validation/validator/ValidUniversityChoiceValidator.java b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoiceValidator.java similarity index 86% rename from src/main/java/com/example/solidconnection/custom/validation/validator/ValidUniversityChoiceValidator.java rename to src/main/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoiceValidator.java index 6ac9fe1c2..63d47b0de 100644 --- a/src/main/java/com/example/solidconnection/custom/validation/validator/ValidUniversityChoiceValidator.java +++ b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoiceValidator.java @@ -1,7 +1,6 @@ -package com.example.solidconnection.custom.validation.validator; +package com.example.solidconnection.university.dto.validation; import com.example.solidconnection.application.dto.UniversityChoiceRequest; -import com.example.solidconnection.custom.validation.annotation.ValidUniversityChoice; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; @@ -10,9 +9,9 @@ import java.util.Set; import java.util.stream.Stream; -import static com.example.solidconnection.custom.exception.ErrorCode.DUPLICATE_UNIVERSITY_CHOICE; -import static com.example.solidconnection.custom.exception.ErrorCode.FIRST_CHOICE_REQUIRED; -import static com.example.solidconnection.custom.exception.ErrorCode.THIRD_CHOICE_REQUIRES_SECOND; +import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_UNIVERSITY_CHOICE; +import static com.example.solidconnection.common.exception.ErrorCode.FIRST_CHOICE_REQUIRED; +import static com.example.solidconnection.common.exception.ErrorCode.THIRD_CHOICE_REQUIRES_SECOND; public class ValidUniversityChoiceValidator implements ConstraintValidator { diff --git a/src/main/java/com/example/solidconnection/university/repository/LanguageRequirementRepository.java b/src/main/java/com/example/solidconnection/university/repository/LanguageRequirementRepository.java index 4cbebc6f5..cfe9f7050 100644 --- a/src/main/java/com/example/solidconnection/university/repository/LanguageRequirementRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/LanguageRequirementRepository.java @@ -1,7 +1,7 @@ package com.example.solidconnection.university.repository; -import com.example.solidconnection.type.LanguageTestType; import com.example.solidconnection.university.domain.LanguageRequirement; +import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.UniversityInfoForApply; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java b/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java index 60474c13d..58fb7aeec 100644 --- a/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java @@ -1,6 +1,6 @@ package com.example.solidconnection.university.repository; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.domain.UniversityInfoForApply; @@ -12,8 +12,8 @@ import java.util.List; import java.util.Optional; -import static com.example.solidconnection.custom.exception.ErrorCode.UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND; -import static com.example.solidconnection.custom.exception.ErrorCode.UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND_FOR_TERM; +import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND_FOR_TERM; @Repository public interface UniversityInfoForApplyRepository extends JpaRepository { diff --git a/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java b/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java index e4cdeade2..b19285469 100644 --- a/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java @@ -1,6 +1,6 @@ package com.example.solidconnection.university.repository; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.repository.custom.UniversityFilterRepository; import org.springframework.data.jpa.repository.JpaRepository; @@ -10,7 +10,7 @@ import java.util.List; -import static com.example.solidconnection.custom.exception.ErrorCode.UNIVERSITY_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_NOT_FOUND; @Repository public interface UniversityRepository extends JpaRepository, UniversityFilterRepository { diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java index 009496be7..c35533877 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java @@ -1,6 +1,6 @@ package com.example.solidconnection.university.repository.custom; -import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.domain.UniversityInfoForApply; diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java index dd84cfbf5..25da850da 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java @@ -1,8 +1,8 @@ package com.example.solidconnection.university.repository.custom; -import com.example.solidconnection.entity.QCountry; -import com.example.solidconnection.entity.QRegion; -import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.location.country.domain.QCountry; +import com.example.solidconnection.location.region.domain.QRegion; +import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.QUniversity; import com.example.solidconnection.university.domain.QUniversityInfoForApply; import com.example.solidconnection.university.domain.University; diff --git a/src/main/java/com/example/solidconnection/university/service/UniversityLikeService.java b/src/main/java/com/example/solidconnection/university/service/UniversityLikeService.java index 85971663b..228cc1ff8 100644 --- a/src/main/java/com/example/solidconnection/university/service/UniversityLikeService.java +++ b/src/main/java/com/example/solidconnection/university/service/UniversityLikeService.java @@ -1,6 +1,6 @@ package com.example.solidconnection.university.service; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; import com.example.solidconnection.university.domain.LikedUniversity; @@ -15,8 +15,8 @@ import java.util.Optional; -import static com.example.solidconnection.custom.exception.ErrorCode.ALREADY_LIKED_UNIVERSITY; -import static com.example.solidconnection.custom.exception.ErrorCode.NOT_LIKED_UNIVERSITY; +import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_UNIVERSITY; +import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_UNIVERSITY; @RequiredArgsConstructor @Service diff --git a/src/main/java/com/example/solidconnection/university/service/UniversityQueryService.java b/src/main/java/com/example/solidconnection/university/service/UniversityQueryService.java index f93f3ffae..e86f5debb 100644 --- a/src/main/java/com/example/solidconnection/university/service/UniversityQueryService.java +++ b/src/main/java/com/example/solidconnection/university/service/UniversityQueryService.java @@ -1,7 +1,7 @@ package com.example.solidconnection.university.service; import com.example.solidconnection.cache.annotation.ThunderingHerdCaching; -import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.dto.UniversityDetailResponse; diff --git a/src/main/java/com/example/solidconnection/util/JwtUtils.java b/src/main/java/com/example/solidconnection/util/JwtUtils.java index a5c96d092..040beb9ba 100644 --- a/src/main/java/com/example/solidconnection/util/JwtUtils.java +++ b/src/main/java/com/example/solidconnection/util/JwtUtils.java @@ -1,13 +1,13 @@ package com.example.solidconnection.util; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import jakarta.servlet.http.HttpServletRequest; import org.springframework.stereotype.Component; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_TOKEN; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; @Component public class JwtUtils { diff --git a/src/main/java/com/example/solidconnection/util/RedisUtils.java b/src/main/java/com/example/solidconnection/util/RedisUtils.java index ed67acac0..6f11eedfa 100644 --- a/src/main/java/com/example/solidconnection/util/RedisUtils.java +++ b/src/main/java/com/example/solidconnection/util/RedisUtils.java @@ -11,10 +11,10 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import static com.example.solidconnection.type.RedisConstants.CREATE_LOCK_PREFIX; -import static com.example.solidconnection.type.RedisConstants.REFRESH_LOCK_PREFIX; -import static com.example.solidconnection.type.RedisConstants.VALIDATE_VIEW_COUNT_KEY_PREFIX; -import static com.example.solidconnection.type.RedisConstants.VIEW_COUNT_KEY_PREFIX; +import static com.example.solidconnection.community.post.service.RedisConstants.CREATE_LOCK_PREFIX; +import static com.example.solidconnection.community.post.service.RedisConstants.REFRESH_LOCK_PREFIX; +import static com.example.solidconnection.community.post.service.RedisConstants.VALIDATE_VIEW_COUNT_KEY_PREFIX; +import static com.example.solidconnection.community.post.service.RedisConstants.VIEW_COUNT_KEY_PREFIX; @Component public class RedisUtils { diff --git a/src/test/java/com/example/solidconnection/custom/validation/validator/RejectedReasonValidatorTest.java b/src/test/java/com/example/solidconnection/admin/dto/validation/RejectedReasonValidatorTest.java similarity index 93% rename from src/test/java/com/example/solidconnection/custom/validation/validator/RejectedReasonValidatorTest.java rename to src/test/java/com/example/solidconnection/admin/dto/validation/RejectedReasonValidatorTest.java index 5af4e8399..8c9af55c6 100644 --- a/src/test/java/com/example/solidconnection/custom/validation/validator/RejectedReasonValidatorTest.java +++ b/src/test/java/com/example/solidconnection/admin/dto/validation/RejectedReasonValidatorTest.java @@ -1,9 +1,9 @@ -package com.example.solidconnection.custom.validation.validator; +package com.example.solidconnection.admin.dto.validation; import com.example.solidconnection.admin.dto.GpaScoreUpdateRequest; import com.example.solidconnection.admin.dto.LanguageTestScoreUpdateRequest; -import com.example.solidconnection.type.LanguageTestType; -import com.example.solidconnection.type.VerifyStatus; +import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.university.domain.LanguageTestType; import jakarta.validation.ConstraintViolation; import jakarta.validation.Validation; import jakarta.validation.Validator; @@ -15,7 +15,7 @@ import java.util.Set; -import static com.example.solidconnection.custom.exception.ErrorCode.REJECTED_REASON_REQUIRED; +import static com.example.solidconnection.common.exception.ErrorCode.REJECTED_REASON_REQUIRED; import static org.assertj.core.api.Assertions.assertThat; @DisplayName("거절 사유 유효성 검사 테스트") diff --git a/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java b/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java index fc7735d86..a891759cd 100644 --- a/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java @@ -5,13 +5,13 @@ import com.example.solidconnection.admin.dto.GpaScoreUpdateRequest; import com.example.solidconnection.admin.dto.ScoreSearchCondition; import com.example.solidconnection.application.domain.Gpa; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.repository.GpaScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.integration.BaseIntegrationTest; -import com.example.solidconnection.type.VerifyStatus; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -24,7 +24,7 @@ import java.time.LocalDate; import java.util.List; -import static com.example.solidconnection.custom.exception.ErrorCode.GPA_SCORE_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.GPA_SCORE_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; diff --git a/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java b/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java index edf34670b..3fe3eb5a8 100644 --- a/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java @@ -5,13 +5,13 @@ import com.example.solidconnection.admin.dto.LanguageTestScoreUpdateRequest; import com.example.solidconnection.admin.dto.ScoreSearchCondition; import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.integration.BaseIntegrationTest; -import com.example.solidconnection.type.VerifyStatus; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -24,8 +24,8 @@ import java.time.LocalDate; import java.util.List; -import static com.example.solidconnection.custom.exception.ErrorCode.LANGUAGE_TEST_SCORE_NOT_FOUND; -import static com.example.solidconnection.type.LanguageTestType.TOEIC; +import static com.example.solidconnection.common.exception.ErrorCode.LANGUAGE_TEST_SCORE_NOT_FOUND; +import static com.example.solidconnection.university.domain.LanguageTestType.TOEIC; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java index 583c31d80..240217496 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java @@ -3,6 +3,7 @@ import com.example.solidconnection.application.domain.Application; import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; +import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.application.dto.ApplicantResponse; import com.example.solidconnection.application.dto.ApplicationsResponse; import com.example.solidconnection.application.dto.UniversityApplicantsResponse; @@ -13,8 +14,7 @@ import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.support.integration.BaseIntegrationTest; -import com.example.solidconnection.type.LanguageTestType; -import com.example.solidconnection.type.VerifyStatus; +import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.UniversityInfoForApply; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java index f4f442840..3c107424a 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java @@ -3,27 +3,27 @@ import com.example.solidconnection.application.domain.Application; import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; +import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.application.dto.ApplicationSubmissionResponse; import com.example.solidconnection.application.dto.ApplyRequest; import com.example.solidconnection.application.dto.UniversityChoiceRequest; import com.example.solidconnection.application.repository.ApplicationRepository; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.repository.GpaScoreRepository; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.support.integration.BaseIntegrationTest; -import com.example.solidconnection.type.LanguageTestType; -import com.example.solidconnection.type.VerifyStatus; +import com.example.solidconnection.university.domain.LanguageTestType; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import static com.example.solidconnection.application.service.ApplicationSubmissionService.APPLICATION_UPDATE_COUNT_LIMIT; -import static com.example.solidconnection.custom.exception.ErrorCode.APPLY_UPDATE_LIMIT_EXCEED; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_GPA_SCORE_STATUS; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE_STATUS; +import static com.example.solidconnection.common.exception.ErrorCode.APPLY_UPDATE_LIMIT_EXCEED; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_GPA_SCORE_STATUS; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE_STATUS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; @@ -141,7 +141,7 @@ class ApplicationSubmissionServiceTest extends BaseIntegrationTest { private GpaScore createUnapprovedGpaScore(SiteUser siteUser) { GpaScore gpaScore = new GpaScore( - new Gpa(4.0, 4.5, "/gpa-report.pdf"), + new Gpa(4.0, 4.5, "/gpa-report.pdf"), siteUser ); return gpaScoreRepository.save(gpaScore); diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java index cb0ffbb96..8fd57eae6 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -3,7 +3,7 @@ import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; @@ -15,7 +15,7 @@ import java.time.LocalDate; -import static com.example.solidconnection.custom.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; +import static com.example.solidconnection.common.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; diff --git a/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java index 4a98de6ce..84c0f362b 100644 --- a/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java @@ -2,8 +2,8 @@ import com.example.solidconnection.auth.dto.EmailSignInRequest; import com.example.solidconnection.auth.dto.SignInResponse; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.custom.exception.ErrorCode; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; diff --git a/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java index 51eee236c..1656ed4e5 100644 --- a/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java @@ -2,7 +2,7 @@ import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.SignInResponse; -import com.example.solidconnection.config.security.JwtProperties; +import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; diff --git a/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java index 12ab6f666..233317458 100644 --- a/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java @@ -1,8 +1,8 @@ package com.example.solidconnection.auth.service.oauth; import com.example.solidconnection.auth.domain.TokenType; -import com.example.solidconnection.config.security.JwtProperties; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.util.JwtUtils; @@ -21,8 +21,8 @@ import java.util.Map; import static com.example.solidconnection.auth.service.oauth.OAuthSignUpTokenProvider.AUTH_TYPE_CLAIM_KEY; -import static com.example.solidconnection.custom.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; -import static com.example.solidconnection.custom.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; +import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; +import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; @@ -163,7 +163,7 @@ class 주어진_회원가입_토큰을_검증한다 { // then assertThat(extractedAuthType).isEqualTo(authType); } - + private String createExpiredToken() { return Jwts.builder() .setSubject("subject") diff --git a/src/test/java/com/example/solidconnection/custom/exception/CustomAccessDeniedHandlerTest.java b/src/test/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandlerTest.java similarity index 90% rename from src/test/java/com/example/solidconnection/custom/exception/CustomAccessDeniedHandlerTest.java rename to src/test/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandlerTest.java index 7e4cae5b2..977733785 100644 --- a/src/test/java/com/example/solidconnection/custom/exception/CustomAccessDeniedHandlerTest.java +++ b/src/test/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandlerTest.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.custom.exception; +package com.example.solidconnection.common.exception; -import com.example.solidconnection.custom.response.ErrorResponse; +import com.example.solidconnection.common.response.ErrorResponse; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeEach; @@ -13,7 +13,7 @@ import java.io.IOException; -import static com.example.solidconnection.custom.exception.ErrorCode.ACCESS_DENIED; +import static com.example.solidconnection.common.exception.ErrorCode.ACCESS_DENIED; import static org.assertj.core.api.Assertions.assertThat; @TestContainerSpringBootTest diff --git a/src/test/java/com/example/solidconnection/custom/exception/CustomAuthenticationEntryPointTest.java b/src/test/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPointTest.java similarity index 91% rename from src/test/java/com/example/solidconnection/custom/exception/CustomAuthenticationEntryPointTest.java rename to src/test/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPointTest.java index 2cef64481..a10fed5df 100644 --- a/src/test/java/com/example/solidconnection/custom/exception/CustomAuthenticationEntryPointTest.java +++ b/src/test/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPointTest.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.custom.exception; +package com.example.solidconnection.common.exception; -import com.example.solidconnection.custom.response.ErrorResponse; +import com.example.solidconnection.common.response.ErrorResponse; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.BeforeEach; @@ -14,7 +14,7 @@ import java.io.IOException; -import static com.example.solidconnection.custom.exception.ErrorCode.AUTHENTICATION_FAILED; +import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; import static org.assertj.core.api.Assertions.assertThat; @TestContainerSpringBootTest diff --git a/src/test/java/com/example/solidconnection/custom/resolver/AuthorizedUserResolverTest.java b/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java similarity index 91% rename from src/test/java/com/example/solidconnection/custom/resolver/AuthorizedUserResolverTest.java rename to src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java index e0c1a006f..7eb144214 100644 --- a/src/test/java/com/example/solidconnection/custom/resolver/AuthorizedUserResolverTest.java +++ b/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java @@ -1,8 +1,8 @@ -package com.example.solidconnection.custom.resolver; +package com.example.solidconnection.common.resolver; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.custom.security.authentication.SiteUserAuthentication; -import com.example.solidconnection.custom.security.userdetails.SiteUserDetails; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.security.authentication.SiteUserAuthentication; +import com.example.solidconnection.security.userdetails.SiteUserDetails; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; @@ -15,7 +15,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import static com.example.solidconnection.custom.exception.ErrorCode.AUTHENTICATION_FAILED; +import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.mockito.BDDMockito.given; diff --git a/src/test/java/com/example/solidconnection/custom/resolver/CustomPageableHandlerMethodArgumentResolverTest.java b/src/test/java/com/example/solidconnection/common/resolver/CustomPageableHandlerMethodArgumentResolverTest.java similarity index 98% rename from src/test/java/com/example/solidconnection/custom/resolver/CustomPageableHandlerMethodArgumentResolverTest.java rename to src/test/java/com/example/solidconnection/common/resolver/CustomPageableHandlerMethodArgumentResolverTest.java index dc628bc67..5d1bc0faa 100644 --- a/src/test/java/com/example/solidconnection/custom/resolver/CustomPageableHandlerMethodArgumentResolverTest.java +++ b/src/test/java/com/example/solidconnection/common/resolver/CustomPageableHandlerMethodArgumentResolverTest.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.custom.resolver; +package com.example.solidconnection.common.resolver; import com.example.solidconnection.support.TestContainerSpringBootTest; import org.junit.jupiter.api.BeforeEach; diff --git a/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java b/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java index ee74bb90b..17309e100 100644 --- a/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java @@ -1,5 +1,6 @@ package com.example.solidconnection.community.comment.service; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.board.domain.Board; import com.example.solidconnection.community.comment.domain.Comment; import com.example.solidconnection.community.comment.dto.CommentCreateRequest; @@ -10,11 +11,10 @@ import com.example.solidconnection.community.comment.dto.PostFindCommentResponse; import com.example.solidconnection.community.comment.repository.CommentRepository; import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.community.post.domain.PostCategory; import com.example.solidconnection.community.post.repository.PostRepository; -import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.support.integration.BaseIntegrationTest; -import com.example.solidconnection.type.PostCategory; import jakarta.transaction.Transactional; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -23,10 +23,10 @@ import java.util.List; -import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_UPDATE_DEPRECATED_COMMENT; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_COMMENT_ID; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_COMMENT_LEVEL; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_ACCESS; +import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPDATE_DEPRECATED_COMMENT; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_COMMENT_ID; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_COMMENT_LEVEL; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ACCESS; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java index d3931008a..2656b2858 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java @@ -1,7 +1,9 @@ package com.example.solidconnection.community.post.service; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.board.domain.Board; import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.community.post.domain.PostCategory; import com.example.solidconnection.community.post.domain.PostImage; import com.example.solidconnection.community.post.dto.PostCreateRequest; import com.example.solidconnection.community.post.dto.PostCreateResponse; @@ -10,15 +12,12 @@ import com.example.solidconnection.community.post.dto.PostUpdateResponse; import com.example.solidconnection.community.post.repository.PostImageRepository; import com.example.solidconnection.community.post.repository.PostRepository; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.s3.S3Service; -import com.example.solidconnection.s3.UploadedFileUrlResponse; -import com.example.solidconnection.service.RedisService; +import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; +import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.integration.BaseIntegrationTest; -import com.example.solidconnection.type.ImgType; -import com.example.solidconnection.type.PostCategory; import com.example.solidconnection.util.RedisUtils; import jakarta.transaction.Transactional; import org.junit.jupiter.api.BeforeEach; @@ -32,10 +31,10 @@ import java.util.List; -import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_DELETE_OR_UPDATE_QUESTION; -import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_ACCESS; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_CATEGORY; +import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_DELETE_OR_UPDATE_QUESTION; +import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ACCESS; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_CATEGORY; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java index 30026e46a..4205019b7 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java @@ -1,8 +1,9 @@ package com.example.solidconnection.community.post.service; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.board.domain.Board; -import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.community.post.domain.PostCategory; import com.example.solidconnection.community.post.dto.PostDislikeResponse; import com.example.solidconnection.community.post.dto.PostLikeResponse; import com.example.solidconnection.community.post.repository.PostLikeRepository; @@ -10,15 +11,14 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.integration.BaseIntegrationTest; -import com.example.solidconnection.type.PostCategory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import static com.example.solidconnection.custom.exception.ErrorCode.DUPLICATE_POST_LIKE; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_LIKE; +import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_POST_LIKE; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_LIKE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java index 67212e014..13a942ef7 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java @@ -1,22 +1,21 @@ package com.example.solidconnection.community.post.service; import com.example.solidconnection.community.board.domain.Board; +import com.example.solidconnection.community.board.domain.BoardCode; import com.example.solidconnection.community.comment.domain.Comment; import com.example.solidconnection.community.comment.dto.PostFindCommentResponse; -import com.example.solidconnection.community.post.dto.PostListResponse; import com.example.solidconnection.community.comment.repository.CommentRepository; -import com.example.solidconnection.community.post.domain.PostImage; import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.community.post.domain.PostCategory; +import com.example.solidconnection.community.post.domain.PostImage; import com.example.solidconnection.community.post.dto.PostFindPostImageResponse; import com.example.solidconnection.community.post.dto.PostFindResponse; -import com.example.solidconnection.community.post.repository.PostRepository; +import com.example.solidconnection.community.post.dto.PostListResponse; import com.example.solidconnection.community.post.repository.PostImageRepository; -import com.example.solidconnection.service.RedisService; +import com.example.solidconnection.community.post.repository.PostRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.integration.BaseIntegrationTest; -import com.example.solidconnection.type.BoardCode; -import com.example.solidconnection.type.PostCategory; import com.example.solidconnection.util.RedisUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; diff --git a/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java b/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java index 4f2ddf687..188f717ca 100644 --- a/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java +++ b/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java @@ -3,13 +3,13 @@ import com.example.solidconnection.community.board.domain.Board; import com.example.solidconnection.community.board.repository.BoardRepository; import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.community.post.domain.PostCategory; import com.example.solidconnection.community.post.repository.PostRepository; import com.example.solidconnection.community.post.service.PostLikeService; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.type.PostCategory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/example/solidconnection/concurrency/PostViewCountConcurrencyTest.java b/src/test/java/com/example/solidconnection/concurrency/PostViewCountConcurrencyTest.java index 6c1a0e18f..5f2b70d37 100644 --- a/src/test/java/com/example/solidconnection/concurrency/PostViewCountConcurrencyTest.java +++ b/src/test/java/com/example/solidconnection/concurrency/PostViewCountConcurrencyTest.java @@ -3,12 +3,12 @@ import com.example.solidconnection.community.board.domain.Board; import com.example.solidconnection.community.board.repository.BoardRepository; import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.community.post.domain.PostCategory; import com.example.solidconnection.community.post.repository.PostRepository; -import com.example.solidconnection.service.RedisService; +import com.example.solidconnection.community.post.service.RedisService; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.type.PostCategory; import com.example.solidconnection.util.RedisUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -21,7 +21,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import static com.example.solidconnection.type.RedisConstants.VALIDATE_VIEW_COUNT_TTL; +import static com.example.solidconnection.community.post.service.RedisConstants.VALIDATE_VIEW_COUNT_TTL; import static org.junit.jupiter.api.Assertions.assertEquals; @TestContainerSpringBootTest diff --git a/src/test/java/com/example/solidconnection/e2e/DynamicFixture.java b/src/test/java/com/example/solidconnection/e2e/DynamicFixture.java index 43af92ea5..4c90e58dc 100644 --- a/src/test/java/com/example/solidconnection/e2e/DynamicFixture.java +++ b/src/test/java/com/example/solidconnection/e2e/DynamicFixture.java @@ -1,8 +1,8 @@ package com.example.solidconnection.e2e; +import com.example.solidconnection.siteuser.domain.PreparationStatus; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; public class DynamicFixture { // todo: test fixture 개선 작업 이후, 이 클래스의 사용이 대체되면 삭제 필요 diff --git a/src/test/java/com/example/solidconnection/country/fixture/CountryFixture.java b/src/test/java/com/example/solidconnection/location/country/fixture/CountryFixture.java similarity index 87% rename from src/test/java/com/example/solidconnection/country/fixture/CountryFixture.java rename to src/test/java/com/example/solidconnection/location/country/fixture/CountryFixture.java index 53f394eae..cf3e2d6b1 100644 --- a/src/test/java/com/example/solidconnection/country/fixture/CountryFixture.java +++ b/src/test/java/com/example/solidconnection/location/country/fixture/CountryFixture.java @@ -1,7 +1,7 @@ -package com.example.solidconnection.country.fixture; +package com.example.solidconnection.location.country.fixture; -import com.example.solidconnection.entity.Country; -import com.example.solidconnection.region.fixture.RegionFixture; +import com.example.solidconnection.location.country.domain.Country; +import com.example.solidconnection.location.region.fixture.RegionFixture; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; diff --git a/src/test/java/com/example/solidconnection/country/fixture/CountryFixtureBuilder.java b/src/test/java/com/example/solidconnection/location/country/fixture/CountryFixtureBuilder.java similarity index 78% rename from src/test/java/com/example/solidconnection/country/fixture/CountryFixtureBuilder.java rename to src/test/java/com/example/solidconnection/location/country/fixture/CountryFixtureBuilder.java index e3ea004c5..85fdb466f 100644 --- a/src/test/java/com/example/solidconnection/country/fixture/CountryFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/location/country/fixture/CountryFixtureBuilder.java @@ -1,8 +1,8 @@ -package com.example.solidconnection.country.fixture; +package com.example.solidconnection.location.country.fixture; -import com.example.solidconnection.entity.Country; -import com.example.solidconnection.entity.Region; -import com.example.solidconnection.country.repository.CountryRepositoryForTest; +import com.example.solidconnection.location.country.domain.Country; +import com.example.solidconnection.location.country.repository.CountryRepositoryForTest; +import com.example.solidconnection.location.region.domain.Region; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; diff --git a/src/test/java/com/example/solidconnection/country/repository/CountryRepositoryForTest.java b/src/test/java/com/example/solidconnection/location/country/repository/CountryRepositoryForTest.java similarity index 62% rename from src/test/java/com/example/solidconnection/country/repository/CountryRepositoryForTest.java rename to src/test/java/com/example/solidconnection/location/country/repository/CountryRepositoryForTest.java index fc5dab0f9..c9c7400a9 100644 --- a/src/test/java/com/example/solidconnection/country/repository/CountryRepositoryForTest.java +++ b/src/test/java/com/example/solidconnection/location/country/repository/CountryRepositoryForTest.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.country.repository; +package com.example.solidconnection.location.country.repository; -import com.example.solidconnection.entity.Country; +import com.example.solidconnection.location.country.domain.Country; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; diff --git a/src/test/java/com/example/solidconnection/region/fixture/RegionFixture.java b/src/test/java/com/example/solidconnection/location/region/fixture/RegionFixture.java similarity index 86% rename from src/test/java/com/example/solidconnection/region/fixture/RegionFixture.java rename to src/test/java/com/example/solidconnection/location/region/fixture/RegionFixture.java index a1cd97e56..2cf13437e 100644 --- a/src/test/java/com/example/solidconnection/region/fixture/RegionFixture.java +++ b/src/test/java/com/example/solidconnection/location/region/fixture/RegionFixture.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.region.fixture; +package com.example.solidconnection.location.region.fixture; -import com.example.solidconnection.entity.Region; +import com.example.solidconnection.location.region.domain.Region; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; diff --git a/src/test/java/com/example/solidconnection/region/fixture/RegionFixtureBuilder.java b/src/test/java/com/example/solidconnection/location/region/fixture/RegionFixtureBuilder.java similarity index 79% rename from src/test/java/com/example/solidconnection/region/fixture/RegionFixtureBuilder.java rename to src/test/java/com/example/solidconnection/location/region/fixture/RegionFixtureBuilder.java index a385a53f8..968665e22 100644 --- a/src/test/java/com/example/solidconnection/region/fixture/RegionFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/location/region/fixture/RegionFixtureBuilder.java @@ -1,7 +1,7 @@ -package com.example.solidconnection.region.fixture; +package com.example.solidconnection.location.region.fixture; -import com.example.solidconnection.entity.Region; -import com.example.solidconnection.region.repository.RegionRepositoryForTest; +import com.example.solidconnection.location.region.domain.Region; +import com.example.solidconnection.location.region.repository.RegionRepositoryForTest; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; diff --git a/src/test/java/com/example/solidconnection/region/repository/RegionRepositoryForTest.java b/src/test/java/com/example/solidconnection/location/region/repository/RegionRepositoryForTest.java similarity index 62% rename from src/test/java/com/example/solidconnection/region/repository/RegionRepositoryForTest.java rename to src/test/java/com/example/solidconnection/location/region/repository/RegionRepositoryForTest.java index 00c35c02d..a27c16b9b 100644 --- a/src/test/java/com/example/solidconnection/region/repository/RegionRepositoryForTest.java +++ b/src/test/java/com/example/solidconnection/location/region/repository/RegionRepositoryForTest.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.region.repository; +package com.example.solidconnection.location.region.repository; -import com.example.solidconnection.entity.Region; +import com.example.solidconnection.location.region.domain.Region; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; diff --git a/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java b/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java index 27ed7a617..e03813607 100644 --- a/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java @@ -2,8 +2,10 @@ import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.s3.S3Service; -import com.example.solidconnection.s3.UploadedFileUrlResponse; +import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; +import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.dto.GpaScoreRequest; @@ -18,9 +20,7 @@ import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.integration.BaseIntegrationTest; -import com.example.solidconnection.type.ImgType; -import com.example.solidconnection.type.LanguageTestType; -import com.example.solidconnection.type.VerifyStatus; +import com.example.solidconnection.university.domain.LanguageTestType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/example/solidconnection/custom/security/aspect/AdminAuthorizationAspectTest.java b/src/test/java/com/example/solidconnection/security/aspect/AdminAuthorizationAspectTest.java similarity index 90% rename from src/test/java/com/example/solidconnection/custom/security/aspect/AdminAuthorizationAspectTest.java rename to src/test/java/com/example/solidconnection/security/aspect/AdminAuthorizationAspectTest.java index b733acceb..343bbbc30 100644 --- a/src/test/java/com/example/solidconnection/custom/security/aspect/AdminAuthorizationAspectTest.java +++ b/src/test/java/com/example/solidconnection/security/aspect/AdminAuthorizationAspectTest.java @@ -1,7 +1,7 @@ -package com.example.solidconnection.custom.security.aspect; +package com.example.solidconnection.security.aspect; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.custom.security.annotation.RequireAdminAccess; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.security.annotation.RequireAdminAccess; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; @@ -12,7 +12,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; -import static com.example.solidconnection.custom.exception.ErrorCode.ACCESS_DENIED; +import static com.example.solidconnection.common.exception.ErrorCode.ACCESS_DENIED; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; diff --git a/src/test/java/com/example/solidconnection/custom/security/authentication/SiteUserAuthenticationTest.java b/src/test/java/com/example/solidconnection/security/authentication/SiteUserAuthenticationTest.java similarity index 88% rename from src/test/java/com/example/solidconnection/custom/security/authentication/SiteUserAuthenticationTest.java rename to src/test/java/com/example/solidconnection/security/authentication/SiteUserAuthenticationTest.java index 6285727cb..8c3b9e216 100644 --- a/src/test/java/com/example/solidconnection/custom/security/authentication/SiteUserAuthenticationTest.java +++ b/src/test/java/com/example/solidconnection/security/authentication/SiteUserAuthenticationTest.java @@ -1,9 +1,9 @@ -package com.example.solidconnection.custom.security.authentication; +package com.example.solidconnection.security.authentication; -import com.example.solidconnection.custom.security.userdetails.SiteUserDetails; +import com.example.solidconnection.security.userdetails.SiteUserDetails; +import com.example.solidconnection.siteuser.domain.PreparationStatus; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilterTest.java b/src/test/java/com/example/solidconnection/security/filter/ExceptionHandlerFilterTest.java similarity index 93% rename from src/test/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilterTest.java rename to src/test/java/com/example/solidconnection/security/filter/ExceptionHandlerFilterTest.java index f1b3c7359..4fa01a234 100644 --- a/src/test/java/com/example/solidconnection/custom/security/filter/ExceptionHandlerFilterTest.java +++ b/src/test/java/com/example/solidconnection/security/filter/ExceptionHandlerFilterTest.java @@ -1,7 +1,7 @@ -package com.example.solidconnection.custom.security.filter; +package com.example.solidconnection.security.filter; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.custom.exception.ErrorCode; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.support.TestContainerSpringBootTest; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; @@ -13,7 +13,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; diff --git a/src/test/java/com/example/solidconnection/custom/security/filter/JwtAuthenticationFilterTest.java b/src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java similarity index 91% rename from src/test/java/com/example/solidconnection/custom/security/filter/JwtAuthenticationFilterTest.java rename to src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java index 61e4c9170..c66037c82 100644 --- a/src/test/java/com/example/solidconnection/custom/security/filter/JwtAuthenticationFilterTest.java +++ b/src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java @@ -1,8 +1,8 @@ -package com.example.solidconnection.custom.security.filter; +package com.example.solidconnection.security.filter; -import com.example.solidconnection.config.security.JwtProperties; -import com.example.solidconnection.custom.security.authentication.SiteUserAuthentication; -import com.example.solidconnection.custom.security.userdetails.SiteUserDetailsService; +import com.example.solidconnection.security.authentication.SiteUserAuthentication; +import com.example.solidconnection.security.config.JwtProperties; +import com.example.solidconnection.security.userdetails.SiteUserDetailsService; import com.example.solidconnection.support.TestContainerSpringBootTest; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; diff --git a/src/test/java/com/example/solidconnection/custom/security/filter/SignOutCheckFilterTest.java b/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java similarity index 85% rename from src/test/java/com/example/solidconnection/custom/security/filter/SignOutCheckFilterTest.java rename to src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java index e76a01c75..5ccc1aa1b 100644 --- a/src/test/java/com/example/solidconnection/custom/security/filter/SignOutCheckFilterTest.java +++ b/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java @@ -1,7 +1,7 @@ -package com.example.solidconnection.custom.security.filter; +package com.example.solidconnection.security.filter; -import com.example.solidconnection.config.security.JwtProperties; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.support.TestContainerSpringBootTest; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @@ -20,7 +20,7 @@ import java.util.Objects; import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST; -import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_SIGN_OUT; +import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_SIGN_OUT; import static org.assertj.core.api.Assertions.assertThatCode; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.spy; @@ -95,12 +95,12 @@ void setUp() { } private String createToken(String subject) { - return Jwts.builder() - .setSubject(subject) - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() + 1000)) - .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) - .compact(); + return Jwts.builder() + .setSubject(subject) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 1000)) + .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) + .compact(); } private HttpServletRequest createRequest(String token) { diff --git a/src/test/java/com/example/solidconnection/custom/security/provider/SiteUserAuthenticationProviderTest.java b/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java similarity index 91% rename from src/test/java/com/example/solidconnection/custom/security/provider/SiteUserAuthenticationProviderTest.java rename to src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java index b8999e3f5..9c51de838 100644 --- a/src/test/java/com/example/solidconnection/custom/security/provider/SiteUserAuthenticationProviderTest.java +++ b/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java @@ -1,9 +1,9 @@ -package com.example.solidconnection.custom.security.provider; +package com.example.solidconnection.security.provider; -import com.example.solidconnection.config.security.JwtProperties; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.custom.security.authentication.SiteUserAuthentication; -import com.example.solidconnection.custom.security.userdetails.SiteUserDetails; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.security.authentication.SiteUserAuthentication; +import com.example.solidconnection.security.config.JwtProperties; +import com.example.solidconnection.security.userdetails.SiteUserDetails; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; @@ -19,8 +19,8 @@ import java.net.PasswordAuthentication; import java.util.Date; -import static com.example.solidconnection.custom.exception.ErrorCode.AUTHENTICATION_FAILED; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_TOKEN; +import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; diff --git a/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsServiceTest.java b/src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsServiceTest.java similarity index 92% rename from src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsServiceTest.java rename to src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsServiceTest.java index 718732a44..c292203af 100644 --- a/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsServiceTest.java +++ b/src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsServiceTest.java @@ -1,6 +1,6 @@ -package com.example.solidconnection.custom.security.userdetails; +package com.example.solidconnection.security.userdetails; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.siteuser.repository.SiteUserRepository; @@ -12,8 +12,8 @@ import java.time.LocalDate; -import static com.example.solidconnection.custom.exception.ErrorCode.AUTHENTICATION_FAILED; -import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_TOKEN; +import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; diff --git a/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsTest.java b/src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsTest.java similarity index 94% rename from src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsTest.java rename to src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsTest.java index 6fe9b6d69..8f533e8b7 100644 --- a/src/test/java/com/example/solidconnection/custom/security/userdetails/SiteUserDetailsTest.java +++ b/src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsTest.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.custom.security.userdetails; +package com.example.solidconnection.security.userdetails; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; diff --git a/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixture.java b/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixture.java index 000b7bc48..664a727c1 100644 --- a/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixture.java +++ b/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixture.java @@ -1,8 +1,8 @@ package com.example.solidconnection.siteuser.fixture; import com.example.solidconnection.siteuser.domain.AuthType; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.type.Role; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; diff --git a/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixtureBuilder.java b/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixtureBuilder.java index f0d8bd406..46db8bae4 100644 --- a/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixtureBuilder.java @@ -1,10 +1,10 @@ package com.example.solidconnection.siteuser.fixture; import com.example.solidconnection.siteuser.domain.AuthType; +import com.example.solidconnection.siteuser.domain.PreparationStatus; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; import org.springframework.security.crypto.password.PasswordEncoder; diff --git a/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java b/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java index c3d9d240e..41806d6cf 100644 --- a/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java @@ -1,10 +1,10 @@ package com.example.solidconnection.siteuser.repository; import com.example.solidconnection.siteuser.domain.AuthType; +import com.example.solidconnection.siteuser.domain.PreparationStatus; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.support.TestContainerDataJpaTest; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java index 1c5680d33..1168a0eeb 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java @@ -1,9 +1,11 @@ package com.example.solidconnection.siteuser.service; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.s3.S3Service; -import com.example.solidconnection.s3.UploadedFileUrlResponse; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; +import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.AuthType; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.MyPageResponse; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; @@ -11,8 +13,6 @@ import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.type.ImgType; -import com.example.solidconnection.type.Role; import com.example.solidconnection.university.domain.LikedUniversity; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; @@ -28,8 +28,8 @@ import java.time.LocalDateTime; import java.util.List; -import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; -import static com.example.solidconnection.custom.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; +import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; +import static com.example.solidconnection.common.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; import static com.example.solidconnection.siteuser.service.MyPageService.MIN_DAYS_BETWEEN_NICKNAME_CHANGES; import static com.example.solidconnection.siteuser.service.MyPageService.NICKNAME_LAST_CHANGE_DATE_FORMAT; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/com/example/solidconnection/support/integration/BaseIntegrationTest.java b/src/test/java/com/example/solidconnection/support/integration/BaseIntegrationTest.java index 05b101985..ae449641f 100644 --- a/src/test/java/com/example/solidconnection/support/integration/BaseIntegrationTest.java +++ b/src/test/java/com/example/solidconnection/support/integration/BaseIntegrationTest.java @@ -3,31 +3,31 @@ import com.example.solidconnection.application.domain.Application; import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; +import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.application.repository.ApplicationRepository; import com.example.solidconnection.community.board.domain.Board; import com.example.solidconnection.community.board.repository.BoardRepository; -import com.example.solidconnection.entity.Country; -import com.example.solidconnection.community.post.domain.PostImage; -import com.example.solidconnection.entity.Region; import com.example.solidconnection.community.post.domain.Post; -import com.example.solidconnection.community.post.repository.PostRepository; -import com.example.solidconnection.repositories.CountryRepository; +import com.example.solidconnection.community.post.domain.PostCategory; +import com.example.solidconnection.community.post.domain.PostImage; import com.example.solidconnection.community.post.repository.PostImageRepository; -import com.example.solidconnection.repositories.RegionRepository; +import com.example.solidconnection.community.post.repository.PostRepository; +import com.example.solidconnection.location.country.domain.Country; +import com.example.solidconnection.location.country.repository.CountryRepository; +import com.example.solidconnection.location.region.domain.Region; +import com.example.solidconnection.location.region.repository.RegionRepository; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.repository.GpaScoreRepository; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; +import com.example.solidconnection.siteuser.domain.PreparationStatus; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.DatabaseClearExtension; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.type.LanguageTestType; -import com.example.solidconnection.type.PostCategory; -import com.example.solidconnection.type.PreparationStatus; -import com.example.solidconnection.type.Role; -import com.example.solidconnection.type.VerifyStatus; import com.example.solidconnection.university.domain.LanguageRequirement; +import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.repository.LanguageRequirementRepository; @@ -41,12 +41,12 @@ import java.util.HashSet; import java.util.List; -import static com.example.solidconnection.type.BoardCode.AMERICAS; -import static com.example.solidconnection.type.BoardCode.ASIA; -import static com.example.solidconnection.type.BoardCode.EUROPE; -import static com.example.solidconnection.type.BoardCode.FREE; -import static com.example.solidconnection.type.SemesterAvailableForDispatch.ONE_SEMESTER; -import static com.example.solidconnection.type.TuitionFeeType.HOME_UNIVERSITY_PAYMENT; +import static com.example.solidconnection.community.board.domain.BoardCode.AMERICAS; +import static com.example.solidconnection.community.board.domain.BoardCode.ASIA; +import static com.example.solidconnection.community.board.domain.BoardCode.EUROPE; +import static com.example.solidconnection.community.board.domain.BoardCode.FREE; +import static com.example.solidconnection.university.domain.SemesterAvailableForDispatch.ONE_SEMESTER; +import static com.example.solidconnection.university.domain.TuitionFeeType.HOME_UNIVERSITY_PAYMENT; @TestContainerSpringBootTest @ExtendWith(DatabaseClearExtension.class) diff --git a/src/test/java/com/example/solidconnection/custom/validation/validator/ValidUniversityChoiceValidatorTest.java b/src/test/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoiceValidatorTest.java similarity index 92% rename from src/test/java/com/example/solidconnection/custom/validation/validator/ValidUniversityChoiceValidatorTest.java rename to src/test/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoiceValidatorTest.java index b0267a08b..e08f49e6a 100644 --- a/src/test/java/com/example/solidconnection/custom/validation/validator/ValidUniversityChoiceValidatorTest.java +++ b/src/test/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoiceValidatorTest.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.custom.validation.validator; +package com.example.solidconnection.university.dto.validation; import com.example.solidconnection.application.dto.UniversityChoiceRequest; import jakarta.validation.ConstraintViolation; @@ -11,9 +11,9 @@ import java.util.Set; -import static com.example.solidconnection.custom.exception.ErrorCode.DUPLICATE_UNIVERSITY_CHOICE; -import static com.example.solidconnection.custom.exception.ErrorCode.FIRST_CHOICE_REQUIRED; -import static com.example.solidconnection.custom.exception.ErrorCode.THIRD_CHOICE_REQUIRES_SECOND; +import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_UNIVERSITY_CHOICE; +import static com.example.solidconnection.common.exception.ErrorCode.FIRST_CHOICE_REQUIRED; +import static com.example.solidconnection.common.exception.ErrorCode.THIRD_CHOICE_REQUIRES_SECOND; import static org.assertj.core.api.Assertions.assertThat; @DisplayName("대학 선택 유효성 검사 테스트") diff --git a/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixture.java b/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixture.java index ba32c1b27..c97a1427e 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixture.java +++ b/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixture.java @@ -1,7 +1,7 @@ package com.example.solidconnection.university.fixture; -import com.example.solidconnection.type.LanguageTestType; import com.example.solidconnection.university.domain.LanguageRequirement; +import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.UniversityInfoForApply; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; diff --git a/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixtureBuilder.java b/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixtureBuilder.java index e6f3fd4d5..f72e7caba 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixtureBuilder.java @@ -1,7 +1,7 @@ package com.example.solidconnection.university.fixture; -import com.example.solidconnection.type.LanguageTestType; import com.example.solidconnection.university.domain.LanguageRequirement; +import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.repository.LanguageRequirementRepository; import lombok.RequiredArgsConstructor; diff --git a/src/test/java/com/example/solidconnection/university/fixture/UniversityFixture.java b/src/test/java/com/example/solidconnection/university/fixture/UniversityFixture.java index f3a41515f..d1c9d2dd4 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/UniversityFixture.java +++ b/src/test/java/com/example/solidconnection/university/fixture/UniversityFixture.java @@ -1,7 +1,7 @@ package com.example.solidconnection.university.fixture; -import com.example.solidconnection.country.fixture.CountryFixture; -import com.example.solidconnection.region.fixture.RegionFixture; +import com.example.solidconnection.location.country.fixture.CountryFixture; +import com.example.solidconnection.location.region.fixture.RegionFixture; import com.example.solidconnection.university.domain.University; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; diff --git a/src/test/java/com/example/solidconnection/university/fixture/UniversityFixtureBuilder.java b/src/test/java/com/example/solidconnection/university/fixture/UniversityFixtureBuilder.java index f51ea9677..4da6cdfd7 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/UniversityFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/university/fixture/UniversityFixtureBuilder.java @@ -1,7 +1,7 @@ package com.example.solidconnection.university.fixture; -import com.example.solidconnection.entity.Country; -import com.example.solidconnection.entity.Region; +import com.example.solidconnection.location.country.domain.Country; +import com.example.solidconnection.location.region.domain.Region; import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.repository.UniversityRepository; import lombok.RequiredArgsConstructor; diff --git a/src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixtureBuilder.java b/src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixtureBuilder.java index 8041bda1e..260a037d9 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixtureBuilder.java @@ -8,8 +8,8 @@ import java.util.HashSet; -import static com.example.solidconnection.type.SemesterAvailableForDispatch.ONE_SEMESTER; -import static com.example.solidconnection.type.TuitionFeeType.HOME_UNIVERSITY_PAYMENT; +import static com.example.solidconnection.university.domain.SemesterAvailableForDispatch.ONE_SEMESTER; +import static com.example.solidconnection.university.domain.TuitionFeeType.HOME_UNIVERSITY_PAYMENT; @TestComponent @RequiredArgsConstructor diff --git a/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java index 7f970108a..9ba0df2b1 100644 --- a/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.university.service; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; @@ -16,9 +16,9 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import static com.example.solidconnection.custom.exception.ErrorCode.ALREADY_LIKED_UNIVERSITY; -import static com.example.solidconnection.custom.exception.ErrorCode.NOT_LIKED_UNIVERSITY; -import static com.example.solidconnection.custom.exception.ErrorCode.UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_UNIVERSITY; +import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_UNIVERSITY; +import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND; import static com.example.solidconnection.university.service.UniversityLikeService.LIKE_CANCELED_MESSAGE; import static com.example.solidconnection.university.service.UniversityLikeService.LIKE_SUCCESS_MESSAGE; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/test/java/com/example/solidconnection/university/service/UniversityQueryServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UniversityQueryServiceTest.java index 731ccec5b..d83e77b1d 100644 --- a/src/test/java/com/example/solidconnection/university/service/UniversityQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UniversityQueryServiceTest.java @@ -1,14 +1,14 @@ package com.example.solidconnection.university.service; -import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.university.fixture.LanguageRequirementFixture; -import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; -import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.dto.UniversityDetailResponse; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponses; +import com.example.solidconnection.university.fixture.LanguageRequirementFixture; +import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; import com.example.solidconnection.university.repository.custom.UniversityFilterRepository; import org.junit.jupiter.api.DisplayName; @@ -18,7 +18,7 @@ import java.util.List; -import static com.example.solidconnection.custom.exception.ErrorCode.UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; import static org.mockito.BDDMockito.then; diff --git a/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java index ffacab33f..ff274b96a 100644 --- a/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java @@ -1,11 +1,11 @@ package com.example.solidconnection.university.service; -import com.example.solidconnection.country.fixture.CountryFixture; -import com.example.solidconnection.entity.InterestedCountry; -import com.example.solidconnection.entity.InterestedRegion; -import com.example.solidconnection.region.fixture.RegionFixture; -import com.example.solidconnection.repositories.InterestedCountyRepository; -import com.example.solidconnection.repositories.InterestedRegionRepository; +import com.example.solidconnection.location.country.domain.InterestedCountry; +import com.example.solidconnection.location.country.fixture.CountryFixture; +import com.example.solidconnection.location.country.repository.InterestedCountyRepository; +import com.example.solidconnection.location.region.domain.InterestedRegion; +import com.example.solidconnection.location.region.fixture.RegionFixture; +import com.example.solidconnection.location.region.repository.InterestedRegionRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; diff --git a/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java b/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java index 0c16de671..c57e85193 100644 --- a/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java +++ b/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.util; -import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.custom.exception.ErrorCode; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.junit.jupiter.api.DisplayName; From f0c83095c2518c95bfd3d204f6c519ca36ff62a7 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Wed, 21 May 2025 15:01:34 +0900 Subject: [PATCH 17/90] =?UTF-8?q?chore:=20code=20owners=EC=97=90=20?= =?UTF-8?q?=EC=8B=A0=EA=B7=9C=20=EA=B0=9C=EB=B0=9C=EC=9E=90=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#328)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 201ad5570..e0d720750 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @Gyuhyeok99 @nayonsoso @wibaek +* @Gyuhyeok99 @nayonsoso @wibaek @whqtker @lsy1307 From 7c1333802ad1667f79da398c40fcb8d65c265770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Wed, 21 May 2025 15:34:56 +0900 Subject: [PATCH 18/90] =?UTF-8?q?refactor:=20=EC=84=B1=EC=A0=81=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20fixture=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#325)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: GpaScoreFixture에 GPA점수 생성 메서드 추가 * refactor: GPA점수 데이터 Fixture 메서드로 변경 * fix: 양방향 연관관계 추가 * refacotr: GPA_점수로 함수명 변경 * feat: LanguageTestScoreFixture에 어학점수 생성 메서드 추가 * refactor: 어학점수 데이터 Fixture 메서드로 변경 * test: score 관련 테스트에서 상세 필드 검증 제거 * test: 어학 점수 fixture 어학 타입 및 점수 파라미터 제거 * test: GPA 점수 fixture GPA 타입 및 점수 파라미터 제거 * refactor: 변수명 순서 오름차순으로 변경 * refactor: 점수 목록 조회 검증 시 닉네임과 상태 추가 --- .../service/AdminGpaScoreServiceTest.java | 75 +++++------------ .../AdminLanguageTestScoreServiceTest.java | 84 +++++-------------- .../score/fixture/GpaScoreFixture.java | 23 +++++ .../score/fixture/GpaScoreFixtureBuilder.java | 46 ++++++++++ .../fixture/LanguageTestScoreFixture.java | 26 ++++++ .../LanguageTestScoreFixtureBuilder.java | 46 ++++++++++ .../score/service/ScoreServiceTest.java | 81 +++++------------- 7 files changed, 202 insertions(+), 179 deletions(-) create mode 100644 src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixture.java create mode 100644 src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixture.java create mode 100644 src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixtureBuilder.java diff --git a/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java b/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java index a891759cd..c649e1ba9 100644 --- a/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java @@ -4,14 +4,13 @@ import com.example.solidconnection.admin.dto.GpaScoreSearchResponse; import com.example.solidconnection.admin.dto.GpaScoreUpdateRequest; import com.example.solidconnection.admin.dto.ScoreSearchCondition; -import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.GpaScore; -import com.example.solidconnection.score.repository.GpaScoreRepository; +import com.example.solidconnection.score.fixture.GpaScoreFixture; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; -import com.example.solidconnection.support.integration.BaseIntegrationTest; +import com.example.solidconnection.support.TestContainerSpringBootTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -29,17 +28,18 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; +@TestContainerSpringBootTest @DisplayName("학점 검증 관리자 서비스 테스트") -class AdminGpaScoreServiceTest extends BaseIntegrationTest { +class AdminGpaScoreServiceTest { @Autowired private AdminGpaScoreService adminGpaScoreService; @Autowired - private GpaScoreRepository gpaScoreRepository; + private SiteUserFixture siteUserFixture; @Autowired - private SiteUserFixture siteUserFixture; + private GpaScoreFixture gpaScoreFixture; private GpaScore gpaScore1; private GpaScore gpaScore2; @@ -50,9 +50,9 @@ void setUp() { SiteUser user1 = siteUserFixture.사용자(1, "test1"); SiteUser user2 = siteUserFixture.사용자(2, "test2"); SiteUser user3 = siteUserFixture.사용자(3, "test3"); - gpaScore3 = createGpaScore(user3, VerifyStatus.REJECTED); - gpaScore2 = createGpaScore(user2, VerifyStatus.PENDING); - gpaScore1 = createGpaScore(user1, VerifyStatus.PENDING); + gpaScore1 = gpaScoreFixture.GPA_점수(VerifyStatus.PENDING, user1); + gpaScore2 = gpaScoreFixture.GPA_점수(VerifyStatus.PENDING, user2); + gpaScore3 = gpaScoreFixture.GPA_점수(VerifyStatus.REJECTED, user3); } @Nested @@ -69,19 +69,10 @@ class 지원한_GPA_목록_조회 { Page response = adminGpaScoreService.searchGpaScores(condition, pageable); // then + assertThat(response.getContent()).hasSize(expectedGpaScores.size()); assertThat(response.getContent()) - .hasSize(expectedGpaScores.size()) - .zipSatisfy(expectedGpaScores, (actual, expected) -> assertAll( - () -> assertThat(actual.gpaScoreStatusResponse().id()).isEqualTo(expected.getId()), - () -> assertThat(actual.gpaScoreStatusResponse().gpaResponse().gpa()).isEqualTo(expected.getGpa().getGpa()), - () -> assertThat(actual.gpaScoreStatusResponse().gpaResponse().gpaCriteria()).isEqualTo(expected.getGpa().getGpaCriteria()), - () -> assertThat(actual.gpaScoreStatusResponse().gpaResponse().gpaReportUrl()).isEqualTo(expected.getGpa().getGpaReportUrl()), - () -> assertThat(actual.gpaScoreStatusResponse().verifyStatus()).isEqualTo(expected.getVerifyStatus()), - - () -> assertThat(actual.siteUserResponse().id()).isEqualTo(expected.getSiteUser().getId()), - () -> assertThat(actual.siteUserResponse().profileImageUrl()).isEqualTo(expected.getSiteUser().getProfileImageUrl()), - () -> assertThat(actual.siteUserResponse().nickname()).isEqualTo(expected.getSiteUser().getNickname()) - )); + .extracting(content -> content.gpaScoreStatusResponse().verifyStatus()) + .containsOnly(VerifyStatus.PENDING); } @Test @@ -95,19 +86,10 @@ class 지원한_GPA_목록_조회 { Page response = adminGpaScoreService.searchGpaScores(condition, pageable); // then + assertThat(response.getContent()).hasSize(expectedGpaScores.size()); assertThat(response.getContent()) - .hasSize(expectedGpaScores.size()) - .zipSatisfy(expectedGpaScores, (actual, expected) -> assertAll( - () -> assertThat(actual.gpaScoreStatusResponse().id()).isEqualTo(expected.getId()), - () -> assertThat(actual.gpaScoreStatusResponse().gpaResponse().gpa()).isEqualTo(expected.getGpa().getGpa()), - () -> assertThat(actual.gpaScoreStatusResponse().gpaResponse().gpaCriteria()).isEqualTo(expected.getGpa().getGpaCriteria()), - () -> assertThat(actual.gpaScoreStatusResponse().gpaResponse().gpaReportUrl()).isEqualTo(expected.getGpa().getGpaReportUrl()), - () -> assertThat(actual.gpaScoreStatusResponse().verifyStatus()).isEqualTo(expected.getVerifyStatus()), - - () -> assertThat(actual.siteUserResponse().id()).isEqualTo(expected.getSiteUser().getId()), - () -> assertThat(actual.siteUserResponse().profileImageUrl()).isEqualTo(expected.getSiteUser().getProfileImageUrl()), - () -> assertThat(actual.siteUserResponse().nickname()).isEqualTo(expected.getSiteUser().getNickname()) - )); + .extracting(content -> content.siteUserResponse().nickname()) + .containsOnly("test1", "test2", "test3"); } @Test @@ -121,19 +103,13 @@ class 지원한_GPA_목록_조회 { Page response = adminGpaScoreService.searchGpaScores(condition, pageable); // then + assertThat(response.getContent()).hasSize(expectedGpaScores.size()); + assertThat(response.getContent()) + .extracting(content -> content.gpaScoreStatusResponse().verifyStatus()) + .containsOnly(VerifyStatus.PENDING); assertThat(response.getContent()) - .hasSize(expectedGpaScores.size()) - .zipSatisfy(expectedGpaScores, (actual, expected) -> assertAll( - () -> assertThat(actual.gpaScoreStatusResponse().id()).isEqualTo(expected.getId()), - () -> assertThat(actual.gpaScoreStatusResponse().gpaResponse().gpa()).isEqualTo(expected.getGpa().getGpa()), - () -> assertThat(actual.gpaScoreStatusResponse().gpaResponse().gpaCriteria()).isEqualTo(expected.getGpa().getGpaCriteria()), - () -> assertThat(actual.gpaScoreStatusResponse().gpaResponse().gpaReportUrl()).isEqualTo(expected.getGpa().getGpaReportUrl()), - () -> assertThat(actual.gpaScoreStatusResponse().verifyStatus()).isEqualTo(expected.getVerifyStatus()), - - () -> assertThat(actual.siteUserResponse().id()).isEqualTo(expected.getSiteUser().getId()), - () -> assertThat(actual.siteUserResponse().profileImageUrl()).isEqualTo(expected.getSiteUser().getProfileImageUrl()), - () -> assertThat(actual.siteUserResponse().nickname()).isEqualTo(expected.getSiteUser().getNickname()) - )); + .extracting(content -> content.siteUserResponse().nickname()) + .containsOnly("test1"); } } @@ -203,13 +179,4 @@ class GPA_점수_검증_및_수정 { .hasMessage(GPA_SCORE_NOT_FOUND.getMessage()); } } - - private GpaScore createGpaScore(SiteUser siteUser, VerifyStatus status) { - GpaScore gpaScore = new GpaScore( - new Gpa(4.0, 4.5, "/gpa-report.pdf"), - siteUser - ); - gpaScore.setVerifyStatus(status); - return gpaScoreRepository.save(gpaScore); - } } diff --git a/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java b/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java index 3fe3eb5a8..669c0b0be 100644 --- a/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java @@ -4,14 +4,13 @@ import com.example.solidconnection.admin.dto.LanguageTestScoreSearchResponse; import com.example.solidconnection.admin.dto.LanguageTestScoreUpdateRequest; import com.example.solidconnection.admin.dto.ScoreSearchCondition; -import com.example.solidconnection.application.domain.LanguageTest; import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.LanguageTestScore; -import com.example.solidconnection.score.repository.LanguageTestScoreRepository; +import com.example.solidconnection.score.fixture.LanguageTestScoreFixture; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; -import com.example.solidconnection.support.integration.BaseIntegrationTest; +import com.example.solidconnection.support.TestContainerSpringBootTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -30,17 +29,18 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; +@TestContainerSpringBootTest @DisplayName("어학 검증 관리자 서비스 테스트") -class AdminLanguageTestScoreServiceTest extends BaseIntegrationTest { +class AdminLanguageTestScoreServiceTest { @Autowired private AdminLanguageTestScoreService adminLanguageTestScoreService; @Autowired - private LanguageTestScoreRepository languageTestScoreRepository; + private SiteUserFixture siteUserFixture; @Autowired - private SiteUserFixture siteUserFixture; + private LanguageTestScoreFixture languageTestScoreFixture; private LanguageTestScore languageTestScore1; private LanguageTestScore languageTestScore2; @@ -51,9 +51,9 @@ void setUp() { SiteUser user1 = siteUserFixture.사용자(1, "test1"); SiteUser user2 = siteUserFixture.사용자(2, "test2"); SiteUser user3 = siteUserFixture.사용자(3, "test3"); - languageTestScore3 = createLanguageTestScore(user3, VerifyStatus.REJECTED); - languageTestScore2 = createLanguageTestScore(user2, VerifyStatus.PENDING); - languageTestScore1 = createLanguageTestScore(user1, VerifyStatus.PENDING); + languageTestScore1 = languageTestScoreFixture.어학_점수(VerifyStatus.PENDING, user1); + languageTestScore2 = languageTestScoreFixture.어학_점수(VerifyStatus.PENDING, user2); + languageTestScore3 = languageTestScoreFixture.어학_점수(VerifyStatus.REJECTED, user3); } @Nested @@ -70,22 +70,10 @@ class 지원한_어학_목록_조회 { Page response = adminLanguageTestScoreService.searchLanguageTestScores(condition, pageable); // then + assertThat(response.getContent()).hasSize(expectedLanguageTestScores.size()); assertThat(response.getContent()) - .hasSize(expectedLanguageTestScores.size()) - .zipSatisfy(expectedLanguageTestScores, (actual, expected) -> assertAll( - () -> assertThat(actual.languageTestScoreStatusResponse().id()).isEqualTo(expected.getId()), - () -> assertThat(actual.languageTestScoreStatusResponse().languageTestResponse().languageTestType()) - .isEqualTo(expected.getLanguageTest().getLanguageTestType()), - () -> assertThat(actual.languageTestScoreStatusResponse().languageTestResponse().languageTestScore()) - .isEqualTo(expected.getLanguageTest().getLanguageTestScore()), - () -> assertThat(actual.languageTestScoreStatusResponse().languageTestResponse().languageTestReportUrl()) - .isEqualTo(expected.getLanguageTest().getLanguageTestReportUrl()), - () -> assertThat(actual.languageTestScoreStatusResponse().verifyStatus()).isEqualTo(expected.getVerifyStatus()), - - () -> assertThat(actual.siteUserResponse().id()).isEqualTo(expected.getSiteUser().getId()), - () -> assertThat(actual.siteUserResponse().profileImageUrl()).isEqualTo(expected.getSiteUser().getProfileImageUrl()), - () -> assertThat(actual.siteUserResponse().nickname()).isEqualTo(expected.getSiteUser().getNickname()) - )); + .extracting(content -> content.languageTestScoreStatusResponse().verifyStatus()) + .containsOnly(VerifyStatus.PENDING); } @Test @@ -99,22 +87,10 @@ class 지원한_어학_목록_조회 { Page response = adminLanguageTestScoreService.searchLanguageTestScores(condition, pageable); // then + assertThat(response.getContent()).hasSize(expectedLanguageTestScores.size()); assertThat(response.getContent()) - .hasSize(expectedLanguageTestScores.size()) - .zipSatisfy(expectedLanguageTestScores, (actual, expected) -> assertAll( - () -> assertThat(actual.languageTestScoreStatusResponse().id()).isEqualTo(expected.getId()), - () -> assertThat(actual.languageTestScoreStatusResponse().languageTestResponse().languageTestType()) - .isEqualTo(expected.getLanguageTest().getLanguageTestType()), - () -> assertThat(actual.languageTestScoreStatusResponse().languageTestResponse().languageTestScore()) - .isEqualTo(expected.getLanguageTest().getLanguageTestScore()), - () -> assertThat(actual.languageTestScoreStatusResponse().languageTestResponse().languageTestReportUrl()) - .isEqualTo(expected.getLanguageTest().getLanguageTestReportUrl()), - () -> assertThat(actual.languageTestScoreStatusResponse().verifyStatus()).isEqualTo(expected.getVerifyStatus()), - - () -> assertThat(actual.siteUserResponse().id()).isEqualTo(expected.getSiteUser().getId()), - () -> assertThat(actual.siteUserResponse().profileImageUrl()).isEqualTo(expected.getSiteUser().getProfileImageUrl()), - () -> assertThat(actual.siteUserResponse().nickname()).isEqualTo(expected.getSiteUser().getNickname()) - )); + .extracting(content -> content.siteUserResponse().nickname()) + .containsOnly("test1", "test2", "test3"); } @Test @@ -128,22 +104,13 @@ class 지원한_어학_목록_조회 { Page response = adminLanguageTestScoreService.searchLanguageTestScores(condition, pageable); // then + assertThat(response.getContent()).hasSize(expectedLanguageTestScores.size()); + assertThat(response.getContent()) + .extracting(content -> content.languageTestScoreStatusResponse().verifyStatus()) + .containsOnly(VerifyStatus.PENDING); assertThat(response.getContent()) - .hasSize(expectedLanguageTestScores.size()) - .zipSatisfy(expectedLanguageTestScores, (actual, expected) -> assertAll( - () -> assertThat(actual.languageTestScoreStatusResponse().id()).isEqualTo(expected.getId()), - () -> assertThat(actual.languageTestScoreStatusResponse().languageTestResponse().languageTestType()) - .isEqualTo(expected.getLanguageTest().getLanguageTestType()), - () -> assertThat(actual.languageTestScoreStatusResponse().languageTestResponse().languageTestScore()) - .isEqualTo(expected.getLanguageTest().getLanguageTestScore()), - () -> assertThat(actual.languageTestScoreStatusResponse().languageTestResponse().languageTestReportUrl()) - .isEqualTo(expected.getLanguageTest().getLanguageTestReportUrl()), - () -> assertThat(actual.languageTestScoreStatusResponse().verifyStatus()).isEqualTo(expected.getVerifyStatus()), - - () -> assertThat(actual.siteUserResponse().id()).isEqualTo(expected.getSiteUser().getId()), - () -> assertThat(actual.siteUserResponse().profileImageUrl()).isEqualTo(expected.getSiteUser().getProfileImageUrl()), - () -> assertThat(actual.siteUserResponse().nickname()).isEqualTo(expected.getSiteUser().getNickname()) - )); + .extracting(content -> content.siteUserResponse().nickname()) + .containsOnly("test1"); } } @@ -213,13 +180,4 @@ class 어학점수_검증_및_수정 { .hasMessage(LANGUAGE_TEST_SCORE_NOT_FOUND.getMessage()); } } - - private LanguageTestScore createLanguageTestScore(SiteUser siteUser, VerifyStatus status) { - LanguageTestScore languageTestScore = new LanguageTestScore( - new LanguageTest(TOEIC, "500", "/toeic-report.pdf"), - siteUser - ); - languageTestScore.setVerifyStatus(status); - return languageTestScoreRepository.save(languageTestScore); - } } diff --git a/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixture.java b/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixture.java new file mode 100644 index 000000000..4a61d1557 --- /dev/null +++ b/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixture.java @@ -0,0 +1,23 @@ +package com.example.solidconnection.score.fixture; + +import com.example.solidconnection.application.domain.Gpa; +import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.score.domain.GpaScore; +import com.example.solidconnection.siteuser.domain.SiteUser; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class GpaScoreFixture { + + private final GpaScoreFixtureBuilder gpaScoreFixtureBuilder; + + public GpaScore GPA_점수 (VerifyStatus verifyStatus, SiteUser siteUser) { + return gpaScoreFixtureBuilder.gpaScore() + .gpa(new Gpa(4.0, 4.5, "/gpa-report.pdf")) + .verifyStatus(verifyStatus) + .siteUser(siteUser) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixtureBuilder.java b/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixtureBuilder.java new file mode 100644 index 000000000..7943008b4 --- /dev/null +++ b/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixtureBuilder.java @@ -0,0 +1,46 @@ +package com.example.solidconnection.score.fixture; + +import com.example.solidconnection.application.domain.Gpa; +import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.score.domain.GpaScore; +import com.example.solidconnection.score.repository.GpaScoreRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class GpaScoreFixtureBuilder { + + private final GpaScoreRepository gpaScoreRepository; + + private Gpa gpa; + private VerifyStatus verifyStatus; + private SiteUser siteUser; + + public GpaScoreFixtureBuilder gpaScore() { + return new GpaScoreFixtureBuilder(gpaScoreRepository); + } + + public GpaScoreFixtureBuilder gpa(Gpa gpa) { + this.gpa = gpa; + return this; + } + + public GpaScoreFixtureBuilder verifyStatus(VerifyStatus verifyStatus) { + this.verifyStatus = verifyStatus; + return this; + } + + public GpaScoreFixtureBuilder siteUser(SiteUser siteUser) { + this.siteUser = siteUser; + return this; + } + + public GpaScore create() { + GpaScore gpaScore = new GpaScore(gpa, siteUser); + gpaScore.setSiteUser(siteUser); + gpaScore.setVerifyStatus(verifyStatus); + return gpaScoreRepository.save(gpaScore); + } +} diff --git a/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixture.java b/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixture.java new file mode 100644 index 000000000..7805692e0 --- /dev/null +++ b/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixture.java @@ -0,0 +1,26 @@ +package com.example.solidconnection.score.fixture; + +import com.example.solidconnection.application.domain.LanguageTest; +import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.score.domain.LanguageTestScore; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.university.domain.LanguageTestType; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +import static com.example.solidconnection.university.domain.LanguageTestType.TOEIC; + +@TestComponent +@RequiredArgsConstructor +public class LanguageTestScoreFixture { + + private final LanguageTestScoreFixtureBuilder languageTestScoreFixtureBuilder; + + public LanguageTestScore 어학_점수 (VerifyStatus verifyStatus, SiteUser siteUser) { + return languageTestScoreFixtureBuilder.languageTestScore() + .languageTest(new LanguageTest(TOEIC, "500", "/language-report.pdf")) + .verifyStatus(verifyStatus) + .siteUser(siteUser) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixtureBuilder.java b/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixtureBuilder.java new file mode 100644 index 000000000..fd5a8d417 --- /dev/null +++ b/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixtureBuilder.java @@ -0,0 +1,46 @@ +package com.example.solidconnection.score.fixture; + +import com.example.solidconnection.application.domain.LanguageTest; +import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.score.domain.LanguageTestScore; +import com.example.solidconnection.score.repository.LanguageTestScoreRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class LanguageTestScoreFixtureBuilder { + + private final LanguageTestScoreRepository languageTestScoreRepository; + + private LanguageTest languageTest; + private VerifyStatus verifyStatus; + private SiteUser siteUser; + + public LanguageTestScoreFixtureBuilder languageTestScore() { + return new LanguageTestScoreFixtureBuilder(languageTestScoreRepository); + } + + public LanguageTestScoreFixtureBuilder languageTest(LanguageTest languageTest) { + this.languageTest = languageTest; + return this; + } + + public LanguageTestScoreFixtureBuilder verifyStatus(VerifyStatus verifyStatus) { + this.verifyStatus = verifyStatus; + return this; + } + + public LanguageTestScoreFixtureBuilder siteUser(SiteUser siteUser) { + this.siteUser = siteUser; + return this; + } + + public LanguageTestScore create() { + LanguageTestScore languageTestScore = new LanguageTestScore(languageTest, siteUser); + languageTestScore.setSiteUser(siteUser); + languageTestScore.setVerifyStatus(verifyStatus); + return languageTestScoreRepository.save(languageTestScore); + } +} diff --git a/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java b/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java index e03813607..0b1aa0718 100644 --- a/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java @@ -1,7 +1,5 @@ package com.example.solidconnection.score.service; -import com.example.solidconnection.application.domain.Gpa; -import com.example.solidconnection.application.domain.LanguageTest; import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.s3.domain.ImgType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; @@ -9,17 +7,16 @@ import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.dto.GpaScoreRequest; -import com.example.solidconnection.score.dto.GpaScoreStatusResponse; import com.example.solidconnection.score.dto.GpaScoreStatusesResponse; import com.example.solidconnection.score.dto.LanguageTestScoreRequest; -import com.example.solidconnection.score.dto.LanguageTestScoreStatusResponse; import com.example.solidconnection.score.dto.LanguageTestScoreStatusesResponse; +import com.example.solidconnection.score.fixture.GpaScoreFixture; +import com.example.solidconnection.score.fixture.LanguageTestScoreFixture; import com.example.solidconnection.score.repository.GpaScoreRepository; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.support.integration.BaseIntegrationTest; +import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.university.domain.LanguageTestType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -31,11 +28,11 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.BDDMockito.given; +@TestContainerSpringBootTest @DisplayName("점수 서비스 테스트") -class ScoreServiceTest extends BaseIntegrationTest { +class ScoreServiceTest { @Autowired private ScoreService scoreService; @@ -43,9 +40,6 @@ class ScoreServiceTest extends BaseIntegrationTest { @Autowired private GpaScoreRepository gpaScoreRepository; - @Autowired - private SiteUserRepository siteUserRepository; - @Autowired private LanguageTestScoreRepository languageTestScoreRepository; @@ -55,6 +49,12 @@ class ScoreServiceTest extends BaseIntegrationTest { @Autowired private SiteUserFixture siteUserFixture; + @Autowired + private GpaScoreFixture gpaScoreFixture; + + @Autowired + private LanguageTestScoreFixture languageTestScoreFixture; + private SiteUser user; @BeforeEach @@ -66,21 +66,15 @@ void setUp() { void GPA_점수_상태를_조회한다() { // given List scores = List.of( - createGpaScore(user, 3.5, 4.5), - createGpaScore(user, 3.8, 4.5) + gpaScoreFixture.GPA_점수(VerifyStatus.PENDING, user), + gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user) ); // when GpaScoreStatusesResponse response = scoreService.getGpaScoreStatus(user); // then - assertThat(response.gpaScoreStatusResponseList()) - .hasSize(scores.size()) - .containsExactlyInAnyOrder( - scores.stream() - .map(GpaScoreStatusResponse::from) - .toArray(GpaScoreStatusResponse[]::new) - ); + assertThat(response.gpaScoreStatusResponseList()).hasSize(scores.size()); } @Test @@ -96,22 +90,15 @@ void setUp() { void 어학_시험_점수_상태를_조회한다() { // given List scores = List.of( - createLanguageTestScore(user, LanguageTestType.TOEIC, "100"), - createLanguageTestScore(user, LanguageTestType.TOEFL_IBT, "7.5") + languageTestScoreFixture.어학_점수(VerifyStatus.PENDING, user), + languageTestScoreFixture.어학_점수(VerifyStatus.PENDING, user) ); - siteUserRepository.save(user); // when LanguageTestScoreStatusesResponse response = scoreService.getLanguageTestScoreStatus(user); // then - assertThat(response.languageTestScoreStatusResponseList()) - .hasSize(scores.size()) - .containsExactlyInAnyOrder( - scores.stream() - .map(LanguageTestScoreStatusResponse::from) - .toArray(LanguageTestScoreStatusResponse[]::new) - ); + assertThat(response.languageTestScoreStatusResponseList()).hasSize(scores.size()); } @Test @@ -136,13 +123,7 @@ void setUp() { GpaScore savedScore = gpaScoreRepository.findById(scoreId).orElseThrow(); // then - assertAll( - () -> assertThat(savedScore.getId()).isEqualTo(scoreId), - () -> assertThat(savedScore.getGpa().getGpa()).isEqualTo(request.gpa()), - () -> assertThat(savedScore.getGpa().getGpaCriteria()).isEqualTo(request.gpaCriteria()), - () -> assertThat(savedScore.getVerifyStatus()).isEqualTo(VerifyStatus.PENDING), - () -> assertThat(savedScore.getGpa().getGpaReportUrl()).isEqualTo(fileUrl) - ); + assertThat(savedScore.getId()).isEqualTo(scoreId); } @Test @@ -158,31 +139,7 @@ void setUp() { LanguageTestScore savedScore = languageTestScoreRepository.findById(scoreId).orElseThrow(); // then - assertAll( - () -> assertThat(savedScore.getId()).isEqualTo(scoreId), - () -> assertThat(savedScore.getLanguageTest().getLanguageTestType()).isEqualTo(request.languageTestType()), - () -> assertThat(savedScore.getLanguageTest().getLanguageTestScore()).isEqualTo(request.languageTestScore()), - () -> assertThat(savedScore.getVerifyStatus()).isEqualTo(VerifyStatus.PENDING), - () -> assertThat(savedScore.getLanguageTest().getLanguageTestReportUrl()).isEqualTo(fileUrl) - ); - } - - private GpaScore createGpaScore(SiteUser siteUser, double gpa, double gpaCriteria) { - GpaScore gpaScore = new GpaScore( - new Gpa(gpa, gpaCriteria, "/gpa-report.pdf"), - siteUser - ); - gpaScore.setSiteUser(siteUser); - return gpaScoreRepository.save(gpaScore); - } - - private LanguageTestScore createLanguageTestScore(SiteUser siteUser, LanguageTestType languageTestType, String score) { - LanguageTestScore languageTestScore = new LanguageTestScore( - new LanguageTest(languageTestType, score, "/gpa-report.pdf"), - siteUser - ); - languageTestScore.setSiteUser(siteUser); - return languageTestScoreRepository.save(languageTestScore); + assertThat(savedScore.getId()).isEqualTo(scoreId); } private GpaScoreRequest createGpaScoreRequest() { From ab9092b65f9c6e8ae34eac60541b2e0da45a86ea Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Thu, 22 May 2025 20:37:34 +0900 Subject: [PATCH 19/90] =?UTF-8?q?refactor:=20Jwt=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=A5=BC=20?= =?UTF-8?q?=EC=83=81=EC=86=8D=EC=9D=B4=20=EC=95=84=EB=8B=88=EB=9D=BC=20?= =?UTF-8?q?=EC=A1=B0=ED=95=A9=ED=95=98=EB=8F=84=EB=A1=9D=20(#324)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 스프링 빈으로 활용하기 위해 private 생성자 제거 * refactor: 헤더에서 토큰 추출하는 코드 클래스로 분리 - 추가로, null 이 아니라 Optional 을 반환하여 빈 값 처리를 강제하도록 수정 * refactor: 토큰의 블랙리스트를 관리하는 코드 분리 * refactor: 토큰 제공과 관련된 코드 TokenProvider로 이동 - JwtUtils를 사용하던 곳에서 TokenProvider를 주입받아 사용하게 하기 위한 사전 작업 * refactor: TokenProvider를 abstract 클래스가 아니라 컴포넌트로 * refactor: TokenProvider를 상속하여 쓰던 곳에서 주입받아 쓰도록 * refactor: JwtUtils를 쓰던 곳에서 TokenProvider 쓰도록 * refactor: TokenProvider의 함수 인자 수정 - AS-IS: JwtUtils에 정의된 xxxParse 함수를 사용하기 위해서는, 함수 자체가 static이라서 호출하는 곳으로부터 jwt 시크릿 키를 건네받아야했다. 이를 위해 여러곳에서 JwtProperties를 주입받아 jwtProperties.secret()을 호출하며 xxxParse()함수에 값을 넘겨줬다. - TO-BE: TokenProvider를 컴포넌트로 만듦으로서, TokenProvider의 내부에서 jwt 시크릿 키를 주입받을 수 있게 되었다. 함수의 인자로 시크릿키를 받을 필요가 없게 되었으므로 함수 인자를 수정한다. * test: TokenProvider 테스트 코드 작성 - AS-IS : TokenProvider가 추상 클래스일 때는 이를 상속하는 클래스들로 TokenProvider의 기능을 테스트했다. - TO-BE : TokenProvider도 컴포넌트가 되었으므로, 테스트 코드를 작성한다. * refactor: 더 작은 작업에서 예외를 발생시키도록 - parseSubject가 parseClaims를 호출하는데, 이들이 발생시키는 예외의 종류가 다르면 안된다. * chore: 개행 수정, 안쓰는 의존 제거 * refactor: 제대로 된 DIP를 위해 패키지 이동 - security가 auth를 의존하지 않게 하기 위해, BlackListChecker 인터페이스를 security패키지 내부로 옮긴다. * chore: todo 삭제 * refactor: jwt 토큰 관련 코드 패키지 이동 - TokenProvider, TokenBlackListService를 auth.token 으로 - JwtProperties를 auth.token.config 로 * refactor: TokenProvider 인터페이스 생성 * refactor: TokenProvider 인터페이스에 의존하도록 * style: 개행 삭제 --- .../auth/service/AuthService.java | 4 +- .../auth/service/AuthTokenProvider.java | 45 ++--- .../service/CommonSignUpTokenProvider.java | 6 +- .../service/EmailSignUpTokenProvider.java | 25 ++- .../auth/service/TokenProvider.java | 42 +---- .../oauth/OAuthSignUpTokenProvider.java | 22 +-- .../auth/token/JwtTokenProvider.java | 67 +++++++ .../auth/token/TokenBlackListService.java | 34 ++++ .../token}/config/JwtProperties.java | 2 +- .../filter/AuthorizationHeaderParser.java | 29 +++ .../filter}/BlacklistChecker.java | 2 +- .../filter/JwtAuthenticationFilter.java | 10 +- .../security/filter/SignOutCheckFilter.java | 12 +- .../SiteUserAuthenticationProvider.java | 8 +- .../solidconnection/util/JwtUtils.java | 42 ----- .../auth/service/AuthServiceTest.java | 12 +- .../auth/service/AuthTokenProviderTest.java | 42 +---- .../auth/service/JwtTokenProviderTest.java | 176 ++++++++++++++++++ .../auth/service/SignInServiceTest.java | 8 +- .../service/TokenBlackListServiceTest.java | 58 ++++++ .../oauth/OAuthSignUpTokenProviderTest.java | 9 +- .../filter/AuthorizationHeaderParserTest.java | 54 ++++++ .../filter/JwtAuthenticationFilterTest.java | 2 +- .../filter/SignOutCheckFilterTest.java | 2 +- .../SiteUserAuthenticationProviderTest.java | 2 +- .../solidconnection/util/JwtUtilsTest.java | 106 ----------- 26 files changed, 502 insertions(+), 319 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java create mode 100644 src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java rename src/main/java/com/example/solidconnection/{security => auth/token}/config/JwtProperties.java (75%) create mode 100644 src/main/java/com/example/solidconnection/security/filter/AuthorizationHeaderParser.java rename src/main/java/com/example/solidconnection/{auth/service => security/filter}/BlacklistChecker.java (61%) delete mode 100644 src/main/java/com/example/solidconnection/util/JwtUtils.java create mode 100644 src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java create mode 100644 src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java create mode 100644 src/test/java/com/example/solidconnection/security/filter/AuthorizationHeaderParserTest.java delete mode 100644 src/test/java/com/example/solidconnection/util/JwtUtilsTest.java diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthService.java b/src/main/java/com/example/solidconnection/auth/service/AuthService.java index 496e48724..6053e8716 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -2,6 +2,7 @@ import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; +import com.example.solidconnection.auth.token.TokenBlackListService; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; @@ -17,6 +18,7 @@ public class AuthService { private final AuthTokenProvider authTokenProvider; + private final TokenBlackListService tokenBlackListService; /* * 로그아웃한다. @@ -26,7 +28,7 @@ public class AuthService { public void signOut(String token) { AccessToken accessToken = authTokenProvider.toAccessToken(token); authTokenProvider.deleteRefreshTokenByAccessToken(accessToken); - authTokenProvider.addToBlacklist(accessToken); + tokenBlackListService.addToBlacklist(accessToken); } /* diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java index fa2a064cc..2ff3e5650 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -1,49 +1,38 @@ package com.example.solidconnection.auth.service; import com.example.solidconnection.auth.domain.TokenType; -import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.util.JwtUtils; +import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.Objects; @Component -public class AuthTokenProvider extends TokenProvider implements BlacklistChecker { +@RequiredArgsConstructor +public class AuthTokenProvider { - public AuthTokenProvider(JwtProperties jwtProperties, RedisTemplate redisTemplate) { - super(jwtProperties, redisTemplate); - } + private final RedisTemplate redisTemplate; + private final TokenProvider tokenProvider; public AccessToken generateAccessToken(Subject subject) { - String token = generateToken(subject.value(), TokenType.ACCESS); + String token = tokenProvider.generateToken(subject.value(), TokenType.ACCESS); return new AccessToken(subject, token); } public RefreshToken generateAndSaveRefreshToken(Subject subject) { - String token = generateToken(subject.value(), TokenType.REFRESH); - saveToken(token, TokenType.REFRESH); + String token = tokenProvider.generateToken(subject.value(), TokenType.REFRESH); + tokenProvider.saveToken(token, TokenType.REFRESH); return new RefreshToken(subject, token); } /* - * 액세스 토큰을 블랙리스트에 저장한다. - * - key = BLACKLIST:{accessToken} - * - value = "signOut" -> key 의 존재만 확인하므로, value 에는 무슨 값이 들어가도 상관없다. - * */ - public void addToBlacklist(AccessToken accessToken) { - String blackListKey = TokenType.BLACKLIST.addPrefix(accessToken.token()); - redisTemplate.opsForValue().set(blackListKey, "signOut"); - } - - /* - * 유효한 리프레시 토큰인지 확인한다. - * - 요청된 토큰과 같은 subject 의 리프레시 토큰을 조회한다. - * - 조회된 리프레시 토큰과 요청된 토큰이 같은지 비교한다. - * */ + * 유효한 리프레시 토큰인지 확인한다. + * - 요청된 토큰과 같은 subject 의 리프레시 토큰을 조회한다. + * - 조회된 리프레시 토큰과 요청된 토큰이 같은지 비교한다. + * */ public boolean isValidRefreshToken(String requestedRefreshToken) { - String subject = JwtUtils.parseSubject(requestedRefreshToken, jwtProperties.secret()); + String subject = tokenProvider.parseSubject(requestedRefreshToken); String refreshTokenKey = TokenType.REFRESH.addPrefix(subject); String foundRefreshToken = redisTemplate.opsForValue().get(refreshTokenKey); return Objects.equals(requestedRefreshToken, foundRefreshToken); @@ -55,14 +44,8 @@ public void deleteRefreshTokenByAccessToken(AccessToken accessToken) { redisTemplate.delete(refreshTokenKey); } - @Override - public boolean isTokenBlacklisted(String accessToken) { - String blackListTokenKey = TokenType.BLACKLIST.addPrefix(accessToken); - return redisTemplate.hasKey(blackListTokenKey); - } - public Subject parseSubject(String token) { - String subject = JwtUtils.parseSubject(token, jwtProperties.secret()); + String subject = tokenProvider.parseSubject(token); return new Subject(subject); } diff --git a/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java index d16cb5134..d1ebe3b52 100644 --- a/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java @@ -1,9 +1,7 @@ package com.example.solidconnection.auth.service; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.AuthType; -import com.example.solidconnection.util.JwtUtils; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -14,11 +12,11 @@ @RequiredArgsConstructor public class CommonSignUpTokenProvider { - private final JwtProperties jwtProperties; + private final TokenProvider tokenProvider; public AuthType parseAuthType(String signUpToken) { try { - String authTypeStr = JwtUtils.parseClaims(signUpToken, jwtProperties.secret()).get(AUTH_TYPE_CLAIM_KEY, String.class); + String authTypeStr = tokenProvider.parseClaims(signUpToken).get(AUTH_TYPE_CLAIM_KEY, String.class); return AuthType.valueOf(authTypeStr); } catch (Exception e) { throw new CustomException(SIGN_UP_TOKEN_INVALID); diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java index c6bee80ab..fe9a176d3 100644 --- a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java @@ -2,12 +2,13 @@ import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.EmailSignUpTokenRequest; +import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.AuthType; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @@ -19,22 +20,18 @@ import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; -import static com.example.solidconnection.util.JwtUtils.parseClaims; -import static com.example.solidconnection.util.JwtUtils.parseSubject; @Component -public class EmailSignUpTokenProvider extends TokenProvider { +@RequiredArgsConstructor +public class EmailSignUpTokenProvider { static final String PASSWORD_CLAIM_KEY = "password"; static final String AUTH_TYPE_CLAIM_KEY = "authType"; private final PasswordEncoder passwordEncoder; - - public EmailSignUpTokenProvider(JwtProperties jwtProperties, RedisTemplate redisTemplate, - PasswordEncoder passwordEncoder) { - super(jwtProperties, redisTemplate); - this.passwordEncoder = passwordEncoder; - } + private final JwtProperties jwtProperties; + private final RedisTemplate redisTemplate; + private final TokenProvider tokenProvider; public String generateAndSaveSignUpToken(EmailSignUpTokenRequest request) { String email = request.email(); @@ -54,7 +51,7 @@ public String generateAndSaveSignUpToken(EmailSignUpTokenRequest request) { .setExpiration(expiredDate) .signWith(SignatureAlgorithm.HS512, jwtProperties.secret()) .compact(); - return saveToken(signUpToken, TokenType.SIGN_UP); + return tokenProvider.saveToken(signUpToken, TokenType.SIGN_UP); } public void validateSignUpToken(String token) { @@ -65,7 +62,7 @@ public void validateSignUpToken(String token) { private void validateFormatAndExpiration(String token) { try { - Claims claims = parseClaims(token, jwtProperties.secret()); + Claims claims = tokenProvider.parseClaims(token); Objects.requireNonNull(claims.getSubject()); String encodedPassword = claims.get(PASSWORD_CLAIM_KEY, String.class); Objects.requireNonNull(encodedPassword); @@ -82,11 +79,11 @@ private void validateIssuedByServer(String email) { } public String parseEmail(String token) { - return parseSubject(token, jwtProperties.secret()); + return tokenProvider.parseSubject(token); } public String parseEncodedPassword(String token) { - Claims claims = parseClaims(token, jwtProperties.secret()); + Claims claims = tokenProvider.parseClaims(token); return claims.get(PASSWORD_CLAIM_KEY, String.class); } } diff --git a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java index 0f3552db2..2cd93fb8e 100644 --- a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java @@ -1,47 +1,15 @@ package com.example.solidconnection.auth.service; import com.example.solidconnection.auth.domain.TokenType; -import com.example.solidconnection.security.config.JwtProperties; import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import org.springframework.data.redis.core.RedisTemplate; -import java.util.Date; -import java.util.concurrent.TimeUnit; +public interface TokenProvider { -import static com.example.solidconnection.util.JwtUtils.parseSubject; + String generateToken(String string, TokenType tokenType); -public abstract class TokenProvider { + String saveToken(String token, TokenType tokenType); - protected final JwtProperties jwtProperties; - protected final RedisTemplate redisTemplate; + String parseSubject(String token); - public TokenProvider(JwtProperties jwtProperties, RedisTemplate redisTemplate) { - this.jwtProperties = jwtProperties; - this.redisTemplate = redisTemplate; - } - - protected final String generateToken(String string, TokenType tokenType) { - Claims claims = Jwts.claims().setSubject(string); - Date now = new Date(); - Date expiredDate = new Date(now.getTime() + tokenType.getExpireTime()); - return Jwts.builder() - .setClaims(claims) - .setIssuedAt(now) - .setExpiration(expiredDate) - .signWith(SignatureAlgorithm.HS512, jwtProperties.secret()) - .compact(); - } - - protected final String saveToken(String token, TokenType tokenType) { - String subject = parseSubject(token, jwtProperties.secret()); - redisTemplate.opsForValue().set( - tokenType.addPrefix(subject), - token, - tokenType.getExpireTime(), - TimeUnit.MILLISECONDS - ); - return token; - } + Claims parseClaims(String token); } diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java index 8fa290d30..1aae0338e 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java @@ -2,12 +2,13 @@ import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.service.TokenProvider; +import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.AuthType; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @@ -18,17 +19,16 @@ import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; -import static com.example.solidconnection.util.JwtUtils.parseClaims; -import static com.example.solidconnection.util.JwtUtils.parseSubject; @Component -public class OAuthSignUpTokenProvider extends TokenProvider { +@RequiredArgsConstructor +public class OAuthSignUpTokenProvider { static final String AUTH_TYPE_CLAIM_KEY = "authType"; - public OAuthSignUpTokenProvider(JwtProperties jwtProperties, RedisTemplate redisTemplate) { - super(jwtProperties, redisTemplate); - } + private final JwtProperties jwtProperties; + private final RedisTemplate redisTemplate; + private final TokenProvider tokenProvider; public String generateAndSaveSignUpToken(String email, AuthType authType) { Map authTypeClaim = new HashMap<>(Map.of(AUTH_TYPE_CLAIM_KEY, authType)); @@ -42,7 +42,7 @@ public String generateAndSaveSignUpToken(String email, AuthType authType) { .setExpiration(expiredDate) .signWith(SignatureAlgorithm.HS512, jwtProperties.secret()) .compact(); - return saveToken(signUpToken, TokenType.SIGN_UP); + return tokenProvider.saveToken(signUpToken, TokenType.SIGN_UP); } public void validateSignUpToken(String token) { @@ -53,7 +53,7 @@ public void validateSignUpToken(String token) { private void validateFormatAndExpiration(String token) { try { - Claims claims = parseClaims(token, jwtProperties.secret()); + Claims claims = tokenProvider.parseClaims(token); Objects.requireNonNull(claims.getSubject()); String serializedAuthType = claims.get(AUTH_TYPE_CLAIM_KEY, String.class); AuthType.valueOf(serializedAuthType); @@ -70,11 +70,11 @@ private void validateIssuedByServer(String email) { } public String parseEmail(String token) { - return parseSubject(token, jwtProperties.secret()); + return tokenProvider.parseSubject(token); } public AuthType parseAuthType(String token) { - Claims claims = parseClaims(token, jwtProperties.secret()); + Claims claims = tokenProvider.parseClaims(token); String authTypeStr = claims.get(AUTH_TYPE_CLAIM_KEY, String.class); return AuthType.valueOf(authTypeStr); } diff --git a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java new file mode 100644 index 000000000..839e58362 --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java @@ -0,0 +1,67 @@ +package com.example.solidconnection.auth.token; + +import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.service.TokenProvider; +import com.example.solidconnection.auth.token.config.JwtProperties; +import com.example.solidconnection.common.exception.CustomException; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; + +@Component +@RequiredArgsConstructor +public class JwtTokenProvider implements TokenProvider { + + private final JwtProperties jwtProperties; + private final RedisTemplate redisTemplate; + + @Override + public final String generateToken(String string, TokenType tokenType) { + Claims claims = Jwts.claims().setSubject(string); + Date now = new Date(); + Date expiredDate = new Date(now.getTime() + tokenType.getExpireTime()); + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) + .setExpiration(expiredDate) + .signWith(SignatureAlgorithm.HS512, jwtProperties.secret()) + .compact(); + } + + @Override + public final String saveToken(String token, TokenType tokenType) { + String subject = parseSubject(token); + redisTemplate.opsForValue().set( + tokenType.addPrefix(subject), + token, + tokenType.getExpireTime(), + TimeUnit.MILLISECONDS + ); + return token; + } + + @Override + public String parseSubject(String token) { + return parseClaims(token).getSubject(); + } + + @Override + public Claims parseClaims(String token) { + try { + return Jwts.parser() + .setSigningKey(jwtProperties.secret()) + .parseClaimsJws(token) + .getBody(); + } catch (Exception e) { + throw new CustomException(INVALID_TOKEN); + } + } +} diff --git a/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java b/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java new file mode 100644 index 000000000..4175a577f --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java @@ -0,0 +1,34 @@ +package com.example.solidconnection.auth.token; + +import com.example.solidconnection.auth.service.AccessToken; +import com.example.solidconnection.security.filter.BlacklistChecker; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST; + +@Component +@RequiredArgsConstructor +public class TokenBlackListService implements BlacklistChecker { + + private static final String SIGN_OUT_VALUE = "signOut"; + + private final RedisTemplate redisTemplate; + + /* + * 액세스 토큰을 블랙리스트에 저장한다. + * - key = BLACKLIST:{accessToken} + * - value = {SIGN_OUT_VALUE} -> key 의 존재만 확인하므로, value 에는 무슨 값이 들어가도 상관없다. + * */ + public void addToBlacklist(AccessToken accessToken) { + String blackListKey = BLACKLIST.addPrefix(accessToken.token()); + redisTemplate.opsForValue().set(blackListKey, SIGN_OUT_VALUE); + } + + @Override + public boolean isTokenBlacklisted(String accessToken) { + String blackListTokenKey = BLACKLIST.addPrefix(accessToken); + return redisTemplate.hasKey(blackListTokenKey); + } +} diff --git a/src/main/java/com/example/solidconnection/security/config/JwtProperties.java b/src/main/java/com/example/solidconnection/auth/token/config/JwtProperties.java similarity index 75% rename from src/main/java/com/example/solidconnection/security/config/JwtProperties.java rename to src/main/java/com/example/solidconnection/auth/token/config/JwtProperties.java index f4afa5245..bf5180218 100644 --- a/src/main/java/com/example/solidconnection/security/config/JwtProperties.java +++ b/src/main/java/com/example/solidconnection/auth/token/config/JwtProperties.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.security.config; +package com.example.solidconnection.auth.token.config; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/java/com/example/solidconnection/security/filter/AuthorizationHeaderParser.java b/src/main/java/com/example/solidconnection/security/filter/AuthorizationHeaderParser.java new file mode 100644 index 000000000..8bbbc30e4 --- /dev/null +++ b/src/main/java/com/example/solidconnection/security/filter/AuthorizationHeaderParser.java @@ -0,0 +1,29 @@ +package com.example.solidconnection.security.filter; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +public class AuthorizationHeaderParser { + + private static final String TOKEN_HEADER = "Authorization"; + private static final String TOKEN_PREFIX = "Bearer "; + private static final int TOKEN_PREFIX_LENGTH = TOKEN_PREFIX.length(); + + public Optional parseToken(HttpServletRequest request) { + String token = request.getHeader(TOKEN_HEADER); + if (isInvalidFormat(token)) { + return Optional.empty(); + } + return Optional.of(token.substring(TOKEN_PREFIX_LENGTH)); + } + + private boolean isInvalidFormat(String token) { + return token == null || + token.isBlank() || + !token.startsWith(TOKEN_PREFIX) || + token.substring(TOKEN_PREFIX_LENGTH).isBlank(); + } +} diff --git a/src/main/java/com/example/solidconnection/auth/service/BlacklistChecker.java b/src/main/java/com/example/solidconnection/security/filter/BlacklistChecker.java similarity index 61% rename from src/main/java/com/example/solidconnection/auth/service/BlacklistChecker.java rename to src/main/java/com/example/solidconnection/security/filter/BlacklistChecker.java index b4e174906..f093d8e8f 100644 --- a/src/main/java/com/example/solidconnection/auth/service/BlacklistChecker.java +++ b/src/main/java/com/example/solidconnection/security/filter/BlacklistChecker.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.auth.service; +package com.example.solidconnection.security.filter; public interface BlacklistChecker { diff --git a/src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java index 39917d42e..8ee6a98e4 100644 --- a/src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java @@ -15,8 +15,7 @@ import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; - -import static com.example.solidconnection.util.JwtUtils.parseTokenFromRequest; +import java.util.Optional; @Component @@ -24,18 +23,19 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final AuthenticationManager authenticationManager; + private final AuthorizationHeaderParser authorizationHeaderParser; @Override public void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { - String token = parseTokenFromRequest(request); - if (token == null) { + Optional token = authorizationHeaderParser.parseToken(request); + if (token.isEmpty()) { filterChain.doFilter(request, response); return; } - JwtAuthentication authToken = createAuthentication(token); + JwtAuthentication authToken = createAuthentication(token.get()); Authentication auth = authenticationManager.authenticate(authToken); SecurityContextHolder.getContext().setAuthentication(auth); diff --git a/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java b/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java index 5c51c53cd..c3926d67f 100644 --- a/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java +++ b/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java @@ -1,6 +1,5 @@ package com.example.solidconnection.security.filter; -import com.example.solidconnection.auth.service.BlacklistChecker; import com.example.solidconnection.common.exception.CustomException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -12,28 +11,29 @@ import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.util.Optional; import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_SIGN_OUT; -import static com.example.solidconnection.util.JwtUtils.parseTokenFromRequest; @Component @RequiredArgsConstructor public class SignOutCheckFilter extends OncePerRequestFilter { - private final BlacklistChecker tokenBlacklistChecker; + private final AuthorizationHeaderParser authorizationHeaderParser; + private final BlacklistChecker blacklistChecker; @Override protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { - String token = parseTokenFromRequest(request); - if (token != null && hasSignedOut(token)) { + Optional token = authorizationHeaderParser.parseToken(request); + if (token.isPresent() && hasSignedOut(token.get())) { throw new CustomException(USER_ALREADY_SIGN_OUT); } filterChain.doFilter(request, response); } private boolean hasSignedOut(String accessToken) { - return tokenBlacklistChecker.isTokenBlacklisted(accessToken); + return blacklistChecker.isTokenBlacklisted(accessToken); } } diff --git a/src/main/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProvider.java b/src/main/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProvider.java index 6ce43b97c..a00b77f9a 100644 --- a/src/main/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProvider.java +++ b/src/main/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProvider.java @@ -1,8 +1,8 @@ package com.example.solidconnection.security.provider; +import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.security.authentication.JwtAuthentication; import com.example.solidconnection.security.authentication.SiteUserAuthentication; -import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.security.userdetails.SiteUserDetails; import com.example.solidconnection.security.userdetails.SiteUserDetailsService; import lombok.RequiredArgsConstructor; @@ -11,21 +11,19 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Component; -import static com.example.solidconnection.util.JwtUtils.parseSubject; - @Component @RequiredArgsConstructor public class SiteUserAuthenticationProvider implements AuthenticationProvider { - private final JwtProperties jwtProperties; private final SiteUserDetailsService siteUserDetailsService; + private final TokenProvider tokenProvider; @Override public Authentication authenticate(Authentication auth) throws AuthenticationException { JwtAuthentication jwtAuth = (JwtAuthentication) auth; String token = jwtAuth.getToken(); - String username = parseSubject(token, jwtProperties.secret()); + String username = tokenProvider.parseSubject(token); SiteUserDetails userDetails = (SiteUserDetails) siteUserDetailsService.loadUserByUsername(username); return new SiteUserAuthentication(token, userDetails); } diff --git a/src/main/java/com/example/solidconnection/util/JwtUtils.java b/src/main/java/com/example/solidconnection/util/JwtUtils.java deleted file mode 100644 index 040beb9ba..000000000 --- a/src/main/java/com/example/solidconnection/util/JwtUtils.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.example.solidconnection.util; - -import com.example.solidconnection.common.exception.CustomException; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.Jwts; -import jakarta.servlet.http.HttpServletRequest; -import org.springframework.stereotype.Component; - -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; - -@Component -public class JwtUtils { - - private static final String TOKEN_HEADER = "Authorization"; - private static final String TOKEN_PREFIX = "Bearer "; - - private JwtUtils() { - } - - public static String parseTokenFromRequest(HttpServletRequest request) { - String token = request.getHeader(TOKEN_HEADER); - if (token == null || token.isBlank() || !token.startsWith(TOKEN_PREFIX)) { - return null; - } - return token.substring(TOKEN_PREFIX.length()); - } - - public static String parseSubject(String token, String secretKey) { - try { - return parseClaims(token, secretKey).getSubject(); - } catch (Exception e) { - throw new CustomException(INVALID_TOKEN); - } - } - public static Claims parseClaims(String token, String secretKey) throws ExpiredJwtException { - return Jwts.parser() - .setSigningKey(secretKey) - .parseClaimsJws(token) - .getBody(); - } -} diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java index 8fd57eae6..c4c2a1708 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -3,6 +3,7 @@ import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; +import com.example.solidconnection.auth.token.TokenBlackListService; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; @@ -30,6 +31,9 @@ class AuthServiceTest { @Autowired private AuthTokenProvider authTokenProvider; + @Autowired + private TokenBlackListService tokenBlackListService; + @Autowired private RedisTemplate redisTemplate; @@ -40,7 +44,7 @@ class AuthServiceTest { void 로그아웃한다() { // given Subject subject = new Subject("subject"); - AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // todo: #296 + AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // when authService.signOut(accessToken.token()); @@ -49,7 +53,7 @@ class AuthServiceTest { String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value()); assertAll( () -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(), - () -> assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue() + () -> assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isTrue() ); } @@ -58,7 +62,7 @@ class AuthServiceTest { // given SiteUser user = siteUserFixture.사용자(); Subject subject = authTokenProvider.toSubject(user); - AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // todo: #296 + AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // when authService.quit(user, accessToken.token()); @@ -69,7 +73,7 @@ class AuthServiceTest { assertAll( () -> assertThat(user.getQuitedAt()).isEqualTo(tomorrow), () -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(), - () -> assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue() + () -> assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isTrue() ); } diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java index dc35ab3a7..b6c111f24 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java @@ -61,7 +61,7 @@ class 리프레시_토큰을_제공한다 { void 유효한_리프레시_토큰인지_확인한다() { // given RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(subject); - AccessToken fakeRefreshToken = authTokenProvider.generateAccessToken(subject); // todo: issue#296 + AccessToken fakeRefreshToken = authTokenProvider.generateAccessToken(subject); // when, then assertAll( @@ -71,7 +71,7 @@ class 리프레시_토큰을_제공한다 { } @Test - void 액세서_토큰에_해당하는_리프레시_토큰을_삭제한다() { + void 액세스_토큰에_해당하는_리프레시_토큰을_삭제한다() { // given authTokenProvider.generateAndSaveRefreshToken(subject); AccessToken accessToken = authTokenProvider.generateAccessToken(subject); @@ -85,44 +85,6 @@ class 리프레시_토큰을_제공한다 { } } - @Nested - class 블랙리스트를_관리한다 { - - @Test - void 액세스_토큰을_블랙리스트에_추가한다() { - // given - AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // todo: issue#296 - - // when - authTokenProvider.addToBlacklist(accessToken); - - // then - String blackListTokenKey = TokenType.BLACKLIST.addPrefix(accessToken.token()); - String foundBlackListToken = redisTemplate.opsForValue().get(blackListTokenKey); - assertThat(foundBlackListToken).isNotNull(); - } - - /* - * todo: JwtUtils 나 TokenProvider 를 스프링 빈으로 주입받도록 변경한다. (issue#296) - * - 아래 테스트 코드에서는, 내부적으로 JwtUtils.parseSubject() 메서드가 호출될 때 발생하는 예외를 피하기 위해 jwt토큰을 생성한다. - * - 테스트 작성자는 예외 발생을 피하기 위해 "제대로된 jwt 토큰 생성이 필요하다"는 것을 몰라야한다. - * - 따라서, JwtUtils 나 TokenProvider 를 스프링 빈으로 주입받도록 변경하고, 테스트에서 mock 을 사용하여 의존성을 끊을 필요가 있다. - */ - @Test - void 블랙리스트에_있는_토큰인지_확인한다() { - // given - AccessToken accessToken = authTokenProvider.generateAccessToken(subject); - authTokenProvider.addToBlacklist(accessToken); - AccessToken notRegisteredAccessToken = authTokenProvider.generateAccessToken(new Subject("!")); - - // when, then - assertAll( - () -> assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue(), - () -> assertThat(authTokenProvider.isTokenBlacklisted(notRegisteredAccessToken.token())).isFalse() - ); - } - } - @Test void 토큰으로부터_Subject_를_추출한다() { // given diff --git a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java new file mode 100644 index 000000000..c36a0bb39 --- /dev/null +++ b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java @@ -0,0 +1,176 @@ +package com.example.solidconnection.auth.service; + +import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.token.JwtTokenProvider; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; +import com.example.solidconnection.auth.token.config.JwtProperties; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + +@DisplayName("토큰 제공자 테스트") +@TestContainerSpringBootTest +class JwtTokenProviderTest { + + @Autowired + private JwtTokenProvider tokenProvider; + + @Autowired + private JwtProperties jwtProperties; + + @Autowired + private RedisTemplate redisTemplate; + + @Test + void 토큰을_생성한다() { + // given + String actualSubject = "subject123"; + TokenType actualTokenType = TokenType.ACCESS; + + // when + String token = tokenProvider.generateToken(actualSubject, actualTokenType); + + // then - subject와 만료 시간이 일치하는지 검증 + Claims claims = tokenProvider.parseClaims(token); + long expectedExpireTime = claims.getExpiration().getTime() - claims.getIssuedAt().getTime(); + assertAll( + () -> assertThat(claims.getSubject()).isEqualTo(actualSubject), + () -> assertThat(expectedExpireTime).isEqualTo(actualTokenType.getExpireTime()) + ); + } + + @Test + void 토큰을_저장한다() { + // given + String subject = "subject123"; + TokenType tokenType = TokenType.ACCESS; + String token = tokenProvider.generateToken(subject, tokenType); + + // when + String savedToken = tokenProvider.saveToken(token, tokenType); + + // then - key: "{TokenType.Prefix}:subject", value: {token} 로 저장되어있는지 검증, 반환하는 값이 value와 같은지 검증 + String key = tokenType.addPrefix(subject); + String value = redisTemplate.opsForValue().get(key); + assertAll( + () -> assertThat(value).isEqualTo(token), + () -> assertThat(savedToken).isEqualTo(value) + ); + } + + @Nested + class 토큰으로부터_subject_를_추출한다 { + + @Test + void 유효한_토큰의_subject_를_추출한다() { + // given + String subject = "subject000"; + String token = createValidToken(subject); + + // when + String extractedSubject = tokenProvider.parseSubject(token); + + // then + assertThat(extractedSubject).isEqualTo(subject); + } + + @Test + void 유효하지_않은_토큰의_subject_를_추출하면_예외_응답을_반환한다() { + // given + String subject = "subject123"; + String token = createExpiredToken(subject); + + // when, then + assertThatCode(() -> tokenProvider.parseSubject(token)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.INVALID_TOKEN.getMessage()); + } + } + + @Nested + class 토큰으로부터_claim_을_추출한다 { + + @Test + void 유효한_토큰의_claim_을_추출한다() { + // given + String subject = "subject"; + String claimKey = "key"; + String claimValue = "value"; + Claims expectedClaims = Jwts.claims(new HashMap<>(Map.of(claimKey, claimValue))).setSubject(subject); + String token = createValidToken(expectedClaims); + + // when + Claims actualClaims = tokenProvider.parseClaims(token); + + // then + assertAll( + () -> assertThat(actualClaims.getSubject()).isEqualTo(subject), + () -> assertThat(actualClaims.get(claimKey)).isEqualTo(claimValue) + ); + } + + @Test + void 유효하지_않은_토큰의_claim_을_추출하면_예외_응답을_반환한다() { + // given + String subject = "subject"; + Claims expectedClaims = Jwts.claims().setSubject(subject); + String token = createExpiredToken(expectedClaims); + + // when + assertThatCode(() -> tokenProvider.parseClaims(token)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.INVALID_TOKEN.getMessage()); + } + } + + private String createValidToken(String subject) { + return Jwts.builder() + .setSubject(subject) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 1000)) + .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) + .compact(); + } + + private String createValidToken(Claims claims) { + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 1000)) + .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) + .compact(); + } + + private String createExpiredToken(String subject) { + return Jwts.builder() + .setSubject(subject) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() - 1000)) + .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) + .compact(); + } + + private String createExpiredToken(Claims claims) { + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() - 1000)) + .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) + .compact(); + } +} diff --git a/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java index 1656ed4e5..463bc4957 100644 --- a/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java @@ -2,11 +2,9 @@ import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.SignInResponse; -import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.util.JwtUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -26,7 +24,7 @@ class SignInServiceTest { private SignInService signInService; @Autowired - private JwtProperties jwtProperties; + private TokenProvider tokenProvider; @Autowired private RedisTemplate redisTemplate; @@ -49,8 +47,8 @@ void setUp() { SignInResponse signInResponse = signInService.signIn(user); // then - String accessTokenSubject = JwtUtils.parseSubject(signInResponse.accessToken(), jwtProperties.secret()); - String refreshTokenSubject = JwtUtils.parseSubject(signInResponse.refreshToken(), jwtProperties.secret()); + String accessTokenSubject = tokenProvider.parseSubject(signInResponse.accessToken()); + String refreshTokenSubject = tokenProvider.parseSubject(signInResponse.refreshToken()); String savedRefreshToken = redisTemplate.opsForValue().get(TokenType.REFRESH.addPrefix(refreshTokenSubject)); assertAll( () -> assertThat(accessTokenSubject).isEqualTo(subject), diff --git a/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java new file mode 100644 index 000000000..e1974cc93 --- /dev/null +++ b/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java @@ -0,0 +1,58 @@ +package com.example.solidconnection.auth.service; + +import com.example.solidconnection.auth.token.TokenBlackListService; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; + +import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST; +import static org.assertj.core.api.Assertions.assertThat; + +@TestContainerSpringBootTest +class TokenBlackListServiceTest { + + @Autowired + private TokenBlackListService tokenBlackListService; + + @Autowired + private RedisTemplate redisTemplate; + + @Test + void 액세스_토큰을_블랙리스트에_추가한다() { + // given + AccessToken accessToken = new AccessToken("subject", "token"); + + // when + tokenBlackListService.addToBlacklist(accessToken); + + // then + String blackListTokenKey = BLACKLIST.addPrefix(accessToken.token()); + String foundBlackListToken = redisTemplate.opsForValue().get(blackListTokenKey); + assertThat(foundBlackListToken).isNotNull(); + } + + @Nested + class 블랙리스트에_있는_토큰인지_확인한다 { + + @Test + void 블랙리스트에_토큰이_있는_경우() { + // given + AccessToken accessToken = new AccessToken("subject", "token"); + tokenBlackListService.addToBlacklist(accessToken); + + // when, then + assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isTrue(); + } + + @Test + void 블랙리스트에_토큰이_없는_경우() { + // given + AccessToken accessToken = new AccessToken("subject", "token"); + + // when, then + assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isFalse(); + } + } +} diff --git a/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java index 233317458..c748987c1 100644 --- a/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java @@ -1,11 +1,11 @@ package com.example.solidconnection.auth.service.oauth; import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.service.TokenProvider; +import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.util.JwtUtils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; @@ -34,6 +34,9 @@ class OAuthSignUpTokenProviderTest { @Autowired private OAuthSignUpTokenProvider OAuthSignUpTokenProvider; + @Autowired + private TokenProvider tokenProvider; + @Autowired private RedisTemplate redisTemplate; @@ -50,7 +53,7 @@ class OAuthSignUpTokenProviderTest { String signUpToken = OAuthSignUpTokenProvider.generateAndSaveSignUpToken(email, authType); // then - Claims claims = JwtUtils.parseClaims(signUpToken, jwtProperties.secret()); + Claims claims = tokenProvider.parseClaims(signUpToken); String actualSubject = claims.getSubject(); AuthType actualAuthType = AuthType.valueOf(claims.get(AUTH_TYPE_CLAIM_KEY, String.class)); String signUpTokenKey = TokenType.SIGN_UP.addPrefix(email); diff --git a/src/test/java/com/example/solidconnection/security/filter/AuthorizationHeaderParserTest.java b/src/test/java/com/example/solidconnection/security/filter/AuthorizationHeaderParserTest.java new file mode 100644 index 000000000..1d4ba2533 --- /dev/null +++ b/src/test/java/com/example/solidconnection/security/filter/AuthorizationHeaderParserTest.java @@ -0,0 +1,54 @@ +package com.example.solidconnection.security.filter; + +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +@TestContainerSpringBootTest +class AuthorizationHeaderParserTest { + + @Autowired + private AuthorizationHeaderParser authorizationHeaderParser; + + @Nested + class 요청으로부터_토큰을_추출한다 { + + @Test + void 지정한_형식의_토큰이_있으면_토큰을_반환한다() { + // given + MockHttpServletRequest request = new MockHttpServletRequest(); + String token = "token"; + request.addHeader("Authorization", "Bearer " + token); + + // when + Optional extractedToken = authorizationHeaderParser.parseToken(request); + + // then + assertThat(extractedToken).get().isEqualTo(token); + } + + @Test + void 형식에_맞는_토큰이_없으면_빈_값을_반환한다() { + // given + MockHttpServletRequest noHeader = new MockHttpServletRequest(); + MockHttpServletRequest wrongPrefix = new MockHttpServletRequest(); + wrongPrefix.addHeader("Authorization", "Wrong token"); + MockHttpServletRequest emptyToken = new MockHttpServletRequest(); + emptyToken.addHeader("Authorization", "Bearer "); + + // when & then + assertAll( + () -> assertThat(authorizationHeaderParser.parseToken(noHeader)).isEmpty(), + () -> assertThat(authorizationHeaderParser.parseToken(wrongPrefix)).isEmpty(), + () -> assertThat(authorizationHeaderParser.parseToken(emptyToken)).isEmpty() + ); + } + } +} diff --git a/src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java b/src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java index c66037c82..229fed27b 100644 --- a/src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java +++ b/src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.security.filter; import com.example.solidconnection.security.authentication.SiteUserAuthentication; -import com.example.solidconnection.security.config.JwtProperties; +import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.security.userdetails.SiteUserDetailsService; import com.example.solidconnection.support.TestContainerSpringBootTest; import io.jsonwebtoken.Jwts; diff --git a/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java b/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java index 5ccc1aa1b..80e927203 100644 --- a/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java +++ b/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.security.filter; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.security.config.JwtProperties; +import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.support.TestContainerSpringBootTest; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; diff --git a/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java b/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java index 9c51de838..0bbb6677f 100644 --- a/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java +++ b/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java @@ -2,7 +2,7 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.security.authentication.SiteUserAuthentication; -import com.example.solidconnection.security.config.JwtProperties; +import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.security.userdetails.SiteUserDetails; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; diff --git a/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java b/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java deleted file mode 100644 index c57e85193..000000000 --- a/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.example.solidconnection.util; - -import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.common.exception.ErrorCode; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -import java.util.Date; - -import static com.example.solidconnection.util.JwtUtils.parseSubject; -import static com.example.solidconnection.util.JwtUtils.parseTokenFromRequest; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; - -@DisplayName("JwtUtils 테스트") -class JwtUtilsTest { - - private final String jwtSecretKey = "jwt-secret-key"; - - @Nested - class 요청으로부터_토큰을_추출한다 { - - @Test - void 토큰이_있으면_토큰을_반환한다() { - // given - MockHttpServletRequest request = new MockHttpServletRequest(); - String token = "token"; - request.addHeader("Authorization", "Bearer " + token); - - // when - String extractedToken = parseTokenFromRequest(request); - - // then - assertThat(extractedToken).isEqualTo(token); - } - - @Test - void 토큰이_없으면_null_을_반환한다() { - // given - MockHttpServletRequest noHeader = new MockHttpServletRequest(); - MockHttpServletRequest wrongPrefix = new MockHttpServletRequest(); - wrongPrefix.addHeader("Authorization", "Wrong token"); - MockHttpServletRequest emptyToken = new MockHttpServletRequest(); - wrongPrefix.addHeader("Authorization", "Bearer "); - - // when & then - assertAll( - () -> assertThat(parseTokenFromRequest(noHeader)).isNull(), - () -> assertThat(parseTokenFromRequest(wrongPrefix)).isNull(), - () -> assertThat(parseTokenFromRequest(emptyToken)).isNull() - ); - } - } - - @Nested - class 토큰으로부터_subject_를_추출한다 { - - @Test - void 유효한_토큰의_subject_를_추출한다() { - // given - String subject = "subject000"; - String token = createValidToken(subject); - - // when - String extractedSubject = parseSubject(token, jwtSecretKey); - - // then - assertThat(extractedSubject).isEqualTo(subject); - } - - @Test - void 유효하지_않은_토큰의_subject_를_추출하면_예외_응답을_반환한다() { - // given - String subject = "subject123"; - String token = createExpiredToken(subject); - - // when - assertThatCode(() -> parseSubject(token, jwtSecretKey)) - .isInstanceOf(CustomException.class) - .hasMessage(ErrorCode.INVALID_TOKEN.getMessage()); - } - } - - private String createValidToken(String subject) { - return Jwts.builder() - .setSubject(subject) - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() + 1000)) - .signWith(SignatureAlgorithm.HS256, jwtSecretKey) - .compact(); - } - - private String createExpiredToken(String subject) { - return Jwts.builder() - .setSubject(subject) - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() - 1000)) - .signWith(SignatureAlgorithm.HS256, jwtSecretKey) - .compact(); - } -} From ac0f60ed13140a799d16c0bad3fc6084630a5f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Thu, 22 May 2025 20:52:47 +0900 Subject: [PATCH 20/90] =?UTF-8?q?refactor:=20Application=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20fixture=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#329)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: ApplicationFixture에 지원서 생성 메서드 추가 * refactor: 지원서 데이터 Fixture 메서드로 변경 * refactor: 불필요한 성적 요구사항 세팅 제거 --- .../fixture/ApplicationFixture.java | 38 ++ .../fixture/ApplicationFixtureBuilder.java | 86 ++++ .../service/ApplicationQueryServiceTest.java | 453 +++++++++++------- .../ApplicationSubmissionServiceTest.java | 118 ++--- 4 files changed, 468 insertions(+), 227 deletions(-) create mode 100644 src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java create mode 100644 src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java diff --git a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java new file mode 100644 index 000000000..b2cbc6460 --- /dev/null +++ b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java @@ -0,0 +1,38 @@ +package com.example.solidconnection.application.fixture; + +import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.application.domain.Gpa; +import com.example.solidconnection.application.domain.LanguageTest; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.university.domain.UniversityInfoForApply; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ApplicationFixture { + + private final ApplicationFixtureBuilder applicationFixtureBuilder; + + public Application 지원서( + SiteUser siteUser, + String nicknameForApply, + String term, + Gpa gpa, + LanguageTest languageTest, + UniversityInfoForApply firstChoiceUniversity, + UniversityInfoForApply secondChoiceUniversity, + UniversityInfoForApply thirdChoiceUniversity + ) { + return applicationFixtureBuilder.application() + .siteUser(siteUser) + .gpa(gpa) + .languageTest(languageTest) + .nicknameForApply(nicknameForApply) + .term(term) + .firstChoiceUniversity(firstChoiceUniversity) + .secondChoiceUniversity(secondChoiceUniversity) + .thirdChoiceUniversity(thirdChoiceUniversity) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java new file mode 100644 index 000000000..5f6c06741 --- /dev/null +++ b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java @@ -0,0 +1,86 @@ +package com.example.solidconnection.application.fixture; + +import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.application.domain.Gpa; +import com.example.solidconnection.application.domain.LanguageTest; +import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.application.repository.ApplicationRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.university.domain.UniversityInfoForApply; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ApplicationFixtureBuilder { + + private final ApplicationRepository applicationRepository; + + private Gpa gpa; + private LanguageTest languageTest; + private UniversityInfoForApply firstChoiceUniversity; + private UniversityInfoForApply secondChoiceUniversity; + private UniversityInfoForApply thirdChoiceUniversity; + private SiteUser siteUser; + private String nicknameForApply; + private String term; + + public ApplicationFixtureBuilder application() { + return new ApplicationFixtureBuilder(applicationRepository); + } + + public ApplicationFixtureBuilder gpa(Gpa gpa) { + this.gpa = gpa; + return this; + } + + public ApplicationFixtureBuilder languageTest(LanguageTest languageTest) { + this.languageTest = languageTest; + return this; + } + + public ApplicationFixtureBuilder firstChoiceUniversity(UniversityInfoForApply firstChoiceUniversity) { + this.firstChoiceUniversity = firstChoiceUniversity; + return this; + } + + public ApplicationFixtureBuilder secondChoiceUniversity(UniversityInfoForApply secondChoiceUniversity) { + this.secondChoiceUniversity = secondChoiceUniversity; + return this; + } + + public ApplicationFixtureBuilder thirdChoiceUniversity(UniversityInfoForApply thirdChoiceUniversity) { + this.thirdChoiceUniversity = thirdChoiceUniversity; + return this; + } + + public ApplicationFixtureBuilder siteUser(SiteUser siteUser) { + this.siteUser = siteUser; + return this; + } + + public ApplicationFixtureBuilder nicknameForApply(String nicknameForApply) { + this.nicknameForApply = nicknameForApply; + return this; + } + + public ApplicationFixtureBuilder term(String term) { + this.term = term; + return this; + } + + public Application create() { + Application application = new Application( + siteUser, + gpa, + languageTest, + term, + firstChoiceUniversity, + secondChoiceUniversity, + thirdChoiceUniversity, + nicknameForApply + ); + application.setVerifyStatus(VerifyStatus.APPROVED); + return applicationRepository.save(application); + } +} diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java index 240217496..ac0b83619 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java @@ -1,32 +1,36 @@ package com.example.solidconnection.application.service; import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.application.domain.Gpa; -import com.example.solidconnection.application.domain.LanguageTest; import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.application.dto.ApplicantResponse; import com.example.solidconnection.application.dto.ApplicationsResponse; import com.example.solidconnection.application.dto.UniversityApplicantsResponse; +import com.example.solidconnection.application.fixture.ApplicationFixture; import com.example.solidconnection.application.repository.ApplicationRepository; +import com.example.solidconnection.location.region.fixture.RegionFixture; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.domain.LanguageTestScore; -import com.example.solidconnection.score.repository.GpaScoreRepository; -import com.example.solidconnection.score.repository.LanguageTestScoreRepository; +import com.example.solidconnection.score.fixture.GpaScoreFixture; +import com.example.solidconnection.score.fixture.LanguageTestScoreFixture; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.support.integration.BaseIntegrationTest; -import com.example.solidconnection.university.domain.LanguageTestType; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +@TestContainerSpringBootTest @DisplayName("지원서 조회 서비스 테스트") -class ApplicationQueryServiceTest extends BaseIntegrationTest { +class ApplicationQueryServiceTest { @Autowired private ApplicationQueryService applicationQueryService; @@ -35,19 +39,101 @@ class ApplicationQueryServiceTest extends BaseIntegrationTest { private ApplicationRepository applicationRepository; @Autowired - private GpaScoreRepository gpaScoreRepository; + private SiteUserFixture siteUserFixture; @Autowired - private LanguageTestScoreRepository languageTestScoreRepository; + private RegionFixture regionFixture; + + @Autowired + private UniversityInfoForApplyFixture universityInfoForApplyFixture; + + @Autowired + private GpaScoreFixture gpaScoreFixture; + + @Autowired + private LanguageTestScoreFixture languageTestScoreFixture; + + @Autowired + private ApplicationFixture applicationFixture; + + @Value("${university.term}") + private String term; + + private SiteUser user1; + private SiteUser user2; + private SiteUser user3; + + private GpaScore gpaScore1; + private GpaScore gpaScore2; + private GpaScore gpaScore3; + + private LanguageTestScore languageTestScore1; + private LanguageTestScore languageTestScore2; + private LanguageTestScore languageTestScore3; + + private UniversityInfoForApply 괌대학_A_지원_정보; + private UniversityInfoForApply 괌대학_B_지원_정보; + private UniversityInfoForApply 서던덴마크대학교_지원_정보; + + @BeforeEach + void setUp() { + user1 = siteUserFixture.사용자(1, "test1"); + gpaScore1 = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user1); + languageTestScore1 = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user1); + + user2 = siteUserFixture.사용자(2, "test2"); + gpaScore2 = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user2); + languageTestScore2 = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user2); + + user3 = siteUserFixture.사용자(3, "test3"); + gpaScore3 = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user3); + languageTestScore3 = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user3); + + 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); + 괌대학_B_지원_정보 = universityInfoForApplyFixture.괌대학_B_지원_정보(); + 서던덴마크대학교_지원_정보 = universityInfoForApplyFixture.서던덴마크대학교_지원_정보(); + } @Nested class 지원자_목록_조회_테스트 { @Test void 이번_학기_전체_지원자를_조회한다() { + // given + Application application1 = applicationFixture.지원서( + user1, + "nickname1", + term, + gpaScore1.getGpa(), + languageTestScore1.getLanguageTest(), + 괌대학_A_지원_정보, + null, + null + ); + Application application2 = applicationFixture.지원서( + user2, + "nickname2", + term, + gpaScore2.getGpa(), + languageTestScore2.getLanguageTest(), + 괌대학_B_지원_정보, + null, + null + ); + Application application3 = applicationFixture.지원서( + user3, + "nickname3", + term, + gpaScore3.getGpa(), + languageTestScore3.getLanguageTest(), + 서던덴마크대학교_지원_정보, + null, + null + ); + // when ApplicationsResponse response = applicationQueryService.getApplicants( - 테스트유저_2, + user1, "", "" ); @@ -55,128 +141,174 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsAll(List.of( UniversityApplicantsResponse.of(괌대학_A_지원_정보, - List.of(ApplicantResponse.of(테스트유저_3_괌대학_A_괌대학_B_그라츠공과대학_지원서, false))), + List.of(ApplicantResponse.of(application1, true))), UniversityApplicantsResponse.of(괌대학_B_지원_정보, - List.of(ApplicantResponse.of(테스트유저_2_괌대학_B_괌대학_A_린츠_카톨릭대학_지원서, true))), - UniversityApplicantsResponse.of(메이지대학_지원_정보, - List.of(ApplicantResponse.of(테스트유저_4_메이지대학_그라츠대학_서던덴마크대학_지원서, false))), - UniversityApplicantsResponse.of(네바다주립대학_라스베이거스_지원_정보, - List.of(ApplicantResponse.of(테스트유저_5_네바다주립대학_그라츠공과대학_메이지대학_지원서, false))), - UniversityApplicantsResponse.of(코펜하겐IT대학_지원_정보, - List.of(ApplicantResponse.of(테스트유저_7_코펜하겐IT대학_X_X_지원서, false))) - )); - - assertThat(response.secondChoice()).containsAll(List.of( - UniversityApplicantsResponse.of(괌대학_A_지원_정보, - List.of(ApplicantResponse.of(테스트유저_2_괌대학_B_괌대학_A_린츠_카톨릭대학_지원서, true))), - UniversityApplicantsResponse.of(괌대학_B_지원_정보, - List.of(ApplicantResponse.of(테스트유저_3_괌대학_A_괌대학_B_그라츠공과대학_지원서, false))), - UniversityApplicantsResponse.of(그라츠대학_지원_정보, - List.of(ApplicantResponse.of(테스트유저_4_메이지대학_그라츠대학_서던덴마크대학_지원서, false))), - UniversityApplicantsResponse.of(그라츠공과대학_지원_정보, - List.of(ApplicantResponse.of(테스트유저_5_네바다주립대학_그라츠공과대학_메이지대학_지원서, false))) - )); - - assertThat(response.thirdChoice()).containsAll(List.of( - UniversityApplicantsResponse.of(린츠_카톨릭대학_지원_정보, - List.of(ApplicantResponse.of(테스트유저_2_괌대학_B_괌대학_A_린츠_카톨릭대학_지원서, true))), - UniversityApplicantsResponse.of(그라츠공과대학_지원_정보, - List.of(ApplicantResponse.of(테스트유저_3_괌대학_A_괌대학_B_그라츠공과대학_지원서, false))), + List.of(ApplicantResponse.of(application2, false))), UniversityApplicantsResponse.of(서던덴마크대학교_지원_정보, - List.of(ApplicantResponse.of(테스트유저_4_메이지대학_그라츠대학_서던덴마크대학_지원서, false))), - UniversityApplicantsResponse.of(메이지대학_지원_정보, - List.of(ApplicantResponse.of(테스트유저_5_네바다주립대학_그라츠공과대학_메이지대학_지원서, false))) + List.of(ApplicantResponse.of(application3, false))) )); } @Test void 이번_학기_특정_지역_지원자를_조회한다() { + //given + Application application1 = applicationFixture.지원서( + user1, + "nickname1", + term, + gpaScore1.getGpa(), + languageTestScore1.getLanguageTest(), + 괌대학_A_지원_정보, + null, + null + ); + Application application2 = applicationFixture.지원서( + user2, + "nickname2", + term, + gpaScore2.getGpa(), + languageTestScore2.getLanguageTest(), + 괌대학_B_지원_정보, + null, + null + ); + applicationFixture.지원서( + user3, + "nickname3", + term, + gpaScore3.getGpa(), + languageTestScore3.getLanguageTest(), + 서던덴마크대학교_지원_정보, + null, + null + ); + // when ApplicationsResponse response = applicationQueryService.getApplicants( - 테스트유저_2, - 영미권.getCode(), + user1, + regionFixture.영미권().getCode(), "" ); // then - assertThat(response.firstChoice()).containsAll(List.of( + assertThat(response.firstChoice()).containsExactlyInAnyOrder( UniversityApplicantsResponse.of(괌대학_A_지원_정보, - List.of(ApplicantResponse.of(테스트유저_3_괌대학_A_괌대학_B_그라츠공과대학_지원서, false))), + List.of(ApplicantResponse.of(application1, true))), UniversityApplicantsResponse.of(괌대학_B_지원_정보, - List.of(ApplicantResponse.of(테스트유저_2_괌대학_B_괌대학_A_린츠_카톨릭대학_지원서, true))), - UniversityApplicantsResponse.of(네바다주립대학_라스베이거스_지원_정보, - List.of(ApplicantResponse.of(테스트유저_5_네바다주립대학_그라츠공과대학_메이지대학_지원서, false))) - )); - - assertThat(response.secondChoice()).containsAll(List.of( - UniversityApplicantsResponse.of(괌대학_A_지원_정보, - List.of(ApplicantResponse.of(테스트유저_2_괌대학_B_괌대학_A_린츠_카톨릭대학_지원서, true))), - UniversityApplicantsResponse.of(괌대학_B_지원_정보, - List.of(ApplicantResponse.of(테스트유저_3_괌대학_A_괌대학_B_그라츠공과대학_지원서, false))) - )); + List.of(ApplicantResponse.of(application2, false))) + ); } @Test void 이번_학기_지원자를_대학_국문_이름으로_필터링해서_조회한다() { + //given + Application application1 = applicationFixture.지원서( + user1, + "nickname1", + term, + gpaScore1.getGpa(), + languageTestScore1.getLanguageTest(), + 괌대학_A_지원_정보, + null, + null + ); + Application application2 = applicationFixture.지원서( + user2, + "nickname2", + term, + gpaScore2.getGpa(), + languageTestScore2.getLanguageTest(), + 괌대학_B_지원_정보, + null, + null + ); + applicationFixture.지원서( + user3, + "nickname3", + term, + gpaScore3.getGpa(), + languageTestScore3.getLanguageTest(), + 서던덴마크대학교_지원_정보, + null, + null + ); + // when ApplicationsResponse response = applicationQueryService.getApplicants( - 테스트유저_2, + user1, null, - "일본" + "괌" ); // then - assertThat(response.firstChoice()).containsAll(List.of( - UniversityApplicantsResponse.of(메이지대학_지원_정보, - List.of(ApplicantResponse.of(테스트유저_4_메이지대학_그라츠대학_서던덴마크대학_지원서, false))) - )); - - assertThat(response.secondChoice()).containsAll(List.of( - UniversityApplicantsResponse.of(메이지대학_지원_정보, List.of()) - )); - - assertThat(response.thirdChoice()).containsExactlyInAnyOrder( - UniversityApplicantsResponse.of(메이지대학_지원_정보, - List.of(ApplicantResponse.of(테스트유저_5_네바다주립대학_그라츠공과대학_메이지대학_지원서, false))) + assertThat(response.firstChoice()).containsExactlyInAnyOrder( + UniversityApplicantsResponse.of(괌대학_A_지원_정보, + List.of(ApplicantResponse.of(application1, true))), + UniversityApplicantsResponse.of(괌대학_B_지원_정보, + List.of(ApplicantResponse.of(application2, false))) ); } @Test void 이전_학기_지원자는_조회되지_않는다() { + // given + Application application = applicationFixture.지원서( + user1, + "nickname1", + "1988-1", + gpaScore1.getGpa(), + languageTestScore1.getLanguageTest(), + 괌대학_A_지원_정보, + null, + null + ); + // when ApplicationsResponse response = applicationQueryService.getApplicants( - 테스트유저_1, + user1, "", "" ); // then assertThat(response.firstChoice()).doesNotContainAnyElementsOf(List.of( - UniversityApplicantsResponse.of(네바다주립대학_라스베이거스_지원_정보, - List.of(ApplicantResponse.of(이전학기_지원서, false))) - )); - assertThat(response.secondChoice()).doesNotContainAnyElementsOf(List.of( - UniversityApplicantsResponse.of(그라츠공과대학_지원_정보, - List.of(ApplicantResponse.of(이전학기_지원서, false))) - )); - assertThat(response.thirdChoice()).doesNotContainAnyElementsOf(List.of( - UniversityApplicantsResponse.of(메이지대학_지원_정보, - List.of(ApplicantResponse.of(이전학기_지원서, false))) + UniversityApplicantsResponse.of(괌대학_A_지원_정보, + List.of(ApplicantResponse.of(application, true))) )); } @Test void 동일_유저의_여러_지원서_중_최신_지원서만_조회된다() { // given - Application firstApplication = createApplication(테스트유저_1, 괌대학_A_지원_정보); + Application firstApplication = applicationFixture.지원서( + user1, + "nickname1", + term, + gpaScore1.getGpa(), + languageTestScore1.getLanguageTest(), + 괌대학_A_지원_정보, + null, + null + ); firstApplication.setIsDeleteTrue(); applicationRepository.save(firstApplication); - Application secondApplication = createApplication(테스트유저_1, 네바다주립대학_라스베이거스_지원_정보); - + Application secondApplication = applicationFixture.지원서( + user1, + "nickname2", + term, + gpaScore1.getGpa(), + languageTestScore1.getLanguageTest(), + 괌대학_B_지원_정보, + null, + null + ); // when ApplicationsResponse response = applicationQueryService.getApplicants( - 테스트유저_1, "", ""); + user1, + "", + "" + ); // then assertThat(response.firstChoice().stream() @@ -191,100 +323,99 @@ class 경쟁자_목록_조회_테스트 { @Test void 이번_학기_지원한_대학의_경쟁자_목록을_조회한다() { - // when - ApplicationsResponse response = applicationQueryService.getApplicantsByUserApplications( - 테스트유저_2 + // given + Application application1 = applicationFixture.지원서( + user1, + "nickname1", + term, + gpaScore1.getGpa(), + languageTestScore1.getLanguageTest(), + 괌대학_A_지원_정보, + null, + null + ); + Application application2 = applicationFixture.지원서( + user2, + "nickname2", + term, + gpaScore2.getGpa(), + languageTestScore2.getLanguageTest(), + 괌대학_A_지원_정보, + null, + null + ); + applicationFixture.지원서( + user3, + "nickname3", + term, + gpaScore3.getGpa(), + languageTestScore3.getLanguageTest(), + 괌대학_B_지원_정보, + null, + null ); + // when + ApplicationsResponse response = applicationQueryService.getApplicantsByUserApplications(user1); // then - assertThat(response.firstChoice()).containsAll(List.of( - UniversityApplicantsResponse.of(괌대학_B_지원_정보, - List.of(ApplicantResponse.of(테스트유저_2_괌대학_B_괌대학_A_린츠_카톨릭대학_지원서, true))), - UniversityApplicantsResponse.of(괌대학_A_지원_정보, - List.of(ApplicantResponse.of(테스트유저_3_괌대학_A_괌대학_B_그라츠공과대학_지원서, false))) - )); - - assertThat(response.secondChoice()).containsAll(List.of( - UniversityApplicantsResponse.of(괌대학_A_지원_정보, - List.of(ApplicantResponse.of(테스트유저_2_괌대학_B_괌대학_A_린츠_카톨릭대학_지원서, true))), - UniversityApplicantsResponse.of(괌대학_B_지원_정보, - List.of(ApplicantResponse.of(테스트유저_3_괌대학_A_괌대학_B_그라츠공과대학_지원서, false))) - )); - - assertThat(response.thirdChoice()).containsAll(List.of( - UniversityApplicantsResponse.of(린츠_카톨릭대학_지원_정보, - List.of(ApplicantResponse.of(테스트유저_2_괌대학_B_괌대학_A_린츠_카톨릭대학_지원서, true))) - )); + assertThat(response.firstChoice()).containsExactlyInAnyOrder( + UniversityApplicantsResponse.of(괌대학_A_지원_정보, List.of( + ApplicantResponse.of(application1, true), + ApplicantResponse.of(application2, false) + )) + ); } @Test void 이번_학기_지원한_대학_중_미선택이_있을_때_경쟁자_목록을_조회한다() { - // when - ApplicationsResponse response = applicationQueryService.getApplicantsByUserApplications( - 테스트유저_7 + // given + Application application1 = applicationFixture.지원서( + user1, + "nickname1", + term, + gpaScore1.getGpa(), + languageTestScore1.getLanguageTest(), + 괌대학_A_지원_정보, + null, + null + ); + applicationFixture.지원서( + user2, + "nickname2", + term, + gpaScore2.getGpa(), + languageTestScore2.getLanguageTest(), + null, + 괌대학_B_지원_정보, + null + ); + applicationFixture.지원서( + user3, + "nickname3", + term, + gpaScore3.getGpa(), + languageTestScore3.getLanguageTest(), + null, + null, + 서던덴마크대학교_지원_정보 ); + // when + ApplicationsResponse response = applicationQueryService.getApplicantsByUserApplications(user1); + // then - assertThat(response.firstChoice()).containsAll(List.of( - UniversityApplicantsResponse.of(코펜하겐IT대학_지원_정보, - List.of(ApplicantResponse.of(테스트유저_7_코펜하겐IT대학_X_X_지원서, true))) - )); + assertThat(response.firstChoice()).containsExactlyInAnyOrder( + UniversityApplicantsResponse.of(괌대학_A_지원_정보, + List.of(ApplicantResponse.of(application1, true))) + ); assertThat(response.secondChoice()).containsExactlyInAnyOrder( - UniversityApplicantsResponse.of(코펜하겐IT대학_지원_정보, List.of()) + UniversityApplicantsResponse.of(괌대학_A_지원_정보, List.of()) ); assertThat(response.thirdChoice()).containsExactlyInAnyOrder( - UniversityApplicantsResponse.of(코펜하겐IT대학_지원_정보, List.of()) + UniversityApplicantsResponse.of(괌대학_A_지원_정보, List.of()) ); } - - @Test - void 이번_학기_지원한_대학이_모두_미선택일_때_경쟁자_목록을_조회한다() { - //when - ApplicationsResponse response = applicationQueryService.getApplicantsByUserApplications( - 테스트유저_6 - ); - - // then - assertThat(response.firstChoice()).isEmpty(); - assertThat(response.secondChoice()).isEmpty(); - assertThat(response.thirdChoice()).isEmpty(); - } - } - - private GpaScore createApprovedGpaScore(SiteUser siteUser) { - GpaScore gpaScore = new GpaScore( - new Gpa(4.0, 4.5, "/gpa-report.pdf"), - siteUser - ); - gpaScore.setVerifyStatus(VerifyStatus.APPROVED); - return gpaScoreRepository.save(gpaScore); - } - - private LanguageTestScore createApprovedLanguageTestScore(SiteUser siteUser) { - LanguageTestScore languageTestScore = new LanguageTestScore( - new LanguageTest(LanguageTestType.TOEIC, "100", "/gpa-report.pdf"), - siteUser - ); - languageTestScore.setVerifyStatus(VerifyStatus.APPROVED); - return languageTestScoreRepository.save(languageTestScore); - } - - private Application createApplication( - SiteUser siteUser, - UniversityInfoForApply universityInfoForApply) { - Application application = new Application( - siteUser, - createApprovedGpaScore(siteUser).getGpa(), - createApprovedLanguageTestScore(siteUser).getLanguageTest(), - term, - universityInfoForApply, - null, - null, - null - ); - application.setVerifyStatus(VerifyStatus.APPROVED); - return applicationRepository.save(application); } } diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java index 3c107424a..f70cd9fc0 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java @@ -1,8 +1,6 @@ package com.example.solidconnection.application.service; import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.application.domain.Gpa; -import com.example.solidconnection.application.domain.LanguageTest; import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.application.dto.ApplicationSubmissionResponse; import com.example.solidconnection.application.dto.ApplyRequest; @@ -11,14 +9,18 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.domain.LanguageTestScore; -import com.example.solidconnection.score.repository.GpaScoreRepository; -import com.example.solidconnection.score.repository.LanguageTestScoreRepository; +import com.example.solidconnection.score.fixture.GpaScoreFixture; +import com.example.solidconnection.score.fixture.LanguageTestScoreFixture; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.support.integration.BaseIntegrationTest; -import com.example.solidconnection.university.domain.LanguageTestType; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import static com.example.solidconnection.application.service.ApplicationSubmissionService.APPLICATION_UPDATE_COUNT_LIMIT; import static com.example.solidconnection.common.exception.ErrorCode.APPLY_UPDATE_LIMIT_EXCEED; @@ -28,8 +30,9 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; +@TestContainerSpringBootTest @DisplayName("지원서 제출 서비스 테스트") -class ApplicationSubmissionServiceTest extends BaseIntegrationTest { +class ApplicationSubmissionServiceTest { @Autowired private ApplicationSubmissionService applicationSubmissionService; @@ -38,48 +41,65 @@ class ApplicationSubmissionServiceTest extends BaseIntegrationTest { private ApplicationRepository applicationRepository; @Autowired - private GpaScoreRepository gpaScoreRepository; + private SiteUserFixture siteUserFixture; @Autowired - private LanguageTestScoreRepository languageTestScoreRepository; + private UniversityInfoForApplyFixture universityInfoForApplyFixture; + + @Autowired + private GpaScoreFixture gpaScoreFixture; + + @Autowired + private LanguageTestScoreFixture languageTestScoreFixture; + + @Value("${university.term}") + private String term; + + private SiteUser user; + private UniversityInfoForApply 괌대학_A_지원_정보; + private UniversityInfoForApply 괌대학_B_지원_정보; + private UniversityInfoForApply 서던덴마크대학교_지원_정보; + + @BeforeEach + void setUp() { + user = siteUserFixture.사용자(); + 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); + 괌대학_B_지원_정보 = universityInfoForApplyFixture.괌대학_B_지원_정보(); + 서던덴마크대학교_지원_정보 = universityInfoForApplyFixture.서던덴마크대학교_지원_정보(); + } @Test void 정상적으로_지원서를_제출한다() { // given - GpaScore gpaScore = createApprovedGpaScore(테스트유저_1); - LanguageTestScore languageTestScore = createApprovedLanguageTestScore(테스트유저_1); + GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user); + LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user); UniversityChoiceRequest universityChoiceRequest = new UniversityChoiceRequest( 괌대학_A_지원_정보.getId(), - 네바다주립대학_라스베이거스_지원_정보.getId(), - 메모리얼대학_세인트존스_A_지원_정보.getId() + 괌대학_B_지원_정보.getId(), + 서던덴마크대학교_지원_정보.getId() ); ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), universityChoiceRequest); // when - ApplicationSubmissionResponse response = applicationSubmissionService.apply(테스트유저_1, request); + ApplicationSubmissionResponse response = applicationSubmissionService.apply(user, request); // then - Application savedApplication = applicationRepository.findBySiteUserAndTerm(테스트유저_1, term).orElseThrow(); + Application savedApplication = applicationRepository.findBySiteUserAndTerm(user, term).orElseThrow(); assertAll( () -> assertThat(response.applyCount()).isEqualTo(savedApplication.getUpdateCount()), - () -> assertThat(savedApplication.getGpa()).isEqualTo(gpaScore.getGpa()), - () -> assertThat(savedApplication.getLanguageTest()).isEqualTo(languageTestScore.getLanguageTest()), () -> assertThat(savedApplication.getVerifyStatus()).isEqualTo(VerifyStatus.APPROVED), - () -> assertThat(savedApplication.getNicknameForApply()).isNotNull(), - () -> assertThat(savedApplication.getTerm()).isEqualTo(term), () -> assertThat(savedApplication.isDelete()).isFalse(), () -> assertThat(savedApplication.getFirstChoiceUniversity().getId()).isEqualTo(괌대학_A_지원_정보.getId()), - () -> assertThat(savedApplication.getSecondChoiceUniversity().getId()).isEqualTo(네바다주립대학_라스베이거스_지원_정보.getId()), - () -> assertThat(savedApplication.getThirdChoiceUniversity().getId()).isEqualTo(메모리얼대학_세인트존스_A_지원_정보.getId()), - () -> assertThat(savedApplication.getSiteUser().getId()).isEqualTo(테스트유저_1.getId()) + () -> assertThat(savedApplication.getSecondChoiceUniversity().getId()).isEqualTo(괌대학_B_지원_정보.getId()), + () -> assertThat(savedApplication.getThirdChoiceUniversity().getId()).isEqualTo(서던덴마크대학교_지원_정보.getId()) ); } @Test void 미승인된_GPA_성적으로_지원하면_예외_응답을_반환한다() { // given - GpaScore gpaScore = createUnapprovedGpaScore(테스트유저_1); - LanguageTestScore languageTestScore = createApprovedLanguageTestScore(테스트유저_1); + GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.PENDING, user); + LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user); UniversityChoiceRequest universityChoiceRequest = new UniversityChoiceRequest( 괌대학_A_지원_정보.getId(), null, @@ -89,7 +109,7 @@ class ApplicationSubmissionServiceTest extends BaseIntegrationTest { // when & then assertThatCode(() -> - applicationSubmissionService.apply(테스트유저_1, request) + applicationSubmissionService.apply(user, request) ) .isInstanceOf(CustomException.class) .hasMessage(INVALID_GPA_SCORE_STATUS.getMessage()); @@ -98,8 +118,8 @@ class ApplicationSubmissionServiceTest extends BaseIntegrationTest { @Test void 미승인된_어학성적으로_지원하면_예외_응답을_반환한다() { // given - GpaScore gpaScore = createApprovedGpaScore(테스트유저_1); - LanguageTestScore languageTestScore = createUnapprovedLanguageTestScore(테스트유저_1); + GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user); + LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.PENDING, user); UniversityChoiceRequest universityChoiceRequest = new UniversityChoiceRequest( 괌대학_A_지원_정보.getId(), null, @@ -109,7 +129,7 @@ class ApplicationSubmissionServiceTest extends BaseIntegrationTest { // when & then assertThatCode(() -> - applicationSubmissionService.apply(테스트유저_1, request) + applicationSubmissionService.apply(user, request) ) .isInstanceOf(CustomException.class) .hasMessage(INVALID_LANGUAGE_TEST_SCORE_STATUS.getMessage()); @@ -118,8 +138,8 @@ class ApplicationSubmissionServiceTest extends BaseIntegrationTest { @Test void 지원서_수정_횟수를_초과하면_예외_응답을_반환한다() { // given - GpaScore gpaScore = createApprovedGpaScore(테스트유저_1); - LanguageTestScore languageTestScore = createApprovedLanguageTestScore(테스트유저_1); + GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user); + LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user); UniversityChoiceRequest universityChoiceRequest = new UniversityChoiceRequest( 괌대학_A_지원_정보.getId(), null, @@ -128,48 +148,14 @@ class ApplicationSubmissionServiceTest extends BaseIntegrationTest { ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), universityChoiceRequest); for (int i = 0; i < APPLICATION_UPDATE_COUNT_LIMIT; i++) { - applicationSubmissionService.apply(테스트유저_1, request); + applicationSubmissionService.apply(user, request); } // when & then assertThatCode(() -> - applicationSubmissionService.apply(테스트유저_1, request) + applicationSubmissionService.apply(user, request) ) .isInstanceOf(CustomException.class) .hasMessage(APPLY_UPDATE_LIMIT_EXCEED.getMessage()); } - - private GpaScore createUnapprovedGpaScore(SiteUser siteUser) { - GpaScore gpaScore = new GpaScore( - new Gpa(4.0, 4.5, "/gpa-report.pdf"), - siteUser - ); - return gpaScoreRepository.save(gpaScore); - } - - private GpaScore createApprovedGpaScore(SiteUser siteUser) { - GpaScore gpaScore = new GpaScore( - new Gpa(4.0, 4.5, "/gpa-report.pdf"), - siteUser - ); - gpaScore.setVerifyStatus(VerifyStatus.APPROVED); - return gpaScoreRepository.save(gpaScore); - } - - private LanguageTestScore createUnapprovedLanguageTestScore(SiteUser siteUser) { - LanguageTestScore languageTestScore = new LanguageTestScore( - new LanguageTest(LanguageTestType.TOEIC, "100", "/gpa-report.pdf"), - siteUser - ); - return languageTestScoreRepository.save(languageTestScore); - } - - private LanguageTestScore createApprovedLanguageTestScore(SiteUser siteUser) { - LanguageTestScore languageTestScore = new LanguageTestScore( - new LanguageTest(LanguageTestType.TOEIC, "100", "/gpa-report.pdf"), - siteUser - ); - languageTestScore.setVerifyStatus(VerifyStatus.APPROVED); - return languageTestScoreRepository.save(languageTestScore); - } } From 3d5820c70808481eb9718e9b7bcf96f060c5522b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Wed, 18 Jun 2025 12:50:29 +0900 Subject: [PATCH 21/90] =?UTF-8?q?refactor:=20=EC=BB=A4=EB=AE=A4=EB=8B=88?= =?UTF-8?q?=ED=8B=B0=20=EA=B4=80=EB=A0=A8=20=ED=86=B5=ED=95=A9=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20fixture=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#3?= =?UTF-8?q?35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: BoardFixture에 Board 생성 메서드 추가 - 지연로딩 문제를 방지하기 위해 Board 조회 시 fetch join 사용 * feat: PostFixture에 Post 생성 메서드 추가 * feat: PostImageFixture에 PostImage 생성 메서드 추가 * feat: CommentFixture에 Comment 생성 메서드 추가 * refactor: Community 관련 데이터 fixture 메서드로 변경 * refactor: BaseIntegrationTest 제거 * refactor: BoardFixture에서 BoardCode enum 상수 일관되게 사용 --- .../community/board/fixture/BoardFixture.java | 41 ++ .../board/fixture/BoardFixtureBuilder.java | 31 ++ .../repository/BoardRepositoryForTest.java | 14 + .../comment/fixture/CommentFixture.java | 35 ++ .../fixture/CommentFixtureBuilder.java | 53 ++ .../comment/service/CommentServiceTest.java | 221 +++----- .../community/post/fixture/PostFixture.java | 35 ++ .../post/fixture/PostFixtureBuilder.java | 77 +++ .../post/fixture/PostImageFixture.java | 20 + .../post/fixture/PostImageFixtureBuilder.java | 33 ++ .../post/service/PostCommandServiceTest.java | 123 ++-- .../post/service/PostLikeServiceTest.java | 68 +-- .../post/service/PostQueryServiceTest.java | 150 +++-- .../integration/BaseIntegrationTest.java | 527 ------------------ 14 files changed, 565 insertions(+), 863 deletions(-) create mode 100644 src/test/java/com/example/solidconnection/community/board/fixture/BoardFixture.java create mode 100644 src/test/java/com/example/solidconnection/community/board/fixture/BoardFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/community/board/repository/BoardRepositoryForTest.java create mode 100644 src/test/java/com/example/solidconnection/community/comment/fixture/CommentFixture.java create mode 100644 src/test/java/com/example/solidconnection/community/comment/fixture/CommentFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/community/post/fixture/PostFixture.java create mode 100644 src/test/java/com/example/solidconnection/community/post/fixture/PostFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/community/post/fixture/PostImageFixture.java create mode 100644 src/test/java/com/example/solidconnection/community/post/fixture/PostImageFixtureBuilder.java delete mode 100644 src/test/java/com/example/solidconnection/support/integration/BaseIntegrationTest.java diff --git a/src/test/java/com/example/solidconnection/community/board/fixture/BoardFixture.java b/src/test/java/com/example/solidconnection/community/board/fixture/BoardFixture.java new file mode 100644 index 000000000..16dc920d2 --- /dev/null +++ b/src/test/java/com/example/solidconnection/community/board/fixture/BoardFixture.java @@ -0,0 +1,41 @@ +package com.example.solidconnection.community.board.fixture; + +import com.example.solidconnection.community.board.domain.Board; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +import static com.example.solidconnection.community.board.domain.BoardCode.AMERICAS; +import static com.example.solidconnection.community.board.domain.BoardCode.ASIA; +import static com.example.solidconnection.community.board.domain.BoardCode.EUROPE; +import static com.example.solidconnection.community.board.domain.BoardCode.FREE; + +@TestComponent +@RequiredArgsConstructor +public class BoardFixture { + + private final BoardFixtureBuilder boardFixtureBuilder; + + public Board 미주권() { + return boardFixtureBuilder.code(AMERICAS.name()) + .koreanName("미주권") + .findOrCreate(); + } + + public Board 아시아권() { + return boardFixtureBuilder.code(ASIA.name()) + .koreanName("아시아권") + .findOrCreate(); + } + + public Board 유럽권() { + return boardFixtureBuilder.code(EUROPE.name()) + .koreanName("유럽권") + .findOrCreate(); + } + + public Board 자유게시판() { + return boardFixtureBuilder.code(FREE.name()) + .koreanName("자유게시판") + .findOrCreate(); + } +} diff --git a/src/test/java/com/example/solidconnection/community/board/fixture/BoardFixtureBuilder.java b/src/test/java/com/example/solidconnection/community/board/fixture/BoardFixtureBuilder.java new file mode 100644 index 000000000..1b7eff6ba --- /dev/null +++ b/src/test/java/com/example/solidconnection/community/board/fixture/BoardFixtureBuilder.java @@ -0,0 +1,31 @@ +package com.example.solidconnection.community.board.fixture; + +import com.example.solidconnection.community.board.domain.Board; +import com.example.solidconnection.community.board.repository.BoardRepositoryForTest; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class BoardFixtureBuilder { + + private final BoardRepositoryForTest boardRepositoryForTest; + + private String code; + private String koreanName; + + public BoardFixtureBuilder code(String code) { + this.code = code; + return this; + } + + public BoardFixtureBuilder koreanName(String koreanName) { + this.koreanName = koreanName; + return this; + } + + public Board findOrCreate() { + return boardRepositoryForTest.findByCodeWithPosts(code) + .orElseGet(() -> boardRepositoryForTest.save(new Board(code, koreanName))); + } +} diff --git a/src/test/java/com/example/solidconnection/community/board/repository/BoardRepositoryForTest.java b/src/test/java/com/example/solidconnection/community/board/repository/BoardRepositoryForTest.java new file mode 100644 index 000000000..0d94e8d41 --- /dev/null +++ b/src/test/java/com/example/solidconnection/community/board/repository/BoardRepositoryForTest.java @@ -0,0 +1,14 @@ +package com.example.solidconnection.community.board.repository; + +import com.example.solidconnection.community.board.domain.Board; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; + +public interface BoardRepositoryForTest extends JpaRepository { + + @Query("SELECT b FROM Board b LEFT JOIN FETCH b.postList WHERE b.code = :code") + Optional findByCodeWithPosts(@Param("code") String code); +} diff --git a/src/test/java/com/example/solidconnection/community/comment/fixture/CommentFixture.java b/src/test/java/com/example/solidconnection/community/comment/fixture/CommentFixture.java new file mode 100644 index 000000000..4d0f3b438 --- /dev/null +++ b/src/test/java/com/example/solidconnection/community/comment/fixture/CommentFixture.java @@ -0,0 +1,35 @@ +package com.example.solidconnection.community.comment.fixture; + +import com.example.solidconnection.community.comment.domain.Comment; +import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.siteuser.domain.SiteUser; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class CommentFixture { + + private final CommentFixtureBuilder commentFixtureBuilder; + + public Comment 부모_댓글(String content, Post post, SiteUser siteUser) { + return commentFixtureBuilder + .content(content) + .post(post) + .siteUser(siteUser) + .createParent(); + } + + public Comment 자식_댓글( + String content, + Post post, + SiteUser siteUser, + Comment parentComment) { + return commentFixtureBuilder + .content(content) + .post(post) + .siteUser(siteUser) + .parentComment(parentComment) + .createChild(); + } +} diff --git a/src/test/java/com/example/solidconnection/community/comment/fixture/CommentFixtureBuilder.java b/src/test/java/com/example/solidconnection/community/comment/fixture/CommentFixtureBuilder.java new file mode 100644 index 000000000..f5dc10ac2 --- /dev/null +++ b/src/test/java/com/example/solidconnection/community/comment/fixture/CommentFixtureBuilder.java @@ -0,0 +1,53 @@ +package com.example.solidconnection.community.comment.fixture; + +import com.example.solidconnection.community.comment.domain.Comment; +import com.example.solidconnection.community.comment.repository.CommentRepository; +import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.siteuser.domain.SiteUser; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class CommentFixtureBuilder { + + private final CommentRepository commentRepository; + + private String content; + private Post post; + private SiteUser siteUser; + private Comment parentComment; + + public CommentFixtureBuilder content(String content) { + this.content = content; + return this; + } + + public CommentFixtureBuilder post(Post post) { + this.post = post; + return this; + } + + public CommentFixtureBuilder siteUser(SiteUser siteUser) { + this.siteUser = siteUser; + return this; + } + + public CommentFixtureBuilder parentComment(Comment parentComment) { + this.parentComment = parentComment; + return this; + } + + public Comment createParent() { + Comment comment = new Comment(content); + comment.setPostAndSiteUser(post, siteUser); + return commentRepository.save(comment); + } + + public Comment createChild() { + Comment comment = new Comment(content); + comment.setPostAndSiteUser(post, siteUser); + comment.setParentCommentAndPostAndSiteUser(parentComment, post, siteUser); + return commentRepository.save(comment); + } +} diff --git a/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java b/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java index 17309e100..8e5e5c995 100644 --- a/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.community.comment.service; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.community.board.domain.Board; +import com.example.solidconnection.community.board.fixture.BoardFixture; import com.example.solidconnection.community.comment.domain.Comment; import com.example.solidconnection.community.comment.dto.CommentCreateRequest; import com.example.solidconnection.community.comment.dto.CommentCreateResponse; @@ -9,13 +9,16 @@ import com.example.solidconnection.community.comment.dto.CommentUpdateRequest; import com.example.solidconnection.community.comment.dto.CommentUpdateResponse; import com.example.solidconnection.community.comment.dto.PostFindCommentResponse; +import com.example.solidconnection.community.comment.fixture.CommentFixture; import com.example.solidconnection.community.comment.repository.CommentRepository; import com.example.solidconnection.community.post.domain.Post; import com.example.solidconnection.community.post.domain.PostCategory; -import com.example.solidconnection.community.post.repository.PostRepository; +import com.example.solidconnection.community.post.fixture.PostFixture; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.support.integration.BaseIntegrationTest; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; import jakarta.transaction.Transactional; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -31,8 +34,9 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; +@TestContainerSpringBootTest @DisplayName("댓글 서비스 테스트") -class CommentServiceTest extends BaseIntegrationTest { +class CommentServiceTest { @Autowired private CommentService commentService; @@ -41,7 +45,34 @@ class CommentServiceTest extends BaseIntegrationTest { private CommentRepository commentRepository; @Autowired - private PostRepository postRepository; + private SiteUserFixture siteUserFixture; + + @Autowired + private BoardFixture boardFixture; + + @Autowired + private PostFixture postFixture; + + @Autowired + private CommentFixture commentFixture; + + private SiteUser user1; + private SiteUser user2; + private Post post; + + @BeforeEach + void setUp() { + user1 = siteUserFixture.사용자(1, "test1"); + user2 = siteUserFixture.사용자(2, "test2"); + post = postFixture.게시글( + "제목1", + "내용1", + false, + PostCategory.자유, + boardFixture.자유게시판(), + user1 + ); + } @Nested class 댓글_조회_테스트 { @@ -49,16 +80,12 @@ class 댓글_조회_테스트 { @Test void 게시글의_모든_댓글을_조회한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); - Comment parentComment = createComment(testPost, 테스트유저_1, "부모 댓글"); - Comment childComment = createChildComment(testPost, 테스트유저_2, parentComment, "자식 댓글"); + Comment parentComment = commentFixture.부모_댓글("부모 댓글", post, user1); + Comment childComment = commentFixture.자식_댓글("자식 댓글 1", post, user2, parentComment); List comments = List.of(parentComment, childComment); // when - List responses = commentService.findCommentsByPostId( - 테스트유저_1, - testPost.getId() - ); + List responses = commentService.findCommentsByPostId(user1, post.getId()); // then assertAll( @@ -69,17 +96,7 @@ class 댓글_조회_테스트 { .satisfies(response -> assertAll( () -> assertThat(response.id()).isEqualTo(parentComment.getId()), () -> assertThat(response.parentId()).isNull(), - () -> assertThat(response.content()).isEqualTo(parentComment.getContent()), - () -> assertThat(response.isOwner()).isTrue(), - () -> assertThat(response.createdAt()).isEqualTo(parentComment.getCreatedAt()), - () -> assertThat(response.updatedAt()).isEqualTo(parentComment.getUpdatedAt()), - - () -> assertThat(response.postFindSiteUserResponse().id()) - .isEqualTo(parentComment.getSiteUser().getId()), - () -> assertThat(response.postFindSiteUserResponse().nickname()) - .isEqualTo(parentComment.getSiteUser().getNickname()), - () -> assertThat(response.postFindSiteUserResponse().profileImageUrl()) - .isEqualTo(parentComment.getSiteUser().getProfileImageUrl()) + () -> assertThat(response.isOwner()).isTrue() )), () -> assertThat(responses) .filteredOn(response -> response.id().equals(childComment.getId())) @@ -87,17 +104,7 @@ class 댓글_조회_테스트 { .satisfies(response -> assertAll( () -> assertThat(response.id()).isEqualTo(childComment.getId()), () -> assertThat(response.parentId()).isEqualTo(parentComment.getId()), - () -> assertThat(response.content()).isEqualTo(childComment.getContent()), - () -> assertThat(response.isOwner()).isFalse(), - () -> assertThat(response.createdAt()).isEqualTo(childComment.getCreatedAt()), - () -> assertThat(response.updatedAt()).isEqualTo(childComment.getUpdatedAt()), - - () -> assertThat(response.postFindSiteUserResponse().id()) - .isEqualTo(childComment.getSiteUser().getId()), - () -> assertThat(response.postFindSiteUserResponse().nickname()) - .isEqualTo(childComment.getSiteUser().getNickname()), - () -> assertThat(response.postFindSiteUserResponse().profileImageUrl()) - .isEqualTo(childComment.getSiteUser().getProfileImageUrl()) + () -> assertThat(response.isOwner()).isFalse() )) ); } @@ -109,14 +116,10 @@ class 댓글_생성_테스트 { @Test void 댓글을_성공적으로_생성한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); - CommentCreateRequest request = new CommentCreateRequest(testPost.getId(), "테스트 댓글", null); + CommentCreateRequest request = new CommentCreateRequest(post.getId(),"댓글", null); // when - CommentCreateResponse response = commentService.createComment( - 테스트유저_1, - request - ); + CommentCreateResponse response = commentService.createComment(user1, request); // then Comment savedComment = commentRepository.findById(response.id()).orElseThrow(); @@ -124,23 +127,19 @@ class 댓글_생성_테스트 { () -> assertThat(savedComment.getId()).isEqualTo(response.id()), () -> assertThat(savedComment.getContent()).isEqualTo(request.content()), () -> assertThat(savedComment.getParentComment()).isNull(), - () -> assertThat(savedComment.getPost().getId()).isEqualTo(testPost.getId()), - () -> assertThat(savedComment.getSiteUser().getId()).isEqualTo(테스트유저_1.getId()) + () -> assertThat(savedComment.getPost().getId()).isEqualTo(post.getId()), + () -> assertThat(savedComment.getSiteUser().getId()).isEqualTo(user1.getId()) ); } @Test void 대댓글을_성공적으로_생성한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); - Comment parentComment = createComment(testPost, 테스트유저_1, "부모 댓글"); - CommentCreateRequest request = new CommentCreateRequest(testPost.getId(), "테스트 대댓글", parentComment.getId()); + Comment parentComment = commentFixture.부모_댓글("부모 댓글", post, user1); + CommentCreateRequest request = new CommentCreateRequest(post.getId(), "자식 댓글", parentComment.getId()); // when - CommentCreateResponse response = commentService.createComment( - 테스트유저_2, - request - ); + CommentCreateResponse response = commentService.createComment(user2, request); // then Comment savedComment = commentRepository.findById(response.id()).orElseThrow(); @@ -148,23 +147,22 @@ class 댓글_생성_테스트 { () -> assertThat(savedComment.getId()).isEqualTo(response.id()), () -> assertThat(savedComment.getContent()).isEqualTo(request.content()), () -> assertThat(savedComment.getParentComment().getId()).isEqualTo(parentComment.getId()), - () -> assertThat(savedComment.getPost().getId()).isEqualTo(testPost.getId()), - () -> assertThat(savedComment.getSiteUser().getId()).isEqualTo(테스트유저_2.getId()) + () -> assertThat(savedComment.getPost().getId()).isEqualTo(post.getId()), + () -> assertThat(savedComment.getSiteUser().getId()).isEqualTo(user2.getId()) ); } @Test void 대대댓글_생성_시도하면_예외_응답을_반환한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); - Comment parentComment = createComment(testPost, 테스트유저_1, "부모 댓글"); - Comment childComment = createChildComment(testPost, 테스트유저_2, parentComment, "자식 댓글"); - CommentCreateRequest request = new CommentCreateRequest(testPost.getId(), "테스트 대대댓글", childComment.getId()); + Comment parentComment = commentFixture.부모_댓글("부모 댓글", post, user1); + Comment childComment = commentFixture.자식_댓글("자식 댓글", post, user2, parentComment); + CommentCreateRequest request = new CommentCreateRequest(post.getId(), "대대댓글", childComment.getId()); // when & then assertThatThrownBy(() -> commentService.createComment( - 테스트유저_1, + user1, request )) .isInstanceOf(CustomException.class) @@ -174,14 +172,13 @@ class 댓글_생성_테스트 { @Test void 존재하지_않는_부모댓글로_대댓글_작성시_예외를_반환한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); long invalidCommentId = 9999L; - CommentCreateRequest request = new CommentCreateRequest(testPost.getId(), "테스트 대댓글", invalidCommentId); + CommentCreateRequest request = new CommentCreateRequest(post.getId(), "자식 댓글", invalidCommentId); // when & then assertThatThrownBy(() -> commentService.createComment( - 테스트유저_1, + user1, request )) .isInstanceOf(CustomException.class) @@ -195,16 +192,11 @@ class 댓글_수정_테스트 { @Test void 댓글을_성공적으로_수정한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); - Comment comment = createComment(testPost, 테스트유저_1, "원본 댓글"); + Comment comment = commentFixture.부모_댓글("원본 댓글", post, user1); CommentUpdateRequest request = new CommentUpdateRequest("수정된 댓글"); // when - CommentUpdateResponse response = commentService.updateComment( - 테스트유저_1, - comment.getId(), - request - ); + CommentUpdateResponse response = commentService.updateComment(user1, comment.getId(), request); // then Comment updatedComment = commentRepository.findById(response.id()).orElseThrow(); @@ -212,22 +204,21 @@ class 댓글_수정_테스트 { () -> assertThat(updatedComment.getId()).isEqualTo(comment.getId()), () -> assertThat(updatedComment.getContent()).isEqualTo(request.content()), () -> assertThat(updatedComment.getParentComment()).isNull(), - () -> assertThat(updatedComment.getPost().getId()).isEqualTo(testPost.getId()), - () -> assertThat(updatedComment.getSiteUser().getId()).isEqualTo(테스트유저_1.getId()) + () -> assertThat(updatedComment.getPost().getId()).isEqualTo(post.getId()), + () -> assertThat(updatedComment.getSiteUser().getId()).isEqualTo(user1.getId()) ); } @Test void 다른_사용자의_댓글을_수정하면_예외_응답을_반환한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); - Comment comment = createComment(testPost, 테스트유저_1, "원본 댓글"); + Comment comment = commentFixture.부모_댓글("원본 댓글", post, user1); CommentUpdateRequest request = new CommentUpdateRequest("수정된 댓글"); // when & then assertThatThrownBy(() -> commentService.updateComment( - 테스트유저_2, + user2, comment.getId(), request )) @@ -238,14 +229,13 @@ class 댓글_수정_테스트 { @Test void 삭제된_댓글을_수정하면_예외_응답을_반환한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); - Comment comment = createComment(testPost, 테스트유저_1, null); + Comment comment = commentFixture.부모_댓글(null, post, user1); CommentUpdateRequest request = new CommentUpdateRequest("수정된 댓글"); // when & then assertThatThrownBy(() -> commentService.updateComment( - 테스트유저_1, + user1, comment.getId(), request )) @@ -261,22 +251,18 @@ class 댓글_삭제_테스트 { @Transactional void 대댓글이_없는_댓글을_삭제한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); - Comment comment = createComment(testPost, 테스트유저_1, "테스트 댓글"); - List comments = testPost.getCommentList(); + Comment comment = commentFixture.부모_댓글("부모 댓글", post, user1); + List comments = post.getCommentList(); int expectedCommentsCount = comments.size() - 1; // when - CommentDeleteResponse response = commentService.deleteCommentById( - 테스트유저_1, - comment.getId() - ); + CommentDeleteResponse response = commentService.deleteCommentById(user1, comment.getId()); // then assertAll( () -> assertThat(response.id()).isEqualTo(comment.getId()), () -> assertThat(commentRepository.findById(comment.getId())).isEmpty(), - () -> assertThat(testPost.getCommentList()).hasSize(expectedCommentsCount) + () -> assertThat(post.getCommentList()).hasSize(expectedCommentsCount) ); } @@ -284,17 +270,13 @@ class 댓글_삭제_테스트 { @Transactional void 대댓글이_있는_댓글을_삭제하면_내용만_삭제된다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); - Comment parentComment = createComment(testPost, 테스트유저_1, "부모 댓글"); - Comment childComment = createChildComment(testPost, 테스트유저_2, parentComment, "자식 댓글"); - List comments = testPost.getCommentList(); + Comment parentComment = commentFixture.부모_댓글("부모 댓글", post, user1); + Comment childComment = commentFixture.자식_댓글("자식 댓글", post, user2, parentComment); + List comments = post.getCommentList(); List childComments = parentComment.getCommentList(); // when - CommentDeleteResponse response = commentService.deleteCommentById( - 테스트유저_1, - parentComment.getId() - ); + CommentDeleteResponse response = commentService.deleteCommentById(user1, parentComment.getId()); // then Comment deletedComment = commentRepository.findById(response.id()).orElseThrow(); @@ -303,7 +285,7 @@ class 댓글_삭제_테스트 { () -> assertThat(deletedComment.getCommentList()) .extracting(Comment::getId) .containsExactlyInAnyOrder(childComment.getId()), - () -> assertThat(testPost.getCommentList()).hasSize(comments.size()), + () -> assertThat(post.getCommentList()).hasSize(comments.size()), () -> assertThat(deletedComment.getCommentList()).hasSize(childComments.size()) ); } @@ -312,18 +294,14 @@ class 댓글_삭제_테스트 { @Transactional void 대댓글을_삭제하면_부모댓글이_삭제되지_않는다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); - Comment parentComment = createComment(testPost, 테스트유저_1, "부모 댓글"); - Comment childComment1 = createChildComment(testPost, 테스트유저_2, parentComment, "자식 댓글 1"); - Comment childComment2 = createChildComment(testPost, 테스트유저_2, parentComment, "자식 댓글 2"); + Comment parentComment = commentFixture.부모_댓글("부모 댓글", post, user1); + Comment childComment1 = commentFixture.자식_댓글("자식 댓글1", post, user2, parentComment); + Comment childComment2 = commentFixture.자식_댓글("자식 댓글2", post, user2, parentComment); List childComments = parentComment.getCommentList(); int expectedChildCommentsCount = childComments.size() - 1; // when - CommentDeleteResponse response = commentService.deleteCommentById( - 테스트유저_2, - childComment1.getId() - ); + CommentDeleteResponse response = commentService.deleteCommentById(user2, childComment1.getId()); // then Comment remainingParentComment = commentRepository.findById(parentComment.getId()).orElseThrow(); @@ -342,67 +320,36 @@ class 댓글_삭제_테스트 { @Transactional void 대댓글을_삭제하고_부모댓글이_삭제된_상태면_부모댓글도_삭제된다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); - Comment parentComment = createComment(testPost, 테스트유저_1, "부모 댓글"); - Comment childComment = createChildComment(testPost, 테스트유저_2, parentComment, "자식 댓글"); - List comments = testPost.getCommentList(); + Comment parentComment = commentFixture.부모_댓글("부모 댓글", post, user1); + Comment childComment = commentFixture.자식_댓글("자식 댓글", post, user2, parentComment); + List comments = post.getCommentList(); int expectedCommentsCount = comments.size() - 2; parentComment.deprecateComment(); // when - CommentDeleteResponse response = commentService.deleteCommentById( - 테스트유저_2, - childComment.getId() - ); + CommentDeleteResponse response = commentService.deleteCommentById(user2, childComment.getId()); // then assertAll( () -> assertThat(commentRepository.findById(response.id())).isEmpty(), () -> assertThat(commentRepository.findById(parentComment.getId())).isEmpty(), - () -> assertThat(testPost.getCommentList()).hasSize(expectedCommentsCount) + () -> assertThat(post.getCommentList()).hasSize(expectedCommentsCount) ); } @Test void 다른_사용자의_댓글을_삭제하면_예외_응답을_반환한다() { // given - Post testPost = createPost(자유게시판, 테스트유저_1); - Comment comment = createComment(testPost, 테스트유저_1, "테스트 댓글"); + Comment comment = commentFixture.부모_댓글("부모 댓글", post, user1); // when & then assertThatThrownBy(() -> commentService.deleteCommentById( - 테스트유저_2, + user2, comment.getId() )) .isInstanceOf(CustomException.class) .hasMessage(INVALID_POST_ACCESS.getMessage()); } } - - private Post createPost(Board board, SiteUser siteUser) { - Post post = new Post( - "테스트 제목", - "테스트 내용", - false, - 0L, - 0L, - PostCategory.자유 - ); - post.setBoardAndSiteUser(board, siteUser); - return postRepository.save(post); - } - - private Comment createComment(Post post, SiteUser siteUser, String content) { - Comment comment = new Comment(content); - comment.setPostAndSiteUser(post, siteUser); - return commentRepository.save(comment); - } - - private Comment createChildComment(Post post, SiteUser siteUser, Comment parentComment, String content) { - Comment comment = new Comment(content); - comment.setPostAndSiteUser(post, siteUser); - comment.setParentCommentAndPostAndSiteUser(parentComment, post, siteUser); - return commentRepository.save(comment); - } } diff --git a/src/test/java/com/example/solidconnection/community/post/fixture/PostFixture.java b/src/test/java/com/example/solidconnection/community/post/fixture/PostFixture.java new file mode 100644 index 000000000..92fa64a37 --- /dev/null +++ b/src/test/java/com/example/solidconnection/community/post/fixture/PostFixture.java @@ -0,0 +1,35 @@ +package com.example.solidconnection.community.post.fixture; + +import com.example.solidconnection.community.board.domain.Board; +import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.community.post.domain.PostCategory; +import com.example.solidconnection.siteuser.domain.SiteUser; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class PostFixture { + + private final PostFixtureBuilder postFixtureBuilder; + + public Post 게시글( + String title, + String content, + Boolean isQuestion, + PostCategory postCategory, + Board board, + SiteUser siteUser + ) { + return postFixtureBuilder + .title(title) + .content(content) + .isQuestion(isQuestion) + .likeCount(0L) + .viewCount(0L) + .postCategory(postCategory) + .board(board) + .siteUser(siteUser) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/community/post/fixture/PostFixtureBuilder.java b/src/test/java/com/example/solidconnection/community/post/fixture/PostFixtureBuilder.java new file mode 100644 index 000000000..ac3440b42 --- /dev/null +++ b/src/test/java/com/example/solidconnection/community/post/fixture/PostFixtureBuilder.java @@ -0,0 +1,77 @@ +package com.example.solidconnection.community.post.fixture; + +import com.example.solidconnection.community.board.domain.Board; +import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.community.post.domain.PostCategory; +import com.example.solidconnection.community.post.repository.PostRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class PostFixtureBuilder { + + private final PostRepository postRepository; + + private String title; + private String content; + private Boolean isQuestion; + private Long likeCount; + private Long viewCount; + private PostCategory postCategory; + private Board board; + private SiteUser siteUser; + + public PostFixtureBuilder title(String title) { + this.title = title; + return this; + } + + public PostFixtureBuilder content(String content) { + this.content = content; + return this; + } + + public PostFixtureBuilder isQuestion(Boolean isQuestion) { + this.isQuestion = isQuestion; + return this; + } + + public PostFixtureBuilder likeCount(Long likeCount) { + this.likeCount = likeCount; + return this; + } + + public PostFixtureBuilder viewCount(Long viewCount) { + this.viewCount = viewCount; + return this; + } + + public PostFixtureBuilder postCategory(PostCategory postCategory) { + this.postCategory = postCategory; + return this; + } + + public PostFixtureBuilder board(Board board) { + this.board = board; + return this; + } + + public PostFixtureBuilder siteUser(SiteUser siteUser) { + this.siteUser = siteUser; + return this; + } + + public Post create() { + Post post = new Post( + title, + content, + isQuestion, + likeCount, + viewCount, + postCategory); + post.setBoardAndSiteUser(board, siteUser); + return postRepository.save(post); + } +} diff --git a/src/test/java/com/example/solidconnection/community/post/fixture/PostImageFixture.java b/src/test/java/com/example/solidconnection/community/post/fixture/PostImageFixture.java new file mode 100644 index 000000000..565f9fde0 --- /dev/null +++ b/src/test/java/com/example/solidconnection/community/post/fixture/PostImageFixture.java @@ -0,0 +1,20 @@ +package com.example.solidconnection.community.post.fixture; + +import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.community.post.domain.PostImage; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class PostImageFixture { + + private final PostImageFixtureBuilder postImageFixtureBuilder; + + public PostImage 게시글_이미지(String url, Post post) { + return postImageFixtureBuilder + .url(url) + .post(post) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/community/post/fixture/PostImageFixtureBuilder.java b/src/test/java/com/example/solidconnection/community/post/fixture/PostImageFixtureBuilder.java new file mode 100644 index 000000000..e12e0f0a0 --- /dev/null +++ b/src/test/java/com/example/solidconnection/community/post/fixture/PostImageFixtureBuilder.java @@ -0,0 +1,33 @@ +package com.example.solidconnection.community.post.fixture; + +import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.community.post.domain.PostImage; +import com.example.solidconnection.community.post.repository.PostImageRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class PostImageFixtureBuilder { + + private final PostImageRepository postImageRepository; + + private String url; + private Post post; + + public PostImageFixtureBuilder url(String url) { + this.url = url; + return this; + } + + public PostImageFixtureBuilder post(Post post) { + this.post = post; + return this; + } + + public PostImage create() { + PostImage postImage = new PostImage(url); + postImage.setPost(post); + return postImageRepository.save(postImage); + } +} diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java index 2656b2858..2cf253cf7 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.community.post.service; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.community.board.domain.Board; +import com.example.solidconnection.community.board.fixture.BoardFixture; import com.example.solidconnection.community.post.domain.Post; import com.example.solidconnection.community.post.domain.PostCategory; import com.example.solidconnection.community.post.domain.PostImage; @@ -10,14 +10,15 @@ import com.example.solidconnection.community.post.dto.PostDeleteResponse; import com.example.solidconnection.community.post.dto.PostUpdateRequest; import com.example.solidconnection.community.post.dto.PostUpdateResponse; -import com.example.solidconnection.community.post.repository.PostImageRepository; +import com.example.solidconnection.community.post.fixture.PostFixture; +import com.example.solidconnection.community.post.fixture.PostImageFixture; import com.example.solidconnection.community.post.repository.PostRepository; import com.example.solidconnection.s3.domain.ImgType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; -import com.example.solidconnection.support.integration.BaseIntegrationTest; +import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.util.RedisUtils; import jakarta.transaction.Transactional; import org.junit.jupiter.api.BeforeEach; @@ -43,8 +44,9 @@ import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; +@TestContainerSpringBootTest @DisplayName("게시글 생성/수정/삭제 서비스 테스트") -class PostCommandServiceTest extends BaseIntegrationTest { +class PostCommandServiceTest { @Autowired private PostCommandService postCommandService; @@ -62,16 +64,40 @@ class PostCommandServiceTest extends BaseIntegrationTest { private PostRepository postRepository; @Autowired - private PostImageRepository postImageRepository; + private SiteUserFixture siteUserFixture; @Autowired - private SiteUserFixture siteUserFixture; + private BoardFixture boardFixture; + + @Autowired + private PostFixture postFixture; + + @Autowired + private PostImageFixture postImageFixture; private SiteUser user1; + private Post post; + private Post questionPost; @BeforeEach void setUp() { user1 = siteUserFixture.사용자(1, "test1"); + post = postFixture.게시글( + "제목", + "내용", + false, + PostCategory.자유, + boardFixture.자유게시판(), + user1 + ); + questionPost = postFixture.게시글( + "제목", + "내용", + true, + PostCategory.질문, + boardFixture.자유게시판(), + user1 + ); } @Nested @@ -88,21 +114,12 @@ class 게시글_생성_테스트 { .willReturn(List.of(new UploadedFileUrlResponse(expectedImageUrl))); // when - PostCreateResponse response = postCommandService.createPost( - user1, - request, - imageFiles - ); + PostCreateResponse response = postCommandService.createPost(user1, request, imageFiles); // then Post savedPost = postRepository.findById(response.id()).orElseThrow(); assertAll( () -> assertThat(response.id()).isEqualTo(savedPost.getId()), - () -> assertThat(savedPost.getTitle()).isEqualTo(request.title()), - () -> assertThat(savedPost.getContent()).isEqualTo(request.content()), - () -> assertThat(savedPost.getIsQuestion()).isEqualTo(request.isQuestion()), - () -> assertThat(savedPost.getCategory().name()).isEqualTo(request.postCategory()), - () -> assertThat(savedPost.getBoard().getCode()).isEqualTo(자유게시판.getCode()), () -> assertThat(savedPost.getPostImageList()).hasSize(imageFiles.size()), () -> assertThat(savedPost.getPostImageList()) .extracting(PostImage::getUrl) @@ -158,8 +175,8 @@ class 게시글_수정_테스트 { void 게시글을_성공적으로_수정한다() { // given String originImageUrl = "origin-image-url"; + postImageFixture.게시글_이미지(originImageUrl, post); String expectedImageUrl = "update-image-url"; - Post testPost = createPost(자유게시판, user1, originImageUrl); PostUpdateRequest request = createPostUpdateRequest(); List imageFiles = List.of(createImageFile()); @@ -169,7 +186,7 @@ class 게시글_수정_테스트 { // when PostUpdateResponse response = postCommandService.updatePost( user1, - testPost.getId(), + post.getId(), request, imageFiles ); @@ -177,9 +194,7 @@ class 게시글_수정_테스트 { // then Post updatedPost = postRepository.findById(response.id()).orElseThrow(); assertAll( - () -> assertThat(updatedPost.getTitle()).isEqualTo(request.title()), - () -> assertThat(updatedPost.getContent()).isEqualTo(request.content()), - () -> assertThat(updatedPost.getCategory().name()).isEqualTo(request.postCategory()), + () -> assertThat(response.id()).isEqualTo(updatedPost.getId()), () -> assertThat(updatedPost.getPostImageList()).hasSize(imageFiles.size()), () -> assertThat(updatedPost.getPostImageList()) .extracting(PostImage::getUrl) @@ -192,7 +207,6 @@ class 게시글_수정_테스트 { void 다른_사용자의_게시글을_수정하면_예외_응답을_반환한다() { // given SiteUser user2 = siteUserFixture.사용자(2, "test2"); - Post testPost = createPost(자유게시판, user1, "origin-image-url"); PostUpdateRequest request = createPostUpdateRequest(); List imageFiles = List.of(); @@ -200,7 +214,7 @@ class 게시글_수정_테스트 { assertThatThrownBy(() -> postCommandService.updatePost( user2, - testPost.getId(), + post.getId(), request, imageFiles )) @@ -211,7 +225,6 @@ class 게시글_수정_테스트 { @Test void 질문_게시글을_수정하면_예외_응답을_반환한다() { // given - Post testPost = createQuestionPost(자유게시판, user1, "origin-image-url"); PostUpdateRequest request = createPostUpdateRequest(); List imageFiles = List.of(); @@ -219,7 +232,7 @@ class 게시글_수정_테스트 { assertThatThrownBy(() -> postCommandService.updatePost( user1, - testPost.getId(), + questionPost.getId(), request, imageFiles )) @@ -230,7 +243,6 @@ class 게시글_수정_테스트 { @Test void 이미지를_5개_초과하여_수정하면_예외_응답을_반환한다() { // given - Post testPost = createPost(자유게시판, user1, "origin-image-url"); PostUpdateRequest request = createPostUpdateRequest(); List imageFiles = createSixImageFiles(); @@ -238,7 +250,7 @@ class 게시글_수정_테스트 { assertThatThrownBy(() -> postCommandService.updatePost( user1, - testPost.getId(), + post.getId(), request, imageFiles )) @@ -254,20 +266,17 @@ class 게시글_삭제_테스트 { void 게시글을_성공적으로_삭제한다() { // given String originImageUrl = "origin-image-url"; - Post testPost = createPost(자유게시판, user1, originImageUrl); - String viewCountKey = redisUtils.getPostViewCountRedisKey(testPost.getId()); + postImageFixture.게시글_이미지(originImageUrl, post); + String viewCountKey = redisUtils.getPostViewCountRedisKey(post.getId()); redisService.increaseViewCount(viewCountKey); // when - PostDeleteResponse response = postCommandService.deletePostById( - user1, - testPost.getId() - ); + PostDeleteResponse response = postCommandService.deletePostById(user1, post.getId()); // then assertAll( - () -> assertThat(response.id()).isEqualTo(testPost.getId()), - () -> assertThat(postRepository.findById(testPost.getId())).isEmpty(), + () -> assertThat(response.id()).isEqualTo(post.getId()), + () -> assertThat(postRepository.findById(post.getId())).isEmpty(), () -> assertThat(redisService.isKeyExists(viewCountKey)).isFalse() ); then(s3Service).should().deletePostImage(originImageUrl); @@ -277,13 +286,12 @@ class 게시글_삭제_테스트 { void 다른_사용자의_게시글을_삭제하면_예외_응답을_반환한다() { // given SiteUser user2 = siteUserFixture.사용자(2, "test2"); - Post testPost = createPost(자유게시판, user1, "origin-image-url"); // when & then assertThatThrownBy(() -> postCommandService.deletePostById( user2, - testPost.getId() + post.getId() )) .isInstanceOf(CustomException.class) .hasMessage(INVALID_POST_ACCESS.getMessage()); @@ -291,14 +299,11 @@ class 게시글_삭제_테스트 { @Test void 질문_게시글을_삭제하면_예외_응답을_반환한다() { - // given - Post testPost = createQuestionPost(자유게시판, user1, "origin-image-url"); - // when & then assertThatThrownBy(() -> postCommandService.deletePostById( user1, - testPost.getId() + questionPost.getId() )) .isInstanceOf(CustomException.class) .hasMessage(CAN_NOT_DELETE_OR_UPDATE_QUESTION.getMessage()); @@ -307,7 +312,7 @@ class 게시글_삭제_테스트 { private PostCreateRequest createPostCreateRequest(String category) { return new PostCreateRequest( - 자유게시판.getCode(), + boardFixture.자유게시판().getCode(), category, "테스트 제목", "테스트 내용", @@ -335,40 +340,6 @@ private List createSixImageFiles() { ); } - private Post createPost(Board board, SiteUser siteUser, String originImageUrl) { - Post post = new Post( - "원본 제목", - "원본 내용", - false, - 0L, - 0L, - PostCategory.자유 - ); - post.setBoardAndSiteUser(board, siteUser); - Post savedPost = postRepository.save(post); - PostImage postImage = new PostImage(originImageUrl); - postImage.setPost(savedPost); - postImageRepository.save(postImage); - return savedPost; - } - - private Post createQuestionPost(Board board, SiteUser siteUser, String originImageUrl) { - Post post = new Post( - "질문 제목", - "질문 내용", - true, - 0L, - 0L, - PostCategory.질문 - ); - post.setBoardAndSiteUser(board, siteUser); - Post savedPost = postRepository.save(post); - PostImage postImage = new PostImage(originImageUrl); - postImage.setPost(savedPost); - postImageRepository.save(postImage); - return savedPost; - } - private PostUpdateRequest createPostUpdateRequest() { return new PostUpdateRequest( PostCategory.자유.name(), diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java index 4205019b7..9e36edeb9 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java @@ -1,16 +1,17 @@ package com.example.solidconnection.community.post.service; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.community.board.domain.Board; +import com.example.solidconnection.community.board.fixture.BoardFixture; import com.example.solidconnection.community.post.domain.Post; import com.example.solidconnection.community.post.domain.PostCategory; import com.example.solidconnection.community.post.dto.PostDislikeResponse; import com.example.solidconnection.community.post.dto.PostLikeResponse; +import com.example.solidconnection.community.post.fixture.PostFixture; import com.example.solidconnection.community.post.repository.PostLikeRepository; import com.example.solidconnection.community.post.repository.PostRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; -import com.example.solidconnection.support.integration.BaseIntegrationTest; +import com.example.solidconnection.support.TestContainerSpringBootTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -23,8 +24,9 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; +@TestContainerSpringBootTest @DisplayName("게시글 좋아요 서비스 테스트") -class PostLikeServiceTest extends BaseIntegrationTest { +class PostLikeServiceTest { @Autowired private PostLikeService postLikeService; @@ -38,11 +40,26 @@ class PostLikeServiceTest extends BaseIntegrationTest { @Autowired private SiteUserFixture siteUserFixture; + @Autowired + private BoardFixture boardFixture; + + @Autowired + private PostFixture postFixture; + private SiteUser user; + private Post post; @BeforeEach void setUp() { - user = siteUserFixture.사용자(1, "test1"); + user = siteUserFixture.사용자(); + post = postFixture.게시글( + "제목1", + "내용1", + false, + PostCategory.자유, + boardFixture.자유게시판(), + user + ); } @Nested @@ -51,17 +68,13 @@ class 게시글_좋아요_테스트 { @Test void 게시글을_성공적으로_좋아요한다() { // given - Post testPost = createPost(자유게시판, user); - long beforeLikeCount = testPost.getLikeCount(); + long beforeLikeCount = post.getLikeCount(); // when - PostLikeResponse response = postLikeService.likePost( - user, - testPost.getId() - ); + PostLikeResponse response = postLikeService.likePost(user, post.getId()); // then - Post likedPost = postRepository.findById(testPost.getId()).orElseThrow(); + Post likedPost = postRepository.findById(post.getId()).orElseThrow(); assertAll( () -> assertThat(response.likeCount()).isEqualTo(beforeLikeCount + 1), () -> assertThat(response.isLiked()).isTrue(), @@ -73,14 +86,13 @@ class 게시글_좋아요_테스트 { @Test void 이미_좋아요한_게시글을_다시_좋아요하면_예외_응답을_반환한다() { // given - Post testPost = createPost(자유게시판, user); - postLikeService.likePost(user, testPost.getId()); + postLikeService.likePost(user, post.getId()); // when & then assertThatThrownBy(() -> postLikeService.likePost( user, - testPost.getId() + post.getId() )) .isInstanceOf(CustomException.class) .hasMessage(DUPLICATE_POST_LIKE.getMessage()); @@ -93,18 +105,14 @@ class 게시글_좋아요_취소_테스트 { @Test void 게시글_좋아요를_성공적으로_취소한다() { // given - Post testPost = createPost(자유게시판, user); - PostLikeResponse beforeResponse = postLikeService.likePost(user, testPost.getId()); + PostLikeResponse beforeResponse = postLikeService.likePost(user, post.getId()); long beforeLikeCount = beforeResponse.likeCount(); // when - PostDislikeResponse response = postLikeService.dislikePost( - user, - testPost.getId() - ); + PostDislikeResponse response = postLikeService.dislikePost(user, post.getId()); // then - Post unlikedPost = postRepository.findById(testPost.getId()).orElseThrow(); + Post unlikedPost = postRepository.findById(post.getId()).orElseThrow(); assertAll( () -> assertThat(response.likeCount()).isEqualTo(beforeLikeCount - 1), () -> assertThat(response.isLiked()).isFalse(), @@ -115,30 +123,14 @@ class 게시글_좋아요_취소_테스트 { @Test void 좋아요하지_않은_게시글을_좋아요_취소하면_예외_응답을_반환한다() { - // given - Post testPost = createPost(자유게시판, user); - // when & then assertThatThrownBy(() -> postLikeService.dislikePost( user, - testPost.getId() + post.getId() )) .isInstanceOf(CustomException.class) .hasMessage(INVALID_POST_LIKE.getMessage()); } } - - private Post createPost(Board board, SiteUser siteUser) { - Post post = new Post( - "테스트 제목", - "테스트 내용", - false, - 0L, - 0L, - PostCategory.자유 - ); - post.setBoardAndSiteUser(board, siteUser); - return postRepository.save(post); - } } diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java index 13a942ef7..dc1e80746 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java @@ -1,21 +1,18 @@ package com.example.solidconnection.community.post.service; -import com.example.solidconnection.community.board.domain.Board; import com.example.solidconnection.community.board.domain.BoardCode; +import com.example.solidconnection.community.board.fixture.BoardFixture; import com.example.solidconnection.community.comment.domain.Comment; -import com.example.solidconnection.community.comment.dto.PostFindCommentResponse; -import com.example.solidconnection.community.comment.repository.CommentRepository; +import com.example.solidconnection.community.comment.fixture.CommentFixture; import com.example.solidconnection.community.post.domain.Post; import com.example.solidconnection.community.post.domain.PostCategory; -import com.example.solidconnection.community.post.domain.PostImage; -import com.example.solidconnection.community.post.dto.PostFindPostImageResponse; import com.example.solidconnection.community.post.dto.PostFindResponse; import com.example.solidconnection.community.post.dto.PostListResponse; -import com.example.solidconnection.community.post.repository.PostImageRepository; -import com.example.solidconnection.community.post.repository.PostRepository; +import com.example.solidconnection.community.post.fixture.PostFixture; +import com.example.solidconnection.community.post.fixture.PostImageFixture; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; -import com.example.solidconnection.support.integration.BaseIntegrationTest; +import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.util.RedisUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -28,8 +25,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; +@TestContainerSpringBootTest @DisplayName("게시글 조회 서비스 테스트") -class PostQueryServiceTest extends BaseIntegrationTest { +class PostQueryServiceTest { @Autowired private PostQueryService postQueryService; @@ -41,33 +39,61 @@ class PostQueryServiceTest extends BaseIntegrationTest { private RedisUtils redisUtils; @Autowired - private PostRepository postRepository; + private SiteUserFixture siteUserFixture; @Autowired - private CommentRepository commentRepository; + private BoardFixture boardFixture; @Autowired - private PostImageRepository postImageRepository; + private PostFixture postFixture; @Autowired - private SiteUserFixture siteUserFixture; + private PostImageFixture postImageFixture; + + @Autowired + private CommentFixture commentFixture; private SiteUser user; + private Post post1; + private Post post2; + private Post post3; @BeforeEach void setUp() { - user = siteUserFixture.사용자(1, "test1"); + user = siteUserFixture.사용자(); + post1 = postFixture.게시글( + "제목1", + "내용1", + false, + PostCategory.자유, + boardFixture.자유게시판(), + user + ); + post2 = postFixture.게시글( + "제목2", + "내용2", + false, + PostCategory.자유, + boardFixture.미주권(), + user + ); + post3 = postFixture.게시글( + "제목3", + "내용3", + true, + PostCategory.질문, + boardFixture.자유게시판(), + user + ); } @Test void 게시판_코드와_카테고리로_게시글_목록을_조회한다() { // given - List posts = List.of( - 미주권_자유게시글, 아시아권_자유게시글, 유럽권_자유게시글, 자유게시판_자유게시글, - 미주권_질문게시글, 아시아권_질문게시글, 유럽권_질문게시글, 자유게시판_질문게시글 - ); + List posts = List.of(post1, post2, post3); List expectedPosts = posts.stream() - .filter(post -> post.getCategory().equals(PostCategory.자유) && post.getBoard().getCode().equals(BoardCode.FREE.name())) + .filter(post -> post.getCategory().equals(PostCategory.자유) + && post.getBoard().getCode().equals(BoardCode.FREE.name())) .toList(); List expectedResponses = PostListResponse.from(expectedPosts); @@ -87,10 +113,7 @@ void setUp() { @Test void 전체_카테고리로_조회시_해당_게시판의_모든_게시글을_조회한다() { // given - List posts = List.of( - 미주권_자유게시글, 아시아권_자유게시글, 유럽권_자유게시글, 자유게시판_자유게시글, - 미주권_질문게시글, 아시아권_질문게시글, 유럽권_질문게시글, 자유게시판_질문게시글 - ); + List posts = List.of(post1, post2, post3); List expectedPosts = posts.stream() .filter(post -> post.getBoard().getCode().equals(BoardCode.FREE.name())) .toList(); @@ -114,77 +137,34 @@ void setUp() { // given String expectedImageUrl = "test-image-url"; List imageUrls = List.of(expectedImageUrl); - Post testPost = createPost(자유게시판, user, expectedImageUrl); - List comments = createComments(testPost, user, List.of("첫번째 댓글", "두번째 댓글")); + Post post = postFixture.게시글( + "제목", + "내용", + false, + PostCategory.자유, + boardFixture.자유게시판(), + user + ); + postImageFixture.게시글_이미지(expectedImageUrl, post); + Comment comment1 = commentFixture.부모_댓글("댓글1", post, user); + Comment comment2 = commentFixture.부모_댓글("댓글2", post, user); + List comments = List.of(comment1, comment2); - String validateKey = redisUtils.getValidatePostViewCountRedisKey(user.getId(), testPost.getId()); - String viewCountKey = redisUtils.getPostViewCountRedisKey(testPost.getId()); + String validateKey = redisUtils.getValidatePostViewCountRedisKey(user.getId(), post.getId()); + String viewCountKey = redisUtils.getPostViewCountRedisKey(post.getId()); // when - PostFindResponse response = postQueryService.findPostById( - user, - testPost.getId() - ); + PostFindResponse response = postQueryService.findPostById(user, post.getId()); // then assertAll( - () -> assertThat(response.id()).isEqualTo(testPost.getId()), - () -> assertThat(response.title()).isEqualTo(testPost.getTitle()), - () -> assertThat(response.content()).isEqualTo(testPost.getContent()), - () -> assertThat(response.isQuestion()).isEqualTo(testPost.getIsQuestion()), - () -> assertThat(response.likeCount()).isEqualTo(testPost.getLikeCount()), - () -> assertThat(response.viewCount()).isEqualTo(testPost.getViewCount()), - () -> assertThat(response.postCategory()).isEqualTo(String.valueOf(testPost.getCategory())), - - () -> assertThat(response.postFindBoardResponse().code()).isEqualTo(자유게시판.getCode()), - () -> assertThat(response.postFindBoardResponse().koreanName()).isEqualTo(자유게시판.getKoreanName()), - + () -> assertThat(response.id()).isEqualTo(post.getId()), + () -> assertThat(response.postFindBoardResponse().code()).isEqualTo(boardFixture.자유게시판().getCode()), () -> assertThat(response.postFindSiteUserResponse().id()).isEqualTo(user.getId()), - () -> assertThat(response.postFindSiteUserResponse().nickname()).isEqualTo(user.getNickname()), - () -> assertThat(response.postFindSiteUserResponse().profileImageUrl()).isEqualTo(user.getProfileImageUrl()), - - () -> assertThat(response.postFindPostImageResponses()) - .hasSize(imageUrls.size()) - .extracting(PostFindPostImageResponse::url) - .containsExactlyElementsOf(imageUrls), - - () -> assertThat(response.postFindCommentResponses()) - .hasSize(comments.size()) - .extracting(PostFindCommentResponse::content) - .containsExactlyElementsOf(comments.stream().map(Comment::getContent).toList()), - - () -> assertThat(response.isOwner()).isTrue(), - () -> assertThat(response.isLiked()).isFalse(), - + () -> assertThat(response.postFindPostImageResponses()).hasSize(imageUrls.size()), + () -> assertThat(response.postFindCommentResponses()).hasSize(comments.size()), () -> assertThat(redisService.isKeyExists(viewCountKey)).isTrue(), () -> assertThat(redisService.isKeyExists(validateKey)).isTrue() ); } - - private Post createPost(Board board, SiteUser siteUser, String originImageUrl) { - Post post = new Post( - "원본 제목", - "원본 내용", - false, - 0L, - 0L, - PostCategory.자유 - ); - post.setBoardAndSiteUser(board, siteUser); - Post savedPost = postRepository.save(post); - PostImage postImage = new PostImage(originImageUrl); - postImage.setPost(savedPost); - postImageRepository.save(postImage); - return savedPost; - } - - private List createComments(Post post, SiteUser siteUser, List contents) { - return contents.stream() - .map(content -> { - Comment comment = new Comment(content); - comment.setPostAndSiteUser(post, siteUser); - return commentRepository.save(comment); - }) - .toList(); - } } diff --git a/src/test/java/com/example/solidconnection/support/integration/BaseIntegrationTest.java b/src/test/java/com/example/solidconnection/support/integration/BaseIntegrationTest.java deleted file mode 100644 index ae449641f..000000000 --- a/src/test/java/com/example/solidconnection/support/integration/BaseIntegrationTest.java +++ /dev/null @@ -1,527 +0,0 @@ -package com.example.solidconnection.support.integration; - -import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.application.domain.Gpa; -import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.application.domain.VerifyStatus; -import com.example.solidconnection.application.repository.ApplicationRepository; -import com.example.solidconnection.community.board.domain.Board; -import com.example.solidconnection.community.board.repository.BoardRepository; -import com.example.solidconnection.community.post.domain.Post; -import com.example.solidconnection.community.post.domain.PostCategory; -import com.example.solidconnection.community.post.domain.PostImage; -import com.example.solidconnection.community.post.repository.PostImageRepository; -import com.example.solidconnection.community.post.repository.PostRepository; -import com.example.solidconnection.location.country.domain.Country; -import com.example.solidconnection.location.country.repository.CountryRepository; -import com.example.solidconnection.location.region.domain.Region; -import com.example.solidconnection.location.region.repository.RegionRepository; -import com.example.solidconnection.score.domain.GpaScore; -import com.example.solidconnection.score.domain.LanguageTestScore; -import com.example.solidconnection.score.repository.GpaScoreRepository; -import com.example.solidconnection.score.repository.LanguageTestScoreRepository; -import com.example.solidconnection.siteuser.domain.PreparationStatus; -import com.example.solidconnection.siteuser.domain.Role; -import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.support.DatabaseClearExtension; -import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.university.domain.LanguageRequirement; -import com.example.solidconnection.university.domain.LanguageTestType; -import com.example.solidconnection.university.domain.University; -import com.example.solidconnection.university.domain.UniversityInfoForApply; -import com.example.solidconnection.university.repository.LanguageRequirementRepository; -import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; -import com.example.solidconnection.university.repository.UniversityRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; - -import java.util.HashSet; -import java.util.List; - -import static com.example.solidconnection.community.board.domain.BoardCode.AMERICAS; -import static com.example.solidconnection.community.board.domain.BoardCode.ASIA; -import static com.example.solidconnection.community.board.domain.BoardCode.EUROPE; -import static com.example.solidconnection.community.board.domain.BoardCode.FREE; -import static com.example.solidconnection.university.domain.SemesterAvailableForDispatch.ONE_SEMESTER; -import static com.example.solidconnection.university.domain.TuitionFeeType.HOME_UNIVERSITY_PAYMENT; - -@TestContainerSpringBootTest -@ExtendWith(DatabaseClearExtension.class) -public abstract class BaseIntegrationTest { - - public static SiteUser 테스트유저_1; - public static SiteUser 테스트유저_2; - public static SiteUser 테스트유저_3; - public static SiteUser 테스트유저_4; - public static SiteUser 테스트유저_5; - public static SiteUser 테스트유저_6; - public static SiteUser 테스트유저_7; - public static SiteUser 이전학기_지원자; - - public static Region 영미권; - public static Region 유럽; - public static Region 아시아; - public static Country 미국; - public static Country 캐나다; - public static Country 덴마크; - public static Country 오스트리아; - public static Country 일본; - - public static University 영미권_미국_괌대학; - public static University 영미권_미국_네바다주립대학_라스베이거스; - public static University 영미권_캐나다_메모리얼대학_세인트존스; - public static University 유럽_덴마크_서던덴마크대학교; - public static University 유럽_덴마크_코펜하겐IT대학; - public static University 유럽_오스트리아_그라츠대학; - public static University 유럽_오스트리아_그라츠공과대학; - public static University 유럽_오스트리아_린츠_카톨릭대학; - public static University 아시아_일본_메이지대학; - - public static UniversityInfoForApply 괌대학_A_지원_정보; - public static UniversityInfoForApply 괌대학_B_지원_정보; - public static UniversityInfoForApply 네바다주립대학_라스베이거스_지원_정보; - public static UniversityInfoForApply 메모리얼대학_세인트존스_A_지원_정보; - public static UniversityInfoForApply 서던덴마크대학교_지원_정보; - public static UniversityInfoForApply 코펜하겐IT대학_지원_정보; - public static UniversityInfoForApply 그라츠대학_지원_정보; - public static UniversityInfoForApply 그라츠공과대학_지원_정보; - public static UniversityInfoForApply 린츠_카톨릭대학_지원_정보; - public static UniversityInfoForApply 메이지대학_지원_정보; - - public static Application 테스트유저_2_괌대학_B_괌대학_A_린츠_카톨릭대학_지원서; - public static Application 테스트유저_3_괌대학_A_괌대학_B_그라츠공과대학_지원서; - public static Application 테스트유저_4_메이지대학_그라츠대학_서던덴마크대학_지원서; - public static Application 테스트유저_5_네바다주립대학_그라츠공과대학_메이지대학_지원서; - public static Application 테스트유저_6_X_X_X_지원서; - public static Application 테스트유저_7_코펜하겐IT대학_X_X_지원서; - public static Application 이전학기_지원서; - - public static Board 미주권; - public static Board 아시아권; - public static Board 유럽권; - public static Board 자유게시판; - - public static Post 미주권_자유게시글; - public static Post 아시아권_자유게시글; - public static Post 유럽권_자유게시글; - public static Post 자유게시판_자유게시글; - public static Post 미주권_질문게시글; - public static Post 아시아권_질문게시글; - public static Post 유럽권_질문게시글; - public static Post 자유게시판_질문게시글; - - @Autowired - private SiteUserRepository siteUserRepository; - - @Autowired - private RegionRepository regionRepository; - - @Autowired - private CountryRepository countryRepository; - - @Autowired - private UniversityRepository universityRepository; - - @Autowired - private UniversityInfoForApplyRepository universityInfoForApplyRepository; - - @Autowired - private LanguageRequirementRepository languageRequirementRepository; - - @Autowired - private ApplicationRepository applicationRepository; - - @Autowired - private GpaScoreRepository gpaScoreRepository; - - @Autowired - private LanguageTestScoreRepository languageTestScoreRepository; - - @Autowired - private BoardRepository boardRepository; - - @Autowired - private PostRepository postRepository; - - @Autowired - private PostImageRepository postImageRepository; - - @Value("${university.term}") - public String term; - - @BeforeEach - public void setUpBaseData() { - setUpSiteUsers(); - setUpRegions(); - setUpCountries(); - setUpUniversities(); - setUpUniversityInfos(); - setUpLanguageRequirements(); - setUpApplications(); - setUpBoards(); - setUpPosts(); - } - - private void setUpSiteUsers() { - 테스트유저_1 = siteUserRepository.save(new SiteUser( - "test1@example.com", - "nickname1", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE)); - - 테스트유저_2 = siteUserRepository.save(new SiteUser( - "test2@example.com", - "nickname2", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE)); - - 테스트유저_3 = siteUserRepository.save(new SiteUser( - "test3@example.com", - "nickname3", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE)); - - 테스트유저_4 = siteUserRepository.save(new SiteUser( - "test4@example.com", - "nickname4", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE)); - - 테스트유저_5 = siteUserRepository.save(new SiteUser( - "test5@example.com", - "nickname5", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE)); - - 테스트유저_6 = siteUserRepository.save(new SiteUser( - "test6@example.com", - "nickname6", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE)); - - 테스트유저_7 = siteUserRepository.save(new SiteUser( - "test7@example.com", - "nickname7", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE)); - - 이전학기_지원자 = siteUserRepository.save(new SiteUser( - "old@example.com", - "oldNickname", - "profileImageUrl", - PreparationStatus.CONSIDERING, - Role.MENTEE)); - } - - private void setUpRegions() { - 영미권 = regionRepository.save(new Region("AMERICAS", "영미권")); - 유럽 = regionRepository.save(new Region("EUROPE", "유럽")); - 아시아 = regionRepository.save(new Region("ASIA", "아시아")); - } - - private void setUpCountries() { - 미국 = countryRepository.save(new Country("US", "미국", 영미권)); - 캐나다 = countryRepository.save(new Country("CA", "캐나다", 영미권)); - 덴마크 = countryRepository.save(new Country("DK", "덴마크", 유럽)); - 오스트리아 = countryRepository.save(new Country("AT", "오스트리아", 유럽)); - 일본 = countryRepository.save(new Country("JP", "일본", 아시아)); - } - - private void setUpUniversities() { - 영미권_미국_괌대학 = universityRepository.save(new University( - null, "괌대학", "University of Guam", "university_of_guam", - "https://www.uog.edu/admissions/international-students", - "https://www.uog.edu/admissions/course-schedule", - "https://www.uog.edu/life-at-uog/residence-halls/", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/university_of_guam/logo.png", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/university_of_guam/1.png", - null, 미국, 영미권 - )); - - 영미권_미국_네바다주립대학_라스베이거스 = universityRepository.save(new University( - null, "네바다주립대학 라스베이거스", "University of Nevada, Las Vegas", "university_of_nevada_las_vegas", - "https://www.unlv.edu/engineering/eip", - "https://www.unlv.edu/engineering/academic-programs", - "https://www.unlv.edu/housing", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/university_of_nevada_las_vegas/logo.png", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/university_of_nevada_las_vegas/1.png", - null, 미국, 영미권 - )); - - 영미권_캐나다_메모리얼대학_세인트존스 = universityRepository.save(new University( - null, "메모리얼 대학 세인트존스", "Memorial University of Newfoundland St. John's", "memorial_university_of_newfoundland_st_johns", - "https://mun.ca/goabroad/visiting-students-inbound/", - "https://www.unlv.edu/engineering/academic-programs", - "https://www.mun.ca/residences/", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/memorial_university_of_newfoundland_st_johns/logo.png", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/memorial_university_of_newfoundland_st_johns/1.png", - null, 캐나다, 영미권 - )); - - 유럽_덴마크_서던덴마크대학교 = universityRepository.save(new University( - null, "서던덴마크대학교", "University of Southern Denmark", "university_of_southern_denmark", - "https://www.sdu.dk/en", - "https://www.sdu.dk/en", - "https://www.sdu.dk/en/uddannelse/information_for_international_students/studenthousing", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/university_of_southern_denmark/logo.png", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/university_of_southern_denmark/1.png", - null, 덴마크, 유럽 - )); - - 유럽_덴마크_코펜하겐IT대학 = universityRepository.save(new University( - null, "코펜하겐 IT대학", "IT University of Copenhagen", "it_university_of_copenhagen", - "https://en.itu.dk/", null, - "https://en.itu.dk/Programmes/Student-Life/Practical-information-for-international-students", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/it_university_of_copenhagen/logo.png", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/it_university_of_copenhagen/1.png", - null, 덴마크, 유럽 - )); - - 유럽_오스트리아_그라츠대학 = universityRepository.save(new University( - null, "그라츠 대학", "University of Graz", "university_of_graz", - "https://www.uni-graz.at/en/", - "https://static.uni-graz.at/fileadmin/veranstaltungen/orientation/documents/incstud_application-courses.pdf", - "https://orientation.uni-graz.at/de/planning-the-arrival/accommodation/", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/university_of_graz/logo.png", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/university_of_graz/1.png", - null, 오스트리아, 유럽 - )); - - 유럽_오스트리아_그라츠공과대학 = universityRepository.save(new University( - null, "그라츠공과대학", "Graz University of Technology", "graz_university_of_technology", - "https://www.tugraz.at/en/home", null, - "https://www.tugraz.at/en/studying-and-teaching/studying-internationally/incoming-students-exchange-at-tu-graz/your-stay-at-tu-graz/preparation#c75033", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/graz_university_of_technology/logo.png", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/graz_university_of_technology/1.png", - null, 오스트리아, 유럽 - )); - - 유럽_오스트리아_린츠_카톨릭대학 = universityRepository.save(new University( - null, "린츠 카톨릭 대학교", "Catholic Private University Linz", "catholic_private_university_linz", - "https://ku-linz.at/en", null, - "https://ku-linz.at/en/ku_international/incomings/kulis", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/catholic_private_university_linz/logo.png", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/catholic_private_university_linz/1.png", - null, 오스트리아, 유럽 - )); - - 아시아_일본_메이지대학 = universityRepository.save(new University( - null, "메이지대학", "Meiji University", "meiji_university", - "https://www.meiji.ac.jp/cip/english/admissions/co7mm90000000461-att/co7mm900000004fa.pdf", null, - "https://www.meiji.ac.jp/cip/english/admissions/co7mm90000000461-att/co7mm900000004fa.pdf", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/meiji_university/logo.png", - "https://solid-connection.s3.ap-northeast-2.amazonaws.com/original/meiji_university/1.png", - null, 일본, 아시아 - )); - } - - private void setUpUniversityInfos() { - 괌대학_A_지원_정보 = universityInfoForApplyRepository.save(new UniversityInfoForApply( - null, term, "괌대학(A형)", 1, HOME_UNIVERSITY_PAYMENT, ONE_SEMESTER, - "1", "detailsForLanguage", "gpaRequirement", - "gpaRequirementCriteria", "detailsForApply", "detailsForMajor", - "detailsForAccommodation", "detailsForEnglishCourse", "details", - new HashSet<>(), 영미권_미국_괌대학 - )); - - 괌대학_B_지원_정보 = universityInfoForApplyRepository.save(new UniversityInfoForApply( - null, term, "괌대학(B형)", 1, HOME_UNIVERSITY_PAYMENT, ONE_SEMESTER, - "1", "detailsForLanguage", "gpaRequirement", - "gpaRequirementCriteria", "detailsForApply", "detailsForMajor", - "detailsForAccommodation", "detailsForEnglishCourse", "details", - new HashSet<>(), 영미권_미국_괌대학 - )); - - 네바다주립대학_라스베이거스_지원_정보 = universityInfoForApplyRepository.save(new UniversityInfoForApply( - null, term, "네바다주립대학 라스베이거스(B형)", 1, HOME_UNIVERSITY_PAYMENT, ONE_SEMESTER, - "1", "detailsForLanguage", "gpaRequirement", - "gpaRequirementCriteria", "detailsForApply", "detailsForMajor", - "detailsForAccommodation", "detailsForEnglishCourse", "details", - new HashSet<>(), 영미권_미국_네바다주립대학_라스베이거스 - )); - - 메모리얼대학_세인트존스_A_지원_정보 = universityInfoForApplyRepository.save(new UniversityInfoForApply( - null, term, "메모리얼 대학 세인트존스(A형)", 1, HOME_UNIVERSITY_PAYMENT, ONE_SEMESTER, - "1", "detailsForLanguage", "gpaRequirement", - "gpaRequirementCriteria", "detailsForApply", "detailsForMajor", - "detailsForAccommodation", "detailsForEnglishCourse", "details", - new HashSet<>(), 영미권_캐나다_메모리얼대학_세인트존스 - )); - - 서던덴마크대학교_지원_정보 = universityInfoForApplyRepository.save(new UniversityInfoForApply( - null, term, "서던덴마크대학교", 1, HOME_UNIVERSITY_PAYMENT, ONE_SEMESTER, - "1", "detailsForLanguage", "gpaRequirement", - "gpaRequirementCriteria", "detailsForApply", "detailsForMajor", - "detailsForAccommodation", "detailsForEnglishCourse", "details", - new HashSet<>(), 유럽_덴마크_서던덴마크대학교 - )); - - 코펜하겐IT대학_지원_정보 = universityInfoForApplyRepository.save(new UniversityInfoForApply( - null, term, "코펜하겐 IT대학", 1, HOME_UNIVERSITY_PAYMENT, ONE_SEMESTER, - "1", "detailsForLanguage", "gpaRequirement", - "gpaRequirementCriteria", "detailsForApply", "detailsForMajor", - "detailsForAccommodation", "detailsForEnglishCourse", "details", - new HashSet<>(), 유럽_덴마크_코펜하겐IT대학 - )); - - 그라츠대학_지원_정보 = universityInfoForApplyRepository.save(new UniversityInfoForApply( - null, term, "그라츠 대학", 1, HOME_UNIVERSITY_PAYMENT, ONE_SEMESTER, - "1", "detailsForLanguage", "gpaRequirement", - "gpaRequirementCriteria", "detailsForApply", "detailsForMajor", - "detailsForAccommodation", "detailsForEnglishCourse", "details", - new HashSet<>(), 유럽_오스트리아_그라츠대학 - )); - - 그라츠공과대학_지원_정보 = universityInfoForApplyRepository.save(new UniversityInfoForApply( - null, term, "그라츠공과대학", 1, HOME_UNIVERSITY_PAYMENT, ONE_SEMESTER, - "1", "detailsForLanguage", "gpaRequirement", - "gpaRequirementCriteria", "detailsForApply", "detailsForMajor", - "detailsForAccommodation", "detailsForEnglishCourse", "details", - new HashSet<>(), 유럽_오스트리아_그라츠공과대학 - )); - - 린츠_카톨릭대학_지원_정보 = universityInfoForApplyRepository.save(new UniversityInfoForApply( - null, term, "린츠 카톨릭 대학교", 1, HOME_UNIVERSITY_PAYMENT, ONE_SEMESTER, - "1", "detailsForLanguage", "gpaRequirement", - "gpaRequirementCriteria", "detailsForApply", "detailsForMajor", - "detailsForAccommodation", "detailsForEnglishCourse", "details", - new HashSet<>(), 유럽_오스트리아_린츠_카톨릭대학 - )); - - 메이지대학_지원_정보 = universityInfoForApplyRepository.save(new UniversityInfoForApply( - null, term, "메이지대학", 1, HOME_UNIVERSITY_PAYMENT, ONE_SEMESTER, - "1", "detailsForLanguage", "gpaRequirement", - "gpaRequirementCriteria", "detailsForApply", "detailsForMajor", - "detailsForAccommodation", "detailsForEnglishCourse", "details", - new HashSet<>(), 아시아_일본_메이지대학 - )); - } - - private void setUpLanguageRequirements() { - saveLanguageTestRequirement(괌대학_A_지원_정보, LanguageTestType.TOEFL_IBT, "80"); - saveLanguageTestRequirement(괌대학_A_지원_정보, LanguageTestType.TOEIC, "800"); - saveLanguageTestRequirement(괌대학_B_지원_정보, LanguageTestType.TOEFL_IBT, "70"); - saveLanguageTestRequirement(괌대학_B_지원_정보, LanguageTestType.TOEIC, "900"); - saveLanguageTestRequirement(네바다주립대학_라스베이거스_지원_정보, LanguageTestType.TOEIC, "800"); - saveLanguageTestRequirement(메모리얼대학_세인트존스_A_지원_정보, LanguageTestType.TOEIC, "800"); - saveLanguageTestRequirement(서던덴마크대학교_지원_정보, LanguageTestType.TOEFL_IBT, "70"); - saveLanguageTestRequirement(코펜하겐IT대학_지원_정보, LanguageTestType.TOEFL_IBT, "80"); - saveLanguageTestRequirement(그라츠대학_지원_정보, LanguageTestType.TOEFL_IBT, "80"); - saveLanguageTestRequirement(그라츠공과대학_지원_정보, LanguageTestType.TOEIC, "800"); - saveLanguageTestRequirement(린츠_카톨릭대학_지원_정보, LanguageTestType.TOEIC, "800"); - saveLanguageTestRequirement(메이지대학_지원_정보, LanguageTestType.JLPT, "N2"); - } - - private void setUpApplications() { - 테스트유저_2_괌대학_B_괌대학_A_린츠_카톨릭대학_지원서 = new Application(테스트유저_2, createApprovedGpaScore(테스트유저_2).getGpa(), createApprovedLanguageTestScore(테스트유저_2).getLanguageTest(), - term, 괌대학_B_지원_정보, 괌대학_A_지원_정보, 린츠_카톨릭대학_지원_정보, "user2_nickname"); - - 테스트유저_3_괌대학_A_괌대학_B_그라츠공과대학_지원서 = new Application(테스트유저_3, createApprovedGpaScore(테스트유저_3).getGpa(), createApprovedLanguageTestScore(테스트유저_3).getLanguageTest(), - term, 괌대학_A_지원_정보, 괌대학_B_지원_정보, 그라츠공과대학_지원_정보, "user3_nickname"); - - 테스트유저_4_메이지대학_그라츠대학_서던덴마크대학_지원서 = new Application(테스트유저_4, createApprovedGpaScore(테스트유저_4).getGpa(), createApprovedLanguageTestScore(테스트유저_4).getLanguageTest(), - term, 메이지대학_지원_정보, 그라츠대학_지원_정보, 서던덴마크대학교_지원_정보, "user4_nickname"); - - 테스트유저_5_네바다주립대학_그라츠공과대학_메이지대학_지원서 = new Application(테스트유저_5, createApprovedGpaScore(테스트유저_5).getGpa(), createApprovedLanguageTestScore(테스트유저_5).getLanguageTest(), - term, 네바다주립대학_라스베이거스_지원_정보, 그라츠공과대학_지원_정보, 메이지대학_지원_정보, "user5_nickname"); - - 테스트유저_6_X_X_X_지원서 = new Application(테스트유저_6, createApprovedGpaScore(테스트유저_6).getGpa(), createApprovedLanguageTestScore(테스트유저_6).getLanguageTest(), - term, null, null, null, "user6_nickname"); - - 테스트유저_7_코펜하겐IT대학_X_X_지원서 = new Application(테스트유저_7, createApprovedGpaScore(테스트유저_7).getGpa(), createApprovedLanguageTestScore(테스트유저_7).getLanguageTest(), - term, 코펜하겐IT대학_지원_정보, null, null, "user7_nickname"); - - 이전학기_지원서 = new Application(이전학기_지원자, createApprovedGpaScore(이전학기_지원자).getGpa(), createApprovedLanguageTestScore(이전학기_지원자).getLanguageTest(), - "1988-1", 네바다주립대학_라스베이거스_지원_정보, 그라츠공과대학_지원_정보, 메이지대학_지원_정보, "old_nickname"); - - 테스트유저_2_괌대학_B_괌대학_A_린츠_카톨릭대학_지원서.setVerifyStatus(VerifyStatus.APPROVED); - 테스트유저_3_괌대학_A_괌대학_B_그라츠공과대학_지원서.setVerifyStatus(VerifyStatus.APPROVED); - 테스트유저_4_메이지대학_그라츠대학_서던덴마크대학_지원서.setVerifyStatus(VerifyStatus.APPROVED); - 테스트유저_5_네바다주립대학_그라츠공과대학_메이지대학_지원서.setVerifyStatus(VerifyStatus.APPROVED); - 테스트유저_6_X_X_X_지원서.setVerifyStatus(VerifyStatus.APPROVED); - 테스트유저_7_코펜하겐IT대학_X_X_지원서.setVerifyStatus(VerifyStatus.APPROVED); - 이전학기_지원서.setVerifyStatus(VerifyStatus.APPROVED); - - applicationRepository.saveAll(List.of( - 테스트유저_2_괌대학_B_괌대학_A_린츠_카톨릭대학_지원서, 테스트유저_3_괌대학_A_괌대학_B_그라츠공과대학_지원서, 테스트유저_4_메이지대학_그라츠대학_서던덴마크대학_지원서, 테스트유저_5_네바다주립대학_그라츠공과대학_메이지대학_지원서, - 테스트유저_6_X_X_X_지원서, 테스트유저_7_코펜하겐IT대학_X_X_지원서, 이전학기_지원서)); - } - - private void setUpBoards() { - 미주권 = boardRepository.save(new Board(AMERICAS.name(), "미주권")); - 아시아권 = boardRepository.save(new Board(ASIA.name(), "아시아권")); - 유럽권 = boardRepository.save(new Board(EUROPE.name(), "유럽권")); - 자유게시판 = boardRepository.save(new Board(FREE.name(), "자유게시판")); - } - - private void setUpPosts() { - 미주권_자유게시글 = createPost(미주권, 테스트유저_1, "미주권 자유게시글", "미주권 자유게시글 내용", PostCategory.자유); - 아시아권_자유게시글 = createPost(아시아권, 테스트유저_2, "아시아권 자유게시글", "아시아권 자유게시글 내용", PostCategory.자유); - 유럽권_자유게시글 = createPost(유럽권, 테스트유저_1, "유럽권 자유게시글", "유럽권 자유게시글 내용", PostCategory.자유); - 자유게시판_자유게시글 = createPost(자유게시판, 테스트유저_2, "자유게시판 자유게시글", "자유게시판 자유게시글 내용", PostCategory.자유); - 미주권_질문게시글 = createPost(미주권, 테스트유저_1, "미주권 질문게시글", "미주권 질문게시글 내용", PostCategory.질문); - 아시아권_질문게시글 = createPost(아시아권, 테스트유저_2, "아시아권 질문게시글", "아시아권 질문게시글 내용", PostCategory.질문); - 유럽권_질문게시글 = createPost(유럽권, 테스트유저_1, "유럽권 질문게시글", "유럽권 질문게시글 내용", PostCategory.질문); - 자유게시판_질문게시글 = createPost(자유게시판, 테스트유저_2, "자유게시판 질문게시글", "자유게시판 질문게시글 내용", PostCategory.질문); - } - - private void saveLanguageTestRequirement( - UniversityInfoForApply universityInfoForApply, - LanguageTestType testType, - String minScore - ) { - LanguageRequirement languageRequirement = new LanguageRequirement( - null, - testType, - minScore, - universityInfoForApply); - universityInfoForApply.addLanguageRequirements(languageRequirement); - universityInfoForApplyRepository.save(universityInfoForApply); - languageRequirementRepository.save(languageRequirement); - } - - private GpaScore createApprovedGpaScore(SiteUser siteUser) { - GpaScore gpaScore = new GpaScore( - new Gpa(4.0, 4.5, "/gpa-report.pdf"), - siteUser - ); - gpaScore.setVerifyStatus(VerifyStatus.APPROVED); - return gpaScoreRepository.save(gpaScore); - } - - private LanguageTestScore createApprovedLanguageTestScore(SiteUser siteUser) { - LanguageTestScore languageTestScore = new LanguageTestScore( - new LanguageTest(LanguageTestType.TOEIC, "100", "/gpa-report.pdf"), - siteUser - ); - languageTestScore.setVerifyStatus(VerifyStatus.APPROVED); - return languageTestScoreRepository.save(languageTestScore); - } - - private Post createPost(Board board, SiteUser siteUser, String title, String content, PostCategory category) { - Post post = new Post( - title, - content, - false, - 0L, - 0L, - category - ); - post.setBoardAndSiteUser(board, siteUser); - Post savedPost = postRepository.save(post); - PostImage postImage = new PostImage("imageUrl"); - postImage.setPost(savedPost); - postImageRepository.save(postImage); - return savedPost; - } -} From a14a510693f70cfe3bb13f301609925d3ee4c95f Mon Sep 17 00:00:00 2001 From: Yeon <84384499+lsy1307@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:40:26 +0900 Subject: [PATCH 22/90] =?UTF-8?q?refactor:=20application=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20(#3?= =?UTF-8?q?30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: Application Entity 변경 - UniversityInfoForApply FK매핑 삭제 및 Long타입으로 변경, 네이밍 변경 - 사용하지 않는 updateUniversityChoice 메서드 제거 * refactor: ApplicationQueryService 변경 - AS-IS: 각 지망별로 Repository를 호출하여 N+1 문제 발생 단일키 임시 캐싱 - TO-BE: 매핑 제거 후 직접 데이터를 분류하여 N+1문제 제거 및 성능 향상 캐싱 제거 * refactor: University관련 Repository 변경 - AS-IS: 기존 Repository에서 University타입으로 반환 - TO-BE: Entity변경, 비즈니스 로직 변환에 맞춰 반환타입 Long으로 수정 findByIdsWithUniversityAndLocation 메서드 추가 사용하지 않게 된 getUniversityInfoForApplyByIdAndTerm 메서드 삭제 * refactor: ApplicationRepository 변경 - 서비스 로직 변경을 위해 3개로 분리되어 있는 기존 메서드를 한 번의 쿼리로 조회하도록 변경 * refactor: ApplicationSubmissionService 변경 - Entity변경에 따른 수정. 기존 로직은 변화 없음 * refactor: ApplicationTest코드 수정 - Entity변경에 따른 수정 - 전원 통과 - ApplicationQueryService 마지막 메서드 테스트 의도에 맞게 수정 * refactor: ApplicationTest코드 수정 - UniversityInfoForApplyId 하드코딩 되어있는 부분 수정 - 테스트 통과 확인 * refactor: 테스트 코드 제외 코드 리뷰 수정사항 반영 - 코드 컨벤션 통일 - 변수명 이해하기 쉽도록 수정 - Application Repository universityId기반과 uiaId기반으로 분기 * refactor: ApplicationTest코드 수정 - 마지막 메서드 테스트 의도에 맞게 수정 - 컨벤션에 맞게 수정 - BaseIntegrationTest에서 firstUIAId에 null값을 입력하는 부분 수정 * refactor: Application Entity Index 추가 - 조회 성능 향상을 위해 Application Entity Index추가 - Application Repository 불필요한 공백 삭제 * refactor: Flyway V13 script 등록 - Application FK제약조건 제거 - column명 변경 - Index 추가 * refactor: 테스트 코드 제외 코드 리뷰 수정사항 반영 - 가벼운 수정 사항(공백 삭제 및 함수 명 변경) - Application Column명 수정 - 쿼리 간결화 - DTO에 매핑책임 이관 * refactor: 테스트 코드 수정 및 Flyway 수정 - 테스트 코드 데이터 형식 수정 - Flyway Column명 수정 * refactor: Flyway V13 FK제약조건 유지로 변경 --- .../application/domain/Application.java | 73 ++++----- .../dto/UniversityApplicantsResponse.java | 13 +- .../repository/ApplicationRepository.java | 31 ++-- .../service/ApplicationQueryService.java | 153 +++++++++--------- .../service/ApplicationSubmissionService.java | 34 ++-- .../UniversityInfoForApplyRepository.java | 35 +++- .../custom/UniversityFilterRepository.java | 2 +- .../UniversityFilterRepositoryImpl.java | 22 ++- ..._index_and_delete_manny_to_one_mapping.sql | 15 ++ .../fixture/ApplicationFixture.java | 12 +- .../fixture/ApplicationFixtureBuilder.java | 24 +-- .../service/ApplicationQueryServiceTest.java | 89 +++++----- .../ApplicationSubmissionServiceTest.java | 18 ++- 13 files changed, 283 insertions(+), 238 deletions(-) create mode 100644 src/main/resources/db/migration/V13_add_application_index_and_delete_manny_to_one_mapping.sql diff --git a/src/main/java/com/example/solidconnection/application/domain/Application.java b/src/main/java/com/example/solidconnection/application/domain/Application.java index 98342ca88..5f982410e 100644 --- a/src/main/java/com/example/solidconnection/application/domain/Application.java +++ b/src/main/java/com/example/solidconnection/application/domain/Application.java @@ -1,7 +1,6 @@ package com.example.solidconnection.application.domain; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.domain.UniversityInfoForApply; import jakarta.persistence.Column; import jakarta.persistence.Embedded; import jakarta.persistence.Entity; @@ -11,7 +10,9 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.Index; import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -25,6 +26,16 @@ @DynamicUpdate @DynamicInsert @Entity +@Table(indexes = { + @Index(name = "idx_app_user_term_delete", + columnList = "site_user_id, term, is_delete"), + @Index(name = "idx_app_first_choice_search", + columnList = "verify_status, term, is_delete, first_choice_university_info_for_apply_id"), + @Index(name = "idx_app_second_choice_search", + columnList = "verify_status, term, is_delete, second_choice_university_info_for_apply_id"), + @Index(name = "idx_app_third_choice_search", + columnList = "verify_status, term, is_delete, third_choice_university_info_for_apply_id") +}) public class Application { @Id @@ -38,30 +49,30 @@ public class Application { private LanguageTest languageTest; @Setter - @Column(columnDefinition = "varchar(50) not null default 'PENDING'") + @Column(columnDefinition = "varchar(50) not null default 'PENDING'", name="verify_status") @Enumerated(EnumType.STRING) private VerifyStatus verifyStatus; - @Column(length = 100) + @Column(length = 100, name="nickname_for_apply") private String nicknameForApply; - @Column(columnDefinition = "int not null default 1") + @Column(columnDefinition = "int not null default 1", name="update_count") private Integer updateCount; - @Column(length = 50, nullable = false) + @Column(length = 50, nullable = false, name="term") private String term; - @Column + @Column(name="is_delete") private boolean isDelete = false; - @ManyToOne(fetch = FetchType.LAZY) - private UniversityInfoForApply firstChoiceUniversity; + @Column(nullable = false , name = "first_choice_university_info_for_apply_id") + private long firstChoiceUnivApplyInfoId; - @ManyToOne(fetch = FetchType.LAZY) - private UniversityInfoForApply secondChoiceUniversity; + @Column(name = "second_choice_university_info_for_apply_id") + private Long secondChoiceUnivApplyInfoId; - @ManyToOne(fetch = FetchType.LAZY) - private UniversityInfoForApply thirdChoiceUniversity; + @Column(name = "third_choice_university_info_for_apply_id") + private Long thirdChoiceUnivApplyInfoId; @ManyToOne(fetch = FetchType.LAZY) private SiteUser siteUser; @@ -85,18 +96,18 @@ public Application( LanguageTest languageTest, String term, Integer updateCount, - UniversityInfoForApply firstChoiceUniversity, - UniversityInfoForApply secondChoiceUniversity, - UniversityInfoForApply thirdChoiceUniversity, + long firstChoiceUnivApplyInfoId, + Long secondChoiceUnivApplyInfoId, + Long thirdChoiceUnivApplyInfoId, String nicknameForApply) { this.siteUser = siteUser; this.gpa = gpa; this.languageTest = languageTest; this.term = term; this.updateCount = updateCount; - this.firstChoiceUniversity = firstChoiceUniversity; - this.secondChoiceUniversity = secondChoiceUniversity; - this.thirdChoiceUniversity = thirdChoiceUniversity; + this.firstChoiceUnivApplyInfoId = firstChoiceUnivApplyInfoId; + this.secondChoiceUnivApplyInfoId = secondChoiceUnivApplyInfoId; + this.thirdChoiceUnivApplyInfoId = thirdChoiceUnivApplyInfoId; this.nicknameForApply = nicknameForApply; this.verifyStatus = PENDING; } @@ -106,18 +117,18 @@ public Application( Gpa gpa, LanguageTest languageTest, String term, - UniversityInfoForApply firstChoiceUniversity, - UniversityInfoForApply secondChoiceUniversity, - UniversityInfoForApply thirdChoiceUniversity, + long firstChoiceUnivApplyInfoId, + Long secondChoiceUnivApplyInfoId, + Long thirdChoiceUnivApplyInfoId, String nicknameForApply) { this.siteUser = siteUser; this.gpa = gpa; this.languageTest = languageTest; this.term = term; this.updateCount = 1; - this.firstChoiceUniversity = firstChoiceUniversity; - this.secondChoiceUniversity = secondChoiceUniversity; - this.thirdChoiceUniversity = thirdChoiceUniversity; + this.firstChoiceUnivApplyInfoId = firstChoiceUnivApplyInfoId; + this.secondChoiceUnivApplyInfoId = secondChoiceUnivApplyInfoId; + this.thirdChoiceUnivApplyInfoId = thirdChoiceUnivApplyInfoId; this.nicknameForApply = nicknameForApply; this.verifyStatus = PENDING; } @@ -125,18 +136,4 @@ public Application( public void setIsDeleteTrue() { this.isDelete = true; } - - public void updateUniversityChoice( - UniversityInfoForApply firstChoiceUniversity, - UniversityInfoForApply secondChoiceUniversity, - UniversityInfoForApply thirdChoiceUniversity, - String nicknameForApply) { - if (this.firstChoiceUniversity != null) { - this.updateCount++; - } - this.firstChoiceUniversity = firstChoiceUniversity; - this.secondChoiceUniversity = secondChoiceUniversity; - this.thirdChoiceUniversity = thirdChoiceUniversity; - this.nicknameForApply = nicknameForApply; - } } diff --git a/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java b/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java index 1d3415003..2e43ab851 100644 --- a/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java @@ -1,5 +1,7 @@ package com.example.solidconnection.application.dto; +import com.example.solidconnection.application.domain.Application; +import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.university.domain.UniversityInfoForApply; import java.util.List; @@ -10,13 +12,18 @@ public record UniversityApplicantsResponse( String region, String country, List applicants) { - - public static UniversityApplicantsResponse of(UniversityInfoForApply universityInfoForApply, List applicant) { + public static UniversityApplicantsResponse of(UniversityInfoForApply universityInfoForApply, List applications, SiteUser siteUser) { return new UniversityApplicantsResponse( universityInfoForApply.getKoreanName(), universityInfoForApply.getStudentCapacity(), universityInfoForApply.getUniversity().getRegion().getKoreanName(), universityInfoForApply.getUniversity().getCountry().getKoreanName(), - applicant); + applications.stream() + .map(application -> ApplicantResponse.of(application, isUsers(application, siteUser))) + .toList()); + } + + private static boolean isUsers(Application application, SiteUser siteUser) { + return application.getSiteUser().getId().equals(siteUser.getId()); } } diff --git a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java index 7df53eae6..badbc8254 100644 --- a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java +++ b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java @@ -20,21 +20,26 @@ public interface ApplicationRepository extends JpaRepository boolean existsByNicknameForApply(String nicknameForApply); - List findAllByFirstChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse( - UniversityInfoForApply firstChoiceUniversity, VerifyStatus verifyStatus, String term); - - List findAllBySecondChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse( - UniversityInfoForApply secondChoiceUniversity, VerifyStatus verifyStatus, String term); - - List findAllByThirdChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse( - UniversityInfoForApply thirdChoiceUniversity, VerifyStatus verifyStatus, String term); + @Query(""" + SELECT a + FROM Application a + JOIN FETCH a.siteUser + WHERE (a.firstChoiceUnivApplyInfoId IN :universityIds + OR a.secondChoiceUnivApplyInfoId IN :universityIds + OR a.thirdChoiceUnivApplyInfoId IN :universityIds) + AND a.verifyStatus = :status + AND a.term = :term + AND a.isDelete = false + """) + List findAllByUnivApplyInfoIds(@Param("universityIds") List universityIds, @Param("status") VerifyStatus status, @Param("term") String term); @Query(""" - SELECT a FROM Application a - WHERE a.siteUser = :siteUser - AND a.term = :term - AND a.isDelete = false - """) + SELECT a + FROM Application a + WHERE a.siteUser = :siteUser + AND a.term = :term + AND a.isDelete = false + """) Optional findBySiteUserAndTerm(@Param("siteUser") SiteUser siteUser, @Param("term") String term); default Application getApplicationBySiteUserAndTerm(SiteUser siteUser, String term) { diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java index e13ca7e3f..8e06ad79f 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java @@ -6,10 +6,8 @@ import com.example.solidconnection.application.dto.ApplicationsResponse; import com.example.solidconnection.application.dto.UniversityApplicantsResponse; import com.example.solidconnection.application.repository.ApplicationRepository; -import com.example.solidconnection.cache.annotation.ThunderingHerdCaching; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; import com.example.solidconnection.university.repository.custom.UniversityFilterRepositoryImpl; @@ -18,12 +16,14 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.List; +import java.util.Objects; +import java.util.Map; +import java.util.HashMap; +import java.util.ArrayList; import static com.example.solidconnection.common.exception.ErrorCode.APPLICATION_NOT_APPROVED; @@ -38,97 +38,92 @@ public class ApplicationQueryService { @Value("${university.term}") public String term; - /* - * 다른 지원자들의 성적을 조회한다. - * - 유저가 다른 지원자들을 볼 수 있는지 검증한다. - * - 지역과 키워드를 통해 대학을 필터링한다. - * - 지역은 영어 대문자로 받는다 e.g. ASIA - * - 1지망, 2지망 지원자들을 조회한다. - * */ + // todo: 캐싱 정책 변경 시 수정 필요 @Transactional(readOnly = true) - // todo: 임시로 단일 키로 캐시 적용. 추후 캐싱 전략 재검토 필요. - @ThunderingHerdCaching(key = "applications:all", cacheManager = "customCacheManager", ttlSec = 86400) public ApplicationsResponse getApplicants(SiteUser siteUser, String regionCode, String keyword) { - // 국가와 키워드와 지역을 통해 대학을 필터링한다. - List universities - = universityFilterRepository.findByRegionCodeAndKeywords(regionCode, List.of(keyword)); - - // 1지망, 2지망, 3지망 지원자들을 조회한다. - List firstChoiceApplicants = getFirstChoiceApplicants(universities, siteUser, term); - List secondChoiceApplicants = getSecondChoiceApplicants(universities, siteUser, term); - List thirdChoiceApplicants = getThirdChoiceApplicants(universities, siteUser, term); - return new ApplicationsResponse(firstChoiceApplicants, secondChoiceApplicants, thirdChoiceApplicants); + // 1. 대학 지원 정보 필터링 (regionCode, keyword) + List univApplyInfos = universityFilterRepository.findByRegionCodeAndKeywords(regionCode, List.of(keyword)); + if (univApplyInfos.isEmpty()) { + return new ApplicationsResponse(List.of(), List.of(), List.of()); + } + // 2. 조건에 맞는 모든 Application 한 번에 조회 + List univApplyInfoIds = univApplyInfos.stream() + .map(UniversityInfoForApply::getId) + .toList(); + List applications = applicationRepository.findAllByUnivApplyInfoIds(univApplyInfoIds, VerifyStatus.APPROVED, term); + // 3. 지원서 분류 및 DTO 변환 + return classifyApplicationsByChoice(univApplyInfos, applications, siteUser); } @Transactional(readOnly = true) public ApplicationsResponse getApplicantsByUserApplications(SiteUser siteUser) { Application userLatestApplication = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term); - List userAppliedUniversities = Arrays.asList( - Optional.ofNullable(userLatestApplication.getFirstChoiceUniversity()) - .map(UniversityInfoForApply::getUniversity) - .orElse(null), - Optional.ofNullable(userLatestApplication.getSecondChoiceUniversity()) - .map(UniversityInfoForApply::getUniversity) - .orElse(null), - Optional.ofNullable(userLatestApplication.getThirdChoiceUniversity()) - .map(UniversityInfoForApply::getUniversity) - .orElse(null) - ).stream() + + List universityInfoForApplyIds = Stream.of( + userLatestApplication.getFirstChoiceUnivApplyInfoId(), + userLatestApplication.getSecondChoiceUnivApplyInfoId(), + userLatestApplication.getThirdChoiceUnivApplyInfoId() + ) .filter(Objects::nonNull) .collect(Collectors.toList()); - List firstChoiceApplicants = getFirstChoiceApplicants(userAppliedUniversities, siteUser, term); - List secondChoiceApplicants = getSecondChoiceApplicants(userAppliedUniversities, siteUser, term); - List thirdChoiceApplicants = getThirdChoiceApplicants(userAppliedUniversities, siteUser, term); - return new ApplicationsResponse(firstChoiceApplicants, secondChoiceApplicants, thirdChoiceApplicants); - } - - // 학기별로 상태가 관리된다. - // 금학기에 지원이력이 있는 사용자만 지원정보를 확인할 수 있도록 한다. - @Transactional(readOnly = true) - public void validateSiteUserCanViewApplicants(SiteUser siteUser) { - VerifyStatus verifyStatus = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term).getVerifyStatus(); - if (verifyStatus != VerifyStatus.APPROVED) { - throw new CustomException(APPLICATION_NOT_APPROVED); + if (universityInfoForApplyIds.isEmpty()) { + return new ApplicationsResponse(List.of(), List.of(), List.of()); } - } - private List getFirstChoiceApplicants(List universities, SiteUser siteUser, String term) { - return getApplicantsByChoice( - universities, - siteUser, - uia -> applicationRepository.findAllByFirstChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(uia, VerifyStatus.APPROVED, term) - ); + List applications = applicationRepository.findAllByUnivApplyInfoIds(universityInfoForApplyIds, VerifyStatus.APPROVED, term); + List universityInfosForApply = universityInfoForApplyRepository.findAllByUniversityIds(universityInfoForApplyIds); + + return classifyApplicationsByChoice(universityInfosForApply, applications, siteUser); } - private List getSecondChoiceApplicants(List universities, SiteUser siteUser, String term) { - return getApplicantsByChoice( - universities, - siteUser, - uia -> applicationRepository.findAllBySecondChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(uia, VerifyStatus.APPROVED, term) - ); + private ApplicationsResponse classifyApplicationsByChoice( + List universityInfosForApply, + List applications, + SiteUser siteUser) { + Map> firstChoiceMap = createChoiceMap(applications, Application::getFirstChoiceUnivApplyInfoId); + Map> secondChoiceMap = createChoiceMap(applications, Application::getSecondChoiceUnivApplyInfoId); + Map> thirdChoiceMap = createChoiceMap(applications, Application::getThirdChoiceUnivApplyInfoId); + + List firstChoiceApplicants = + createUniversityApplicantsResponses(universityInfosForApply, firstChoiceMap, siteUser); + List secondChoiceApplicants = + createUniversityApplicantsResponses(universityInfosForApply, secondChoiceMap, siteUser); + List thirdChoiceApplicants = + createUniversityApplicantsResponses(universityInfosForApply, thirdChoiceMap, siteUser); + + return new ApplicationsResponse(firstChoiceApplicants, secondChoiceApplicants, thirdChoiceApplicants); } - private List getThirdChoiceApplicants(List universities, SiteUser siteUser, String term) { - return getApplicantsByChoice( - universities, - siteUser, - uia -> applicationRepository.findAllByThirdChoiceUniversityAndVerifyStatusAndTermAndIsDeleteFalse(uia, VerifyStatus.APPROVED, term) - ); + private Map> createChoiceMap( + List applications, + Function choiceIdExtractor) { + Map> choiceMap = new HashMap<>(); + + for (Application application : applications) { + Long choiceId = choiceIdExtractor.apply(application); + if (choiceId != null) { + choiceMap.computeIfAbsent(choiceId, k -> new ArrayList<>()).add(application); + } + } + + return choiceMap; } - private List getApplicantsByChoice( - List searchedUniversities, - SiteUser siteUser, - Function> findApplicationsByChoice) { - return universityInfoForApplyRepository.findByUniversitiesAndTerm(searchedUniversities, term).stream() - .map(universityInfoForApply -> UniversityApplicantsResponse.of( - universityInfoForApply, - findApplicationsByChoice.apply(universityInfoForApply).stream() - .map(ap -> ApplicantResponse.of( - ap, - Objects.equals(siteUser.getId(), ap.getSiteUser().getId()))) - .toList())) + private List createUniversityApplicantsResponses( + List universityInfosForApply, + Map> choiceMap, + SiteUser siteUser) { + return universityInfosForApply.stream() + .map(uia -> UniversityApplicantsResponse.of(uia, choiceMap.getOrDefault(uia.getId(), List.of()), siteUser)) .toList(); } + + @Transactional(readOnly = true) + public void validateSiteUserCanViewApplicants(SiteUser siteUser) { + VerifyStatus verifyStatus = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term).getVerifyStatus(); + if (verifyStatus != VerifyStatus.APPROVED) { + throw new CustomException(APPLICATION_NOT_APPROVED); + } + } } diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java index af1678a16..721693f38 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java @@ -6,14 +6,12 @@ import com.example.solidconnection.application.dto.ApplyRequest; import com.example.solidconnection.application.dto.UniversityChoiceRequest; import com.example.solidconnection.application.repository.ApplicationRepository; -import com.example.solidconnection.cache.annotation.DefaultCacheOut; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.repository.GpaScoreRepository; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -45,24 +43,14 @@ public class ApplicationSubmissionService { // 학점 및 어학성적이 모두 유효한 경우에만 지원서 등록이 가능하다. // 기존에 있던 status field 우선 APRROVED로 입력시킨다. @Transactional - // todo: 임시로 새로운 신청 생성 시 기존 캐싱 데이터를 삭제한다. 추후 수정 필요 - @DefaultCacheOut( - key = {"applications:all"}, - cacheManager = "customCacheManager" - ) public ApplicationSubmissionResponse apply(SiteUser siteUser, ApplyRequest applyRequest) { UniversityChoiceRequest universityChoiceRequest = applyRequest.universityChoiceRequest(); GpaScore gpaScore = getValidGpaScore(siteUser, applyRequest.gpaScoreId()); LanguageTestScore languageTestScore = getValidLanguageTestScore(siteUser, applyRequest.languageTestScoreId()); - UniversityInfoForApply firstChoiceUniversity = universityInfoForApplyRepository - .getUniversityInfoForApplyByIdAndTerm(universityChoiceRequest.firstChoiceUniversityId(), term); - UniversityInfoForApply secondChoiceUniversity = Optional.ofNullable(universityChoiceRequest.secondChoiceUniversityId()) - .map(id -> universityInfoForApplyRepository.getUniversityInfoForApplyByIdAndTerm(id, term)) - .orElse(null); - UniversityInfoForApply thirdChoiceUniversity = Optional.ofNullable(universityChoiceRequest.thirdChoiceUniversityId()) - .map(id -> universityInfoForApplyRepository.getUniversityInfoForApplyByIdAndTerm(id, term)) - .orElse(null); + long firstChoiceUniversityId = universityChoiceRequest.firstChoiceUniversityId(); + Long secondChoiceUniversityId = universityChoiceRequest.secondChoiceUniversityId(); + Long thirdChoiceUniversityId = universityChoiceRequest.thirdChoiceUniversityId(); Optional existingApplication = applicationRepository.findBySiteUserAndTerm(siteUser, term); int updateCount = existingApplication @@ -72,10 +60,22 @@ public ApplicationSubmissionResponse apply(SiteUser siteUser, ApplyRequest apply return application.getUpdateCount() + 1; }) .orElse(1); - Application newApplication = new Application(siteUser, gpaScore.getGpa(), languageTestScore.getLanguageTest(), - term, updateCount, firstChoiceUniversity, secondChoiceUniversity, thirdChoiceUniversity, getRandomNickname()); + + Application newApplication = new Application( + siteUser, + gpaScore.getGpa(), + languageTestScore.getLanguageTest(), + term, + updateCount, + firstChoiceUniversityId, + secondChoiceUniversityId, + thirdChoiceUniversityId, + getRandomNickname() + ); + newApplication.setVerifyStatus(VerifyStatus.APPROVED); applicationRepository.save(newApplication); + return ApplicationSubmissionResponse.from(newApplication); } diff --git a/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java b/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java index 58fb7aeec..73a5ee14b 100644 --- a/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java @@ -22,7 +22,12 @@ public interface UniversityInfoForApplyRepository extends JpaRepository findFirstByKoreanNameAndTerm(String koreanName, String term); - @Query("SELECT c FROM UniversityInfoForApply c WHERE c.university IN :universities AND c.term = :term") + @Query(""" + SELECT uifa + FROM UniversityInfoForApply uifa + WHERE uifa.university IN :universities + AND uifa.term = :term + """) List findByUniversitiesAndTerm(@Param("universities") List universities, @Param("term") String term); @Query(""" @@ -49,7 +54,8 @@ OR u.region.code IN ( SELECT * FROM university_info_for_apply WHERE term = :term - ORDER BY RAND() LIMIT :limitNum + ORDER BY RAND() + LIMIT :limitNum """, nativeQuery = true) List findRandomByTerm(@Param("term") String term, @Param("limitNum") int limitNum); @@ -58,8 +64,23 @@ default UniversityInfoForApply getUniversityInfoForApplyById(Long id) { .orElseThrow(() -> new CustomException(UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND)); } - default UniversityInfoForApply getUniversityInfoForApplyByIdAndTerm(Long id, String term) { - return findByIdAndTerm(id, term) - .orElseThrow(() -> new CustomException(UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND_FOR_TERM)); - } -} + @Query(""" + SELECT DISTINCT uifa + FROM UniversityInfoForApply uifa + JOIN FETCH uifa.university u + JOIN FETCH u.country c + JOIN FETCH u.region r + WHERE uifa.id IN :ids + """) + List findAllByUniversityIds(@Param("ids") List ids); + + @Query(""" + SELECT DISTINCT uifa + FROM UniversityInfoForApply uifa + JOIN FETCH uifa.university u + JOIN FETCH u.country c + JOIN FETCH u.region r + WHERE u.id IN :universityIds + """) + List findByUniversityIdsWithUniversityAndLocation(@Param("universityIds") List universityIds); +} \ No newline at end of file diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java index c35533877..93850a2f4 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java @@ -8,7 +8,7 @@ public interface UniversityFilterRepository { - List findByRegionCodeAndKeywords(String regionCode, List keywords); + List findByRegionCodeAndKeywords(String regionCode, List keywords); List findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( String regionCode, List keywords, LanguageTestType testType, String testScore, String term); diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java index 25da850da..b01095996 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java @@ -5,8 +5,8 @@ import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.QUniversity; import com.example.solidconnection.university.domain.QUniversityInfoForApply; -import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.QLanguageRequirement; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.StringPath; @@ -28,21 +28,28 @@ public UniversityFilterRepositoryImpl(EntityManager em) { } @Override - public List findByRegionCodeAndKeywords(String regionCode, List keywords) { + public List findByRegionCodeAndKeywords(String regionCode, List keywords) { + QUniversityInfoForApply universityInfoForApply = QUniversityInfoForApply.universityInfoForApply; QUniversity university = QUniversity.university; QCountry country = QCountry.country; QRegion region = QRegion.region; + QLanguageRequirement languageRequirement = QLanguageRequirement.languageRequirement; return queryFactory - .selectFrom(university) - .join(university.country, country) - .join(country.region, region) - .where(regionCodeEq(region, regionCode) - .and(countryOrUniversityContainsKeyword(country, university, keywords)) + .selectFrom(universityInfoForApply) + .join(universityInfoForApply.university, university).fetchJoin() + .join(university.country, country).fetchJoin() + .join(country.region, region).fetchJoin() + .leftJoin(universityInfoForApply.languageRequirements, languageRequirement).fetchJoin() + .where( + regionCodeEq(region, regionCode) + .and(countryOrUniversityContainsKeyword(country, university, keywords)) ) + .distinct() .fetch(); } + private BooleanExpression regionCodeEq(QRegion region, String regionCode) { if (regionCode == null || regionCode.isEmpty()) { return Expressions.asBoolean(true).isTrue(); @@ -69,7 +76,6 @@ private BooleanExpression createKeywordCondition(StringPath namePath, List findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( String regionCode, List keywords, LanguageTestType testType, String testScore, String term) { - QUniversity university = QUniversity.university; QCountry country = QCountry.country; QRegion region = QRegion.region; diff --git a/src/main/resources/db/migration/V13_add_application_index_and_delete_manny_to_one_mapping.sql b/src/main/resources/db/migration/V13_add_application_index_and_delete_manny_to_one_mapping.sql new file mode 100644 index 000000000..3f3018724 --- /dev/null +++ b/src/main/resources/db/migration/V13_add_application_index_and_delete_manny_to_one_mapping.sql @@ -0,0 +1,15 @@ +ALTER TABLE application RENAME COLUMN first_choice_university_id TO first_choice_university_info_for_apply_id; +ALTER TABLE application RENAME COLUMN second_choice_university_id TO second_choice_university_info_for_apply_id; +ALTER TABLE application RENAME COLUMN third_choice_university_id TO third_choice_university_info_for_apply_id; + +CREATE INDEX idx_app_user_term_delete + ON application(site_user_id, term, is_delete); + +CREATE INDEX idx_app_first_choice_search + ON application(verify_status, term, is_delete, first_choice_university_apply_info_id); + +CREATE INDEX idx_app_second_choice_search + ON application(verify_status, term, is_delete, second_choice_university_apply_info_id); + +CREATE INDEX idx_app_third_choice_search + ON application(verify_status, term, is_delete, third_choice_university_apply_info_id); diff --git a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java index b2cbc6460..91dc29c59 100644 --- a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java +++ b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java @@ -20,9 +20,9 @@ public class ApplicationFixture { String term, Gpa gpa, LanguageTest languageTest, - UniversityInfoForApply firstChoiceUniversity, - UniversityInfoForApply secondChoiceUniversity, - UniversityInfoForApply thirdChoiceUniversity + Long firstChoiceUniversityApplyInfoId, + Long secondChoiceUniversityApplyInfoId, + Long thirdChoiceUniversityApplyInfoId ) { return applicationFixtureBuilder.application() .siteUser(siteUser) @@ -30,9 +30,9 @@ public class ApplicationFixture { .languageTest(languageTest) .nicknameForApply(nicknameForApply) .term(term) - .firstChoiceUniversity(firstChoiceUniversity) - .secondChoiceUniversity(secondChoiceUniversity) - .thirdChoiceUniversity(thirdChoiceUniversity) + .firstChoiceUniversityApplyInfoId(firstChoiceUniversityApplyInfoId) + .secondChoiceUniversityApplyInfoId(secondChoiceUniversityApplyInfoId) + .thirdChoiceUniversityApplyInfoId(thirdChoiceUniversityApplyInfoId) .create(); } } diff --git a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java index 5f6c06741..c9bf33c3e 100644 --- a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java @@ -18,9 +18,9 @@ public class ApplicationFixtureBuilder { private Gpa gpa; private LanguageTest languageTest; - private UniversityInfoForApply firstChoiceUniversity; - private UniversityInfoForApply secondChoiceUniversity; - private UniversityInfoForApply thirdChoiceUniversity; + private Long firstChoiceUniversityApplyInfoId; + private Long secondChoiceUniversityApplyInfoId; + private Long thirdChoiceUniversityApplyInfoId; private SiteUser siteUser; private String nicknameForApply; private String term; @@ -39,18 +39,18 @@ public ApplicationFixtureBuilder languageTest(LanguageTest languageTest) { return this; } - public ApplicationFixtureBuilder firstChoiceUniversity(UniversityInfoForApply firstChoiceUniversity) { - this.firstChoiceUniversity = firstChoiceUniversity; + public ApplicationFixtureBuilder firstChoiceUniversityApplyInfoId(Long firstChoiceUniversityApplyInfoId) { + this.firstChoiceUniversityApplyInfoId = firstChoiceUniversityApplyInfoId; return this; } - public ApplicationFixtureBuilder secondChoiceUniversity(UniversityInfoForApply secondChoiceUniversity) { - this.secondChoiceUniversity = secondChoiceUniversity; + public ApplicationFixtureBuilder secondChoiceUniversityApplyInfoId(Long secondChoiceUniversityApplyInfoId) { + this.secondChoiceUniversityApplyInfoId = secondChoiceUniversityApplyInfoId; return this; } - public ApplicationFixtureBuilder thirdChoiceUniversity(UniversityInfoForApply thirdChoiceUniversity) { - this.thirdChoiceUniversity = thirdChoiceUniversity; + public ApplicationFixtureBuilder thirdChoiceUniversityApplyInfoId(Long thirdChoiceUniversityApplyInfoId) { + this.thirdChoiceUniversityApplyInfoId = thirdChoiceUniversityApplyInfoId; return this; } @@ -75,9 +75,9 @@ public Application create() { gpa, languageTest, term, - firstChoiceUniversity, - secondChoiceUniversity, - thirdChoiceUniversity, + firstChoiceUniversityApplyInfoId, + secondChoiceUniversityApplyInfoId, + thirdChoiceUniversityApplyInfoId, nicknameForApply ); application.setVerifyStatus(VerifyStatus.APPROVED); diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java index ac0b83619..1a0f927a2 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java @@ -106,7 +106,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보, + 괌대학_A_지원_정보.getId(), null, null ); @@ -116,7 +116,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), - 괌대학_B_지원_정보, + 괌대학_B_지원_정보.getId(), null, null ); @@ -126,7 +126,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), - 서던덴마크대학교_지원_정보, + 서던덴마크대학교_지원_정보.getId(), null, null ); @@ -141,11 +141,11 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsAll(List.of( UniversityApplicantsResponse.of(괌대학_A_지원_정보, - List.of(ApplicantResponse.of(application1, true))), + List.of(application1), user1), UniversityApplicantsResponse.of(괌대학_B_지원_정보, - List.of(ApplicantResponse.of(application2, false))), + List.of(application2), user1), UniversityApplicantsResponse.of(서던덴마크대학교_지원_정보, - List.of(ApplicantResponse.of(application3, false))) + List.of(application3), user1) )); } @@ -158,7 +158,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보, + 괌대학_A_지원_정보.getId(), null, null ); @@ -168,7 +168,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), - 괌대학_B_지원_정보, + 괌대학_B_지원_정보.getId(), null, null ); @@ -178,7 +178,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), - 서던덴마크대학교_지원_정보, + 서던덴마크대학교_지원_정보.getId(), null, null ); @@ -193,9 +193,9 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsExactlyInAnyOrder( UniversityApplicantsResponse.of(괌대학_A_지원_정보, - List.of(ApplicantResponse.of(application1, true))), + List.of(application1), user1), UniversityApplicantsResponse.of(괌대학_B_지원_정보, - List.of(ApplicantResponse.of(application2, false))) + List.of(application2), user1) ); } @@ -208,7 +208,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보, + 괌대학_A_지원_정보.getId(), null, null ); @@ -218,7 +218,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), - 괌대학_B_지원_정보, + 괌대학_B_지원_정보.getId(), null, null ); @@ -228,7 +228,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), - 서던덴마크대학교_지원_정보, + 서던덴마크대학교_지원_정보.getId(), null, null ); @@ -243,9 +243,9 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsExactlyInAnyOrder( UniversityApplicantsResponse.of(괌대학_A_지원_정보, - List.of(ApplicantResponse.of(application1, true))), + List.of(application1), user1), UniversityApplicantsResponse.of(괌대학_B_지원_정보, - List.of(ApplicantResponse.of(application2, false))) + List.of(application2), user1) ); } @@ -258,7 +258,7 @@ class 지원자_목록_조회_테스트 { "1988-1", gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보, + 괌대학_A_지원_정보.getId(), null, null ); @@ -273,7 +273,7 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).doesNotContainAnyElementsOf(List.of( UniversityApplicantsResponse.of(괌대학_A_지원_정보, - List.of(ApplicantResponse.of(application, true))) + List.of(application), user1) )); } @@ -286,7 +286,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보, + 괌대학_A_지원_정보.getId(), null, null ); @@ -298,7 +298,7 @@ class 지원자_목록_조회_테스트 { term, gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), - 괌대학_B_지원_정보, + 괌대학_B_지원_정보.getId(), null, null ); @@ -320,7 +320,6 @@ class 지원자_목록_조회_테스트 { @Nested class 경쟁자_목록_조회_테스트 { - @Test void 이번_학기_지원한_대학의_경쟁자_목록을_조회한다() { // given @@ -330,7 +329,7 @@ class 경쟁자_목록_조회_테스트 { term, gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보, + 괌대학_A_지원_정보.getId(), null, null ); @@ -340,7 +339,7 @@ class 경쟁자_목록_조회_테스트 { term, gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), - 괌대학_A_지원_정보, + 괌대학_A_지원_정보.getId(), null, null ); @@ -350,7 +349,7 @@ class 경쟁자_목록_조회_테스트 { term, gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), - 괌대학_B_지원_정보, + 서던덴마크대학교_지원_정보.getId(), null, null ); @@ -359,10 +358,8 @@ class 경쟁자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsExactlyInAnyOrder( - UniversityApplicantsResponse.of(괌대학_A_지원_정보, List.of( - ApplicantResponse.of(application1, true), - ApplicantResponse.of(application2, false) - )) + UniversityApplicantsResponse.of(괌대학_A_지원_정보, + List.of(application1, application2), user1) ); } @@ -375,47 +372,43 @@ class 경쟁자_목록_조회_테스트 { term, gpaScore1.getGpa(), languageTestScore1.getLanguageTest(), - 괌대학_A_지원_정보, + 괌대학_A_지원_정보.getId(), null, null ); - applicationFixture.지원서( + Application application2 = applicationFixture.지원서( user2, "nickname2", term, gpaScore2.getGpa(), languageTestScore2.getLanguageTest(), - null, - 괌대학_B_지원_정보, - null + 괌대학_A_지원_정보.getId(), + 괌대학_B_지원_정보.getId(), + 서던덴마크대학교_지원_정보.getId() ); - applicationFixture.지원서( + Application application3 = applicationFixture.지원서( user3, "nickname3", term, gpaScore3.getGpa(), languageTestScore3.getLanguageTest(), + 서던덴마크대학교_지원_정보.getId(), null, - null, - 서던덴마크대학교_지원_정보 + null ); // when ApplicationsResponse response = applicationQueryService.getApplicantsByUserApplications(user1); // then - assertThat(response.firstChoice()).containsExactlyInAnyOrder( - UniversityApplicantsResponse.of(괌대학_A_지원_정보, - List.of(ApplicantResponse.of(application1, true))) - ); - - assertThat(response.secondChoice()).containsExactlyInAnyOrder( - UniversityApplicantsResponse.of(괌대학_A_지원_정보, List.of()) - ); - - assertThat(response.thirdChoice()).containsExactlyInAnyOrder( - UniversityApplicantsResponse.of(괌대학_A_지원_정보, List.of()) - ); + assertThat(response.firstChoice()) + .hasSize(1) + .allSatisfy(uar -> { + assertThat(uar.koreanName()).isEqualTo(괌대학_A_지원_정보.getKoreanName()); + assertThat(uar.applicants()) + .extracting(ApplicantResponse::nicknameForApply) + .containsExactlyInAnyOrder("nickname1", "nickname2"); + }); } } } diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java index f70cd9fc0..2718fb90b 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java @@ -86,12 +86,18 @@ void setUp() { // then Application savedApplication = applicationRepository.findBySiteUserAndTerm(user, term).orElseThrow(); assertAll( - () -> assertThat(response.applyCount()).isEqualTo(savedApplication.getUpdateCount()), - () -> assertThat(savedApplication.getVerifyStatus()).isEqualTo(VerifyStatus.APPROVED), - () -> assertThat(savedApplication.isDelete()).isFalse(), - () -> assertThat(savedApplication.getFirstChoiceUniversity().getId()).isEqualTo(괌대학_A_지원_정보.getId()), - () -> assertThat(savedApplication.getSecondChoiceUniversity().getId()).isEqualTo(괌대학_B_지원_정보.getId()), - () -> assertThat(savedApplication.getThirdChoiceUniversity().getId()).isEqualTo(서던덴마크대학교_지원_정보.getId()) + () -> assertThat(response.applyCount()) + .isEqualTo(savedApplication.getUpdateCount()), + () -> assertThat(savedApplication.getVerifyStatus()) + .isEqualTo(VerifyStatus.APPROVED), + () -> assertThat(savedApplication.isDelete()) + .isFalse(), + () -> assertThat(savedApplication.getFirstChoiceUnivApplyInfoId()) + .isEqualTo(괌대학_A_지원_정보.getId()), + () -> assertThat(savedApplication.getSecondChoiceUnivApplyInfoId()) + .isEqualTo(괌대학_B_지원_정보.getId()), + () -> assertThat(savedApplication.getThirdChoiceUnivApplyInfoId()) + .isEqualTo(서던덴마크대학교_지원_정보.getId()) ); } From af2ba816391146b1a5c5967712b5a146f70248e6 Mon Sep 17 00:00:00 2001 From: seonghyeok cho <65901319+whqtker@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:58:17 +0900 Subject: [PATCH 23/90] =?UTF-8?q?refactor:=20Siteuser=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20(#336)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: SiteUser 엔티티 - SiteUser 엔티티의 nickname 컬럼에 unique 제약 조건 추가 * fix: site_user 테이블의 nickname 컬럼에 unique 제약 조건 추가 * refactor: SiteUser 도메인 관련 - MyPageService에서 중복 닉네임 검증 로직 제거 - 컨트롤러에서 try-catch 문을 통해 예외 처리 * refactor: 회원 정보 변경 관련 로직 수정 - 변경된 컬럼만 업데이트하는 쿼리 사용 * refactor: sign up 시 닉네임 검증 로직 제거 - nickname 제약 조건의 이름 명시적으로 지정 * refactor: 도메인 로직 변경에 따른 테스트 코드 수정 - 게시글_좋아요_동시성_문제를_해결한다: 동일한 닉네임을 사용하지 않도록 변경 - 이메일이_같더라도_인증_유형이_다른_사용자는_정상_저장한다: 동일한 닉네임을 사용하지 않도록 변경 - 닉네임 무결성 테스트는 repository test로 분리 - 새로운_이미지로_성공적으로_업데이트한다: DB에서 사용자 조회 후 URL 비교 * refactor: 예외 처리를 컨트롤러가 아닌 서비스 계층에서 처리하도록 변경 * refactor: JPQL이 아닌 dirty check를 사용하도록 변경 외 - 중복 닉네임 검증보다 닉네임 수정 시간 검증이 선행되도록 변경 - 준영속 엔티티를 save()하는 과정에서 발생하는 N+1 문제 해결 - DB 레벨, 애플리케이션 레벨 검증 모두 사용하도록 - 위 변경사항에 따른 테스트 코드 수정 * refactor: 로그에만 예외 원문 포함하도록 변경 및 상태코드 수정 * refactor: 회원가입 시 닉네임 관련 애플리케이션 레벨 검증 추가 - CustomExceptionHandler에 DB 레벨에서 중복 닉네임 검증 추가에 따라 try-catch 문 삭제 * refactor: 미사용 코드 제거 (JPQL 관련) * test: 중복된_닉네임으로_사용자를_저장하면_예외_응답을_반환한다 테스트 수정 - save -> saveAndFlush * chore: 미사용 import문 제거 * chore: 함수명과 중괄호 사이 띄어쓰기 추가 (컨벤션 관련) * chore: 호출 순서에 따라 메서드 선언 재배치 * chore: 마이그레이션 파일 버전 수정 V13 -> V14 --- .../exception/CustomExceptionHandler.java | 11 +++++ .../common/exception/ErrorCode.java | 3 ++ .../siteuser/controller/MyPageController.java | 1 + .../siteuser/domain/SiteUser.java | 4 ++ .../repository/SiteUserRepository.java | 1 + .../siteuser/service/MyPageService.java | 29 ++++++------ ...V14__set_unique_constraint_to_nickname.sql | 3 ++ .../PostLikeCountConcurrencyTest.java | 5 ++- .../solidconnection/e2e/DynamicFixture.java | 4 +- .../repository/SiteUserRepositoryTest.java | 44 ++++++++++++++++--- .../siteuser/service/MyPageServiceTest.java | 20 +++------ 11 files changed, 88 insertions(+), 37 deletions(-) create mode 100644 src/main/resources/db/migration/V14__set_unique_constraint_to_nickname.sql diff --git a/src/main/java/com/example/solidconnection/common/exception/CustomExceptionHandler.java b/src/main/java/com/example/solidconnection/common/exception/CustomExceptionHandler.java index bc03ca98a..57d6f4769 100644 --- a/src/main/java/com/example/solidconnection/common/exception/CustomExceptionHandler.java +++ b/src/main/java/com/example/solidconnection/common/exception/CustomExceptionHandler.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.exc.InvalidFormatException; import io.jsonwebtoken.JwtException; import lombok.extern.slf4j.Slf4j; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -13,6 +14,7 @@ import java.util.ArrayList; import java.util.List; +import static com.example.solidconnection.common.exception.ErrorCode.DATA_INTEGRITY_VIOLATION; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_INPUT; import static com.example.solidconnection.common.exception.ErrorCode.JSON_PARSING_FAILED; import static com.example.solidconnection.common.exception.ErrorCode.JWT_EXCEPTION; @@ -56,6 +58,15 @@ public ResponseEntity handleValidationExceptions(MethodArgumentNo .body(errorResponse); } + @ExceptionHandler(DataIntegrityViolationException.class) + public ResponseEntity handleDataIntegrityViolationException(DataIntegrityViolationException ex) { + log.error("데이터 무결성 제약조건 위반 예외 발생 : {}", ex.getMessage()); + ErrorResponse errorResponse = new ErrorResponse(DATA_INTEGRITY_VIOLATION, "데이터 무결성 제약조건 위반 예외 발생"); + return ResponseEntity + .status(DATA_INTEGRITY_VIOLATION.getCode()) + .body(errorResponse); + } + @ExceptionHandler(JwtException.class) public ResponseEntity handleJwtException(JwtException ex) { String errorMessage = ex.getMessage(); diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 6e932a159..00e600201 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -96,6 +96,9 @@ public enum ErrorCode { USER_DO_NOT_HAVE_GPA(HttpStatus.BAD_REQUEST.value(), "해당 유저의 학점을 찾을 수 없음"), REJECTED_REASON_REQUIRED(HttpStatus.BAD_REQUEST.value(), "거절 사유가 필요합니다."), + // database + DATA_INTEGRITY_VIOLATION(HttpStatus.CONFLICT.value(), "데이터베이스 무결성 제약조건 위반이 발생했습니다."), + // general JSON_PARSING_FAILED(HttpStatus.BAD_REQUEST.value(), "JSON 파싱을 할 수 없습니다."), JWT_EXCEPTION(HttpStatus.BAD_REQUEST.value(), "JWT 토큰을 처리할 수 없습니다."), diff --git a/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java b/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java index ff36d7baa..772da0d32 100644 --- a/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java +++ b/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java @@ -1,5 +1,6 @@ package com.example.solidconnection.siteuser.controller; + import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.MyPageResponse; diff --git a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java index bcfb1f9ac..98c18b56a 100644 --- a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java +++ b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java @@ -35,6 +35,10 @@ @UniqueConstraint( name = "uk_site_user_email_auth_type", columnNames = {"email", "auth_type"} + ), + @UniqueConstraint( + name = "uk_site_user_nickname", + columnNames = {"nickname"} ) }) public class SiteUser { diff --git a/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java b/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java index e0617f046..51cb410f6 100644 --- a/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java +++ b/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java @@ -1,5 +1,6 @@ package com.example.solidconnection.siteuser.repository; + import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java index 2c84f0518..f5b463c0a 100644 --- a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java +++ b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java @@ -21,6 +21,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; import static com.example.solidconnection.common.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; @RequiredArgsConstructor @Service @@ -47,27 +48,23 @@ public MyPageResponse getMyPageInfo(SiteUser siteUser) { * */ @Transactional public void updateMyPageInfo(SiteUser siteUser, MultipartFile imageFile, String nickname) { + SiteUser user = siteUserRepository.findById(siteUser.getId()) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); + if (nickname != null) { + validateNicknameNotChangedRecently(user.getNicknameModifiedAt()); validateNicknameUnique(nickname); - validateNicknameNotChangedRecently(siteUser.getNicknameModifiedAt()); - siteUser.setNickname(nickname); - siteUser.setNicknameModifiedAt(LocalDateTime.now()); + user.setNickname(nickname); + user.setNicknameModifiedAt(LocalDateTime.now()); } if (imageFile != null && !imageFile.isEmpty()) { UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(imageFile, ImgType.PROFILE); - if (!isDefaultProfileImage(siteUser.getProfileImageUrl())) { - s3Service.deleteExProfile(siteUser); + if (!isDefaultProfileImage(user.getProfileImageUrl())) { + s3Service.deleteExProfile(user); } String profileImageUrl = uploadedFile.fileUrl(); - siteUser.setProfileImageUrl(profileImageUrl); - } - siteUserRepository.save(siteUser); - } - - private void validateNicknameUnique(String nickname) { - if (siteUserRepository.existsByNickname(nickname)) { - throw new CustomException(NICKNAME_ALREADY_EXISTED); + user.setProfileImageUrl(profileImageUrl); } } @@ -82,6 +79,12 @@ private void validateNicknameNotChangedRecently(LocalDateTime lastModifiedAt) { } } + private void validateNicknameUnique(String nickname) { + if (siteUserRepository.existsByNickname(nickname)) { + throw new CustomException(NICKNAME_ALREADY_EXISTED); + } + } + private boolean isDefaultProfileImage(String profileImageUrl) { String prefix = "profile/"; return profileImageUrl == null || !profileImageUrl.startsWith(prefix); diff --git a/src/main/resources/db/migration/V14__set_unique_constraint_to_nickname.sql b/src/main/resources/db/migration/V14__set_unique_constraint_to_nickname.sql new file mode 100644 index 000000000..75d290f7b --- /dev/null +++ b/src/main/resources/db/migration/V14__set_unique_constraint_to_nickname.sql @@ -0,0 +1,3 @@ +ALTER TABLE site_user +ADD CONSTRAINT uk_site_user_nickname +UNIQUE (nickname); diff --git a/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java b/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java index 188f717ca..472cb3aca 100644 --- a/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java +++ b/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java @@ -21,7 +21,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import static com.example.solidconnection.e2e.DynamicFixture.createSiteUserByEmail; +import static com.example.solidconnection.e2e.DynamicFixture.createSiteUserByEmailAndNickname; import static org.junit.jupiter.api.Assertions.assertEquals; @TestContainerSpringBootTest @@ -92,7 +92,8 @@ private Post createPost(Board board, SiteUser siteUser) { for (int i = 0; i < THREAD_NUMS; i++) { String email = "email" + i; - SiteUser tmpSiteUser = siteUserRepository.save(createSiteUserByEmail(email)); + String nickname = "nickname" + i; + SiteUser tmpSiteUser = siteUserRepository.save(createSiteUserByEmailAndNickname(email, nickname)); executorService.submit(() -> { try { postLikeService.likePost(tmpSiteUser, post.getId()); diff --git a/src/test/java/com/example/solidconnection/e2e/DynamicFixture.java b/src/test/java/com/example/solidconnection/e2e/DynamicFixture.java index 4c90e58dc..5187877d2 100644 --- a/src/test/java/com/example/solidconnection/e2e/DynamicFixture.java +++ b/src/test/java/com/example/solidconnection/e2e/DynamicFixture.java @@ -6,10 +6,10 @@ public class DynamicFixture { // todo: test fixture 개선 작업 이후, 이 클래스의 사용이 대체되면 삭제 필요 - public static SiteUser createSiteUserByEmail(String email) { + public static SiteUser createSiteUserByEmailAndNickname(String email, String nickname) { return new SiteUser( email, - "nickname", + nickname, "profileImage", PreparationStatus.CONSIDERING, Role.MENTEE diff --git a/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java b/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java index 41806d6cf..b8c51c148 100644 --- a/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java @@ -24,8 +24,8 @@ class 이메일과_인증_유형이_동일한_사용자는_저장할_수_없다 @Test void 이메일과_인증_유형이_동일한_사용자를_저장하면_예외_응답을_반환한다() { // given - SiteUser user1 = createSiteUser("email", AuthType.KAKAO); - SiteUser user2 = createSiteUser("email", AuthType.KAKAO); + SiteUser user1 = createSiteUser("email", "nickname1", AuthType.KAKAO); + SiteUser user2 = createSiteUser("email", "nickname2", AuthType.KAKAO); siteUserRepository.save(user1); // when, then @@ -36,8 +36,8 @@ class 이메일과_인증_유형이_동일한_사용자는_저장할_수_없다 @Test void 이메일이_같더라도_인증_유형이_다른_사용자는_정상_저장한다() { // given - SiteUser user1 = createSiteUser("email", AuthType.KAKAO); - SiteUser user2 = createSiteUser("email", AuthType.APPLE); + SiteUser user1 = createSiteUser("email", "nickname1", AuthType.KAKAO); + SiteUser user2 = createSiteUser("email", "nickname2", AuthType.APPLE); siteUserRepository.save(user1); // when, then @@ -46,10 +46,42 @@ class 이메일과_인증_유형이_동일한_사용자는_저장할_수_없다 } } - private SiteUser createSiteUser(String email, AuthType authType) { + @Nested + class 닉네임은_중복될_수_없다 { + + @Test + void 중복된_닉네임으로_사용자를_저장하면_예외_응답을_반환한다() { + // given + SiteUser user1 = createSiteUser("email1", "nickname", AuthType.KAKAO); + SiteUser user2 = createSiteUser("email2", "nickname", AuthType.KAKAO); + siteUserRepository.save(user1); + + // when, then + assertThatCode(() -> siteUserRepository.saveAndFlush(user2)) + .isInstanceOf(DataIntegrityViolationException.class); + } + + @Test + void 중복된_닉네임으로_변경하면_예외_응답을_반환한다() { + // given + SiteUser user1 = createSiteUser("email1", "nickname1", AuthType.KAKAO); + SiteUser user2 = createSiteUser("email2", "nickname2", AuthType.KAKAO); + siteUserRepository.save(user1); + siteUserRepository.save(user2); + + // when + user2.setNickname("nickname1"); + + // then + assertThatCode(() -> siteUserRepository.saveAndFlush(user2)) + .isInstanceOf(DataIntegrityViolationException.class); + } + } + + private SiteUser createSiteUser(String email, String nickname, AuthType authType) { return new SiteUser( email, - "nickname", + nickname, "profileImageUrl", PreparationStatus.CONSIDERING, Role.MENTEE, diff --git a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java index 1168a0eeb..a86d80883 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java @@ -29,11 +29,11 @@ import java.util.List; import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; -import static com.example.solidconnection.common.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; import static com.example.solidconnection.siteuser.service.MyPageService.MIN_DAYS_BETWEEN_NICKNAME_CHANGES; import static com.example.solidconnection.siteuser.service.MyPageService.NICKNAME_LAST_CHANGE_DATE_FORMAT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.BDDMockito.any; import static org.mockito.BDDMockito.eq; import static org.mockito.BDDMockito.given; @@ -118,7 +118,8 @@ class 프로필_이미지_수정_테스트 { myPageService.updateMyPageInfo(user, imageFile, "newNickname"); // then - assertThat(user.getProfileImageUrl()).isEqualTo(expectedUrl); + SiteUser updatedUser = siteUserRepository.findById(user.getId()).get(); + assertThat(updatedUser.getProfileImageUrl()).isEqualTo(expectedUrl); } @Test @@ -147,7 +148,8 @@ class 프로필_이미지_수정_테스트 { myPageService.updateMyPageInfo(커스텀_프로필_사용자, imageFile, "newNickname"); // then - then(s3Service).should().deleteExProfile(커스텀_프로필_사용자); + then(s3Service).should().deleteExProfile(argThat(user -> + user.getId().equals(커스텀_프로필_사용자.getId()))); } } @@ -175,23 +177,13 @@ void setUp() { assertThat(updatedUser.getNickname()).isEqualTo(newNickname); } - @Test - void 중복된_닉네임으로_변경하면_예외_응답을_반환한다() { - // given - SiteUser existingUser = siteUserFixture.사용자(1, "existing nickname"); - - // when & then - assertThatCode(() -> myPageService.updateMyPageInfo(user, null, existingUser.getNickname())) - .isInstanceOf(CustomException.class) - .hasMessage(NICKNAME_ALREADY_EXISTED.getMessage()); - } - @Test void 최소_대기기간이_지나지_않은_상태에서_변경하면_예외_응답을_반환한다() { // given MockMultipartFile imageFile = createValidImageFile(); LocalDateTime modifiedAt = LocalDateTime.now().minusDays(MIN_DAYS_BETWEEN_NICKNAME_CHANGES - 1); user.setNicknameModifiedAt(modifiedAt); + siteUserRepository.save(user); // when & then assertThatCode(() -> myPageService.updateMyPageInfo(user, imageFile, "nickname12")) From 0144cb280b371625c058b99e5a0838fb106b48f1 Mon Sep 17 00:00:00 2001 From: seonghyeok cho <65901319+whqtker@users.noreply.github.com> Date: Wed, 25 Jun 2025 01:14:39 +0900 Subject: [PATCH 24/90] =?UTF-8?q?refactor:=20University=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20(#3?= =?UTF-8?q?38)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: rename UniversityInfoForApply to UnivApplyInfo - 엔티티명만 변경, 이에 따른 변수명 변경 * fix: 동일한 사용자가 동일한 대학 좋아요가 가능했던 문제 수정 - 복합 unique 제약 조건 추가, 관련 테스트 코드 추가 * refactor: liked_university 테이블에 복합 unique 제약 조건을 설정하는 sql 작성 * refactor: 파일 및 변수명 변경 * chore: 미사용 함수 제거 * chore: UK 제약 조건 이름 변경 * chore: 테스트 코드에서 불필요한 검증 제거 * chore: 마이그레이션 파일 버전 수정 V14 -> V15 --- .../dto/UniversityApplicantsResponse.java | 12 +-- .../repository/ApplicationRepository.java | 1 - .../service/ApplicationQueryService.java | 23 +++-- .../service/ApplicationSubmissionService.java | 2 - .../repository/LikedUniversityRepository.java | 4 +- .../siteuser/service/MyPageService.java | 2 +- .../domain/LanguageRequirement.java | 4 +- .../university/domain/LikedUniversity.java | 12 ++- ...tyInfoForApply.java => UnivApplyInfo.java} | 6 +- .../dto/UniversityDetailResponse.java | 36 ++++---- ...UniversityInfoForApplyPreviewResponse.java | 22 ++--- .../LanguageRequirementRepository.java | 9 -- .../UniversityInfoForApplyRepository.java | 31 ++++--- .../custom/UniversityFilterRepository.java | 7 +- .../UniversityFilterRepositoryImpl.java | 36 ++++---- .../GeneralUniversityRecommendService.java | 4 +- .../service/UniversityLikeService.java | 16 ++-- .../service/UniversityQueryService.java | 8 +- .../service/UniversityRecommendService.java | 12 +-- ..._unique_constraint_to_liked_university.sql | 3 + src/main/resources/secret | 2 +- .../fixture/ApplicationFixture.java | 2 +- .../fixture/ApplicationFixtureBuilder.java | 1 - .../service/ApplicationQueryServiceTest.java | 18 ++-- .../ApplicationSubmissionServiceTest.java | 18 ++-- .../siteuser/service/MyPageServiceTest.java | 10 +-- .../fixture/LanguageRequirementFixture.java | 12 +-- .../LanguageRequirementFixtureBuilder.java | 12 +-- ...Fixture.java => UnivApplyInfoFixture.java} | 46 +++++----- ....java => UnivApplyInfoFixtureBuilder.java} | 20 ++--- .../UniversityLikeRepositoryTest.java | 89 +++++++++++++++++++ ...GeneralUniversityRecommendServiceTest.java | 28 +++--- .../service/UniversityLikeServiceTest.java | 18 ++-- .../service/UniversityQueryServiceTest.java | 46 +++++----- .../UniversityRecommendServiceTest.java | 38 ++++---- 35 files changed, 350 insertions(+), 260 deletions(-) rename src/main/java/com/example/solidconnection/university/domain/{UniversityInfoForApply.java => UnivApplyInfo.java} (92%) create mode 100644 src/main/resources/db/migration/V15__add_unique_constraint_to_liked_university.sql rename src/test/java/com/example/solidconnection/university/fixture/{UniversityInfoForApplyFixture.java => UnivApplyInfoFixture.java} (56%) rename src/test/java/com/example/solidconnection/university/fixture/{UniversityInfoForApplyFixtureBuilder.java => UnivApplyInfoFixtureBuilder.java} (64%) create mode 100644 src/test/java/com/example/solidconnection/university/repository/UniversityLikeRepositoryTest.java diff --git a/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java b/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java index 2e43ab851..1fe24546f 100644 --- a/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java @@ -2,7 +2,7 @@ import com.example.solidconnection.application.domain.Application; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import java.util.List; @@ -12,12 +12,12 @@ public record UniversityApplicantsResponse( String region, String country, List applicants) { - public static UniversityApplicantsResponse of(UniversityInfoForApply universityInfoForApply, List applications, SiteUser siteUser) { + public static UniversityApplicantsResponse of(UnivApplyInfo univApplyInfo, List applications, SiteUser siteUser) { return new UniversityApplicantsResponse( - universityInfoForApply.getKoreanName(), - universityInfoForApply.getStudentCapacity(), - universityInfoForApply.getUniversity().getRegion().getKoreanName(), - universityInfoForApply.getUniversity().getCountry().getKoreanName(), + univApplyInfo.getKoreanName(), + univApplyInfo.getStudentCapacity(), + univApplyInfo.getUniversity().getRegion().getKoreanName(), + univApplyInfo.getUniversity().getCountry().getKoreanName(), applications.stream() .map(application -> ApplicantResponse.of(application, isUsers(application, siteUser))) .toList()); diff --git a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java index badbc8254..0cbf1cf68 100644 --- a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java +++ b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java @@ -4,7 +4,6 @@ import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.domain.UniversityInfoForApply; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java index 8e06ad79f..dbfa28990 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java @@ -2,13 +2,12 @@ import com.example.solidconnection.application.domain.Application; import com.example.solidconnection.application.domain.VerifyStatus; -import com.example.solidconnection.application.dto.ApplicantResponse; import com.example.solidconnection.application.dto.ApplicationsResponse; import com.example.solidconnection.application.dto.UniversityApplicantsResponse; import com.example.solidconnection.application.repository.ApplicationRepository; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; import com.example.solidconnection.university.repository.custom.UniversityFilterRepositoryImpl; import lombok.RequiredArgsConstructor; @@ -16,14 +15,14 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import java.util.List; -import java.util.Objects; -import java.util.Map; -import java.util.HashMap; -import java.util.ArrayList; import static com.example.solidconnection.common.exception.ErrorCode.APPLICATION_NOT_APPROVED; @@ -42,13 +41,13 @@ public class ApplicationQueryService { @Transactional(readOnly = true) public ApplicationsResponse getApplicants(SiteUser siteUser, String regionCode, String keyword) { // 1. 대학 지원 정보 필터링 (regionCode, keyword) - List univApplyInfos = universityFilterRepository.findByRegionCodeAndKeywords(regionCode, List.of(keyword)); + List univApplyInfos = universityFilterRepository.findByRegionCodeAndKeywords(regionCode, List.of(keyword)); if (univApplyInfos.isEmpty()) { return new ApplicationsResponse(List.of(), List.of(), List.of()); } // 2. 조건에 맞는 모든 Application 한 번에 조회 List univApplyInfoIds = univApplyInfos.stream() - .map(UniversityInfoForApply::getId) + .map(UnivApplyInfo::getId) .toList(); List applications = applicationRepository.findAllByUnivApplyInfoIds(univApplyInfoIds, VerifyStatus.APPROVED, term); // 3. 지원서 분류 및 DTO 변환 @@ -72,13 +71,13 @@ public ApplicationsResponse getApplicantsByUserApplications(SiteUser siteUser) { } List applications = applicationRepository.findAllByUnivApplyInfoIds(universityInfoForApplyIds, VerifyStatus.APPROVED, term); - List universityInfosForApply = universityInfoForApplyRepository.findAllByUniversityIds(universityInfoForApplyIds); + List universityInfosForApply = universityInfoForApplyRepository.findAllByUniversityIds(universityInfoForApplyIds); return classifyApplicationsByChoice(universityInfosForApply, applications, siteUser); } private ApplicationsResponse classifyApplicationsByChoice( - List universityInfosForApply, + List universityInfosForApply, List applications, SiteUser siteUser) { Map> firstChoiceMap = createChoiceMap(applications, Application::getFirstChoiceUnivApplyInfoId); @@ -111,7 +110,7 @@ private Map> createChoiceMap( } private List createUniversityApplicantsResponses( - List universityInfosForApply, + List universityInfosForApply, Map> choiceMap, SiteUser siteUser) { return universityInfosForApply.stream() diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java index 721693f38..ea44840ee 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java @@ -12,7 +12,6 @@ import com.example.solidconnection.score.repository.GpaScoreRepository; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -33,7 +32,6 @@ public class ApplicationSubmissionService { public static final int APPLICATION_UPDATE_COUNT_LIMIT = 3; private final ApplicationRepository applicationRepository; - private final UniversityInfoForApplyRepository universityInfoForApplyRepository; private final GpaScoreRepository gpaScoreRepository; private final LanguageTestScoreRepository languageTestScoreRepository; diff --git a/src/main/java/com/example/solidconnection/siteuser/repository/LikedUniversityRepository.java b/src/main/java/com/example/solidconnection/siteuser/repository/LikedUniversityRepository.java index d15949723..f0d328c0a 100644 --- a/src/main/java/com/example/solidconnection/siteuser/repository/LikedUniversityRepository.java +++ b/src/main/java/com/example/solidconnection/siteuser/repository/LikedUniversityRepository.java @@ -2,7 +2,7 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.university.domain.LikedUniversity; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; @@ -14,5 +14,5 @@ public interface LikedUniversityRepository extends JpaRepository findBySiteUserAndUniversityInfoForApply(SiteUser siteUser, UniversityInfoForApply universityInfoForApply); + Optional findBySiteUserAndUnivApplyInfo(SiteUser siteUser, UnivApplyInfo univApplyInfo); } diff --git a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java index f5b463c0a..7e22fd4bf 100644 --- a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java +++ b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java @@ -97,7 +97,7 @@ private boolean isDefaultProfileImage(String profileImageUrl) { public List getWishUniversity(SiteUser siteUser) { List likedUniversities = likedUniversityRepository.findAllBySiteUser_Id(siteUser.getId()); return likedUniversities.stream() - .map(likedUniversity -> UniversityInfoForApplyPreviewResponse.from(likedUniversity.getUniversityInfoForApply())) + .map(likedUniversity -> UniversityInfoForApplyPreviewResponse.from(likedUniversity.getUnivApplyInfo())) .toList(); } } diff --git a/src/main/java/com/example/solidconnection/university/domain/LanguageRequirement.java b/src/main/java/com/example/solidconnection/university/domain/LanguageRequirement.java index 55377131b..ddb111fde 100644 --- a/src/main/java/com/example/solidconnection/university/domain/LanguageRequirement.java +++ b/src/main/java/com/example/solidconnection/university/domain/LanguageRequirement.java @@ -8,6 +8,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -32,5 +33,6 @@ public class LanguageRequirement { private String minScore; @ManyToOne(fetch = FetchType.LAZY) - private UniversityInfoForApply universityInfoForApply; + @JoinColumn(name = "university_info_for_apply_id") + private UnivApplyInfo univApplyInfo; } diff --git a/src/main/java/com/example/solidconnection/university/domain/LikedUniversity.java b/src/main/java/com/example/solidconnection/university/domain/LikedUniversity.java index ad7ee02c8..036346d5a 100644 --- a/src/main/java/com/example/solidconnection/university/domain/LikedUniversity.java +++ b/src/main/java/com/example/solidconnection/university/domain/LikedUniversity.java @@ -5,7 +5,10 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -16,6 +19,12 @@ @Builder @AllArgsConstructor @NoArgsConstructor +@Table(uniqueConstraints = { + @UniqueConstraint( + name = "uk_liked_university_site_user_id_university_info_for_apply_id", + columnNames = {"site_user_id", "university_info_for_apply_id"} + ) +}) public class LikedUniversity { @Id @@ -23,7 +32,8 @@ public class LikedUniversity { private Long id; @ManyToOne - private UniversityInfoForApply universityInfoForApply; + @JoinColumn(name = "university_info_for_apply_id") + private UnivApplyInfo univApplyInfo; @ManyToOne private SiteUser siteUser; diff --git a/src/main/java/com/example/solidconnection/university/domain/UniversityInfoForApply.java b/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java similarity index 92% rename from src/main/java/com/example/solidconnection/university/domain/UniversityInfoForApply.java rename to src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java index c968a3a1a..47edd15c8 100644 --- a/src/main/java/com/example/solidconnection/university/domain/UniversityInfoForApply.java +++ b/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java @@ -10,6 +10,7 @@ import jakarta.persistence.Id; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; @@ -24,7 +25,8 @@ @AllArgsConstructor(access = AccessLevel.PUBLIC) @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity -public class UniversityInfoForApply { +@Table(name = "university_info_for_apply") +public class UnivApplyInfo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -74,7 +76,7 @@ public class UniversityInfoForApply { @Column(length = 1000) private String details; - @OneToMany(mappedBy = "universityInfoForApply", fetch = FetchType.EAGER) + @OneToMany(mappedBy = "univApplyInfo", fetch = FetchType.EAGER) private Set languageRequirements = new HashSet<>(); @ManyToOne(fetch = FetchType.EAGER) diff --git a/src/main/java/com/example/solidconnection/university/dto/UniversityDetailResponse.java b/src/main/java/com/example/solidconnection/university/dto/UniversityDetailResponse.java index 4121654a3..86da18e7e 100644 --- a/src/main/java/com/example/solidconnection/university/dto/UniversityDetailResponse.java +++ b/src/main/java/com/example/solidconnection/university/dto/UniversityDetailResponse.java @@ -1,7 +1,7 @@ package com.example.solidconnection.university.dto; import com.example.solidconnection.university.domain.University; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import java.util.List; @@ -35,11 +35,11 @@ public record UniversityDetailResponse( public static UniversityDetailResponse of( University university, - UniversityInfoForApply universityInfoForApply) { + UnivApplyInfo univApplyInfo) { return new UniversityDetailResponse( - universityInfoForApply.getId(), - universityInfoForApply.getTerm(), - universityInfoForApply.getKoreanName(), + univApplyInfo.getId(), + univApplyInfo.getTerm(), + univApplyInfo.getKoreanName(), university.getEnglishName(), university.getFormatName(), university.getRegion().getKoreanName(), @@ -48,21 +48,21 @@ public static UniversityDetailResponse of( university.getLogoImageUrl(), university.getBackgroundImageUrl(), university.getDetailsForLocal(), - universityInfoForApply.getStudentCapacity(), - universityInfoForApply.getTuitionFeeType().getKoreanName(), - universityInfoForApply.getSemesterAvailableForDispatch().getKoreanName(), - universityInfoForApply.getLanguageRequirements().stream() + univApplyInfo.getStudentCapacity(), + univApplyInfo.getTuitionFeeType().getKoreanName(), + univApplyInfo.getSemesterAvailableForDispatch().getKoreanName(), + univApplyInfo.getLanguageRequirements().stream() .map(LanguageRequirementResponse::from) .toList(), - universityInfoForApply.getDetailsForLanguage(), - universityInfoForApply.getGpaRequirement(), - universityInfoForApply.getGpaRequirementCriteria(), - universityInfoForApply.getSemesterRequirement(), - universityInfoForApply.getDetailsForApply(), - universityInfoForApply.getDetailsForMajor(), - universityInfoForApply.getDetailsForAccommodation(), - universityInfoForApply.getDetailsForEnglishCourse(), - universityInfoForApply.getDetails(), + univApplyInfo.getDetailsForLanguage(), + univApplyInfo.getGpaRequirement(), + univApplyInfo.getGpaRequirementCriteria(), + univApplyInfo.getSemesterRequirement(), + univApplyInfo.getDetailsForApply(), + univApplyInfo.getDetailsForMajor(), + univApplyInfo.getDetailsForAccommodation(), + univApplyInfo.getDetailsForEnglishCourse(), + univApplyInfo.getDetails(), university.getAccommodationUrl(), university.getEnglishCourseUrl() ); diff --git a/src/main/java/com/example/solidconnection/university/dto/UniversityInfoForApplyPreviewResponse.java b/src/main/java/com/example/solidconnection/university/dto/UniversityInfoForApplyPreviewResponse.java index f6c2b4969..bca888d03 100644 --- a/src/main/java/com/example/solidconnection/university/dto/UniversityInfoForApplyPreviewResponse.java +++ b/src/main/java/com/example/solidconnection/university/dto/UniversityInfoForApplyPreviewResponse.java @@ -1,6 +1,6 @@ package com.example.solidconnection.university.dto; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import java.util.Collections; import java.util.List; @@ -16,22 +16,22 @@ public record UniversityInfoForApplyPreviewResponse( int studentCapacity, List languageRequirements) { - public static UniversityInfoForApplyPreviewResponse from(UniversityInfoForApply universityInfoForApply) { + public static UniversityInfoForApplyPreviewResponse from(UnivApplyInfo univApplyInfo) { List languageRequirementResponses = new java.util.ArrayList<>( - universityInfoForApply.getLanguageRequirements().stream() + univApplyInfo.getLanguageRequirements().stream() .map(LanguageRequirementResponse::from) .toList()); Collections.sort(languageRequirementResponses); return new UniversityInfoForApplyPreviewResponse( - universityInfoForApply.getId(), - universityInfoForApply.getTerm(), - universityInfoForApply.getKoreanName(), - universityInfoForApply.getUniversity().getRegion().getKoreanName(), - universityInfoForApply.getUniversity().getCountry().getKoreanName(), - universityInfoForApply.getUniversity().getLogoImageUrl(), - universityInfoForApply.getUniversity().getBackgroundImageUrl(), - universityInfoForApply.getStudentCapacity(), + univApplyInfo.getId(), + univApplyInfo.getTerm(), + univApplyInfo.getKoreanName(), + univApplyInfo.getUniversity().getRegion().getKoreanName(), + univApplyInfo.getUniversity().getCountry().getKoreanName(), + univApplyInfo.getUniversity().getLogoImageUrl(), + univApplyInfo.getUniversity().getBackgroundImageUrl(), + univApplyInfo.getStudentCapacity(), languageRequirementResponses ); } diff --git a/src/main/java/com/example/solidconnection/university/repository/LanguageRequirementRepository.java b/src/main/java/com/example/solidconnection/university/repository/LanguageRequirementRepository.java index cfe9f7050..2cab8f14e 100644 --- a/src/main/java/com/example/solidconnection/university/repository/LanguageRequirementRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/LanguageRequirementRepository.java @@ -1,18 +1,9 @@ package com.example.solidconnection.university.repository; import com.example.solidconnection.university.domain.LanguageRequirement; -import com.example.solidconnection.university.domain.LanguageTestType; -import com.example.solidconnection.university.domain.UniversityInfoForApply; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.Optional; - @Repository public interface LanguageRequirementRepository extends JpaRepository { - - @Query("SELECT lr FROM LanguageRequirement lr WHERE lr.minScore <= :myScore AND lr.languageTestType = :testType AND lr.universityInfoForApply = :universityInfoForApply ORDER BY lr.minScore ASC") - Optional findByUniversityInfoForApplyAndLanguageTestTypeAndLessThanMyScore(@Param("universityInfoForApply") UniversityInfoForApply universityInfoForApply, @Param("testType") LanguageTestType testType, @Param("myScore") String myScore); } diff --git a/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java b/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java index 73a5ee14b..1ef541334 100644 --- a/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java @@ -2,8 +2,8 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.domain.University; -import com.example.solidconnection.university.domain.UniversityInfoForApply; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -13,26 +13,25 @@ import java.util.Optional; import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND; -import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND_FOR_TERM; @Repository -public interface UniversityInfoForApplyRepository extends JpaRepository { +public interface UniversityInfoForApplyRepository extends JpaRepository { - Optional findByIdAndTerm(Long id, String term); + Optional findByIdAndTerm(Long id, String term); - Optional findFirstByKoreanNameAndTerm(String koreanName, String term); + Optional findFirstByKoreanNameAndTerm(String koreanName, String term); @Query(""" SELECT uifa - FROM UniversityInfoForApply uifa + FROM UnivApplyInfo uifa WHERE uifa.university IN :universities AND uifa.term = :term """) - List findByUniversitiesAndTerm(@Param("universities") List universities, @Param("term") String term); + List findByUniversitiesAndTerm(@Param("universities") List universities, @Param("term") String term); @Query(""" SELECT uifa - FROM UniversityInfoForApply uifa + FROM UnivApplyInfo uifa JOIN University u ON uifa.university = u WHERE (u.country.code IN ( SELECT c.code @@ -48,7 +47,7 @@ OR u.region.code IN ( )) AND uifa.term = :term """) - List findUniversityInfoForAppliesBySiteUsersInterestedCountryOrRegionAndTerm(@Param("siteUser") SiteUser siteUser, @Param("term") String term); + List findUniversityInfoForAppliesBySiteUsersInterestedCountryOrRegionAndTerm(@Param("siteUser") SiteUser siteUser, @Param("term") String term); @Query(value = """ SELECT * @@ -57,30 +56,30 @@ OR u.region.code IN ( ORDER BY RAND() LIMIT :limitNum """, nativeQuery = true) - List findRandomByTerm(@Param("term") String term, @Param("limitNum") int limitNum); + List findRandomByTerm(@Param("term") String term, @Param("limitNum") int limitNum); - default UniversityInfoForApply getUniversityInfoForApplyById(Long id) { + default UnivApplyInfo getUniversityInfoForApplyById(Long id) { return findById(id) .orElseThrow(() -> new CustomException(UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND)); } @Query(""" SELECT DISTINCT uifa - FROM UniversityInfoForApply uifa + FROM UnivApplyInfo uifa JOIN FETCH uifa.university u JOIN FETCH u.country c JOIN FETCH u.region r WHERE uifa.id IN :ids """) - List findAllByUniversityIds(@Param("ids") List ids); + List findAllByUniversityIds(@Param("ids") List ids); @Query(""" SELECT DISTINCT uifa - FROM UniversityInfoForApply uifa + FROM UnivApplyInfo uifa JOIN FETCH uifa.university u JOIN FETCH u.country c JOIN FETCH u.region r WHERE u.id IN :universityIds """) - List findByUniversityIdsWithUniversityAndLocation(@Param("universityIds") List universityIds); -} \ No newline at end of file + List findByUniversityIdsWithUniversityAndLocation(@Param("universityIds") List universityIds); +} diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java index 93850a2f4..f3c9767b9 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java @@ -1,15 +1,14 @@ package com.example.solidconnection.university.repository.custom; import com.example.solidconnection.university.domain.LanguageTestType; -import com.example.solidconnection.university.domain.University; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import java.util.List; public interface UniversityFilterRepository { - List findByRegionCodeAndKeywords(String regionCode, List keywords); + List findByRegionCodeAndKeywords(String regionCode, List keywords); - List findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( + List findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( String regionCode, List keywords, LanguageTestType testType, String testScore, String term); } diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java index b01095996..ee8416b9c 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java @@ -3,9 +3,9 @@ import com.example.solidconnection.location.country.domain.QCountry; import com.example.solidconnection.location.region.domain.QRegion; import com.example.solidconnection.university.domain.LanguageTestType; +import com.example.solidconnection.university.domain.QUnivApplyInfo; import com.example.solidconnection.university.domain.QUniversity; -import com.example.solidconnection.university.domain.QUniversityInfoForApply; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.domain.QLanguageRequirement; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.Expressions; @@ -28,19 +28,19 @@ public UniversityFilterRepositoryImpl(EntityManager em) { } @Override - public List findByRegionCodeAndKeywords(String regionCode, List keywords) { - QUniversityInfoForApply universityInfoForApply = QUniversityInfoForApply.universityInfoForApply; + public List findByRegionCodeAndKeywords(String regionCode, List keywords) { + QUnivApplyInfo univApplyInfo = QUnivApplyInfo.univApplyInfo; QUniversity university = QUniversity.university; QCountry country = QCountry.country; QRegion region = QRegion.region; QLanguageRequirement languageRequirement = QLanguageRequirement.languageRequirement; return queryFactory - .selectFrom(universityInfoForApply) - .join(universityInfoForApply.university, university).fetchJoin() + .selectFrom(univApplyInfo) + .join(univApplyInfo.university, university).fetchJoin() .join(university.country, country).fetchJoin() .join(country.region, region).fetchJoin() - .leftJoin(universityInfoForApply.languageRequirements, languageRequirement).fetchJoin() + .leftJoin(univApplyInfo.languageRequirements, languageRequirement).fetchJoin() .where( regionCodeEq(region, regionCode) .and(countryOrUniversityContainsKeyword(country, university, keywords)) @@ -74,40 +74,40 @@ private BooleanExpression createKeywordCondition(StringPath namePath, List findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( + public List findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( String regionCode, List keywords, LanguageTestType testType, String testScore, String term) { QUniversity university = QUniversity.university; QCountry country = QCountry.country; QRegion region = QRegion.region; - QUniversityInfoForApply universityInfoForApply = QUniversityInfoForApply.universityInfoForApply; + QUnivApplyInfo univApplyInfo = QUnivApplyInfo.univApplyInfo; - List filteredUniversityInfoForApply = queryFactory - .selectFrom(universityInfoForApply) - .join(universityInfoForApply.university, university) + List filteredUnivApplyInfo = queryFactory + .selectFrom(univApplyInfo) + .join(univApplyInfo.university, university) .join(university.country, country) .join(university.region, region) .where(regionCodeEq(region, regionCode) .and(countryOrUniversityContainsKeyword(country, university, keywords)) - .and(universityInfoForApply.term.eq(term))) + .and(univApplyInfo.term.eq(term))) .fetch(); if (testScore == null || testScore.isEmpty()) { if (testType != null) { - return filteredUniversityInfoForApply.stream() + return filteredUnivApplyInfo.stream() .filter(uifa -> uifa.getLanguageRequirements().stream() .anyMatch(lr -> lr.getLanguageTestType().equals(testType))) .toList(); } - return filteredUniversityInfoForApply; + return filteredUnivApplyInfo; } - return filteredUniversityInfoForApply.stream() + return filteredUnivApplyInfo.stream() .filter(uifa -> compareMyTestScoreToMinPassScore(uifa, testType, testScore) >= 0) .toList(); } - private int compareMyTestScoreToMinPassScore(UniversityInfoForApply universityInfoForApply, LanguageTestType testType, String testScore) { - return universityInfoForApply.getLanguageRequirements().stream() + private int compareMyTestScoreToMinPassScore(UnivApplyInfo univApplyInfo, LanguageTestType testType, String testScore) { + return univApplyInfo.getLanguageRequirements().stream() .filter(languageRequirement -> languageRequirement.getLanguageTestType().equals(testType)) .findFirst() .map(requirement -> testType.compare(testScore, requirement.getMinScore())) diff --git a/src/main/java/com/example/solidconnection/university/service/GeneralUniversityRecommendService.java b/src/main/java/com/example/solidconnection/university/service/GeneralUniversityRecommendService.java index d39fee1ec..af2921c9a 100644 --- a/src/main/java/com/example/solidconnection/university/service/GeneralUniversityRecommendService.java +++ b/src/main/java/com/example/solidconnection/university/service/GeneralUniversityRecommendService.java @@ -1,6 +1,6 @@ package com.example.solidconnection.university.service; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -23,7 +23,7 @@ public class GeneralUniversityRecommendService { private final UniversityInfoForApplyRepository universityInfoForApplyRepository; @Getter - private List recommendUniversities; + private List recommendUniversities; @Value("${university.term}") public String term; diff --git a/src/main/java/com/example/solidconnection/university/service/UniversityLikeService.java b/src/main/java/com/example/solidconnection/university/service/UniversityLikeService.java index 228cc1ff8..7544ed7a8 100644 --- a/src/main/java/com/example/solidconnection/university/service/UniversityLikeService.java +++ b/src/main/java/com/example/solidconnection/university/service/UniversityLikeService.java @@ -4,7 +4,7 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; import com.example.solidconnection.university.domain.LikedUniversity; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.dto.IsLikeResponse; import com.example.solidconnection.university.dto.LikeResultResponse; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; @@ -36,15 +36,15 @@ public class UniversityLikeService { * */ @Transactional public LikeResultResponse likeUniversity(SiteUser siteUser, Long universityInfoForApplyId) { - UniversityInfoForApply universityInfoForApply = universityInfoForApplyRepository.getUniversityInfoForApplyById(universityInfoForApplyId); + UnivApplyInfo univApplyInfo = universityInfoForApplyRepository.getUniversityInfoForApplyById(universityInfoForApplyId); - Optional optionalLikedUniversity = likedUniversityRepository.findBySiteUserAndUniversityInfoForApply(siteUser, universityInfoForApply); + Optional optionalLikedUniversity = likedUniversityRepository.findBySiteUserAndUnivApplyInfo(siteUser, univApplyInfo); if (optionalLikedUniversity.isPresent()) { throw new CustomException(ALREADY_LIKED_UNIVERSITY); } LikedUniversity likedUniversity = LikedUniversity.builder() - .universityInfoForApply(universityInfoForApply) + .univApplyInfo(univApplyInfo) .siteUser(siteUser) .build(); likedUniversityRepository.save(likedUniversity); @@ -56,9 +56,9 @@ public LikeResultResponse likeUniversity(SiteUser siteUser, Long universityInfoF * */ @Transactional public LikeResultResponse cancelLikeUniversity(SiteUser siteUser, long universityInfoForApplyId) throws CustomException { - UniversityInfoForApply universityInfoForApply = universityInfoForApplyRepository.getUniversityInfoForApplyById(universityInfoForApplyId); + UnivApplyInfo univApplyInfo = universityInfoForApplyRepository.getUniversityInfoForApplyById(universityInfoForApplyId); - Optional optionalLikedUniversity = likedUniversityRepository.findBySiteUserAndUniversityInfoForApply(siteUser, universityInfoForApply); + Optional optionalLikedUniversity = likedUniversityRepository.findBySiteUserAndUnivApplyInfo(siteUser, univApplyInfo); if (optionalLikedUniversity.isEmpty()) { throw new CustomException(NOT_LIKED_UNIVERSITY); } @@ -72,8 +72,8 @@ public LikeResultResponse cancelLikeUniversity(SiteUser siteUser, long universit * */ @Transactional(readOnly = true) public IsLikeResponse getIsLiked(SiteUser siteUser, Long universityInfoForApplyId) { - UniversityInfoForApply universityInfoForApply = universityInfoForApplyRepository.getUniversityInfoForApplyById(universityInfoForApplyId); - boolean isLike = likedUniversityRepository.findBySiteUserAndUniversityInfoForApply(siteUser, universityInfoForApply).isPresent(); + UnivApplyInfo univApplyInfo = universityInfoForApplyRepository.getUniversityInfoForApplyById(universityInfoForApplyId); + boolean isLike = likedUniversityRepository.findBySiteUserAndUnivApplyInfo(siteUser, univApplyInfo).isPresent(); return new IsLikeResponse(isLike); } } diff --git a/src/main/java/com/example/solidconnection/university/service/UniversityQueryService.java b/src/main/java/com/example/solidconnection/university/service/UniversityQueryService.java index e86f5debb..5c4415396 100644 --- a/src/main/java/com/example/solidconnection/university/service/UniversityQueryService.java +++ b/src/main/java/com/example/solidconnection/university/service/UniversityQueryService.java @@ -3,7 +3,7 @@ import com.example.solidconnection.cache.annotation.ThunderingHerdCaching; import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.University; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.dto.UniversityDetailResponse; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponses; @@ -33,11 +33,11 @@ public class UniversityQueryService { @Transactional(readOnly = true) @ThunderingHerdCaching(key = "university:{0}", cacheManager = "customCacheManager", ttlSec = 86400) public UniversityDetailResponse getUniversityDetail(Long universityInfoForApplyId) { - UniversityInfoForApply universityInfoForApply + UnivApplyInfo univApplyInfo = universityInfoForApplyRepository.getUniversityInfoForApplyById(universityInfoForApplyId); - University university = universityInfoForApply.getUniversity(); + University university = univApplyInfo.getUniversity(); - return UniversityDetailResponse.of(university, universityInfoForApply); + return UniversityDetailResponse.of(university, univApplyInfo); } /* diff --git a/src/main/java/com/example/solidconnection/university/service/UniversityRecommendService.java b/src/main/java/com/example/solidconnection/university/service/UniversityRecommendService.java index 4d9ab6242..94447316d 100644 --- a/src/main/java/com/example/solidconnection/university/service/UniversityRecommendService.java +++ b/src/main/java/com/example/solidconnection/university/service/UniversityRecommendService.java @@ -2,7 +2,7 @@ import com.example.solidconnection.cache.annotation.ThunderingHerdCaching; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; import com.example.solidconnection.university.dto.UniversityRecommendsResponse; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; @@ -36,9 +36,9 @@ public class UniversityRecommendService { @Transactional(readOnly = true) public UniversityRecommendsResponse getPersonalRecommends(SiteUser siteUser) { // 맞춤 추천 대학교를 불러온다. - List personalRecommends = universityInfoForApplyRepository + List personalRecommends = universityInfoForApplyRepository .findUniversityInfoForAppliesBySiteUsersInterestedCountryOrRegionAndTerm(siteUser, term); - List trimmedRecommendUniversities + List trimmedRecommendUniversities = personalRecommends.subList(0, Math.min(RECOMMEND_UNIVERSITY_NUM, personalRecommends.size())); Collections.shuffle(trimmedRecommendUniversities); @@ -52,8 +52,8 @@ public UniversityRecommendsResponse getPersonalRecommends(SiteUser siteUser) { .toList()); } - private List getGeneralRecommendsExcludingSelected(List alreadyPicked) { - List generalRecommend = new ArrayList<>(generalUniversityRecommendService.getRecommendUniversities()); + private List getGeneralRecommendsExcludingSelected(List alreadyPicked) { + List generalRecommend = new ArrayList<>(generalUniversityRecommendService.getRecommendUniversities()); generalRecommend.removeAll(alreadyPicked); Collections.shuffle(generalRecommend); return generalRecommend.subList(0, RECOMMEND_UNIVERSITY_NUM - alreadyPicked.size()); @@ -65,7 +65,7 @@ private List getGeneralRecommendsExcludingSelected(List< @Transactional(readOnly = true) @ThunderingHerdCaching(key = "university:recommend:general", cacheManager = "customCacheManager", ttlSec = 86400) public UniversityRecommendsResponse getGeneralRecommends() { - List generalRecommends = new ArrayList<>(generalUniversityRecommendService.getRecommendUniversities()); + List generalRecommends = new ArrayList<>(generalUniversityRecommendService.getRecommendUniversities()); return new UniversityRecommendsResponse(generalRecommends.stream() .map(UniversityInfoForApplyPreviewResponse::from) .toList()); diff --git a/src/main/resources/db/migration/V15__add_unique_constraint_to_liked_university.sql b/src/main/resources/db/migration/V15__add_unique_constraint_to_liked_university.sql new file mode 100644 index 000000000..2c49f9ffb --- /dev/null +++ b/src/main/resources/db/migration/V15__add_unique_constraint_to_liked_university.sql @@ -0,0 +1,3 @@ +ALTER TABLE liked_university +ADD CONSTRAINT uk_liked_university_site_user_id_university_info_for_apply_id +UNIQUE (site_user_id, university_info_for_apply_id); diff --git a/src/main/resources/secret b/src/main/resources/secret index cbb94e8df..84002e866 160000 --- a/src/main/resources/secret +++ b/src/main/resources/secret @@ -1 +1 @@ -Subproject commit cbb94e8dfd95b773decd4193c2821284bdde8adc +Subproject commit 84002e86670d380219f580c6605fb7c66ed7d977 diff --git a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java index 91dc29c59..43854ca80 100644 --- a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java +++ b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java @@ -4,7 +4,7 @@ import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; diff --git a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java index c9bf33c3e..2f1ae7bbe 100644 --- a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java @@ -6,7 +6,6 @@ import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.application.repository.ApplicationRepository; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.domain.UniversityInfoForApply; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java index 1a0f927a2..2f93b08f0 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java @@ -15,8 +15,8 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.university.domain.UniversityInfoForApply; -import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; +import com.example.solidconnection.university.domain.UnivApplyInfo; +import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -45,7 +45,7 @@ class ApplicationQueryServiceTest { private RegionFixture regionFixture; @Autowired - private UniversityInfoForApplyFixture universityInfoForApplyFixture; + private UnivApplyInfoFixture univApplyInfoFixture; @Autowired private GpaScoreFixture gpaScoreFixture; @@ -71,9 +71,9 @@ class ApplicationQueryServiceTest { private LanguageTestScore languageTestScore2; private LanguageTestScore languageTestScore3; - private UniversityInfoForApply 괌대학_A_지원_정보; - private UniversityInfoForApply 괌대학_B_지원_정보; - private UniversityInfoForApply 서던덴마크대학교_지원_정보; + private UnivApplyInfo 괌대학_A_지원_정보; + private UnivApplyInfo 괌대학_B_지원_정보; + private UnivApplyInfo 서던덴마크대학교_지원_정보; @BeforeEach void setUp() { @@ -89,9 +89,9 @@ void setUp() { gpaScore3 = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user3); languageTestScore3 = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user3); - 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); - 괌대학_B_지원_정보 = universityInfoForApplyFixture.괌대학_B_지원_정보(); - 서던덴마크대학교_지원_정보 = universityInfoForApplyFixture.서던덴마크대학교_지원_정보(); + 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + 괌대학_B_지원_정보 = univApplyInfoFixture.괌대학_B_지원_정보(); + 서던덴마크대학교_지원_정보 = univApplyInfoFixture.서던덴마크대학교_지원_정보(); } @Nested diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java index 2718fb90b..4d23073f7 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java @@ -14,8 +14,8 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.university.domain.UniversityInfoForApply; -import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; +import com.example.solidconnection.university.domain.UnivApplyInfo; +import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -44,7 +44,7 @@ class ApplicationSubmissionServiceTest { private SiteUserFixture siteUserFixture; @Autowired - private UniversityInfoForApplyFixture universityInfoForApplyFixture; + private UnivApplyInfoFixture univApplyInfoFixture; @Autowired private GpaScoreFixture gpaScoreFixture; @@ -56,16 +56,16 @@ class ApplicationSubmissionServiceTest { private String term; private SiteUser user; - private UniversityInfoForApply 괌대학_A_지원_정보; - private UniversityInfoForApply 괌대학_B_지원_정보; - private UniversityInfoForApply 서던덴마크대학교_지원_정보; + private UnivApplyInfo 괌대학_A_지원_정보; + private UnivApplyInfo 괌대학_B_지원_정보; + private UnivApplyInfo 서던덴마크대학교_지원_정보; @BeforeEach void setUp() { user = siteUserFixture.사용자(); - 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); - 괌대학_B_지원_정보 = universityInfoForApplyFixture.괌대학_B_지원_정보(); - 서던덴마크대학교_지원_정보 = universityInfoForApplyFixture.서던덴마크대학교_지원_정보(); + 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + 괌대학_B_지원_정보 = univApplyInfoFixture.괌대학_B_지원_정보(); + 서던덴마크대학교_지원_정보 = univApplyInfoFixture.서던덴마크대학교_지원_정보(); } @Test diff --git a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java index a86d80883..59e0d6455 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java @@ -15,7 +15,7 @@ import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.university.domain.LikedUniversity; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; -import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; +import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -60,7 +60,7 @@ class MyPageServiceTest { private SiteUserFixture siteUserFixture; @Autowired - private UniversityInfoForApplyFixture universityInfoForApplyFixture; + private UnivApplyInfoFixture univApplyInfoFixture; @Autowired private SiteUserFixtureBuilder siteUserFixtureBuilder; @@ -193,9 +193,9 @@ void setUp() { } private int createLikedUniversities(SiteUser testUser) { - LikedUniversity likedUniversity1 = new LikedUniversity(null, universityInfoForApplyFixture.괌대학_A_지원_정보(), testUser); - LikedUniversity likedUniversity2 = new LikedUniversity(null, universityInfoForApplyFixture.메이지대학_지원_정보(), testUser); - LikedUniversity likedUniversity3 = new LikedUniversity(null, universityInfoForApplyFixture.코펜하겐IT대학_지원_정보(), testUser); + LikedUniversity likedUniversity1 = new LikedUniversity(null, univApplyInfoFixture.괌대학_A_지원_정보(), testUser); + LikedUniversity likedUniversity2 = new LikedUniversity(null, univApplyInfoFixture.메이지대학_지원_정보(), testUser); + LikedUniversity likedUniversity3 = new LikedUniversity(null, univApplyInfoFixture.코펜하겐IT대학_지원_정보(), testUser); likedUniversityRepository.save(likedUniversity1); likedUniversityRepository.save(likedUniversity2); diff --git a/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixture.java b/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixture.java index c97a1427e..0c7bb48bd 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixture.java +++ b/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixture.java @@ -2,7 +2,7 @@ import com.example.solidconnection.university.domain.LanguageRequirement; import com.example.solidconnection.university.domain.LanguageTestType; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; @@ -12,7 +12,7 @@ public class LanguageRequirementFixture { private final LanguageRequirementFixtureBuilder languageRequirementFixtureBuilder; - public LanguageRequirement 토플_80(UniversityInfoForApply universityInfo) { + public LanguageRequirement 토플_80(UnivApplyInfo universityInfo) { return languageRequirementFixtureBuilder .languageTestType(LanguageTestType.TOEFL_IBT) .minScore("80") @@ -20,7 +20,7 @@ public class LanguageRequirementFixture { .create(); } - public LanguageRequirement 토플_70(UniversityInfoForApply universityInfo) { + public LanguageRequirement 토플_70(UnivApplyInfo universityInfo) { return languageRequirementFixtureBuilder .languageTestType(LanguageTestType.TOEFL_IBT) .minScore("70") @@ -28,7 +28,7 @@ public class LanguageRequirementFixture { .create(); } - public LanguageRequirement 토익_800(UniversityInfoForApply universityInfo) { + public LanguageRequirement 토익_800(UnivApplyInfo universityInfo) { return languageRequirementFixtureBuilder .languageTestType(LanguageTestType.TOEIC) .minScore("800") @@ -36,7 +36,7 @@ public class LanguageRequirementFixture { .create(); } - public LanguageRequirement 토익_900(UniversityInfoForApply universityInfo) { + public LanguageRequirement 토익_900(UnivApplyInfo universityInfo) { return languageRequirementFixtureBuilder .languageTestType(LanguageTestType.TOEIC) .minScore("900") @@ -44,7 +44,7 @@ public class LanguageRequirementFixture { .create(); } - public LanguageRequirement JLPT_N2(UniversityInfoForApply universityInfo) { + public LanguageRequirement JLPT_N2(UnivApplyInfo universityInfo) { return languageRequirementFixtureBuilder .languageTestType(LanguageTestType.JLPT) .minScore("N2") diff --git a/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixtureBuilder.java b/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixtureBuilder.java index f72e7caba..81e856f4b 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixtureBuilder.java @@ -2,7 +2,7 @@ import com.example.solidconnection.university.domain.LanguageRequirement; import com.example.solidconnection.university.domain.LanguageTestType; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.repository.LanguageRequirementRepository; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; @@ -15,7 +15,7 @@ public class LanguageRequirementFixtureBuilder { private LanguageTestType languageTestType; private String minScore; - private UniversityInfoForApply universityInfoForApply; + private UnivApplyInfo univApplyInfo; public LanguageRequirementFixtureBuilder languageTestType(LanguageTestType languageTestType) { this.languageTestType = languageTestType; @@ -27,8 +27,8 @@ public LanguageRequirementFixtureBuilder minScore(String minScore) { return this; } - public LanguageRequirementFixtureBuilder universityInfoForApply(UniversityInfoForApply universityInfoForApply) { - this.universityInfoForApply = universityInfoForApply; + public LanguageRequirementFixtureBuilder universityInfoForApply(UnivApplyInfo univApplyInfo) { + this.univApplyInfo = univApplyInfo; return this; } @@ -37,9 +37,9 @@ public LanguageRequirement create() { null, languageTestType, minScore, - universityInfoForApply + univApplyInfo ); - universityInfoForApply.addLanguageRequirements(languageRequirement); + univApplyInfo.addLanguageRequirements(languageRequirement); return languageRequirementRepository.save(languageRequirement); } } diff --git a/src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixture.java b/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixture.java similarity index 56% rename from src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixture.java rename to src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixture.java index 32a32cf6d..d394b6206 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixture.java +++ b/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixture.java @@ -1,94 +1,94 @@ package com.example.solidconnection.university.fixture; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.TestComponent; @TestComponent @RequiredArgsConstructor -public class UniversityInfoForApplyFixture { +public class UnivApplyInfoFixture { - private final UniversityInfoForApplyFixtureBuilder universityInfoForApplyFixtureBuilder; + private final UnivApplyInfoFixtureBuilder univApplyInfoFixtureBuilder; private final UniversityFixture universityFixture; @Value("${university.term}") public String term; - public UniversityInfoForApply 괌대학_A_지원_정보() { - return universityInfoForApplyFixtureBuilder.universityInfoForApply() + public UnivApplyInfo 괌대학_A_지원_정보() { + return univApplyInfoFixtureBuilder.universityInfoForApply() .term(term) .koreanName("괌대학(A형)") .university(universityFixture.괌_대학()) .create(); } - public UniversityInfoForApply 괌대학_B_지원_정보() { - return universityInfoForApplyFixtureBuilder.universityInfoForApply() + public UnivApplyInfo 괌대학_B_지원_정보() { + return univApplyInfoFixtureBuilder.universityInfoForApply() .term(term) .koreanName("괌대학(B형)") .university(universityFixture.괌_대학()) .create(); } - public UniversityInfoForApply 네바다주립대학_라스베이거스_지원_정보() { - return universityInfoForApplyFixtureBuilder.universityInfoForApply() + public UnivApplyInfo 네바다주립대학_라스베이거스_지원_정보() { + return univApplyInfoFixtureBuilder.universityInfoForApply() .term(term) .koreanName("네바다주립대학 라스베이거스(B형)") .university(universityFixture.네바다주립_대학_라스베이거스()) .create(); } - public UniversityInfoForApply 메모리얼대학_세인트존스_A_지원_정보() { - return universityInfoForApplyFixtureBuilder.universityInfoForApply() + public UnivApplyInfo 메모리얼대학_세인트존스_A_지원_정보() { + return univApplyInfoFixtureBuilder.universityInfoForApply() .term(term) .koreanName("메모리얼 대학 세인트존스(A형)") .university(universityFixture.메모리얼_대학_세인트존스()) .create(); } - public UniversityInfoForApply 서던덴마크대학교_지원_정보() { - return universityInfoForApplyFixtureBuilder.universityInfoForApply() + public UnivApplyInfo 서던덴마크대학교_지원_정보() { + return univApplyInfoFixtureBuilder.universityInfoForApply() .term(term) .koreanName("서던덴마크대학교") .university(universityFixture.서던덴마크_대학()) .create(); } - public UniversityInfoForApply 코펜하겐IT대학_지원_정보() { - return universityInfoForApplyFixtureBuilder.universityInfoForApply() + public UnivApplyInfo 코펜하겐IT대학_지원_정보() { + return univApplyInfoFixtureBuilder.universityInfoForApply() .term(term) .koreanName("코펜하겐 IT대학") .university(universityFixture.코펜하겐IT_대학()) .create(); } - public UniversityInfoForApply 그라츠대학_지원_정보() { - return universityInfoForApplyFixtureBuilder.universityInfoForApply() + public UnivApplyInfo 그라츠대학_지원_정보() { + return univApplyInfoFixtureBuilder.universityInfoForApply() .term(term) .koreanName("그라츠 대학") .university(universityFixture.그라츠_대학()) .create(); } - public UniversityInfoForApply 그라츠공과대학_지원_정보() { - return universityInfoForApplyFixtureBuilder.universityInfoForApply() + public UnivApplyInfo 그라츠공과대학_지원_정보() { + return univApplyInfoFixtureBuilder.universityInfoForApply() .term(term) .koreanName("그라츠공과대학") .university(universityFixture.그라츠공과_대학()) .create(); } - public UniversityInfoForApply 린츠_카톨릭대학_지원_정보() { - return universityInfoForApplyFixtureBuilder.universityInfoForApply() + public UnivApplyInfo 린츠_카톨릭대학_지원_정보() { + return univApplyInfoFixtureBuilder.universityInfoForApply() .term(term) .koreanName("린츠 카톨릭 대학교") .university(universityFixture.린츠_카톨릭_대학()) .create(); } - public UniversityInfoForApply 메이지대학_지원_정보() { - return universityInfoForApplyFixtureBuilder.universityInfoForApply() + public UnivApplyInfo 메이지대학_지원_정보() { + return univApplyInfoFixtureBuilder.universityInfoForApply() .term(term) .koreanName("메이지대학") .university(universityFixture.메이지_대학()) diff --git a/src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixtureBuilder.java b/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixtureBuilder.java similarity index 64% rename from src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixtureBuilder.java rename to src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixtureBuilder.java index 260a037d9..048389d49 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/UniversityInfoForApplyFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixtureBuilder.java @@ -1,7 +1,7 @@ package com.example.solidconnection.university.fixture; import com.example.solidconnection.university.domain.University; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; @@ -13,7 +13,7 @@ @TestComponent @RequiredArgsConstructor -public class UniversityInfoForApplyFixtureBuilder { +public class UnivApplyInfoFixtureBuilder { private final UniversityInfoForApplyRepository universityInfoForApplyRepository; @@ -21,33 +21,33 @@ public class UniversityInfoForApplyFixtureBuilder { private String koreanName; private University university; - public UniversityInfoForApplyFixtureBuilder universityInfoForApply() { - return new UniversityInfoForApplyFixtureBuilder(universityInfoForApplyRepository); + public UnivApplyInfoFixtureBuilder universityInfoForApply() { + return new UnivApplyInfoFixtureBuilder(universityInfoForApplyRepository); } - public UniversityInfoForApplyFixtureBuilder term(String term) { + public UnivApplyInfoFixtureBuilder term(String term) { this.term = term; return this; } - public UniversityInfoForApplyFixtureBuilder koreanName(String koreanName) { + public UnivApplyInfoFixtureBuilder koreanName(String koreanName) { this.koreanName = koreanName; return this; } - public UniversityInfoForApplyFixtureBuilder university(University university) { + public UnivApplyInfoFixtureBuilder university(University university) { this.university = university; return this; } - public UniversityInfoForApply create() { - UniversityInfoForApply universityInfoForApply = new UniversityInfoForApply( + public UnivApplyInfo create() { + UnivApplyInfo univApplyInfo = new UnivApplyInfo( null, term, koreanName, 1, HOME_UNIVERSITY_PAYMENT, ONE_SEMESTER, "1", "detailsForLanguage", "gpaRequirement", "gpaRequirementCriteria", "detailsForApply", "detailsForMajor", "detailsForAccommodation", "detailsForEnglishCourse", "details", new HashSet<>(), university ); - return universityInfoForApplyRepository.save(universityInfoForApply); + return universityInfoForApplyRepository.save(univApplyInfo); } } diff --git a/src/test/java/com/example/solidconnection/university/repository/UniversityLikeRepositoryTest.java b/src/test/java/com/example/solidconnection/university/repository/UniversityLikeRepositoryTest.java new file mode 100644 index 000000000..1d34597be --- /dev/null +++ b/src/test/java/com/example/solidconnection/university/repository/UniversityLikeRepositoryTest.java @@ -0,0 +1,89 @@ +package com.example.solidconnection.university.repository; + +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import com.example.solidconnection.university.domain.LikedUniversity; +import com.example.solidconnection.university.domain.UnivApplyInfo; +import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; + +@TestContainerSpringBootTest +@DisplayName("대학교 좋아요 레파지토리 테스트") +public class UniversityLikeRepositoryTest { + + @Autowired + private LikedUniversityRepository likedUniversityRepository; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private UnivApplyInfoFixture univApplyInfoFixture; + + @Nested + class 사용자와_좋아요한_대학은_복합_유니크_제약조건을_갖는다 { + + @Test + void 같은_사용자가_같은_대학에_중복으로_좋아요하면_예외_응답을_반환한다() { + // given + SiteUser user = siteUserFixture.사용자(); + UnivApplyInfo university = univApplyInfoFixture.괌대학_A_지원_정보(); + + LikedUniversity firstLike = createLikedUniversity(user, university); + likedUniversityRepository.save(firstLike); + + LikedUniversity secondLike = createLikedUniversity(user, university); + + // when & then + assertThatCode(() -> likedUniversityRepository.save(secondLike)) + .isInstanceOf(DataIntegrityViolationException.class); + } + + @Test + void 다른_사용자가_같은_대학에_좋아요하면_정상_저장된다() { + // given + SiteUser user1 = siteUserFixture.사용자(1, "user1"); + SiteUser user2 = siteUserFixture.사용자(2, "user2"); + UnivApplyInfo university = univApplyInfoFixture.괌대학_A_지원_정보(); + + LikedUniversity firstLike = createLikedUniversity(user1, university); + likedUniversityRepository.save(firstLike); + + LikedUniversity secondLike = createLikedUniversity(user2, university); + + // when & then + assertThatCode(() -> likedUniversityRepository.save(secondLike)).doesNotThrowAnyException(); + } + + @Test + void 같은_사용자가_다른_대학에_좋아요하면_정상_저장된다() { + // given + SiteUser user = siteUserFixture.사용자(); + UnivApplyInfo university1 = univApplyInfoFixture.괌대학_A_지원_정보(); + UnivApplyInfo university2 = univApplyInfoFixture.메이지대학_지원_정보(); + + LikedUniversity firstLike = createLikedUniversity(user, university1); + likedUniversityRepository.save(firstLike); + + LikedUniversity secondLike = createLikedUniversity(user, university2); + + // when & then + assertThatCode(() -> likedUniversityRepository.save(secondLike)).doesNotThrowAnyException(); + } + } + + private LikedUniversity createLikedUniversity(SiteUser siteUser, UnivApplyInfo univApplyInfo) { + return LikedUniversity.builder() + .siteUser(siteUser) + .univApplyInfo(univApplyInfo) + .build(); + } +} diff --git a/src/test/java/com/example/solidconnection/university/service/GeneralUniversityRecommendServiceTest.java b/src/test/java/com/example/solidconnection/university/service/GeneralUniversityRecommendServiceTest.java index c431bca6c..5152dfbb2 100644 --- a/src/test/java/com/example/solidconnection/university/service/GeneralUniversityRecommendServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/GeneralUniversityRecommendServiceTest.java @@ -1,8 +1,8 @@ package com.example.solidconnection.university.service; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.university.domain.UniversityInfoForApply; -import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; +import com.example.solidconnection.university.domain.UnivApplyInfo; +import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -23,30 +23,30 @@ class GeneralUniversityRecommendServiceTest { private GeneralUniversityRecommendService generalUniversityRecommendService; @Autowired - private UniversityInfoForApplyFixture universityInfoForApplyFixture; + private UnivApplyInfoFixture univApplyInfoFixture; @Value("${university.term}") private String term; @BeforeEach void setUp() { - universityInfoForApplyFixture.괌대학_A_지원_정보(); - universityInfoForApplyFixture.괌대학_B_지원_정보(); - universityInfoForApplyFixture.네바다주립대학_라스베이거스_지원_정보(); - universityInfoForApplyFixture.메모리얼대학_세인트존스_A_지원_정보(); - universityInfoForApplyFixture.서던덴마크대학교_지원_정보(); - universityInfoForApplyFixture.코펜하겐IT대학_지원_정보(); - universityInfoForApplyFixture.그라츠대학_지원_정보(); - universityInfoForApplyFixture.그라츠공과대학_지원_정보(); - universityInfoForApplyFixture.린츠_카톨릭대학_지원_정보(); - universityInfoForApplyFixture.메이지대학_지원_정보(); + univApplyInfoFixture.괌대학_A_지원_정보(); + univApplyInfoFixture.괌대학_B_지원_정보(); + univApplyInfoFixture.네바다주립대학_라스베이거스_지원_정보(); + univApplyInfoFixture.메모리얼대학_세인트존스_A_지원_정보(); + univApplyInfoFixture.서던덴마크대학교_지원_정보(); + univApplyInfoFixture.코펜하겐IT대학_지원_정보(); + univApplyInfoFixture.그라츠대학_지원_정보(); + univApplyInfoFixture.그라츠공과대학_지원_정보(); + univApplyInfoFixture.린츠_카톨릭대학_지원_정보(); + univApplyInfoFixture.메이지대학_지원_정보(); generalUniversityRecommendService.init(); } @Test void 모집_시기의_대학들_중에서_랜덤하게_N개를_추천_목록으로_구성한다() { // given - List universities = generalUniversityRecommendService.getRecommendUniversities(); + List universities = generalUniversityRecommendService.getRecommendUniversities(); // when & then assertAll( diff --git a/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java index 9ba0df2b1..729c74c3b 100644 --- a/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java @@ -6,10 +6,10 @@ import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.university.domain.LikedUniversity; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.dto.IsLikeResponse; import com.example.solidconnection.university.dto.LikeResultResponse; -import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; +import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -39,15 +39,15 @@ class UniversityLikeServiceTest { private SiteUserFixture siteUserFixture; @Autowired - private UniversityInfoForApplyFixture universityInfoForApplyFixture; + private UnivApplyInfoFixture univApplyInfoFixture; private SiteUser user; - private UniversityInfoForApply 괌대학_A_지원_정보; + private UnivApplyInfo 괌대학_A_지원_정보; @BeforeEach void setUp() { user = siteUserFixture.사용자(); - 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); + 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); } @Nested @@ -61,7 +61,7 @@ class 대학_좋아요를_등록한다 { // then assertAll( () -> assertThat(response.result()).isEqualTo(LIKE_SUCCESS_MESSAGE), - () -> assertThat(likedUniversityRepository.findBySiteUserAndUniversityInfoForApply( + () -> assertThat(likedUniversityRepository.findBySiteUserAndUnivApplyInfo( user, 괌대학_A_지원_정보 )).isPresent() ); @@ -93,7 +93,7 @@ class 대학_좋아요를_취소한다 { // then assertAll( () -> assertThat(response.result()).isEqualTo(LIKE_CANCELED_MESSAGE), - () -> assertThat(likedUniversityRepository.findBySiteUserAndUniversityInfoForApply( + () -> assertThat(likedUniversityRepository.findBySiteUserAndUnivApplyInfo( user, 괌대학_A_지원_정보 )).isEmpty() ); @@ -151,10 +151,10 @@ class 대학_좋아요를_취소한다 { .hasMessage(UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND.getMessage()); } - private void saveLikedUniversity(SiteUser siteUser, UniversityInfoForApply universityInfoForApply) { + private void saveLikedUniversity(SiteUser siteUser, UnivApplyInfo univApplyInfo) { LikedUniversity likedUniversity = LikedUniversity.builder() .siteUser(siteUser) - .universityInfoForApply(universityInfoForApply) + .univApplyInfo(univApplyInfo) .build(); likedUniversityRepository.save(likedUniversity); } diff --git a/src/test/java/com/example/solidconnection/university/service/UniversityQueryServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UniversityQueryServiceTest.java index d83e77b1d..fbad2d174 100644 --- a/src/test/java/com/example/solidconnection/university/service/UniversityQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UniversityQueryServiceTest.java @@ -3,12 +3,12 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.university.domain.LanguageTestType; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.dto.UniversityDetailResponse; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponses; import com.example.solidconnection.university.fixture.LanguageRequirementFixture; -import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; +import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; import com.example.solidconnection.university.repository.custom.UniversityFilterRepository; import org.junit.jupiter.api.DisplayName; @@ -38,7 +38,7 @@ class UniversityQueryServiceTest { private UniversityInfoForApplyRepository universityInfoForApplyRepository; @Autowired - private UniversityInfoForApplyFixture universityInfoForApplyFixture; + private UnivApplyInfoFixture univApplyInfoFixture; @Autowired private LanguageRequirementFixture languageRequirementFixture; @@ -46,7 +46,7 @@ class UniversityQueryServiceTest { @Test void 대학_상세정보를_정상_조회한다() { // given - UniversityInfoForApply 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); // when UniversityDetailResponse response = universityQueryService.getUniversityDetail(괌대학_A_지원_정보.getId()); @@ -58,7 +58,7 @@ class UniversityQueryServiceTest { @Test void 대학_상세정보_조회시_캐시가_적용된다() { // given - UniversityInfoForApply 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); // when UniversityDetailResponse firstResponse = universityQueryService.getUniversityDetail(괌대학_A_지원_정보.getId()); @@ -85,12 +85,12 @@ class UniversityQueryServiceTest { @Test void 전체_대학을_조회한다() { // given - UniversityInfoForApply 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); - UniversityInfoForApply 괌대학_B_지원_정보 = universityInfoForApplyFixture.괌대학_B_지원_정보(); - UniversityInfoForApply 네바다주립대학_라스베이거스_지원_정보 = universityInfoForApplyFixture.네바다주립대학_라스베이거스_지원_정보(); - UniversityInfoForApply 서던덴마크대학교_지원_정보 = universityInfoForApplyFixture.서던덴마크대학교_지원_정보(); - UniversityInfoForApply 그라츠대학_지원_정보 = universityInfoForApplyFixture.그라츠대학_지원_정보(); - UniversityInfoForApply 메이지대학_지원_정보 = universityInfoForApplyFixture.메이지대학_지원_정보(); + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + UnivApplyInfo 괌대학_B_지원_정보 = univApplyInfoFixture.괌대학_B_지원_정보(); + UnivApplyInfo 네바다주립대학_라스베이거스_지원_정보 = univApplyInfoFixture.네바다주립대학_라스베이거스_지원_정보(); + UnivApplyInfo 서던덴마크대학교_지원_정보 = univApplyInfoFixture.서던덴마크대학교_지원_정보(); + UnivApplyInfo 그라츠대학_지원_정보 = univApplyInfoFixture.그라츠대학_지원_정보(); + UnivApplyInfo 메이지대학_지원_정보 = univApplyInfoFixture.메이지대학_지원_정보(); // when UniversityInfoForApplyPreviewResponses response = universityQueryService.searchUniversity( @@ -111,7 +111,7 @@ class UniversityQueryServiceTest { @Test void 대학_조회시_캐시가_적용된다() { // given - universityInfoForApplyFixture.괌대학_A_지원_정보(); + univApplyInfoFixture.괌대학_A_지원_정보(); String regionCode = "AMERICAS"; List keywords = List.of("괌"); LanguageTestType testType = LanguageTestType.TOEFL_IBT; @@ -134,10 +134,10 @@ class UniversityQueryServiceTest { @Test void 지역으로_대학을_필터링한다() { // given - UniversityInfoForApply 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); - universityInfoForApplyFixture.코펜하겐IT대학_지원_정보(); - universityInfoForApplyFixture.그라츠공과대학_지원_정보(); - universityInfoForApplyFixture.메이지대학_지원_정보(); + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + univApplyInfoFixture.코펜하겐IT대학_지원_정보(); + univApplyInfoFixture.그라츠공과대학_지원_정보(); + univApplyInfoFixture.메이지대학_지원_정보(); // when UniversityInfoForApplyPreviewResponses response = universityQueryService.searchUniversity( @@ -151,9 +151,9 @@ class UniversityQueryServiceTest { @Test void 키워드로_대학을_필터링한다() { // given - universityInfoForApplyFixture.괌대학_A_지원_정보(); - UniversityInfoForApply 그라츠대학_지원_정보 = universityInfoForApplyFixture.그라츠대학_지원_정보(); - UniversityInfoForApply 메이지대학_지원_정보 = universityInfoForApplyFixture.메이지대학_지원_정보(); + univApplyInfoFixture.괌대학_A_지원_정보(); + UnivApplyInfo 그라츠대학_지원_정보 = univApplyInfoFixture.그라츠대학_지원_정보(); + UnivApplyInfo 메이지대학_지원_정보 = univApplyInfoFixture.메이지대학_지원_정보(); // when UniversityInfoForApplyPreviewResponses response = universityQueryService.searchUniversity( @@ -170,10 +170,10 @@ class UniversityQueryServiceTest { @Test void 어학시험_조건으로_대학을_필터링한다() { // given - UniversityInfoForApply 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); languageRequirementFixture.토플_80(괌대학_A_지원_정보); languageRequirementFixture.토익_800(괌대학_A_지원_정보); - UniversityInfoForApply 괌대학_B_지원_정보 = universityInfoForApplyFixture.괌대학_B_지원_정보(); + UnivApplyInfo 괌대학_B_지원_정보 = univApplyInfoFixture.괌대학_B_지원_정보(); languageRequirementFixture.토플_70(괌대학_B_지원_정보); languageRequirementFixture.토익_900(괌대학_B_지원_정보); @@ -189,10 +189,10 @@ class UniversityQueryServiceTest { @Test void 모든_조건으로_대학을_필터링한다() { // given - UniversityInfoForApply 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); languageRequirementFixture.토플_80(괌대학_A_지원_정보); languageRequirementFixture.토익_800(괌대학_A_지원_정보); - UniversityInfoForApply 서던덴마크대학교_지원_정보 = universityInfoForApplyFixture.서던덴마크대학교_지원_정보(); + UnivApplyInfo 서던덴마크대학교_지원_정보 = univApplyInfoFixture.서던덴마크대학교_지원_정보(); languageRequirementFixture.토플_70(서던덴마크대학교_지원_정보); // when diff --git a/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java index ff274b96a..6292a82e1 100644 --- a/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java @@ -9,10 +9,10 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.university.domain.UniversityInfoForApply; +import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; import com.example.solidconnection.university.dto.UniversityRecommendsResponse; -import com.example.solidconnection.university.fixture.UniversityInfoForApplyFixture; +import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -49,29 +49,29 @@ class UniversityRecommendServiceTest { private CountryFixture countryFixture; @Autowired - private UniversityInfoForApplyFixture universityInfoForApplyFixture; + private UnivApplyInfoFixture univApplyInfoFixture; private SiteUser user; - private UniversityInfoForApply 괌대학_A_지원_정보; - private UniversityInfoForApply 괌대학_B_지원_정보; - private UniversityInfoForApply 네바다주립대학_라스베이거스_지원_정보; - private UniversityInfoForApply 메모리얼대학_세인트존스_A_지원_정보; - private UniversityInfoForApply 서던덴마크대학교_지원_정보; - private UniversityInfoForApply 코펜하겐IT대학_지원_정보; + private UnivApplyInfo 괌대학_A_지원_정보; + private UnivApplyInfo 괌대학_B_지원_정보; + private UnivApplyInfo 네바다주립대학_라스베이거스_지원_정보; + private UnivApplyInfo 메모리얼대학_세인트존스_A_지원_정보; + private UnivApplyInfo 서던덴마크대학교_지원_정보; + private UnivApplyInfo 코펜하겐IT대학_지원_정보; @BeforeEach void setUp() { user = siteUserFixture.사용자(); - 괌대학_A_지원_정보 = universityInfoForApplyFixture.괌대학_A_지원_정보(); - 괌대학_B_지원_정보 = universityInfoForApplyFixture.괌대학_B_지원_정보(); - 네바다주립대학_라스베이거스_지원_정보 = universityInfoForApplyFixture.네바다주립대학_라스베이거스_지원_정보(); - 메모리얼대학_세인트존스_A_지원_정보 = universityInfoForApplyFixture.메모리얼대학_세인트존스_A_지원_정보(); - 서던덴마크대학교_지원_정보 = universityInfoForApplyFixture.서던덴마크대학교_지원_정보(); - 코펜하겐IT대학_지원_정보 = universityInfoForApplyFixture.코펜하겐IT대학_지원_정보(); - universityInfoForApplyFixture.그라츠대학_지원_정보(); - universityInfoForApplyFixture.그라츠공과대학_지원_정보(); - universityInfoForApplyFixture.린츠_카톨릭대학_지원_정보(); - universityInfoForApplyFixture.메이지대학_지원_정보(); + 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + 괌대학_B_지원_정보 = univApplyInfoFixture.괌대학_B_지원_정보(); + 네바다주립대학_라스베이거스_지원_정보 = univApplyInfoFixture.네바다주립대학_라스베이거스_지원_정보(); + 메모리얼대학_세인트존스_A_지원_정보 = univApplyInfoFixture.메모리얼대학_세인트존스_A_지원_정보(); + 서던덴마크대학교_지원_정보 = univApplyInfoFixture.서던덴마크대학교_지원_정보(); + 코펜하겐IT대학_지원_정보 = univApplyInfoFixture.코펜하겐IT대학_지원_정보(); + univApplyInfoFixture.그라츠대학_지원_정보(); + univApplyInfoFixture.그라츠공과대학_지원_정보(); + univApplyInfoFixture.린츠_카톨릭대학_지원_정보(); + univApplyInfoFixture.메이지대학_지원_정보(); generalUniversityRecommendService.init(); } From e086e8c5ff4091d3426db673cc27fce64f802903 Mon Sep 17 00:00:00 2001 From: seonghyeok cho <65901319+whqtker@users.noreply.github.com> Date: Wed, 25 Jun 2025 01:20:14 +0900 Subject: [PATCH 25/90] =?UTF-8?q?refactor:=20Location=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20(#340)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: InterestedCountryRepository rename * refactor: InterestedCountry, InterestedRegion에 복합 unique 제약 조건 설정 - 테이블에 제약 조건 설정하는 sql 파일 작성 * test: interested_region, interested_country에 대한 테스트 코드 작성 * chore: CountryRepositoryTest, RegionRepositoryTest 파일명 앞에 Interested 추가 * chore: 파일 끝 개행 추가 * chore: UK 제약조건 이름 변경 * chore: 마이그레이션 파일 버전 수정 V15 -> V16 --- .../auth/service/EmailSignUpService.java | 6 +- .../auth/service/SignUpService.java | 10 +-- .../service/oauth/OAuthSignUpService.java | 6 +- .../country/domain/InterestedCountry.java | 8 ++ ....java => InterestedCountryRepository.java} | 2 +- .../region/domain/InterestedRegion.java | 8 ++ ...16__add_unique_constraint_to_intersted.sql | 7 ++ .../InterestedCountryRepositoryTest.java | 86 +++++++++++++++++++ .../InterestedRegionRepositoryTest.java | 86 +++++++++++++++++++ .../UniversityRecommendServiceTest.java | 8 +- 10 files changed, 211 insertions(+), 16 deletions(-) rename src/main/java/com/example/solidconnection/location/country/repository/{InterestedCountyRepository.java => InterestedCountryRepository.java} (81%) create mode 100644 src/main/resources/db/migration/V16__add_unique_constraint_to_intersted.sql create mode 100644 src/test/java/com/example/solidconnection/location/country/repository/InterestedCountryRepositoryTest.java create mode 100644 src/test/java/com/example/solidconnection/location/region/repository/InterestedRegionRepositoryTest.java diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java index 01e117ea0..4e796b81d 100644 --- a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java +++ b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java @@ -3,7 +3,7 @@ import com.example.solidconnection.auth.dto.SignUpRequest; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.location.country.repository.CountryRepository; -import com.example.solidconnection.location.country.repository.InterestedCountyRepository; +import com.example.solidconnection.location.country.repository.InterestedCountryRepository; import com.example.solidconnection.location.region.repository.InterestedRegionRepository; import com.example.solidconnection.location.region.repository.RegionRepository; import com.example.solidconnection.siteuser.domain.AuthType; @@ -20,9 +20,9 @@ public class EmailSignUpService extends SignUpService { public EmailSignUpService(SignInService signInService, SiteUserRepository siteUserRepository, RegionRepository regionRepository, InterestedRegionRepository interestedRegionRepository, - CountryRepository countryRepository, InterestedCountyRepository interestedCountyRepository, + CountryRepository countryRepository, InterestedCountryRepository interestedCountryRepository, EmailSignUpTokenProvider emailSignUpTokenProvider) { - super(signInService, siteUserRepository, regionRepository, interestedRegionRepository, countryRepository, interestedCountyRepository); + super(signInService, siteUserRepository, regionRepository, interestedRegionRepository, countryRepository, interestedCountryRepository); this.emailSignUpTokenProvider = emailSignUpTokenProvider; } diff --git a/src/main/java/com/example/solidconnection/auth/service/SignUpService.java b/src/main/java/com/example/solidconnection/auth/service/SignUpService.java index 95bd4ecac..8cce448f7 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignUpService.java +++ b/src/main/java/com/example/solidconnection/auth/service/SignUpService.java @@ -5,7 +5,7 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.location.country.domain.InterestedCountry; import com.example.solidconnection.location.country.repository.CountryRepository; -import com.example.solidconnection.location.country.repository.InterestedCountyRepository; +import com.example.solidconnection.location.country.repository.InterestedCountryRepository; import com.example.solidconnection.location.region.domain.InterestedRegion; import com.example.solidconnection.location.region.repository.InterestedRegionRepository; import com.example.solidconnection.location.region.repository.RegionRepository; @@ -31,17 +31,17 @@ public abstract class SignUpService { protected final RegionRepository regionRepository; protected final InterestedRegionRepository interestedRegionRepository; protected final CountryRepository countryRepository; - protected final InterestedCountyRepository interestedCountyRepository; + protected final InterestedCountryRepository interestedCountryRepository; protected SignUpService(SignInService signInService, SiteUserRepository siteUserRepository, RegionRepository regionRepository, InterestedRegionRepository interestedRegionRepository, - CountryRepository countryRepository, InterestedCountyRepository interestedCountyRepository) { + CountryRepository countryRepository, InterestedCountryRepository interestedCountryRepository) { this.signInService = signInService; this.siteUserRepository = siteUserRepository; this.regionRepository = regionRepository; this.interestedRegionRepository = interestedRegionRepository; this.countryRepository = countryRepository; - this.interestedCountyRepository = interestedCountyRepository; + this.interestedCountryRepository = interestedCountryRepository; } @Transactional @@ -81,7 +81,7 @@ private void saveInterestedCountry(SignUpRequest signUpRequest, SiteUser savedSi List interestedCountries = countryRepository.findByKoreanNames(interestedCountryNames).stream() .map(country -> new InterestedCountry(savedSiteUser, country)) .toList(); - interestedCountyRepository.saveAll(interestedCountries); + interestedCountryRepository.saveAll(interestedCountries); } protected abstract void validateSignUpToken(SignUpRequest signUpRequest); diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java index 5676f1c7d..96253f548 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java @@ -5,7 +5,7 @@ import com.example.solidconnection.auth.service.SignUpService; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.location.country.repository.CountryRepository; -import com.example.solidconnection.location.country.repository.InterestedCountyRepository; +import com.example.solidconnection.location.country.repository.InterestedCountryRepository; import com.example.solidconnection.location.region.repository.InterestedRegionRepository; import com.example.solidconnection.location.region.repository.RegionRepository; import com.example.solidconnection.siteuser.domain.AuthType; @@ -22,9 +22,9 @@ public class OAuthSignUpService extends SignUpService { OAuthSignUpService(SignInService signInService, SiteUserRepository siteUserRepository, RegionRepository regionRepository, InterestedRegionRepository interestedRegionRepository, - CountryRepository countryRepository, InterestedCountyRepository interestedCountyRepository, + CountryRepository countryRepository, InterestedCountryRepository interestedCountryRepository, OAuthSignUpTokenProvider oAuthSignUpTokenProvider) { - super(signInService, siteUserRepository, regionRepository, interestedRegionRepository, countryRepository, interestedCountyRepository); + super(signInService, siteUserRepository, regionRepository, interestedRegionRepository, countryRepository, interestedCountryRepository); this.oAuthSignUpTokenProvider = oAuthSignUpTokenProvider; } diff --git a/src/main/java/com/example/solidconnection/location/country/domain/InterestedCountry.java b/src/main/java/com/example/solidconnection/location/country/domain/InterestedCountry.java index 9d75c51ed..edc68ba79 100644 --- a/src/main/java/com/example/solidconnection/location/country/domain/InterestedCountry.java +++ b/src/main/java/com/example/solidconnection/location/country/domain/InterestedCountry.java @@ -6,6 +6,8 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -13,6 +15,12 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity +@Table(uniqueConstraints = { + @UniqueConstraint( + name = "uk_interested_country_site_user_id_country_code", + columnNames = {"site_user_id", "country_code"} + ) +}) public class InterestedCountry { @Id diff --git a/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountyRepository.java b/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java similarity index 81% rename from src/main/java/com/example/solidconnection/location/country/repository/InterestedCountyRepository.java rename to src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java index 879323e28..2eacc2ca9 100644 --- a/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountyRepository.java +++ b/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java @@ -8,6 +8,6 @@ import java.util.List; @Repository -public interface InterestedCountyRepository extends JpaRepository { +public interface InterestedCountryRepository extends JpaRepository { List findAllBySiteUser(SiteUser siteUser); } diff --git a/src/main/java/com/example/solidconnection/location/region/domain/InterestedRegion.java b/src/main/java/com/example/solidconnection/location/region/domain/InterestedRegion.java index 457ee4eb6..35214a95c 100644 --- a/src/main/java/com/example/solidconnection/location/region/domain/InterestedRegion.java +++ b/src/main/java/com/example/solidconnection/location/region/domain/InterestedRegion.java @@ -6,6 +6,8 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -13,6 +15,12 @@ @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity +@Table(uniqueConstraints = { + @UniqueConstraint( + name = "uk_interested_region_site_user_id_region_code", + columnNames = {"site_user_id", "region_code"} + ) +}) public class InterestedRegion { @Id diff --git a/src/main/resources/db/migration/V16__add_unique_constraint_to_intersted.sql b/src/main/resources/db/migration/V16__add_unique_constraint_to_intersted.sql new file mode 100644 index 000000000..2f7a8524c --- /dev/null +++ b/src/main/resources/db/migration/V16__add_unique_constraint_to_intersted.sql @@ -0,0 +1,7 @@ +ALTER TABLE interested_country +ADD CONSTRAINT uk_interested_country_site_user_id_country_code +UNIQUE (site_user_id, country_code); + +ALTER TABLE interested_region +ADD CONSTRAINT uk_interested_region_site_user_id_region_code +UNIQUE (site_user_id, region_code); diff --git a/src/test/java/com/example/solidconnection/location/country/repository/InterestedCountryRepositoryTest.java b/src/test/java/com/example/solidconnection/location/country/repository/InterestedCountryRepositoryTest.java new file mode 100644 index 000000000..4daeb55e1 --- /dev/null +++ b/src/test/java/com/example/solidconnection/location/country/repository/InterestedCountryRepositoryTest.java @@ -0,0 +1,86 @@ +package com.example.solidconnection.location.country.repository; + +import com.example.solidconnection.location.country.domain.Country; +import com.example.solidconnection.location.country.domain.InterestedCountry; +import com.example.solidconnection.location.country.fixture.CountryFixture; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +@TestContainerSpringBootTest +public class InterestedCountryRepositoryTest { + + @Autowired + private InterestedCountryRepository interestedCountryRepository; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private CountryFixture countryFixture; + + @Nested + class 사용자와_나라는_복합_유니크_제약_조건을_가진다 { + + @Test + void 같은_사용자가_같은_나라에_관심_표시를_하면_예외_응답을_반환한다() { + // given + SiteUser user = siteUserFixture.사용자(); + Country country = countryFixture.미국(); + + InterestedCountry firstInterest = new InterestedCountry(user, country); + interestedCountryRepository.save(firstInterest); + + InterestedCountry secondInterest = new InterestedCountry(user, country); + + // when & then + assertThatCode(() -> interestedCountryRepository.save(secondInterest)) + .isInstanceOf(DataIntegrityViolationException.class); + } + + @Test + void 다른_사용자가_같은_나라에_관심_표시를_하면_정상_저장된다() { + // given + SiteUser user1 = siteUserFixture.사용자(1, "user1"); + SiteUser user2 = siteUserFixture.사용자(2, "user2"); + Country country = countryFixture.미국(); + + InterestedCountry firstInterest = new InterestedCountry(user1, country); + interestedCountryRepository.save(firstInterest); + + InterestedCountry secondInterest = new InterestedCountry(user2, country); + + // when & then + assertThatCode(() -> { + InterestedCountry saved = interestedCountryRepository.save(secondInterest); + assertThat(saved.getId()).isNotNull(); + }).doesNotThrowAnyException(); + } + + @Test + void 같은_사용자가_다른_나라에_관심_표시를_하면_정상_저장된다() { + // given + SiteUser user = siteUserFixture.사용자(); + Country country1 = countryFixture.미국(); + Country country2 = countryFixture.일본(); + + InterestedCountry firstInterest = new InterestedCountry(user, country1); + interestedCountryRepository.save(firstInterest); + + InterestedCountry secondInterest = new InterestedCountry(user, country2); + + // when & then + assertThatCode(() -> { + InterestedCountry saved = interestedCountryRepository.save(secondInterest); + assertThat(saved.getId()).isNotNull(); + }).doesNotThrowAnyException(); + } + } +} diff --git a/src/test/java/com/example/solidconnection/location/region/repository/InterestedRegionRepositoryTest.java b/src/test/java/com/example/solidconnection/location/region/repository/InterestedRegionRepositoryTest.java new file mode 100644 index 000000000..a9594e0f8 --- /dev/null +++ b/src/test/java/com/example/solidconnection/location/region/repository/InterestedRegionRepositoryTest.java @@ -0,0 +1,86 @@ +package com.example.solidconnection.location.region.repository; + +import com.example.solidconnection.location.region.domain.InterestedRegion; +import com.example.solidconnection.location.region.domain.Region; +import com.example.solidconnection.location.region.fixture.RegionFixture; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +@TestContainerSpringBootTest +public class InterestedRegionRepositoryTest { + + @Autowired + private InterestedRegionRepository interestedRegionRepository; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private RegionFixture regionFixture; + + @Nested + class 사용자와_지역은_복합_유니크_제약_조건을_가진다 { + + @Test + void 같은_사용자가_같은_지역에_관심_표시를_하면_예외_응답을_반환한다() { + // given + SiteUser user = siteUserFixture.사용자(); + Region region = regionFixture.영미권(); + + InterestedRegion firstInterest = new InterestedRegion(user, region); + interestedRegionRepository.save(firstInterest); + + InterestedRegion secondInterest = new InterestedRegion(user, region); + + // when & then + assertThatCode(() -> interestedRegionRepository.save(secondInterest)) + .isInstanceOf(DataIntegrityViolationException.class); + } + + @Test + void 다른_사용자가_같은_지역에_관심_표시를_하면_정상_저장된다() { + // given + SiteUser user1 = siteUserFixture.사용자(1, "user1"); + SiteUser user2 = siteUserFixture.사용자(2, "user2"); + Region region = regionFixture.영미권(); + + InterestedRegion firstInterest = new InterestedRegion(user1, region); + interestedRegionRepository.save(firstInterest); + + InterestedRegion secondInterest = new InterestedRegion(user2, region); + + // when & then + assertThatCode(() -> { + InterestedRegion saved = interestedRegionRepository.save(secondInterest); + assertThat(saved.getId()).isNotNull(); + }).doesNotThrowAnyException(); + } + + @Test + void 같은_사용자가_다른_지역에_관심_표시를_하면_정상_저장된다() { + // given + SiteUser user = siteUserFixture.사용자(); + Region region1 = regionFixture.영미권(); + Region region2 = regionFixture.유럽(); + + InterestedRegion firstInterest = new InterestedRegion(user, region1); + interestedRegionRepository.save(firstInterest); + + InterestedRegion secondInterest = new InterestedRegion(user, region2); + + // when & then + assertThatCode(() -> { + InterestedRegion saved = interestedRegionRepository.save(secondInterest); + assertThat(saved.getId()).isNotNull(); + }).doesNotThrowAnyException(); + } + } +} diff --git a/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java index 6292a82e1..a1bfba10b 100644 --- a/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java @@ -2,7 +2,7 @@ import com.example.solidconnection.location.country.domain.InterestedCountry; import com.example.solidconnection.location.country.fixture.CountryFixture; -import com.example.solidconnection.location.country.repository.InterestedCountyRepository; +import com.example.solidconnection.location.country.repository.InterestedCountryRepository; import com.example.solidconnection.location.region.domain.InterestedRegion; import com.example.solidconnection.location.region.fixture.RegionFixture; import com.example.solidconnection.location.region.repository.InterestedRegionRepository; @@ -34,7 +34,7 @@ class UniversityRecommendServiceTest { private InterestedRegionRepository interestedRegionRepository; @Autowired - private InterestedCountyRepository interestedCountyRepository; + private InterestedCountryRepository interestedCountryRepository; @Autowired private GeneralUniversityRecommendService generalUniversityRecommendService; @@ -97,7 +97,7 @@ void setUp() { @Test void 관심_국가_설정한_사용자의_맞춤_추천_대학을_조회한다() { // given - interestedCountyRepository.save(new InterestedCountry(user, countryFixture.덴마크())); + interestedCountryRepository.save(new InterestedCountry(user, countryFixture.덴마크())); // when UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(user); @@ -115,7 +115,7 @@ void setUp() { void 관심_지역과_국가_모두_설정한_사용자의_맞춤_추천_대학을_조회한다() { // given interestedRegionRepository.save(new InterestedRegion(user, regionFixture.영미권())); - interestedCountyRepository.save(new InterestedCountry(user, countryFixture.덴마크())); + interestedCountryRepository.save(new InterestedCountry(user, countryFixture.덴마크())); // when UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(user); From 45430c9acb5ecf3100cb80b056d6f8c92fb43872 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Wed, 25 Jun 2025 11:40:36 +0900 Subject: [PATCH 26/90] =?UTF-8?q?fix:=20flyway=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#342)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: flyway 스크립트 이름 오타 수정 - 언더바 2개로 * chore: flyway 스크립트 컬럼 오타 수정 --- ...d_application_index_and_delete_manny_to_one_mapping.sql} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename src/main/resources/db/migration/{V13_add_application_index_and_delete_manny_to_one_mapping.sql => V13__add_application_index_and_delete_manny_to_one_mapping.sql} (88%) diff --git a/src/main/resources/db/migration/V13_add_application_index_and_delete_manny_to_one_mapping.sql b/src/main/resources/db/migration/V13__add_application_index_and_delete_manny_to_one_mapping.sql similarity index 88% rename from src/main/resources/db/migration/V13_add_application_index_and_delete_manny_to_one_mapping.sql rename to src/main/resources/db/migration/V13__add_application_index_and_delete_manny_to_one_mapping.sql index 3f3018724..df8437c3a 100644 --- a/src/main/resources/db/migration/V13_add_application_index_and_delete_manny_to_one_mapping.sql +++ b/src/main/resources/db/migration/V13__add_application_index_and_delete_manny_to_one_mapping.sql @@ -6,10 +6,10 @@ CREATE INDEX idx_app_user_term_delete ON application(site_user_id, term, is_delete); CREATE INDEX idx_app_first_choice_search - ON application(verify_status, term, is_delete, first_choice_university_apply_info_id); + ON application(verify_status, term, is_delete, first_choice_university_info_for_apply_id); CREATE INDEX idx_app_second_choice_search - ON application(verify_status, term, is_delete, second_choice_university_apply_info_id); + ON application(verify_status, term, is_delete, second_choice_university_info_for_apply_id); CREATE INDEX idx_app_third_choice_search - ON application(verify_status, term, is_delete, third_choice_university_apply_info_id); + ON application(verify_status, term, is_delete, third_choice_university_info_for_apply_id); From 2c5851e073c29cb542fadadd3d5a4699db7308af Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Tue, 1 Jul 2025 16:54:38 +0900 Subject: [PATCH 27/90] =?UTF-8?q?refactor:=20'=EB=8C=80=ED=95=99=EA=B5=90'?= =?UTF-8?q?=EC=99=80=20'=EB=8C=80=ED=95=99=20=EC=A7=80=EC=9B=90=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4'=20=EC=9A=A9=EC=96=B4=EB=A5=BC=20=EA=B5=AC?= =?UTF-8?q?=EB=B6=84=ED=95=98=EB=8F=84=EB=A1=9D=20(#346)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: univ-apply-info로 URI 변경 * refactor: UnivApplyInfo 사용하도록 컨트롤러 함수 변경 * refactor: UnivApplyInfo 사용하도록 recommendService 변경 * refactor: UnivApplyInfo 사용하도록 레포지토리 이름 변경 * refactor: JPQL alias 변경 * refactor: JPA 표준에 맞게 함수 이름 변경 * refactor: UnivApplyInfo 사용하도록 레포지토리 변경 * refactor: JPA 표준에 맞게 함수 이름 변경 * refactor: UnivApplyInfo 반영하도록 JPQL 별칭 변경 * refactor: 잘못되어있던 상속 관계 변경 - University와 UniversityInformationForApply의 혼용으로 발생한 문제가 있었다. - UnivApplyInfo 를 적용하며 발견했고, 바로잡았다. * refactor: 좋아요한 대학 도메인 이름 변경 - LikedUniversity -> LikedUnivApplyInfo * refactor: UnivApplyInfo 사용하도록 likeService 변경 * refactor: LikedUnivApplyInfoRepository 패키지 이동 - siteuser 하위 -> university 하위 * refactor: UnivApplyInfo 사용하도록 Dto 이름 변경 - 프론트와 협의가 되어야 하는 요청/응답의 json에 해당하는 이름들을 백엔드가 임의로 바꾸는 것은 바람직하지 않다. - issue를 만들고, todo로 표시하고 넘어간다. - 프론트와 협의 이후, 별도의 PR에서 수정한다. * refactor: UnivApplyInfo 사용하도록 테스트 코드 변경 * refactor: UNIV_APPLY_INFO 사용하도록 ErrorCode 변경 * refactor: 지원자 응답 Dto 이름 변경 - Application 도메인의 의미상, '대학에 지원한다'는 내용이 포함되어있다. - 코드 전체적으로 위 내용의 합의되어있기도 하다. (application 앞에 University를 붙이지 않음) - 따라서 University나 UnivApplyInfo이 없는 이름으로 변경한다. * refactor: UnivApplyInfo 사용하도록 applicationService 변경 * refactor: JPA 표준에 맞게 함수 이름 변경 * chore: 추후 수정해야 할 부분 표시 * refactor: UnivApplyInfo 사용하도록 Dto필드명 변경 - JsonProperties로 Json key는 유지하되, 필드와 인자명은 코드 전체에서 통일되게한다. * refactor: 이름변경 누락된 부분 수정 * refactor: 테스트 코드에서 이름변경 누락된 부분 수정 * refactor: UnivApplyInfo 사용하도록 MyPageService 변경 * refactor: 사용되지 않는 함수 삭제 --- ...sResponse.java => ApplicantsResponse.java} | 6 +- .../application/dto/ApplicationsResponse.java | 6 +- .../application/dto/ApplyRequest.java | 4 +- .../dto/UnivApplyInfoChoiceRequest.java | 17 +++ .../dto/UniversityChoiceRequest.java | 10 -- .../repository/ApplicationRepository.java | 8 +- .../service/ApplicationQueryService.java | 44 +++---- .../service/ApplicationSubmissionService.java | 16 +-- .../common/exception/ErrorCode.java | 10 +- .../siteuser/dto/MyPageResponse.java | 9 +- .../repository/LikedUniversityRepository.java | 18 --- .../siteuser/service/MyPageService.java | 20 ++-- .../controller/UnivApplyInfoController.java | 104 +++++++++++++++++ .../controller/UniversityController.java | 103 ----------------- ...niversity.java => LikedUnivApplyInfo.java} | 6 +- ....java => UnivApplyInfoDetailResponse.java} | 6 +- ...java => UnivApplyInfoPreviewResponse.java} | 6 +- .../dto/UnivApplyInfoPreviewResponses.java | 9 ++ .../dto/UnivApplyInfoRecommendsResponse.java | 8 ++ ...niversityInfoForApplyPreviewResponses.java | 8 -- .../dto/UniversityRecommendsResponse.java | 7 -- ...ice.java => ValidUnivApplyInfoChoice.java} | 4 +- ...=> ValidUnivApplyInfoChoiceValidator.java} | 26 ++--- .../LikedUnivApplyInfoRepository.java | 18 +++ .../repository/UnivApplyInfoRepository.java | 62 ++++++++++ .../UniversityInfoForApplyRepository.java | 85 -------------- .../repository/UniversityRepository.java | 3 +- ...ava => UnivApplyInfoFilterRepository.java} | 6 +- ...=> UnivApplyInfoFilterRepositoryImpl.java} | 12 +- ...GeneralUnivApplyInfoRecommendService.java} | 12 +- .../service/UnivApplyInfoLikeService.java | 79 +++++++++++++ ...ce.java => UnivApplyInfoQueryService.java} | 34 +++--- ...ava => UnivApplyInfoRecommendService.java} | 46 ++++---- .../service/UniversityLikeService.java | 79 ------------- .../migration/V17__rename_like_university.sql | 1 + .../fixture/ApplicationFixture.java | 13 +-- .../fixture/ApplicationFixtureBuilder.java | 24 ++-- .../service/ApplicationQueryServiceTest.java | 20 ++-- .../ApplicationSubmissionServiceTest.java | 18 +-- .../siteuser/service/MyPageServiceTest.java | 36 +++--- ...alidUnivApplyInfoChoiceValidatorTest.java} | 28 ++--- .../fixture/LanguageRequirementFixture.java | 20 ++-- .../LanguageRequirementFixtureBuilder.java | 2 +- .../fixture/UnivApplyInfoFixture.java | 20 ++-- .../fixture/UnivApplyInfoFixtureBuilder.java | 10 +- ... => LikedUnivApplyInfoRepositoryTest.java} | 43 ++++--- ...ralUnivApplyInfoRecommendServiceTest.java} | 16 +-- ...java => UnivApplyInfoLikeServiceTest.java} | 74 ++++++------ ...ava => UnivApplyInfoQueryServiceTest.java} | 108 +++++++++--------- ...=> UnivApplyInfoRecommendServiceTest.java} | 78 ++++++------- 50 files changed, 697 insertions(+), 705 deletions(-) rename src/main/java/com/example/solidconnection/application/dto/{UniversityApplicantsResponse.java => ApplicantsResponse.java} (82%) create mode 100644 src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoChoiceRequest.java delete mode 100644 src/main/java/com/example/solidconnection/application/dto/UniversityChoiceRequest.java delete mode 100644 src/main/java/com/example/solidconnection/siteuser/repository/LikedUniversityRepository.java create mode 100644 src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java delete mode 100644 src/main/java/com/example/solidconnection/university/controller/UniversityController.java rename src/main/java/com/example/solidconnection/university/domain/{LikedUniversity.java => LikedUnivApplyInfo.java} (89%) rename src/main/java/com/example/solidconnection/university/dto/{UniversityDetailResponse.java => UnivApplyInfoDetailResponse.java} (94%) rename src/main/java/com/example/solidconnection/university/dto/{UniversityInfoForApplyPreviewResponse.java => UnivApplyInfoPreviewResponse.java} (86%) create mode 100644 src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoPreviewResponses.java create mode 100644 src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoRecommendsResponse.java delete mode 100644 src/main/java/com/example/solidconnection/university/dto/UniversityInfoForApplyPreviewResponses.java delete mode 100644 src/main/java/com/example/solidconnection/university/dto/UniversityRecommendsResponse.java rename src/main/java/com/example/solidconnection/university/dto/validation/{ValidUniversityChoice.java => ValidUnivApplyInfoChoice.java} (82%) rename src/main/java/com/example/solidconnection/university/dto/validation/{ValidUniversityChoiceValidator.java => ValidUnivApplyInfoChoiceValidator.java} (59%) create mode 100644 src/main/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepository.java create mode 100644 src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java delete mode 100644 src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java rename src/main/java/com/example/solidconnection/university/repository/custom/{UniversityFilterRepository.java => UnivApplyInfoFilterRepository.java} (59%) rename src/main/java/com/example/solidconnection/university/repository/custom/{UniversityFilterRepositoryImpl.java => UnivApplyInfoFilterRepositoryImpl.java} (89%) rename src/main/java/com/example/solidconnection/university/service/{GeneralUniversityRecommendService.java => GeneralUnivApplyInfoRecommendService.java} (64%) create mode 100644 src/main/java/com/example/solidconnection/university/service/UnivApplyInfoLikeService.java rename src/main/java/com/example/solidconnection/university/service/{UniversityQueryService.java => UnivApplyInfoQueryService.java} (54%) rename src/main/java/com/example/solidconnection/university/service/{UniversityRecommendService.java => UnivApplyInfoRecommendService.java} (54%) delete mode 100644 src/main/java/com/example/solidconnection/university/service/UniversityLikeService.java create mode 100644 src/main/resources/db/migration/V17__rename_like_university.sql rename src/test/java/com/example/solidconnection/university/dto/validation/{ValidUniversityChoiceValidatorTest.java => ValidUnivApplyInfoChoiceValidatorTest.java} (64%) rename src/test/java/com/example/solidconnection/university/repository/{UniversityLikeRepositoryTest.java => LikedUnivApplyInfoRepositoryTest.java} (54%) rename src/test/java/com/example/solidconnection/university/service/{GeneralUniversityRecommendServiceTest.java => GeneralUnivApplyInfoRecommendServiceTest.java} (77%) rename src/test/java/com/example/solidconnection/university/service/{UniversityLikeServiceTest.java => UnivApplyInfoLikeServiceTest.java} (55%) rename src/test/java/com/example/solidconnection/university/service/{UniversityQueryServiceTest.java => UnivApplyInfoQueryServiceTest.java} (54%) rename src/test/java/com/example/solidconnection/university/service/{UniversityRecommendServiceTest.java => UnivApplyInfoRecommendServiceTest.java} (59%) diff --git a/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java b/src/main/java/com/example/solidconnection/application/dto/ApplicantsResponse.java similarity index 82% rename from src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java rename to src/main/java/com/example/solidconnection/application/dto/ApplicantsResponse.java index 1fe24546f..72fbc9c22 100644 --- a/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/ApplicantsResponse.java @@ -6,14 +6,14 @@ import java.util.List; -public record UniversityApplicantsResponse( +public record ApplicantsResponse( String koreanName, int studentCapacity, String region, String country, List applicants) { - public static UniversityApplicantsResponse of(UnivApplyInfo univApplyInfo, List applications, SiteUser siteUser) { - return new UniversityApplicantsResponse( + public static ApplicantsResponse of(UnivApplyInfo univApplyInfo, List applications, SiteUser siteUser) { + return new ApplicantsResponse( univApplyInfo.getKoreanName(), univApplyInfo.getStudentCapacity(), univApplyInfo.getUniversity().getRegion().getKoreanName(), diff --git a/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java b/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java index a3429c1ef..13c641bb0 100644 --- a/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java @@ -3,7 +3,7 @@ import java.util.List; public record ApplicationsResponse( - List firstChoice, - List secondChoice, - List thirdChoice) { + List firstChoice, + List secondChoice, + List thirdChoice) { } diff --git a/src/main/java/com/example/solidconnection/application/dto/ApplyRequest.java b/src/main/java/com/example/solidconnection/application/dto/ApplyRequest.java index 7c4da1c99..cab079b21 100644 --- a/src/main/java/com/example/solidconnection/application/dto/ApplyRequest.java +++ b/src/main/java/com/example/solidconnection/application/dto/ApplyRequest.java @@ -1,5 +1,6 @@ package com.example.solidconnection.application.dto; +import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; @@ -12,6 +13,7 @@ public record ApplyRequest( Long languageTestScoreId, @Valid - UniversityChoiceRequest universityChoiceRequest + @JsonProperty("universityChoiceRequest") + UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest ) { } diff --git a/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoChoiceRequest.java b/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoChoiceRequest.java new file mode 100644 index 000000000..9598a191b --- /dev/null +++ b/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoChoiceRequest.java @@ -0,0 +1,17 @@ +package com.example.solidconnection.application.dto; + +import com.example.solidconnection.university.dto.validation.ValidUnivApplyInfoChoice; +import com.fasterxml.jackson.annotation.JsonProperty; + +@ValidUnivApplyInfoChoice +public record UnivApplyInfoChoiceRequest( + + @JsonProperty("firstChoiceUniversityId") + Long firstChoiceUnivApplyInfoId, + + @JsonProperty("secondChoiceUniversityId") + Long secondChoiceUnivApplyInfoId, + + @JsonProperty("thirdChoiceUniversityId") + Long thirdChoiceUnivApplyInfoId) { +} diff --git a/src/main/java/com/example/solidconnection/application/dto/UniversityChoiceRequest.java b/src/main/java/com/example/solidconnection/application/dto/UniversityChoiceRequest.java deleted file mode 100644 index 220b803b3..000000000 --- a/src/main/java/com/example/solidconnection/application/dto/UniversityChoiceRequest.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.solidconnection.application.dto; - -import com.example.solidconnection.university.dto.validation.ValidUniversityChoice; - -@ValidUniversityChoice -public record UniversityChoiceRequest( - Long firstChoiceUniversityId, - Long secondChoiceUniversityId, - Long thirdChoiceUniversityId) { -} diff --git a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java index 0cbf1cf68..9a7754896 100644 --- a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java +++ b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java @@ -23,14 +23,14 @@ public interface ApplicationRepository extends JpaRepository SELECT a FROM Application a JOIN FETCH a.siteUser - WHERE (a.firstChoiceUnivApplyInfoId IN :universityIds - OR a.secondChoiceUnivApplyInfoId IN :universityIds - OR a.thirdChoiceUnivApplyInfoId IN :universityIds) + WHERE (a.firstChoiceUnivApplyInfoId IN :univApplyInfoIds + OR a.secondChoiceUnivApplyInfoId IN :univApplyInfoIds + OR a.thirdChoiceUnivApplyInfoId IN :univApplyInfoIds) AND a.verifyStatus = :status AND a.term = :term AND a.isDelete = false """) - List findAllByUnivApplyInfoIds(@Param("universityIds") List universityIds, @Param("status") VerifyStatus status, @Param("term") String term); + List findAllByUnivApplyInfoIds(@Param("univApplyInfoIds") List univApplyInfoIds, @Param("status") VerifyStatus status, @Param("term") String term); @Query(""" SELECT a diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java index dbfa28990..2d7adbc58 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java @@ -3,13 +3,13 @@ import com.example.solidconnection.application.domain.Application; import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.application.dto.ApplicationsResponse; -import com.example.solidconnection.application.dto.UniversityApplicantsResponse; +import com.example.solidconnection.application.dto.ApplicantsResponse; import com.example.solidconnection.application.repository.ApplicationRepository; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.university.domain.UnivApplyInfo; -import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; -import com.example.solidconnection.university.repository.custom.UniversityFilterRepositoryImpl; +import com.example.solidconnection.university.repository.UnivApplyInfoRepository; +import com.example.solidconnection.university.repository.custom.UnivApplyInfoFilterRepositoryImpl; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -31,8 +31,8 @@ public class ApplicationQueryService { private final ApplicationRepository applicationRepository; - private final UniversityInfoForApplyRepository universityInfoForApplyRepository; - private final UniversityFilterRepositoryImpl universityFilterRepository; + private final UnivApplyInfoRepository univApplyInfoRepository; + private final UnivApplyInfoFilterRepositoryImpl universityFilterRepository; @Value("${university.term}") public String term; @@ -41,7 +41,7 @@ public class ApplicationQueryService { @Transactional(readOnly = true) public ApplicationsResponse getApplicants(SiteUser siteUser, String regionCode, String keyword) { // 1. 대학 지원 정보 필터링 (regionCode, keyword) - List univApplyInfos = universityFilterRepository.findByRegionCodeAndKeywords(regionCode, List.of(keyword)); + List univApplyInfos = universityFilterRepository.findAllByRegionCodeAndKeywords(regionCode, List.of(keyword)); if (univApplyInfos.isEmpty()) { return new ApplicationsResponse(List.of(), List.of(), List.of()); } @@ -58,7 +58,7 @@ public ApplicationsResponse getApplicants(SiteUser siteUser, String regionCode, public ApplicationsResponse getApplicantsByUserApplications(SiteUser siteUser) { Application userLatestApplication = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term); - List universityInfoForApplyIds = Stream.of( + List univApplyInfoIds = Stream.of( userLatestApplication.getFirstChoiceUnivApplyInfoId(), userLatestApplication.getSecondChoiceUnivApplyInfoId(), userLatestApplication.getThirdChoiceUnivApplyInfoId() @@ -66,30 +66,30 @@ public ApplicationsResponse getApplicantsByUserApplications(SiteUser siteUser) { .filter(Objects::nonNull) .collect(Collectors.toList()); - if (universityInfoForApplyIds.isEmpty()) { + if (univApplyInfoIds.isEmpty()) { return new ApplicationsResponse(List.of(), List.of(), List.of()); } - List applications = applicationRepository.findAllByUnivApplyInfoIds(universityInfoForApplyIds, VerifyStatus.APPROVED, term); - List universityInfosForApply = universityInfoForApplyRepository.findAllByUniversityIds(universityInfoForApplyIds); + List applications = applicationRepository.findAllByUnivApplyInfoIds(univApplyInfoIds, VerifyStatus.APPROVED, term); + List univApplyInfos = univApplyInfoRepository.findAllByIds(univApplyInfoIds); - return classifyApplicationsByChoice(universityInfosForApply, applications, siteUser); + return classifyApplicationsByChoice(univApplyInfos, applications, siteUser); } private ApplicationsResponse classifyApplicationsByChoice( - List universityInfosForApply, + List univApplyInfos, List applications, SiteUser siteUser) { Map> firstChoiceMap = createChoiceMap(applications, Application::getFirstChoiceUnivApplyInfoId); Map> secondChoiceMap = createChoiceMap(applications, Application::getSecondChoiceUnivApplyInfoId); Map> thirdChoiceMap = createChoiceMap(applications, Application::getThirdChoiceUnivApplyInfoId); - List firstChoiceApplicants = - createUniversityApplicantsResponses(universityInfosForApply, firstChoiceMap, siteUser); - List secondChoiceApplicants = - createUniversityApplicantsResponses(universityInfosForApply, secondChoiceMap, siteUser); - List thirdChoiceApplicants = - createUniversityApplicantsResponses(universityInfosForApply, thirdChoiceMap, siteUser); + List firstChoiceApplicants = + createUniversityApplicantsResponses(univApplyInfos, firstChoiceMap, siteUser); + List secondChoiceApplicants = + createUniversityApplicantsResponses(univApplyInfos, secondChoiceMap, siteUser); + List thirdChoiceApplicants = + createUniversityApplicantsResponses(univApplyInfos, thirdChoiceMap, siteUser); return new ApplicationsResponse(firstChoiceApplicants, secondChoiceApplicants, thirdChoiceApplicants); } @@ -109,12 +109,12 @@ private Map> createChoiceMap( return choiceMap; } - private List createUniversityApplicantsResponses( - List universityInfosForApply, + private List createUniversityApplicantsResponses( + List univApplyInfos, Map> choiceMap, SiteUser siteUser) { - return universityInfosForApply.stream() - .map(uia -> UniversityApplicantsResponse.of(uia, choiceMap.getOrDefault(uia.getId(), List.of()), siteUser)) + return univApplyInfos.stream() + .map(uia -> ApplicantsResponse.of(uia, choiceMap.getOrDefault(uia.getId(), List.of()), siteUser)) .toList(); } diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java index ea44840ee..2d307992b 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java @@ -4,7 +4,7 @@ import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.application.dto.ApplicationSubmissionResponse; import com.example.solidconnection.application.dto.ApplyRequest; -import com.example.solidconnection.application.dto.UniversityChoiceRequest; +import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; import com.example.solidconnection.application.repository.ApplicationRepository; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.GpaScore; @@ -42,13 +42,13 @@ public class ApplicationSubmissionService { // 기존에 있던 status field 우선 APRROVED로 입력시킨다. @Transactional public ApplicationSubmissionResponse apply(SiteUser siteUser, ApplyRequest applyRequest) { - UniversityChoiceRequest universityChoiceRequest = applyRequest.universityChoiceRequest(); + UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest = applyRequest.univApplyInfoChoiceRequest(); GpaScore gpaScore = getValidGpaScore(siteUser, applyRequest.gpaScoreId()); LanguageTestScore languageTestScore = getValidLanguageTestScore(siteUser, applyRequest.languageTestScoreId()); - long firstChoiceUniversityId = universityChoiceRequest.firstChoiceUniversityId(); - Long secondChoiceUniversityId = universityChoiceRequest.secondChoiceUniversityId(); - Long thirdChoiceUniversityId = universityChoiceRequest.thirdChoiceUniversityId(); + long firstChoiceUnivApplyInfoId = univApplyInfoChoiceRequest.firstChoiceUnivApplyInfoId(); + Long secondChoiceUnivApplyInfoId = univApplyInfoChoiceRequest.secondChoiceUnivApplyInfoId(); + Long thirdChoiceUnivApplyInfoId = univApplyInfoChoiceRequest.thirdChoiceUnivApplyInfoId(); Optional existingApplication = applicationRepository.findBySiteUserAndTerm(siteUser, term); int updateCount = existingApplication @@ -65,9 +65,9 @@ public ApplicationSubmissionResponse apply(SiteUser siteUser, ApplyRequest apply languageTestScore.getLanguageTest(), term, updateCount, - firstChoiceUniversityId, - secondChoiceUniversityId, - thirdChoiceUniversityId, + firstChoiceUnivApplyInfoId, + secondChoiceUnivApplyInfoId, + thirdChoiceUnivApplyInfoId, getRandomNickname() ); diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 00e600201..ecacb1700 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -33,8 +33,8 @@ public enum ErrorCode { SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER(HttpStatus.BAD_REQUEST.value(), "회원가입 토큰이 우리 서버에서 발급되지 않았습니다."), // data not found - UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 대학교 지원 정보입니다."), - UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND_FOR_TERM(HttpStatus.NOT_FOUND.value(), "해당하는 대학교가 이번 모집 기간에 열리지 않았습니다."), + UNIV_APPLY_INFO_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 대학교 지원 정보입니다."), + UNIV_APPLY_INFO_NOT_FOUND_FOR_TERM(HttpStatus.NOT_FOUND.value(), "해당하는 대학교가 이번 모집 기간에 열리지 않았습니다."), APPLICATION_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "사용자의 대학 지원 정보를 찾을 수 없습니다."), USER_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "회원을 찾을 수 없습니다."), UNIVERSITY_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "대학교를 찾을 수 없습니다."), @@ -71,7 +71,7 @@ public enum ErrorCode { PROFILE_IMAGE_NEEDED(HttpStatus.BAD_REQUEST.value(), "프로필 이미지가 필요합니다."), FIRST_CHOICE_REQUIRED(HttpStatus.BAD_REQUEST.value(), "1지망 대학교를 입력해주세요."), THIRD_CHOICE_REQUIRES_SECOND(HttpStatus.BAD_REQUEST.value(), "2지망 없이 3지망을 선택할 수 없습니다."), - DUPLICATE_UNIVERSITY_CHOICE(HttpStatus.BAD_REQUEST.value(), "지망 선택이 중복되었습니다."), + DUPLICATE_UNIV_APPLY_INFO_CHOICE(HttpStatus.BAD_REQUEST.value(), "지망 선택이 중복되었습니다."), // community INVALID_POST_CATEGORY(HttpStatus.BAD_REQUEST.value(), "잘못된 카테고리명입니다."), @@ -86,8 +86,8 @@ public enum ErrorCode { CAN_NOT_UPDATE_DEPRECATED_COMMENT(HttpStatus.BAD_REQUEST.value(), "이미 삭제된 댓글을 수정할 수 없습니다."), INVALID_POST_LIKE(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 게시글 좋아요입니다."), DUPLICATE_POST_LIKE(HttpStatus.BAD_REQUEST.value(), "이미 좋아요한 게시글입니다."), - ALREADY_LIKED_UNIVERSITY(HttpStatus.BAD_REQUEST.value(), "이미 좋아요한 대학입니다."), - NOT_LIKED_UNIVERSITY(HttpStatus.BAD_REQUEST.value(), "좋아요하지 않은 대학입니다."), + ALREADY_LIKED_UNIV_APPLY_INFO(HttpStatus.BAD_REQUEST.value(), "이미 좋아요한 대학입니다."), + NOT_LIKED_UNIV_APPLY_INFO(HttpStatus.BAD_REQUEST.value(), "좋아요하지 않은 대학입니다."), // score INVALID_GPA_SCORE_STATUS(HttpStatus.BAD_REQUEST.value(), "학점이 승인되지 않았습니다."), diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/MyPageResponse.java b/src/main/java/com/example/solidconnection/siteuser/dto/MyPageResponse.java index 9185362cf..d84ed61ec 100644 --- a/src/main/java/com/example/solidconnection/siteuser/dto/MyPageResponse.java +++ b/src/main/java/com/example/solidconnection/siteuser/dto/MyPageResponse.java @@ -3,6 +3,7 @@ import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.fasterxml.jackson.annotation.JsonProperty; public record MyPageResponse( String nickname, @@ -12,9 +13,11 @@ public record MyPageResponse( String email, int likedPostCount, int likedMentorCount, - int likedUniversityCount) { - public static MyPageResponse of(SiteUser siteUser, int likedUniversityCount) { + @JsonProperty("likedUniversityCount") + int likedUnivApplyInfoCount) { + + public static MyPageResponse of(SiteUser siteUser, int likedUnivApplyInfoCount) { return new MyPageResponse( siteUser.getNickname(), siteUser.getProfileImageUrl(), @@ -23,7 +26,7 @@ public static MyPageResponse of(SiteUser siteUser, int likedUniversityCount) { siteUser.getEmail(), 0, // TODO: 커뮤니티 기능 생기면 업데이트 필요 0, // TODO: 멘토 기능 생기면 업데이트 필요 - likedUniversityCount + likedUnivApplyInfoCount ); } } diff --git a/src/main/java/com/example/solidconnection/siteuser/repository/LikedUniversityRepository.java b/src/main/java/com/example/solidconnection/siteuser/repository/LikedUniversityRepository.java deleted file mode 100644 index f0d328c0a..000000000 --- a/src/main/java/com/example/solidconnection/siteuser/repository/LikedUniversityRepository.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.example.solidconnection.siteuser.repository; - -import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.domain.LikedUniversity; -import com.example.solidconnection.university.domain.UnivApplyInfo; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.List; -import java.util.Optional; - -public interface LikedUniversityRepository extends JpaRepository { - - List findAllBySiteUser_Id(long siteUserId); - - int countBySiteUser_Id(long siteUserId); - - Optional findBySiteUserAndUnivApplyInfo(SiteUser siteUser, UnivApplyInfo univApplyInfo); -} diff --git a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java index 7e22fd4bf..c524487e8 100644 --- a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java +++ b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java @@ -6,10 +6,10 @@ import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.MyPageResponse; -import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; +import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.university.domain.LikedUniversity; -import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; +import com.example.solidconnection.university.domain.LikedUnivApplyInfo; +import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,7 +31,7 @@ public class MyPageService { public static final DateTimeFormatter NICKNAME_LAST_CHANGE_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); private final SiteUserRepository siteUserRepository; - private final LikedUniversityRepository likedUniversityRepository; + private final LikedUnivApplyInfoRepository likedUnivApplyInfoRepository; private final S3Service s3Service; /* @@ -39,8 +39,8 @@ public class MyPageService { * */ @Transactional(readOnly = true) public MyPageResponse getMyPageInfo(SiteUser siteUser) { - int likedUniversityCount = likedUniversityRepository.countBySiteUser_Id(siteUser.getId()); - return MyPageResponse.of(siteUser, likedUniversityCount); + int likedUnivApplyInfoCount = likedUnivApplyInfoRepository.countBySiteUser_Id(siteUser.getId()); + return MyPageResponse.of(siteUser, likedUnivApplyInfoCount); } /* @@ -94,10 +94,10 @@ private boolean isDefaultProfileImage(String profileImageUrl) { * 관심 대학교 목록을 조회한다. * */ @Transactional(readOnly = true) - public List getWishUniversity(SiteUser siteUser) { - List likedUniversities = likedUniversityRepository.findAllBySiteUser_Id(siteUser.getId()); - return likedUniversities.stream() - .map(likedUniversity -> UniversityInfoForApplyPreviewResponse.from(likedUniversity.getUnivApplyInfo())) + public List getWishUnivApplyInfo(SiteUser siteUser) { + List likedUnivApplyInfos = likedUnivApplyInfoRepository.findAllBySiteUser_Id(siteUser.getId()); + return likedUnivApplyInfos.stream() + .map(likedUnivApplyInfo -> UnivApplyInfoPreviewResponse.from(likedUnivApplyInfo.getUnivApplyInfo())) .toList(); } } diff --git a/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java b/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java new file mode 100644 index 000000000..d39518e5d --- /dev/null +++ b/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java @@ -0,0 +1,104 @@ +package com.example.solidconnection.university.controller; + +import com.example.solidconnection.common.resolver.AuthorizedUser; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.service.MyPageService; +import com.example.solidconnection.university.domain.LanguageTestType; +import com.example.solidconnection.university.dto.IsLikeResponse; +import com.example.solidconnection.university.dto.LikeResultResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoDetailResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoRecommendsResponse; +import com.example.solidconnection.university.service.UnivApplyInfoLikeService; +import com.example.solidconnection.university.service.UnivApplyInfoQueryService; +import com.example.solidconnection.university.service.UnivApplyInfoRecommendService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RequiredArgsConstructor +@RequestMapping("/univ-apply-infos") +@RestController +public class UnivApplyInfoController { + + private final UnivApplyInfoQueryService univApplyInfoQueryService; + private final UnivApplyInfoLikeService univApplyInfoLikeService; + private final UnivApplyInfoRecommendService univApplyInfoRecommendService; + private final MyPageService myPageService; + + @GetMapping("/recommend") + public ResponseEntity getUnivApplyInfoRecommends( + @AuthorizedUser(required = false) SiteUser siteUser + ) { + if (siteUser == null) { + return ResponseEntity.ok(univApplyInfoRecommendService.getGeneralRecommends()); + } else { + return ResponseEntity.ok(univApplyInfoRecommendService.getPersonalRecommends(siteUser)); + } + } + + // todo: return 타입 UnivApplyInfoPreviewResponses 같이 객체로 묶어서 반환하는 것으로 변경 필요 + @GetMapping("/like") + public ResponseEntity> getMyWishUnivApplyInfo( /* todo: wish 가 아니라 liked 로 변경 필요 - 코드 용어 통일 */ + @AuthorizedUser SiteUser siteUser + ) { + List wishUniversities = myPageService.getWishUnivApplyInfo(siteUser); + return ResponseEntity.ok(wishUniversities); + } + + @GetMapping("/{univ-apply-info-id}/like") + public ResponseEntity getIsLiked( + @AuthorizedUser SiteUser siteUser, + @PathVariable("univ-apply-info-id") Long univApplyInfoId + ) { + IsLikeResponse isLiked = univApplyInfoLikeService.getIsLiked(siteUser, univApplyInfoId); + return ResponseEntity.ok(isLiked); + } + + @PostMapping("/{univ-apply-info-id}/like") + public ResponseEntity addWishUnivApplyInfo( + @AuthorizedUser SiteUser siteUser, + @PathVariable("univ-apply-info-id") Long univApplyInfoId + ) { + LikeResultResponse likeResultResponse = univApplyInfoLikeService.likeUnivApplyInfo(siteUser, univApplyInfoId); + return ResponseEntity.ok(likeResultResponse); + } + + @DeleteMapping("/{univ-apply-info-id}/like") + public ResponseEntity cancelWishUnivApplyInfo( + @AuthorizedUser SiteUser siteUser, + @PathVariable("univ-apply-info-id") Long univApplyInfoId + ) { + LikeResultResponse likeResultResponse = univApplyInfoLikeService.cancelLikeUnivApplyInfo(siteUser, univApplyInfoId); + return ResponseEntity.ok(likeResultResponse); + } + + @GetMapping("/{univ-apply-info-id}") + public ResponseEntity getUnivApplyInfoDetails( + @PathVariable("univ-apply-info-id") Long univApplyInfoId + ) { + UnivApplyInfoDetailResponse univApplyInfoDetailResponse = univApplyInfoQueryService.getUnivApplyInfoDetail(univApplyInfoId); + return ResponseEntity.ok(univApplyInfoDetailResponse); + } + + // todo: return타입 UniversityInfoForApplyPreviewResponses로 추후 수정 필요 + @GetMapping("/search") + public ResponseEntity> searchUnivApplyInfo( + @RequestParam(required = false, defaultValue = "") String region, + @RequestParam(required = false, defaultValue = "") List keyword, + @RequestParam(required = false, defaultValue = "") LanguageTestType testType, + @RequestParam(required = false, defaultValue = "") String testScore + ) { + List univApplyInfoPreviewResponse + = univApplyInfoQueryService.searchUnivApplyInfo(region, keyword, testType, testScore).univApplyInfoPreviews(); + return ResponseEntity.ok(univApplyInfoPreviewResponse); + } +} diff --git a/src/main/java/com/example/solidconnection/university/controller/UniversityController.java b/src/main/java/com/example/solidconnection/university/controller/UniversityController.java deleted file mode 100644 index 6345876a9..000000000 --- a/src/main/java/com/example/solidconnection/university/controller/UniversityController.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.example.solidconnection.university.controller; - -import com.example.solidconnection.common.resolver.AuthorizedUser; -import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.service.MyPageService; -import com.example.solidconnection.university.domain.LanguageTestType; -import com.example.solidconnection.university.dto.IsLikeResponse; -import com.example.solidconnection.university.dto.LikeResultResponse; -import com.example.solidconnection.university.dto.UniversityDetailResponse; -import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; -import com.example.solidconnection.university.dto.UniversityRecommendsResponse; -import com.example.solidconnection.university.service.UniversityLikeService; -import com.example.solidconnection.university.service.UniversityQueryService; -import com.example.solidconnection.university.service.UniversityRecommendService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -@RequiredArgsConstructor -@RequestMapping("/universities") -@RestController -public class UniversityController { - - private final UniversityQueryService universityQueryService; - private final UniversityLikeService universityLikeService; - private final UniversityRecommendService universityRecommendService; - private final MyPageService myPageService; - - @GetMapping("/recommend") - public ResponseEntity getUniversityRecommends( - @AuthorizedUser(required = false) SiteUser siteUser - ) { - if (siteUser == null) { - return ResponseEntity.ok(universityRecommendService.getGeneralRecommends()); - } else { - return ResponseEntity.ok(universityRecommendService.getPersonalRecommends(siteUser)); - } - } - - @GetMapping("/like") - public ResponseEntity> getMyWishUniversity( - @AuthorizedUser SiteUser siteUser - ) { - List wishUniversities = myPageService.getWishUniversity(siteUser); - return ResponseEntity.ok(wishUniversities); - } - - @GetMapping("/{universityInfoForApplyId}/like") - public ResponseEntity getIsLiked( - @AuthorizedUser SiteUser siteUser, - @PathVariable Long universityInfoForApplyId - ) { - IsLikeResponse isLiked = universityLikeService.getIsLiked(siteUser, universityInfoForApplyId); - return ResponseEntity.ok(isLiked); - } - - @PostMapping("/{universityInfoForApplyId}/like") - public ResponseEntity addWishUniversity( - @AuthorizedUser SiteUser siteUser, - @PathVariable Long universityInfoForApplyId - ) { - LikeResultResponse likeResultResponse = universityLikeService.likeUniversity(siteUser, universityInfoForApplyId); - return ResponseEntity.ok(likeResultResponse); - } - - @DeleteMapping("/{universityInfoForApplyId}/like") - public ResponseEntity cancelWishUniversity( - @AuthorizedUser SiteUser siteUser, - @PathVariable Long universityInfoForApplyId - ) { - LikeResultResponse likeResultResponse = universityLikeService.cancelLikeUniversity(siteUser, universityInfoForApplyId); - return ResponseEntity.ok(likeResultResponse); - } - - @GetMapping("/{universityInfoForApplyId}") - public ResponseEntity getUniversityDetails( - @PathVariable Long universityInfoForApplyId - ) { - UniversityDetailResponse universityDetailResponse = universityQueryService.getUniversityDetail(universityInfoForApplyId); - return ResponseEntity.ok(universityDetailResponse); - } - - // todo return타입 UniversityInfoForApplyPreviewResponses로 추후 수정 필요 - @GetMapping("/search") - public ResponseEntity> searchUniversity( - @RequestParam(required = false, defaultValue = "") String region, - @RequestParam(required = false, defaultValue = "") List keyword, - @RequestParam(required = false, defaultValue = "") LanguageTestType testType, - @RequestParam(required = false, defaultValue = "") String testScore - ) { - List universityInfoForApplyPreviewResponse - = universityQueryService.searchUniversity(region, keyword, testType, testScore).universityInfoForApplyPreviewResponses(); - return ResponseEntity.ok(universityInfoForApplyPreviewResponse); - } -} diff --git a/src/main/java/com/example/solidconnection/university/domain/LikedUniversity.java b/src/main/java/com/example/solidconnection/university/domain/LikedUnivApplyInfo.java similarity index 89% rename from src/main/java/com/example/solidconnection/university/domain/LikedUniversity.java rename to src/main/java/com/example/solidconnection/university/domain/LikedUnivApplyInfo.java index 036346d5a..08612fa42 100644 --- a/src/main/java/com/example/solidconnection/university/domain/LikedUniversity.java +++ b/src/main/java/com/example/solidconnection/university/domain/LikedUnivApplyInfo.java @@ -19,13 +19,15 @@ @Builder @AllArgsConstructor @NoArgsConstructor -@Table(uniqueConstraints = { +@Table( + name = "liked_university_info_for_apply", + uniqueConstraints = { @UniqueConstraint( name = "uk_liked_university_site_user_id_university_info_for_apply_id", columnNames = {"site_user_id", "university_info_for_apply_id"} ) }) -public class LikedUniversity { +public class LikedUnivApplyInfo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/example/solidconnection/university/dto/UniversityDetailResponse.java b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoDetailResponse.java similarity index 94% rename from src/main/java/com/example/solidconnection/university/dto/UniversityDetailResponse.java rename to src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoDetailResponse.java index 86da18e7e..673254d88 100644 --- a/src/main/java/com/example/solidconnection/university/dto/UniversityDetailResponse.java +++ b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoDetailResponse.java @@ -5,7 +5,7 @@ import java.util.List; -public record UniversityDetailResponse( +public record UnivApplyInfoDetailResponse( long id, String term, String koreanName, @@ -33,10 +33,10 @@ public record UniversityDetailResponse( String accommodationUrl, String englishCourseUrl) { - public static UniversityDetailResponse of( + public static UnivApplyInfoDetailResponse of( University university, UnivApplyInfo univApplyInfo) { - return new UniversityDetailResponse( + return new UnivApplyInfoDetailResponse( univApplyInfo.getId(), univApplyInfo.getTerm(), univApplyInfo.getKoreanName(), diff --git a/src/main/java/com/example/solidconnection/university/dto/UniversityInfoForApplyPreviewResponse.java b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoPreviewResponse.java similarity index 86% rename from src/main/java/com/example/solidconnection/university/dto/UniversityInfoForApplyPreviewResponse.java rename to src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoPreviewResponse.java index bca888d03..4f7f7c3f1 100644 --- a/src/main/java/com/example/solidconnection/university/dto/UniversityInfoForApplyPreviewResponse.java +++ b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoPreviewResponse.java @@ -5,7 +5,7 @@ import java.util.Collections; import java.util.List; -public record UniversityInfoForApplyPreviewResponse( +public record UnivApplyInfoPreviewResponse( long id, String term, String koreanName, @@ -16,14 +16,14 @@ public record UniversityInfoForApplyPreviewResponse( int studentCapacity, List languageRequirements) { - public static UniversityInfoForApplyPreviewResponse from(UnivApplyInfo univApplyInfo) { + public static UnivApplyInfoPreviewResponse from(UnivApplyInfo univApplyInfo) { List languageRequirementResponses = new java.util.ArrayList<>( univApplyInfo.getLanguageRequirements().stream() .map(LanguageRequirementResponse::from) .toList()); Collections.sort(languageRequirementResponses); - return new UniversityInfoForApplyPreviewResponse( + return new UnivApplyInfoPreviewResponse( univApplyInfo.getId(), univApplyInfo.getTerm(), univApplyInfo.getKoreanName(), diff --git a/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoPreviewResponses.java b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoPreviewResponses.java new file mode 100644 index 000000000..2afec8262 --- /dev/null +++ b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoPreviewResponses.java @@ -0,0 +1,9 @@ +package com.example.solidconnection.university.dto; + +import java.util.List; + +public record UnivApplyInfoPreviewResponses( + List univApplyInfoPreviews + // todo: #345 응답 형식으로 바로 배열이 아니라, univApplyInfoPreviews로 감싸 응답한다고 전달 후, 코드 변경 필요 +) { +} diff --git a/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoRecommendsResponse.java b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoRecommendsResponse.java new file mode 100644 index 000000000..759f1d813 --- /dev/null +++ b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoRecommendsResponse.java @@ -0,0 +1,8 @@ +package com.example.solidconnection.university.dto; + +import java.util.List; + +public record UnivApplyInfoRecommendsResponse( + List recommendedUniversities) { + // todo: #345 프론트에 recommendedUnivApplyInfos 로 응답한다고 전달 후, 인자명 변경 필요 +} diff --git a/src/main/java/com/example/solidconnection/university/dto/UniversityInfoForApplyPreviewResponses.java b/src/main/java/com/example/solidconnection/university/dto/UniversityInfoForApplyPreviewResponses.java deleted file mode 100644 index 3c8a00df4..000000000 --- a/src/main/java/com/example/solidconnection/university/dto/UniversityInfoForApplyPreviewResponses.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.example.solidconnection.university.dto; - -import java.util.List; - -public record UniversityInfoForApplyPreviewResponses( - List universityInfoForApplyPreviewResponses -) { -} diff --git a/src/main/java/com/example/solidconnection/university/dto/UniversityRecommendsResponse.java b/src/main/java/com/example/solidconnection/university/dto/UniversityRecommendsResponse.java deleted file mode 100644 index 057061f3e..000000000 --- a/src/main/java/com/example/solidconnection/university/dto/UniversityRecommendsResponse.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.example.solidconnection.university.dto; - -import java.util.List; - -public record UniversityRecommendsResponse( - List recommendedUniversities) { -} diff --git a/src/main/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoice.java b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoice.java similarity index 82% rename from src/main/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoice.java rename to src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoice.java index 658de067c..7d925287a 100644 --- a/src/main/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoice.java +++ b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoice.java @@ -10,8 +10,8 @@ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Constraint(validatedBy = ValidUniversityChoiceValidator.class) -public @interface ValidUniversityChoice { +@Constraint(validatedBy = ValidUnivApplyInfoChoiceValidator.class) +public @interface ValidUnivApplyInfoChoice { String message() default "유효하지 않은 지망 대학 선택입니다."; Class[] groups() default {}; diff --git a/src/main/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoiceValidator.java b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java similarity index 59% rename from src/main/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoiceValidator.java rename to src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java index 63d47b0de..a49d631d1 100644 --- a/src/main/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoiceValidator.java +++ b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java @@ -1,6 +1,6 @@ package com.example.solidconnection.university.dto.validation; -import com.example.solidconnection.application.dto.UniversityChoiceRequest; +import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; @@ -9,14 +9,14 @@ import java.util.Set; import java.util.stream.Stream; -import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_UNIVERSITY_CHOICE; +import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_UNIV_APPLY_INFO_CHOICE; import static com.example.solidconnection.common.exception.ErrorCode.FIRST_CHOICE_REQUIRED; import static com.example.solidconnection.common.exception.ErrorCode.THIRD_CHOICE_REQUIRES_SECOND; -public class ValidUniversityChoiceValidator implements ConstraintValidator { +public class ValidUnivApplyInfoChoiceValidator implements ConstraintValidator { @Override - public boolean isValid(UniversityChoiceRequest request, ConstraintValidatorContext context) { + public boolean isValid(UnivApplyInfoChoiceRequest request, ConstraintValidatorContext context) { context.disableDefaultConstraintViolation(); if (isFirstChoiceNotSelected(request)) { @@ -32,7 +32,7 @@ public boolean isValid(UniversityChoiceRequest request, ConstraintValidatorConte } if (isDuplicate(request)) { - context.buildConstraintViolationWithTemplate(DUPLICATE_UNIVERSITY_CHOICE.getMessage()) + context.buildConstraintViolationWithTemplate(DUPLICATE_UNIV_APPLY_INFO_CHOICE.getMessage()) .addConstraintViolation(); return false; } @@ -40,20 +40,20 @@ public boolean isValid(UniversityChoiceRequest request, ConstraintValidatorConte return true; } - private boolean isFirstChoiceNotSelected(UniversityChoiceRequest request) { - return request.firstChoiceUniversityId() == null; + private boolean isFirstChoiceNotSelected(UnivApplyInfoChoiceRequest request) { + return request.firstChoiceUnivApplyInfoId() == null; } - private boolean isThirdChoiceWithoutSecond(UniversityChoiceRequest request) { - return request.thirdChoiceUniversityId() != null && request.secondChoiceUniversityId() == null; + private boolean isThirdChoiceWithoutSecond(UnivApplyInfoChoiceRequest request) { + return request.thirdChoiceUnivApplyInfoId() != null && request.secondChoiceUnivApplyInfoId() == null; } - private boolean isDuplicate(UniversityChoiceRequest request) { + private boolean isDuplicate(UnivApplyInfoChoiceRequest request) { Set uniqueIds = new HashSet<>(); return Stream.of( - request.firstChoiceUniversityId(), - request.secondChoiceUniversityId(), - request.thirdChoiceUniversityId() + request.firstChoiceUnivApplyInfoId(), + request.secondChoiceUnivApplyInfoId(), + request.thirdChoiceUnivApplyInfoId() ) .filter(Objects::nonNull) .anyMatch(id -> !uniqueIds.add(id)); diff --git a/src/main/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepository.java b/src/main/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepository.java new file mode 100644 index 000000000..bca7a7eef --- /dev/null +++ b/src/main/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepository.java @@ -0,0 +1,18 @@ +package com.example.solidconnection.university.repository; + +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.university.domain.LikedUnivApplyInfo; +import com.example.solidconnection.university.domain.UnivApplyInfo; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface LikedUnivApplyInfoRepository extends JpaRepository { + + List findAllBySiteUser_Id(long siteUserId); + + int countBySiteUser_Id(long siteUserId); + + Optional findBySiteUserAndUnivApplyInfo(SiteUser siteUser, UnivApplyInfo univApplyInfo); +} diff --git a/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java b/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java new file mode 100644 index 000000000..389f66531 --- /dev/null +++ b/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java @@ -0,0 +1,62 @@ +package com.example.solidconnection.university.repository; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.university.domain.UnivApplyInfo; +import com.example.solidconnection.university.repository.custom.UnivApplyInfoFilterRepository; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND; + +@Repository +public interface UnivApplyInfoRepository extends JpaRepository, UnivApplyInfoFilterRepository { + + @Query(""" + SELECT uai + FROM UnivApplyInfo uai + JOIN University u ON uai.university = u + WHERE (u.country.code IN ( + SELECT c.code + FROM InterestedCountry ic + JOIN ic.country c + WHERE ic.siteUser = :siteUser + ) + OR u.region.code IN ( + SELECT r.code + FROM InterestedRegion ir + JOIN ir.region r + WHERE ir.siteUser = :siteUser + )) + AND uai.term = :term + """) + List findAllBySiteUsersInterestedCountryOrRegionAndTerm(@Param("siteUser") SiteUser siteUser, @Param("term") String term); + + @Query(value = """ + SELECT * + FROM university_info_for_apply + WHERE term = :term + ORDER BY RAND() + LIMIT :limitNum + """, nativeQuery = true) + List findRandomByTerm(@Param("term") String term, @Param("limitNum") int limitNum); + + default UnivApplyInfo getUnivApplyInfoById(Long id) { + return findById(id) + .orElseThrow(() -> new CustomException(UNIV_APPLY_INFO_NOT_FOUND)); + } + + @Query(""" + SELECT DISTINCT uai + FROM UnivApplyInfo uai + JOIN FETCH uai.university u + JOIN FETCH u.country c + JOIN FETCH u.region r + WHERE uai.id IN :ids + """) + List findAllByIds(@Param("ids") List ids); +} diff --git a/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java b/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java deleted file mode 100644 index 1ef541334..000000000 --- a/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.example.solidconnection.university.repository; - -import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.domain.UnivApplyInfo; -import com.example.solidconnection.university.domain.University; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Optional; - -import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND; - -@Repository -public interface UniversityInfoForApplyRepository extends JpaRepository { - - Optional findByIdAndTerm(Long id, String term); - - Optional findFirstByKoreanNameAndTerm(String koreanName, String term); - - @Query(""" - SELECT uifa - FROM UnivApplyInfo uifa - WHERE uifa.university IN :universities - AND uifa.term = :term - """) - List findByUniversitiesAndTerm(@Param("universities") List universities, @Param("term") String term); - - @Query(""" - SELECT uifa - FROM UnivApplyInfo uifa - JOIN University u ON uifa.university = u - WHERE (u.country.code IN ( - SELECT c.code - FROM InterestedCountry ic - JOIN ic.country c - WHERE ic.siteUser = :siteUser - ) - OR u.region.code IN ( - SELECT r.code - FROM InterestedRegion ir - JOIN ir.region r - WHERE ir.siteUser = :siteUser - )) - AND uifa.term = :term - """) - List findUniversityInfoForAppliesBySiteUsersInterestedCountryOrRegionAndTerm(@Param("siteUser") SiteUser siteUser, @Param("term") String term); - - @Query(value = """ - SELECT * - FROM university_info_for_apply - WHERE term = :term - ORDER BY RAND() - LIMIT :limitNum - """, nativeQuery = true) - List findRandomByTerm(@Param("term") String term, @Param("limitNum") int limitNum); - - default UnivApplyInfo getUniversityInfoForApplyById(Long id) { - return findById(id) - .orElseThrow(() -> new CustomException(UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND)); - } - - @Query(""" - SELECT DISTINCT uifa - FROM UnivApplyInfo uifa - JOIN FETCH uifa.university u - JOIN FETCH u.country c - JOIN FETCH u.region r - WHERE uifa.id IN :ids - """) - List findAllByUniversityIds(@Param("ids") List ids); - - @Query(""" - SELECT DISTINCT uifa - FROM UnivApplyInfo uifa - JOIN FETCH uifa.university u - JOIN FETCH u.country c - JOIN FETCH u.region r - WHERE u.id IN :universityIds - """) - List findByUniversityIdsWithUniversityAndLocation(@Param("universityIds") List universityIds); -} diff --git a/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java b/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java index b19285469..b5a9adb59 100644 --- a/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java @@ -2,7 +2,6 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.university.domain.University; -import com.example.solidconnection.university.repository.custom.UniversityFilterRepository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -13,7 +12,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_NOT_FOUND; @Repository -public interface UniversityRepository extends JpaRepository, UniversityFilterRepository { +public interface UniversityRepository extends JpaRepository { @Query("SELECT u FROM University u WHERE u.country.code IN :countryCodes OR u.region.code IN :regionCodes") List findByCountryCodeInOrRegionCodeIn(@Param("countryCodes") List countryCodes, @Param("regionCodes") List regionCodes); diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java similarity index 59% rename from src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java rename to src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java index f3c9767b9..588bfbf9f 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java @@ -5,10 +5,10 @@ import java.util.List; -public interface UniversityFilterRepository { +public interface UnivApplyInfoFilterRepository { - List findByRegionCodeAndKeywords(String regionCode, List keywords); + List findAllByRegionCodeAndKeywords(String regionCode, List keywords); - List findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( + List findAllByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( String regionCode, List keywords, LanguageTestType testType, String testScore, String term); } diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java similarity index 89% rename from src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java rename to src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java index ee8416b9c..50c9bcf67 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java @@ -18,17 +18,17 @@ import java.util.List; @Repository -public class UniversityFilterRepositoryImpl implements UniversityFilterRepository { +public class UnivApplyInfoFilterRepositoryImpl implements UnivApplyInfoFilterRepository { private final JPAQueryFactory queryFactory; @Autowired - public UniversityFilterRepositoryImpl(EntityManager em) { + public UnivApplyInfoFilterRepositoryImpl(EntityManager em) { this.queryFactory = new JPAQueryFactory(em); } @Override - public List findByRegionCodeAndKeywords(String regionCode, List keywords) { + public List findAllByRegionCodeAndKeywords(String regionCode, List keywords) { QUnivApplyInfo univApplyInfo = QUnivApplyInfo.univApplyInfo; QUniversity university = QUniversity.university; QCountry country = QCountry.country; @@ -74,7 +74,7 @@ private BooleanExpression createKeywordCondition(StringPath namePath, List findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( + public List findAllByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( String regionCode, List keywords, LanguageTestType testType, String testScore, String term) { QUniversity university = QUniversity.university; QCountry country = QCountry.country; @@ -94,7 +94,7 @@ public List findByRegionCodeAndKeywordsAndLanguageTestTypeAndTest if (testScore == null || testScore.isEmpty()) { if (testType != null) { return filteredUnivApplyInfo.stream() - .filter(uifa -> uifa.getLanguageRequirements().stream() + .filter(uai -> uai.getLanguageRequirements().stream() .anyMatch(lr -> lr.getLanguageTestType().equals(testType))) .toList(); } @@ -102,7 +102,7 @@ public List findByRegionCodeAndKeywordsAndLanguageTestTypeAndTest } return filteredUnivApplyInfo.stream() - .filter(uifa -> compareMyTestScoreToMinPassScore(uifa, testType, testScore) >= 0) + .filter(uai -> compareMyTestScoreToMinPassScore(uai, testType, testScore) >= 0) .toList(); } diff --git a/src/main/java/com/example/solidconnection/university/service/GeneralUniversityRecommendService.java b/src/main/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendService.java similarity index 64% rename from src/main/java/com/example/solidconnection/university/service/GeneralUniversityRecommendService.java rename to src/main/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendService.java index af2921c9a..1835f4ed0 100644 --- a/src/main/java/com/example/solidconnection/university/service/GeneralUniversityRecommendService.java +++ b/src/main/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendService.java @@ -1,7 +1,7 @@ package com.example.solidconnection.university.service; import com.example.solidconnection.university.domain.UnivApplyInfo; -import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; +import com.example.solidconnection.university.repository.UnivApplyInfoRepository; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -11,25 +11,25 @@ import java.util.List; -import static com.example.solidconnection.university.service.UniversityRecommendService.RECOMMEND_UNIVERSITY_NUM; +import static com.example.solidconnection.university.service.UnivApplyInfoRecommendService.RECOMMEND_UNIV_APPLY_INFO_NUM; @Service @RequiredArgsConstructor -public class GeneralUniversityRecommendService { +public class GeneralUnivApplyInfoRecommendService { /* * 해당 시기에 열리는 대학교들 중 랜덤으로 선택해서 목록을 구성한다. * */ - private final UniversityInfoForApplyRepository universityInfoForApplyRepository; + private final UnivApplyInfoRepository univApplyInfoRepository; @Getter - private List recommendUniversities; + private List generalRecommends; @Value("${university.term}") public String term; @EventListener(ApplicationReadyEvent.class) public void init() { - recommendUniversities = universityInfoForApplyRepository.findRandomByTerm(term, RECOMMEND_UNIVERSITY_NUM); + generalRecommends = univApplyInfoRepository.findRandomByTerm(term, RECOMMEND_UNIV_APPLY_INFO_NUM); } } diff --git a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoLikeService.java b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoLikeService.java new file mode 100644 index 000000000..0d3fc4d96 --- /dev/null +++ b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoLikeService.java @@ -0,0 +1,79 @@ +package com.example.solidconnection.university.service; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; +import com.example.solidconnection.university.domain.LikedUnivApplyInfo; +import com.example.solidconnection.university.domain.UnivApplyInfo; +import com.example.solidconnection.university.dto.IsLikeResponse; +import com.example.solidconnection.university.dto.LikeResultResponse; +import com.example.solidconnection.university.repository.UnivApplyInfoRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_UNIV_APPLY_INFO; +import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_UNIV_APPLY_INFO; + +@RequiredArgsConstructor +@Service +public class UnivApplyInfoLikeService { + + public static final String LIKE_SUCCESS_MESSAGE = "LIKE_SUCCESS"; + public static final String LIKE_CANCELED_MESSAGE = "LIKE_CANCELED"; + + private final UnivApplyInfoRepository univApplyInfoRepository; + private final LikedUnivApplyInfoRepository likedUnivApplyInfoRepository; + + @Value("${university.term}") + public String term; + + /* + * 대학교를 '좋아요' 한다. + * */ + @Transactional + public LikeResultResponse likeUnivApplyInfo(SiteUser siteUser, Long univApplyInfoId) { + UnivApplyInfo univApplyInfo = univApplyInfoRepository.getUnivApplyInfoById(univApplyInfoId); + + Optional optionalLikedUnivApplyInfo = likedUnivApplyInfoRepository.findBySiteUserAndUnivApplyInfo(siteUser, univApplyInfo); + if (optionalLikedUnivApplyInfo.isPresent()) { + throw new CustomException(ALREADY_LIKED_UNIV_APPLY_INFO); + } + + LikedUnivApplyInfo likedUnivApplyInfo = LikedUnivApplyInfo.builder() + .univApplyInfo(univApplyInfo) + .siteUser(siteUser) + .build(); + likedUnivApplyInfoRepository.save(likedUnivApplyInfo); + return new LikeResultResponse(LIKE_SUCCESS_MESSAGE); + } + + /* + * 대학교 '좋아요'를 취소한다. + * */ + @Transactional + public LikeResultResponse cancelLikeUnivApplyInfo(SiteUser siteUser, long univApplyInfoId) { + UnivApplyInfo univApplyInfo = univApplyInfoRepository.getUnivApplyInfoById(univApplyInfoId); + + Optional optionalLikedUnivApplyInfo = likedUnivApplyInfoRepository.findBySiteUserAndUnivApplyInfo(siteUser, univApplyInfo); + if (optionalLikedUnivApplyInfo.isEmpty()) { + throw new CustomException(NOT_LIKED_UNIV_APPLY_INFO); + } + + likedUnivApplyInfoRepository.delete(optionalLikedUnivApplyInfo.get()); + return new LikeResultResponse(LIKE_CANCELED_MESSAGE); + } + + /* + * '좋아요'한 대학교인지 확인한다. + * */ + @Transactional(readOnly = true) + public IsLikeResponse getIsLiked(SiteUser siteUser, Long univApplyInfoId) { + UnivApplyInfo univApplyInfo = univApplyInfoRepository.getUnivApplyInfoById(univApplyInfoId); + boolean isLike = likedUnivApplyInfoRepository.findBySiteUserAndUnivApplyInfo(siteUser, univApplyInfo).isPresent(); + return new IsLikeResponse(isLike); + } +} diff --git a/src/main/java/com/example/solidconnection/university/service/UniversityQueryService.java b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java similarity index 54% rename from src/main/java/com/example/solidconnection/university/service/UniversityQueryService.java rename to src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java index 5c4415396..86717c382 100644 --- a/src/main/java/com/example/solidconnection/university/service/UniversityQueryService.java +++ b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java @@ -4,11 +4,11 @@ import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.domain.UnivApplyInfo; -import com.example.solidconnection.university.dto.UniversityDetailResponse; -import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; -import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponses; -import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; -import com.example.solidconnection.university.repository.custom.UniversityFilterRepositoryImpl; +import com.example.solidconnection.university.dto.UnivApplyInfoDetailResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponses; +import com.example.solidconnection.university.repository.UnivApplyInfoRepository; +import com.example.solidconnection.university.repository.custom.UnivApplyInfoFilterRepositoryImpl; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -18,10 +18,10 @@ @RequiredArgsConstructor @Service -public class UniversityQueryService { +public class UnivApplyInfoQueryService { - private final UniversityInfoForApplyRepository universityInfoForApplyRepository; - private final UniversityFilterRepositoryImpl universityFilterRepository; + private final UnivApplyInfoRepository univApplyInfoRepository; + private final UnivApplyInfoFilterRepositoryImpl universityFilterRepository; // todo: 구현체 숨기고 univApplyInfoRepository만 사용하도록 @Value("${university.term}") public String term; @@ -31,13 +31,13 @@ public class UniversityQueryService { * - 대학교(University) 정보와 대학 지원 정보(UniversityInfoForApply) 정보를 조합하여 반환한다. * */ @Transactional(readOnly = true) - @ThunderingHerdCaching(key = "university:{0}", cacheManager = "customCacheManager", ttlSec = 86400) - public UniversityDetailResponse getUniversityDetail(Long universityInfoForApplyId) { + @ThunderingHerdCaching(key = "univApplyInfo:{0}", cacheManager = "customCacheManager", ttlSec = 86400) + public UnivApplyInfoDetailResponse getUnivApplyInfoDetail(Long univApplyInfoId) { UnivApplyInfo univApplyInfo - = universityInfoForApplyRepository.getUniversityInfoForApplyById(universityInfoForApplyId); + = univApplyInfoRepository.getUnivApplyInfoById(univApplyInfoId); University university = univApplyInfo.getUniversity(); - return UniversityDetailResponse.of(university, univApplyInfo); + return UnivApplyInfoDetailResponse.of(university, univApplyInfo); } /* @@ -48,14 +48,14 @@ public UniversityDetailResponse getUniversityDetail(Long universityInfoForApplyI * - 언어 시험 점수는 합격 최소 점수보다 높은 것이 조건이다. * */ @Transactional(readOnly = true) - @ThunderingHerdCaching(key = "university:{0}:{1}:{2}:{3}", cacheManager = "customCacheManager", ttlSec = 86400) - public UniversityInfoForApplyPreviewResponses searchUniversity( + @ThunderingHerdCaching(key = "univApplyInfo:{0}:{1}:{2}:{3}", cacheManager = "customCacheManager", ttlSec = 86400) + public UnivApplyInfoPreviewResponses searchUnivApplyInfo( String regionCode, List keywords, LanguageTestType testType, String testScore) { - return new UniversityInfoForApplyPreviewResponses(universityFilterRepository - .findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm(regionCode, keywords, testType, testScore, term) + return new UnivApplyInfoPreviewResponses(universityFilterRepository + .findAllByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm(regionCode, keywords, testType, testScore, term) .stream() - .map(UniversityInfoForApplyPreviewResponse::from) + .map(UnivApplyInfoPreviewResponse::from) .toList()); } } diff --git a/src/main/java/com/example/solidconnection/university/service/UniversityRecommendService.java b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendService.java similarity index 54% rename from src/main/java/com/example/solidconnection/university/service/UniversityRecommendService.java rename to src/main/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendService.java index 94447316d..aa6f34f79 100644 --- a/src/main/java/com/example/solidconnection/university/service/UniversityRecommendService.java +++ b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendService.java @@ -3,9 +3,9 @@ import com.example.solidconnection.cache.annotation.ThunderingHerdCaching; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.university.domain.UnivApplyInfo; -import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; -import com.example.solidconnection.university.dto.UniversityRecommendsResponse; -import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; +import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoRecommendsResponse; +import com.example.solidconnection.university.repository.UnivApplyInfoRepository; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -17,12 +17,12 @@ @RequiredArgsConstructor @Service -public class UniversityRecommendService { +public class UnivApplyInfoRecommendService { - public static final int RECOMMEND_UNIVERSITY_NUM = 6; + public static final int RECOMMEND_UNIV_APPLY_INFO_NUM = 6; - private final UniversityInfoForApplyRepository universityInfoForApplyRepository; - private final GeneralUniversityRecommendService generalUniversityRecommendService; + private final UnivApplyInfoRepository univApplyInfoRepository; + private final GeneralUnivApplyInfoRecommendService generalUnivApplyInfoRecommendService; @Value("${university.term}") private String term; @@ -34,29 +34,29 @@ public class UniversityRecommendService { * - 맞춤 추천 대학교의 수가 6개보다 적다면, 공통 추천 대학교 후보에서 이번 term 에 열리는 학교들을 부족한 수 만큼 불러온다. * */ @Transactional(readOnly = true) - public UniversityRecommendsResponse getPersonalRecommends(SiteUser siteUser) { + public UnivApplyInfoRecommendsResponse getPersonalRecommends(SiteUser siteUser) { // 맞춤 추천 대학교를 불러온다. - List personalRecommends = universityInfoForApplyRepository - .findUniversityInfoForAppliesBySiteUsersInterestedCountryOrRegionAndTerm(siteUser, term); - List trimmedRecommendUniversities - = personalRecommends.subList(0, Math.min(RECOMMEND_UNIVERSITY_NUM, personalRecommends.size())); - Collections.shuffle(trimmedRecommendUniversities); + List personalRecommends = univApplyInfoRepository + .findAllBySiteUsersInterestedCountryOrRegionAndTerm(siteUser, term); + List trimmedRecommends + = personalRecommends.subList(0, Math.min(RECOMMEND_UNIV_APPLY_INFO_NUM, personalRecommends.size())); + Collections.shuffle(trimmedRecommends); // 맞춤 추천 대학교의 수가 6개보다 적다면, 일반 추천 대학교를 부족한 수 만큼 불러온다. - if (trimmedRecommendUniversities.size() < RECOMMEND_UNIVERSITY_NUM) { - trimmedRecommendUniversities.addAll(getGeneralRecommendsExcludingSelected(trimmedRecommendUniversities)); + if (trimmedRecommends.size() < RECOMMEND_UNIV_APPLY_INFO_NUM) { + trimmedRecommends.addAll(getGeneralRecommendsExcludingSelected(trimmedRecommends)); } - return new UniversityRecommendsResponse(trimmedRecommendUniversities.stream() - .map(UniversityInfoForApplyPreviewResponse::from) + return new UnivApplyInfoRecommendsResponse(trimmedRecommends.stream() + .map(UnivApplyInfoPreviewResponse::from) .toList()); } private List getGeneralRecommendsExcludingSelected(List alreadyPicked) { - List generalRecommend = new ArrayList<>(generalUniversityRecommendService.getRecommendUniversities()); + List generalRecommend = new ArrayList<>(generalUnivApplyInfoRecommendService.getGeneralRecommends()); generalRecommend.removeAll(alreadyPicked); Collections.shuffle(generalRecommend); - return generalRecommend.subList(0, RECOMMEND_UNIVERSITY_NUM - alreadyPicked.size()); + return generalRecommend.subList(0, RECOMMEND_UNIV_APPLY_INFO_NUM - alreadyPicked.size()); } /* @@ -64,10 +64,10 @@ private List getGeneralRecommendsExcludingSelected(List generalRecommends = new ArrayList<>(generalUniversityRecommendService.getRecommendUniversities()); - return new UniversityRecommendsResponse(generalRecommends.stream() - .map(UniversityInfoForApplyPreviewResponse::from) + public UnivApplyInfoRecommendsResponse getGeneralRecommends() { + List generalRecommends = new ArrayList<>(generalUnivApplyInfoRecommendService.getGeneralRecommends()); + return new UnivApplyInfoRecommendsResponse(generalRecommends.stream() + .map(UnivApplyInfoPreviewResponse::from) .toList()); } } diff --git a/src/main/java/com/example/solidconnection/university/service/UniversityLikeService.java b/src/main/java/com/example/solidconnection/university/service/UniversityLikeService.java deleted file mode 100644 index 7544ed7a8..000000000 --- a/src/main/java/com/example/solidconnection/university/service/UniversityLikeService.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.example.solidconnection.university.service; - -import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; -import com.example.solidconnection.university.domain.LikedUniversity; -import com.example.solidconnection.university.domain.UnivApplyInfo; -import com.example.solidconnection.university.dto.IsLikeResponse; -import com.example.solidconnection.university.dto.LikeResultResponse; -import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.Optional; - -import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_UNIVERSITY; -import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_UNIVERSITY; - -@RequiredArgsConstructor -@Service -public class UniversityLikeService { - - public static final String LIKE_SUCCESS_MESSAGE = "LIKE_SUCCESS"; - public static final String LIKE_CANCELED_MESSAGE = "LIKE_CANCELED"; - - private final UniversityInfoForApplyRepository universityInfoForApplyRepository; - private final LikedUniversityRepository likedUniversityRepository; - - @Value("${university.term}") - public String term; - - /* - * 대학교를 '좋아요' 한다. - * */ - @Transactional - public LikeResultResponse likeUniversity(SiteUser siteUser, Long universityInfoForApplyId) { - UnivApplyInfo univApplyInfo = universityInfoForApplyRepository.getUniversityInfoForApplyById(universityInfoForApplyId); - - Optional optionalLikedUniversity = likedUniversityRepository.findBySiteUserAndUnivApplyInfo(siteUser, univApplyInfo); - if (optionalLikedUniversity.isPresent()) { - throw new CustomException(ALREADY_LIKED_UNIVERSITY); - } - - LikedUniversity likedUniversity = LikedUniversity.builder() - .univApplyInfo(univApplyInfo) - .siteUser(siteUser) - .build(); - likedUniversityRepository.save(likedUniversity); - return new LikeResultResponse(LIKE_SUCCESS_MESSAGE); - } - - /* - * 대학교 '좋아요'를 취소한다. - * */ - @Transactional - public LikeResultResponse cancelLikeUniversity(SiteUser siteUser, long universityInfoForApplyId) throws CustomException { - UnivApplyInfo univApplyInfo = universityInfoForApplyRepository.getUniversityInfoForApplyById(universityInfoForApplyId); - - Optional optionalLikedUniversity = likedUniversityRepository.findBySiteUserAndUnivApplyInfo(siteUser, univApplyInfo); - if (optionalLikedUniversity.isEmpty()) { - throw new CustomException(NOT_LIKED_UNIVERSITY); - } - - likedUniversityRepository.delete(optionalLikedUniversity.get()); - return new LikeResultResponse(LIKE_CANCELED_MESSAGE); - } - - /* - * '좋아요'한 대학교인지 확인한다. - * */ - @Transactional(readOnly = true) - public IsLikeResponse getIsLiked(SiteUser siteUser, Long universityInfoForApplyId) { - UnivApplyInfo univApplyInfo = universityInfoForApplyRepository.getUniversityInfoForApplyById(universityInfoForApplyId); - boolean isLike = likedUniversityRepository.findBySiteUserAndUnivApplyInfo(siteUser, univApplyInfo).isPresent(); - return new IsLikeResponse(isLike); - } -} diff --git a/src/main/resources/db/migration/V17__rename_like_university.sql b/src/main/resources/db/migration/V17__rename_like_university.sql new file mode 100644 index 000000000..1d244157a --- /dev/null +++ b/src/main/resources/db/migration/V17__rename_like_university.sql @@ -0,0 +1 @@ +ALTER TABLE liked_university RENAME liked_university_info_for_apply; diff --git a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java index 43854ca80..9cfc7a805 100644 --- a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java +++ b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixture.java @@ -4,7 +4,6 @@ import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.domain.UnivApplyInfo; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; @@ -20,9 +19,9 @@ public class ApplicationFixture { String term, Gpa gpa, LanguageTest languageTest, - Long firstChoiceUniversityApplyInfoId, - Long secondChoiceUniversityApplyInfoId, - Long thirdChoiceUniversityApplyInfoId + Long firstChoiceUnivApplyInfoId, + Long secondChoiceUnivApplyInfoId, + Long thirdChoiceUnivApplyInfoId ) { return applicationFixtureBuilder.application() .siteUser(siteUser) @@ -30,9 +29,9 @@ public class ApplicationFixture { .languageTest(languageTest) .nicknameForApply(nicknameForApply) .term(term) - .firstChoiceUniversityApplyInfoId(firstChoiceUniversityApplyInfoId) - .secondChoiceUniversityApplyInfoId(secondChoiceUniversityApplyInfoId) - .thirdChoiceUniversityApplyInfoId(thirdChoiceUniversityApplyInfoId) + .firstChoiceUnivApplyInfoId(firstChoiceUnivApplyInfoId) + .secondChoiceUnivApplyInfoId(secondChoiceUnivApplyInfoId) + .thirdChoiceUnivApplyInfoId(thirdChoiceUnivApplyInfoId) .create(); } } diff --git a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java index 2f1ae7bbe..d860504e4 100644 --- a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java @@ -17,9 +17,9 @@ public class ApplicationFixtureBuilder { private Gpa gpa; private LanguageTest languageTest; - private Long firstChoiceUniversityApplyInfoId; - private Long secondChoiceUniversityApplyInfoId; - private Long thirdChoiceUniversityApplyInfoId; + private Long firstChoiceUnivApplyInfoId; + private Long secondChoiceUnivApplyInfoId; + private Long thirdChoiceUnivApplyInfoId; private SiteUser siteUser; private String nicknameForApply; private String term; @@ -38,18 +38,18 @@ public ApplicationFixtureBuilder languageTest(LanguageTest languageTest) { return this; } - public ApplicationFixtureBuilder firstChoiceUniversityApplyInfoId(Long firstChoiceUniversityApplyInfoId) { - this.firstChoiceUniversityApplyInfoId = firstChoiceUniversityApplyInfoId; + public ApplicationFixtureBuilder firstChoiceUnivApplyInfoId(Long firstChoiceUnivApplyInfoId) { + this.firstChoiceUnivApplyInfoId = firstChoiceUnivApplyInfoId; return this; } - public ApplicationFixtureBuilder secondChoiceUniversityApplyInfoId(Long secondChoiceUniversityApplyInfoId) { - this.secondChoiceUniversityApplyInfoId = secondChoiceUniversityApplyInfoId; + public ApplicationFixtureBuilder secondChoiceUnivApplyInfoId(Long secondChoiceUnivApplyInfoId) { + this.secondChoiceUnivApplyInfoId = secondChoiceUnivApplyInfoId; return this; } - public ApplicationFixtureBuilder thirdChoiceUniversityApplyInfoId(Long thirdChoiceUniversityApplyInfoId) { - this.thirdChoiceUniversityApplyInfoId = thirdChoiceUniversityApplyInfoId; + public ApplicationFixtureBuilder thirdChoiceUnivApplyInfoId(Long thirdChoiceUnivApplyInfoId) { + this.thirdChoiceUnivApplyInfoId = thirdChoiceUnivApplyInfoId; return this; } @@ -74,9 +74,9 @@ public Application create() { gpa, languageTest, term, - firstChoiceUniversityApplyInfoId, - secondChoiceUniversityApplyInfoId, - thirdChoiceUniversityApplyInfoId, + firstChoiceUnivApplyInfoId, + secondChoiceUnivApplyInfoId, + thirdChoiceUnivApplyInfoId, nicknameForApply ); application.setVerifyStatus(VerifyStatus.APPROVED); diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java index 2f93b08f0..488e85e63 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java @@ -4,7 +4,7 @@ import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.application.dto.ApplicantResponse; import com.example.solidconnection.application.dto.ApplicationsResponse; -import com.example.solidconnection.application.dto.UniversityApplicantsResponse; +import com.example.solidconnection.application.dto.ApplicantsResponse; import com.example.solidconnection.application.fixture.ApplicationFixture; import com.example.solidconnection.application.repository.ApplicationRepository; import com.example.solidconnection.location.region.fixture.RegionFixture; @@ -140,11 +140,11 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsAll(List.of( - UniversityApplicantsResponse.of(괌대학_A_지원_정보, + ApplicantsResponse.of(괌대학_A_지원_정보, List.of(application1), user1), - UniversityApplicantsResponse.of(괌대학_B_지원_정보, + ApplicantsResponse.of(괌대학_B_지원_정보, List.of(application2), user1), - UniversityApplicantsResponse.of(서던덴마크대학교_지원_정보, + ApplicantsResponse.of(서던덴마크대학교_지원_정보, List.of(application3), user1) )); } @@ -192,9 +192,9 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsExactlyInAnyOrder( - UniversityApplicantsResponse.of(괌대학_A_지원_정보, + ApplicantsResponse.of(괌대학_A_지원_정보, List.of(application1), user1), - UniversityApplicantsResponse.of(괌대학_B_지원_정보, + ApplicantsResponse.of(괌대학_B_지원_정보, List.of(application2), user1) ); } @@ -242,9 +242,9 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsExactlyInAnyOrder( - UniversityApplicantsResponse.of(괌대학_A_지원_정보, + ApplicantsResponse.of(괌대학_A_지원_정보, List.of(application1), user1), - UniversityApplicantsResponse.of(괌대학_B_지원_정보, + ApplicantsResponse.of(괌대학_B_지원_정보, List.of(application2), user1) ); } @@ -272,7 +272,7 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).doesNotContainAnyElementsOf(List.of( - UniversityApplicantsResponse.of(괌대학_A_지원_정보, + ApplicantsResponse.of(괌대학_A_지원_정보, List.of(application), user1) )); } @@ -358,7 +358,7 @@ class 경쟁자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsExactlyInAnyOrder( - UniversityApplicantsResponse.of(괌대학_A_지원_정보, + ApplicantsResponse.of(괌대학_A_지원_정보, List.of(application1, application2), user1) ); } diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java index 4d23073f7..b9389739f 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java @@ -4,7 +4,7 @@ import com.example.solidconnection.application.domain.VerifyStatus; import com.example.solidconnection.application.dto.ApplicationSubmissionResponse; import com.example.solidconnection.application.dto.ApplyRequest; -import com.example.solidconnection.application.dto.UniversityChoiceRequest; +import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; import com.example.solidconnection.application.repository.ApplicationRepository; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.GpaScore; @@ -73,12 +73,12 @@ void setUp() { // given GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user); LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user); - UniversityChoiceRequest universityChoiceRequest = new UniversityChoiceRequest( + UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest = new UnivApplyInfoChoiceRequest( 괌대학_A_지원_정보.getId(), 괌대학_B_지원_정보.getId(), 서던덴마크대학교_지원_정보.getId() ); - ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), universityChoiceRequest); + ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), univApplyInfoChoiceRequest); // when ApplicationSubmissionResponse response = applicationSubmissionService.apply(user, request); @@ -106,12 +106,12 @@ void setUp() { // given GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.PENDING, user); LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user); - UniversityChoiceRequest universityChoiceRequest = new UniversityChoiceRequest( + UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest = new UnivApplyInfoChoiceRequest( 괌대학_A_지원_정보.getId(), null, null ); - ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), universityChoiceRequest); + ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), univApplyInfoChoiceRequest); // when & then assertThatCode(() -> @@ -126,12 +126,12 @@ void setUp() { // given GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user); LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.PENDING, user); - UniversityChoiceRequest universityChoiceRequest = new UniversityChoiceRequest( + UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest = new UnivApplyInfoChoiceRequest( 괌대학_A_지원_정보.getId(), null, null ); - ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), universityChoiceRequest); + ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), univApplyInfoChoiceRequest); // when & then assertThatCode(() -> @@ -146,12 +146,12 @@ void setUp() { // given GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user); LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user); - UniversityChoiceRequest universityChoiceRequest = new UniversityChoiceRequest( + UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest = new UnivApplyInfoChoiceRequest( 괌대학_A_지원_정보.getId(), null, null ); - ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), universityChoiceRequest); + ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), univApplyInfoChoiceRequest); for (int i = 0; i < APPLICATION_UPDATE_COUNT_LIMIT; i++) { applicationSubmissionService.apply(user, request); diff --git a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java index 59e0d6455..62732f1c1 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java @@ -10,11 +10,11 @@ import com.example.solidconnection.siteuser.dto.MyPageResponse; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.siteuser.fixture.SiteUserFixtureBuilder; -import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; +import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.university.domain.LikedUniversity; -import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; +import com.example.solidconnection.university.domain.LikedUnivApplyInfo; +import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -54,7 +54,7 @@ class MyPageServiceTest { private SiteUserRepository siteUserRepository; @Autowired - private LikedUniversityRepository likedUniversityRepository; + private LikedUnivApplyInfoRepository likedUnivApplyInfoRepository; @Autowired private SiteUserFixture siteUserFixture; @@ -75,7 +75,7 @@ void setUp() { @Test void 마이페이지_정보를_조회한다() { // given - int likedUniversityCount = createLikedUniversities(user); + int likedUnivApplyInfoCount = createLikedUnivApplyInfos(user); // when MyPageResponse response = myPageService.getMyPageInfo(user); @@ -87,20 +87,20 @@ void setUp() { () -> assertThat(response.role()).isEqualTo(user.getRole()), () -> assertThat(response.email()).isEqualTo(user.getEmail()), () -> assertThat(response.likedPostCount()).isEqualTo(user.getPostLikeList().size()), - () -> assertThat(response.likedUniversityCount()).isEqualTo(likedUniversityCount) + () -> assertThat(response.likedUnivApplyInfoCount()).isEqualTo(likedUnivApplyInfoCount) ); } @Test - void 관심_대학교_목록을_조회한다() { + void 관심_대학_지원_정보_목록을_조회한다() { // given - int likedUniversityCount = createLikedUniversities(user); + int likedUnivApplyInfo = createLikedUnivApplyInfos(user); // when - List response = myPageService.getWishUniversity(user); + List response = myPageService.getWishUnivApplyInfo(user); // then - assertThat(response).hasSize(likedUniversityCount); + assertThat(response).hasSize(likedUnivApplyInfo); } @Nested @@ -192,15 +192,15 @@ void setUp() { } } - private int createLikedUniversities(SiteUser testUser) { - LikedUniversity likedUniversity1 = new LikedUniversity(null, univApplyInfoFixture.괌대학_A_지원_정보(), testUser); - LikedUniversity likedUniversity2 = new LikedUniversity(null, univApplyInfoFixture.메이지대학_지원_정보(), testUser); - LikedUniversity likedUniversity3 = new LikedUniversity(null, univApplyInfoFixture.코펜하겐IT대학_지원_정보(), testUser); + private int createLikedUnivApplyInfos(SiteUser testUser) { + LikedUnivApplyInfo likedUnivApplyInfo1 = new LikedUnivApplyInfo(null, univApplyInfoFixture.괌대학_A_지원_정보(), testUser); + LikedUnivApplyInfo likedUnivApplyInfo2 = new LikedUnivApplyInfo(null, univApplyInfoFixture.메이지대학_지원_정보(), testUser); + LikedUnivApplyInfo likedUnivApplyInfo3 = new LikedUnivApplyInfo(null, univApplyInfoFixture.코펜하겐IT대학_지원_정보(), testUser); - likedUniversityRepository.save(likedUniversity1); - likedUniversityRepository.save(likedUniversity2); - likedUniversityRepository.save(likedUniversity3); - return likedUniversityRepository.countBySiteUser_Id(testUser.getId()); + likedUnivApplyInfoRepository.save(likedUnivApplyInfo1); + likedUnivApplyInfoRepository.save(likedUnivApplyInfo2); + likedUnivApplyInfoRepository.save(likedUnivApplyInfo3); + return likedUnivApplyInfoRepository.countBySiteUser_Id(testUser.getId()); } private MockMultipartFile createValidImageFile() { diff --git a/src/test/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoiceValidatorTest.java b/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java similarity index 64% rename from src/test/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoiceValidatorTest.java rename to src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java index e08f49e6a..84a41d801 100644 --- a/src/test/java/com/example/solidconnection/university/dto/validation/ValidUniversityChoiceValidatorTest.java +++ b/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.university.dto.validation; -import com.example.solidconnection.application.dto.UniversityChoiceRequest; +import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; import jakarta.validation.ConstraintViolation; import jakarta.validation.Validation; import jakarta.validation.Validator; @@ -11,13 +11,13 @@ import java.util.Set; -import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_UNIVERSITY_CHOICE; +import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_UNIV_APPLY_INFO_CHOICE; import static com.example.solidconnection.common.exception.ErrorCode.FIRST_CHOICE_REQUIRED; import static com.example.solidconnection.common.exception.ErrorCode.THIRD_CHOICE_REQUIRES_SECOND; import static org.assertj.core.api.Assertions.assertThat; @DisplayName("대학 선택 유효성 검사 테스트") -class ValidUniversityChoiceValidatorTest { +class ValidUnivApplyInfoChoiceValidatorTest { private static final String MESSAGE = "message"; @@ -32,10 +32,10 @@ void setUp() { @Test void 정상적인_지망_선택은_유효하다() { // given - UniversityChoiceRequest request = new UniversityChoiceRequest(1L, 2L, 3L); + UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(1L, 2L, 3L); // when - Set> violations = validator.validate(request); + Set> violations = validator.validate(request); // then assertThat(violations).isEmpty(); @@ -44,10 +44,10 @@ void setUp() { @Test void 첫_번째_지망만_선택하는_것은_유효하다() { // given - UniversityChoiceRequest request = new UniversityChoiceRequest(1L, null, null); + UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(1L, null, null); // when - Set> violations = validator.validate(request); + Set> violations = validator.validate(request); // then assertThat(violations).isEmpty(); @@ -56,10 +56,10 @@ void setUp() { @Test void 두_번째_지망_없이_세_번째_지망을_선택하면_예외_응답을_반환한다() { // given - UniversityChoiceRequest request = new UniversityChoiceRequest(1L, null, 3L); + UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(1L, null, 3L); // when - Set> violations = validator.validate(request); + Set> violations = validator.validate(request); // then assertThat(violations) @@ -70,10 +70,10 @@ void setUp() { @Test void 첫_번째_지망을_선택하지_않으면_예외_응답을_반환한다() { // given - UniversityChoiceRequest request = new UniversityChoiceRequest(null, 2L, 3L); + UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(null, 2L, 3L); // when - Set> violations = validator.validate(request); + Set> violations = validator.validate(request); // then assertThat(violations) @@ -85,15 +85,15 @@ void setUp() { @Test void 대학을_중복_선택하면_예외_응답을_반환한다() { // given - UniversityChoiceRequest request = new UniversityChoiceRequest(1L, 1L, 2L); + UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(1L, 1L, 2L); // when - Set> violations = validator.validate(request); + Set> violations = validator.validate(request); // then assertThat(violations) .isNotEmpty() .extracting(MESSAGE) - .contains(DUPLICATE_UNIVERSITY_CHOICE.getMessage()); + .contains(DUPLICATE_UNIV_APPLY_INFO_CHOICE.getMessage()); } } diff --git a/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixture.java b/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixture.java index 0c7bb48bd..b70515b89 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixture.java +++ b/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixture.java @@ -12,43 +12,43 @@ public class LanguageRequirementFixture { private final LanguageRequirementFixtureBuilder languageRequirementFixtureBuilder; - public LanguageRequirement 토플_80(UnivApplyInfo universityInfo) { + public LanguageRequirement 토플_80(UnivApplyInfo univApplyInfo) { return languageRequirementFixtureBuilder .languageTestType(LanguageTestType.TOEFL_IBT) .minScore("80") - .universityInfoForApply(universityInfo) + .univApplyInfo(univApplyInfo) .create(); } - public LanguageRequirement 토플_70(UnivApplyInfo universityInfo) { + public LanguageRequirement 토플_70(UnivApplyInfo univApplyInfo) { return languageRequirementFixtureBuilder .languageTestType(LanguageTestType.TOEFL_IBT) .minScore("70") - .universityInfoForApply(universityInfo) + .univApplyInfo(univApplyInfo) .create(); } - public LanguageRequirement 토익_800(UnivApplyInfo universityInfo) { + public LanguageRequirement 토익_800(UnivApplyInfo univApplyInfo) { return languageRequirementFixtureBuilder .languageTestType(LanguageTestType.TOEIC) .minScore("800") - .universityInfoForApply(universityInfo) + .univApplyInfo(univApplyInfo) .create(); } - public LanguageRequirement 토익_900(UnivApplyInfo universityInfo) { + public LanguageRequirement 토익_900(UnivApplyInfo univApplyInfo) { return languageRequirementFixtureBuilder .languageTestType(LanguageTestType.TOEIC) .minScore("900") - .universityInfoForApply(universityInfo) + .univApplyInfo(univApplyInfo) .create(); } - public LanguageRequirement JLPT_N2(UnivApplyInfo universityInfo) { + public LanguageRequirement JLPT_N2(UnivApplyInfo univApplyInfo) { return languageRequirementFixtureBuilder .languageTestType(LanguageTestType.JLPT) .minScore("N2") - .universityInfoForApply(universityInfo) + .univApplyInfo(univApplyInfo) .create(); } } diff --git a/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixtureBuilder.java b/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixtureBuilder.java index 81e856f4b..01f03e716 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/university/fixture/LanguageRequirementFixtureBuilder.java @@ -27,7 +27,7 @@ public LanguageRequirementFixtureBuilder minScore(String minScore) { return this; } - public LanguageRequirementFixtureBuilder universityInfoForApply(UnivApplyInfo univApplyInfo) { + public LanguageRequirementFixtureBuilder univApplyInfo(UnivApplyInfo univApplyInfo) { this.univApplyInfo = univApplyInfo; return this; } diff --git a/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixture.java b/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixture.java index d394b6206..12c5efa07 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixture.java +++ b/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixture.java @@ -16,7 +16,7 @@ public class UnivApplyInfoFixture { public String term; public UnivApplyInfo 괌대학_A_지원_정보() { - return univApplyInfoFixtureBuilder.universityInfoForApply() + return univApplyInfoFixtureBuilder.univApplyInfo() .term(term) .koreanName("괌대학(A형)") .university(universityFixture.괌_대학()) @@ -24,7 +24,7 @@ public class UnivApplyInfoFixture { } public UnivApplyInfo 괌대학_B_지원_정보() { - return univApplyInfoFixtureBuilder.universityInfoForApply() + return univApplyInfoFixtureBuilder.univApplyInfo() .term(term) .koreanName("괌대학(B형)") .university(universityFixture.괌_대학()) @@ -32,7 +32,7 @@ public class UnivApplyInfoFixture { } public UnivApplyInfo 네바다주립대학_라스베이거스_지원_정보() { - return univApplyInfoFixtureBuilder.universityInfoForApply() + return univApplyInfoFixtureBuilder.univApplyInfo() .term(term) .koreanName("네바다주립대학 라스베이거스(B형)") .university(universityFixture.네바다주립_대학_라스베이거스()) @@ -40,7 +40,7 @@ public class UnivApplyInfoFixture { } public UnivApplyInfo 메모리얼대학_세인트존스_A_지원_정보() { - return univApplyInfoFixtureBuilder.universityInfoForApply() + return univApplyInfoFixtureBuilder.univApplyInfo() .term(term) .koreanName("메모리얼 대학 세인트존스(A형)") .university(universityFixture.메모리얼_대학_세인트존스()) @@ -48,7 +48,7 @@ public class UnivApplyInfoFixture { } public UnivApplyInfo 서던덴마크대학교_지원_정보() { - return univApplyInfoFixtureBuilder.universityInfoForApply() + return univApplyInfoFixtureBuilder.univApplyInfo() .term(term) .koreanName("서던덴마크대학교") .university(universityFixture.서던덴마크_대학()) @@ -56,7 +56,7 @@ public class UnivApplyInfoFixture { } public UnivApplyInfo 코펜하겐IT대학_지원_정보() { - return univApplyInfoFixtureBuilder.universityInfoForApply() + return univApplyInfoFixtureBuilder.univApplyInfo() .term(term) .koreanName("코펜하겐 IT대학") .university(universityFixture.코펜하겐IT_대학()) @@ -64,7 +64,7 @@ public class UnivApplyInfoFixture { } public UnivApplyInfo 그라츠대학_지원_정보() { - return univApplyInfoFixtureBuilder.universityInfoForApply() + return univApplyInfoFixtureBuilder.univApplyInfo() .term(term) .koreanName("그라츠 대학") .university(universityFixture.그라츠_대학()) @@ -72,7 +72,7 @@ public class UnivApplyInfoFixture { } public UnivApplyInfo 그라츠공과대학_지원_정보() { - return univApplyInfoFixtureBuilder.universityInfoForApply() + return univApplyInfoFixtureBuilder.univApplyInfo() .term(term) .koreanName("그라츠공과대학") .university(universityFixture.그라츠공과_대학()) @@ -80,7 +80,7 @@ public class UnivApplyInfoFixture { } public UnivApplyInfo 린츠_카톨릭대학_지원_정보() { - return univApplyInfoFixtureBuilder.universityInfoForApply() + return univApplyInfoFixtureBuilder.univApplyInfo() .term(term) .koreanName("린츠 카톨릭 대학교") .university(universityFixture.린츠_카톨릭_대학()) @@ -88,7 +88,7 @@ public class UnivApplyInfoFixture { } public UnivApplyInfo 메이지대학_지원_정보() { - return univApplyInfoFixtureBuilder.universityInfoForApply() + return univApplyInfoFixtureBuilder.univApplyInfo() .term(term) .koreanName("메이지대학") .university(universityFixture.메이지_대학()) diff --git a/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixtureBuilder.java b/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixtureBuilder.java index 048389d49..0989beaca 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixtureBuilder.java @@ -2,7 +2,7 @@ import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.domain.UnivApplyInfo; -import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; +import com.example.solidconnection.university.repository.UnivApplyInfoRepository; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; @@ -15,14 +15,14 @@ @RequiredArgsConstructor public class UnivApplyInfoFixtureBuilder { - private final UniversityInfoForApplyRepository universityInfoForApplyRepository; + private final UnivApplyInfoRepository univApplyInfoRepository; private String term; private String koreanName; private University university; - public UnivApplyInfoFixtureBuilder universityInfoForApply() { - return new UnivApplyInfoFixtureBuilder(universityInfoForApplyRepository); + public UnivApplyInfoFixtureBuilder univApplyInfo() { + return new UnivApplyInfoFixtureBuilder(univApplyInfoRepository); } public UnivApplyInfoFixtureBuilder term(String term) { @@ -48,6 +48,6 @@ public UnivApplyInfo create() { "detailsForAccommodation", "detailsForEnglishCourse", "details", new HashSet<>(), university ); - return universityInfoForApplyRepository.save(univApplyInfo); + return univApplyInfoRepository.save(univApplyInfo); } } diff --git a/src/test/java/com/example/solidconnection/university/repository/UniversityLikeRepositoryTest.java b/src/test/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepositoryTest.java similarity index 54% rename from src/test/java/com/example/solidconnection/university/repository/UniversityLikeRepositoryTest.java rename to src/test/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepositoryTest.java index 1d34597be..1d3094e31 100644 --- a/src/test/java/com/example/solidconnection/university/repository/UniversityLikeRepositoryTest.java +++ b/src/test/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepositoryTest.java @@ -2,9 +2,8 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; -import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.university.domain.LikedUniversity; +import com.example.solidconnection.university.domain.LikedUnivApplyInfo; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import org.junit.jupiter.api.DisplayName; @@ -17,10 +16,10 @@ @TestContainerSpringBootTest @DisplayName("대학교 좋아요 레파지토리 테스트") -public class UniversityLikeRepositoryTest { +public class LikedUnivApplyInfoRepositoryTest { @Autowired - private LikedUniversityRepository likedUniversityRepository; + private LikedUnivApplyInfoRepository likedUnivApplyInfoRepository; @Autowired private SiteUserFixture siteUserFixture; @@ -35,15 +34,15 @@ class 사용자와_좋아요한_대학은_복합_유니크_제약조건을_갖 void 같은_사용자가_같은_대학에_중복으로_좋아요하면_예외_응답을_반환한다() { // given SiteUser user = siteUserFixture.사용자(); - UnivApplyInfo university = univApplyInfoFixture.괌대학_A_지원_정보(); + UnivApplyInfo univApplyInfo = univApplyInfoFixture.괌대학_A_지원_정보(); - LikedUniversity firstLike = createLikedUniversity(user, university); - likedUniversityRepository.save(firstLike); + LikedUnivApplyInfo firstLike = createLikedUnivApplyInfo(user, univApplyInfo); + likedUnivApplyInfoRepository.save(firstLike); - LikedUniversity secondLike = createLikedUniversity(user, university); + LikedUnivApplyInfo secondLike = createLikedUnivApplyInfo(user, univApplyInfo); // when & then - assertThatCode(() -> likedUniversityRepository.save(secondLike)) + assertThatCode(() -> likedUnivApplyInfoRepository.save(secondLike)) .isInstanceOf(DataIntegrityViolationException.class); } @@ -52,36 +51,36 @@ class 사용자와_좋아요한_대학은_복합_유니크_제약조건을_갖 // given SiteUser user1 = siteUserFixture.사용자(1, "user1"); SiteUser user2 = siteUserFixture.사용자(2, "user2"); - UnivApplyInfo university = univApplyInfoFixture.괌대학_A_지원_정보(); + UnivApplyInfo univApplyInfo = univApplyInfoFixture.괌대학_A_지원_정보(); - LikedUniversity firstLike = createLikedUniversity(user1, university); - likedUniversityRepository.save(firstLike); + LikedUnivApplyInfo firstLike = createLikedUnivApplyInfo(user1, univApplyInfo); + likedUnivApplyInfoRepository.save(firstLike); - LikedUniversity secondLike = createLikedUniversity(user2, university); + LikedUnivApplyInfo secondLike = createLikedUnivApplyInfo(user2, univApplyInfo); // when & then - assertThatCode(() -> likedUniversityRepository.save(secondLike)).doesNotThrowAnyException(); + assertThatCode(() -> likedUnivApplyInfoRepository.save(secondLike)).doesNotThrowAnyException(); } @Test void 같은_사용자가_다른_대학에_좋아요하면_정상_저장된다() { // given SiteUser user = siteUserFixture.사용자(); - UnivApplyInfo university1 = univApplyInfoFixture.괌대학_A_지원_정보(); - UnivApplyInfo university2 = univApplyInfoFixture.메이지대학_지원_정보(); + UnivApplyInfo univApplyInfo1 = univApplyInfoFixture.괌대학_A_지원_정보(); + UnivApplyInfo univApplyInfo2 = univApplyInfoFixture.메이지대학_지원_정보(); - LikedUniversity firstLike = createLikedUniversity(user, university1); - likedUniversityRepository.save(firstLike); + LikedUnivApplyInfo firstLike = createLikedUnivApplyInfo(user, univApplyInfo1); + likedUnivApplyInfoRepository.save(firstLike); - LikedUniversity secondLike = createLikedUniversity(user, university2); + LikedUnivApplyInfo secondLike = createLikedUnivApplyInfo(user, univApplyInfo2); // when & then - assertThatCode(() -> likedUniversityRepository.save(secondLike)).doesNotThrowAnyException(); + assertThatCode(() -> likedUnivApplyInfoRepository.save(secondLike)).doesNotThrowAnyException(); } } - private LikedUniversity createLikedUniversity(SiteUser siteUser, UnivApplyInfo univApplyInfo) { - return LikedUniversity.builder() + private LikedUnivApplyInfo createLikedUnivApplyInfo(SiteUser siteUser, UnivApplyInfo univApplyInfo) { + return LikedUnivApplyInfo.builder() .siteUser(siteUser) .univApplyInfo(univApplyInfo) .build(); diff --git a/src/test/java/com/example/solidconnection/university/service/GeneralUniversityRecommendServiceTest.java b/src/test/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendServiceTest.java similarity index 77% rename from src/test/java/com/example/solidconnection/university/service/GeneralUniversityRecommendServiceTest.java rename to src/test/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendServiceTest.java index 5152dfbb2..e2dcdd255 100644 --- a/src/test/java/com/example/solidconnection/university/service/GeneralUniversityRecommendServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendServiceTest.java @@ -11,16 +11,16 @@ import java.util.List; -import static com.example.solidconnection.university.service.UniversityRecommendService.RECOMMEND_UNIVERSITY_NUM; +import static com.example.solidconnection.university.service.UnivApplyInfoRecommendService.RECOMMEND_UNIV_APPLY_INFO_NUM; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; @TestContainerSpringBootTest -@DisplayName("공통 추천 대학 서비스 테스트") -class GeneralUniversityRecommendServiceTest { +@DisplayName("대학 지원 정보 공통 추천 서비스 테스트") +class GeneralUnivApplyInfoRecommendServiceTest { @Autowired - private GeneralUniversityRecommendService generalUniversityRecommendService; + private GeneralUnivApplyInfoRecommendService generalUnivApplyInfoRecommendService; @Autowired private UnivApplyInfoFixture univApplyInfoFixture; @@ -40,20 +40,20 @@ void setUp() { univApplyInfoFixture.그라츠공과대학_지원_정보(); univApplyInfoFixture.린츠_카톨릭대학_지원_정보(); univApplyInfoFixture.메이지대학_지원_정보(); - generalUniversityRecommendService.init(); + generalUnivApplyInfoRecommendService.init(); } @Test - void 모집_시기의_대학들_중에서_랜덤하게_N개를_추천_목록으로_구성한다() { + void 모집_시기의_대학_지원_정보_중에서_랜덤하게_N개를_추천_목록으로_구성한다() { // given - List universities = generalUniversityRecommendService.getRecommendUniversities(); + List universities = generalUnivApplyInfoRecommendService.getGeneralRecommends(); // when & then assertAll( () -> assertThat(universities) .extracting("term") .allMatch(term::equals), - () -> assertThat(universities).hasSize(RECOMMEND_UNIVERSITY_NUM) + () -> assertThat(universities).hasSize(RECOMMEND_UNIV_APPLY_INFO_NUM) ); } } diff --git a/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoLikeServiceTest.java similarity index 55% rename from src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java rename to src/test/java/com/example/solidconnection/university/service/UnivApplyInfoLikeServiceTest.java index 729c74c3b..548ddd9f5 100644 --- a/src/test/java/com/example/solidconnection/university/service/UniversityLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoLikeServiceTest.java @@ -3,9 +3,9 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; -import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; +import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.university.domain.LikedUniversity; +import com.example.solidconnection.university.domain.LikedUnivApplyInfo; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.dto.IsLikeResponse; import com.example.solidconnection.university.dto.LikeResultResponse; @@ -16,24 +16,24 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_UNIVERSITY; -import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_UNIVERSITY; -import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND; -import static com.example.solidconnection.university.service.UniversityLikeService.LIKE_CANCELED_MESSAGE; -import static com.example.solidconnection.university.service.UniversityLikeService.LIKE_SUCCESS_MESSAGE; +import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_UNIV_APPLY_INFO; +import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_UNIV_APPLY_INFO; +import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND; +import static com.example.solidconnection.university.service.UnivApplyInfoLikeService.LIKE_CANCELED_MESSAGE; +import static com.example.solidconnection.university.service.UnivApplyInfoLikeService.LIKE_SUCCESS_MESSAGE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; @TestContainerSpringBootTest -@DisplayName("대학교 좋아요 서비스 테스트") -class UniversityLikeServiceTest { +@DisplayName("대학 지원 정보 좋아요 서비스 테스트") +class UnivApplyInfoLikeServiceTest { @Autowired - private UniversityLikeService universityLikeService; + private UnivApplyInfoLikeService univApplyInfoLikeService; @Autowired - private LikedUniversityRepository likedUniversityRepository; + private LikedUnivApplyInfoRepository likedUnivApplyInfoRepository; @Autowired private SiteUserFixture siteUserFixture; @@ -51,36 +51,36 @@ void setUp() { } @Nested - class 대학_좋아요를_등록한다 { + class 대학_지원_정보_좋아요를_등록한다 { @Test void 성공적으로_좋아요를_등록한다() { // when - LikeResultResponse response = universityLikeService.likeUniversity(user, 괌대학_A_지원_정보.getId()); + LikeResultResponse response = univApplyInfoLikeService.likeUnivApplyInfo(user, 괌대학_A_지원_정보.getId()); // then assertAll( () -> assertThat(response.result()).isEqualTo(LIKE_SUCCESS_MESSAGE), - () -> assertThat(likedUniversityRepository.findBySiteUserAndUnivApplyInfo( + () -> assertThat(likedUnivApplyInfoRepository.findBySiteUserAndUnivApplyInfo( user, 괌대학_A_지원_정보 )).isPresent() ); } @Test - void 이미_좋아요한_대학이면_예외_응답을_반환한다() { + void 이미_좋아요했으면_예외_응답을_반환한다() { // given saveLikedUniversity(user, 괌대학_A_지원_정보); // when & then - assertThatCode(() -> universityLikeService.likeUniversity(user, 괌대학_A_지원_정보.getId())) + assertThatCode(() -> univApplyInfoLikeService.likeUnivApplyInfo(user, 괌대학_A_지원_정보.getId())) .isInstanceOf(CustomException.class) - .hasMessage(ALREADY_LIKED_UNIVERSITY.getMessage()); + .hasMessage(ALREADY_LIKED_UNIV_APPLY_INFO.getMessage()); } } @Nested - class 대학_좋아요를_취소한다 { + class 대학_지원_정보_좋아요를_취소한다 { @Test void 성공적으로_좋아요를_취소한다() { @@ -88,74 +88,74 @@ class 대학_좋아요를_취소한다 { saveLikedUniversity(user, 괌대학_A_지원_정보); // when - LikeResultResponse response = universityLikeService.cancelLikeUniversity(user, 괌대학_A_지원_정보.getId()); + LikeResultResponse response = univApplyInfoLikeService.cancelLikeUnivApplyInfo(user, 괌대학_A_지원_정보.getId()); // then assertAll( () -> assertThat(response.result()).isEqualTo(LIKE_CANCELED_MESSAGE), - () -> assertThat(likedUniversityRepository.findBySiteUserAndUnivApplyInfo( + () -> assertThat(likedUnivApplyInfoRepository.findBySiteUserAndUnivApplyInfo( user, 괌대학_A_지원_정보 )).isEmpty() ); } @Test - void 좋아요하지_않은_대학이면_예외_응답을_반환한다() { + void 좋아요하지_않았으면_예외_응답을_반환한다() { // when & then - assertThatCode(() -> universityLikeService.cancelLikeUniversity(user, 괌대학_A_지원_정보.getId())) + assertThatCode(() -> univApplyInfoLikeService.cancelLikeUnivApplyInfo(user, 괌대학_A_지원_정보.getId())) .isInstanceOf(CustomException.class) - .hasMessage(NOT_LIKED_UNIVERSITY.getMessage()); + .hasMessage(NOT_LIKED_UNIV_APPLY_INFO.getMessage()); } } @Test - void 존재하지_않는_대학_좋아요_시도하면_예외_응답을_반환한다() { + void 존재하지_않는_지원_정보에_좋아요_시도하면_예외_응답을_반환한다() { // given - Long invalidUniversityId = 9999L; + Long invalidUnivApplyInfoId = 9999L; // when & then - assertThatCode(() -> universityLikeService.likeUniversity(user, invalidUniversityId)) + assertThatCode(() -> univApplyInfoLikeService.likeUnivApplyInfo(user, invalidUnivApplyInfoId)) .isInstanceOf(CustomException.class) - .hasMessage(UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND.getMessage()); + .hasMessage(UNIV_APPLY_INFO_NOT_FOUND.getMessage()); } @Test - void 좋아요한_대학인지_확인한다() { + void 좋아요한_대학_지원_정보인지_확인한다() { // given saveLikedUniversity(user, 괌대학_A_지원_정보); // when - IsLikeResponse response = universityLikeService.getIsLiked(user, 괌대학_A_지원_정보.getId()); + IsLikeResponse response = univApplyInfoLikeService.getIsLiked(user, 괌대학_A_지원_정보.getId()); // then assertThat(response.isLike()).isTrue(); } @Test - void 좋아요하지_않은_대학인지_확인한다() { + void 좋아요하지_않은_대학_지원_정보인지_확인한다() { // when - IsLikeResponse response = universityLikeService.getIsLiked(user, 괌대학_A_지원_정보.getId()); + IsLikeResponse response = univApplyInfoLikeService.getIsLiked(user, 괌대학_A_지원_정보.getId()); // then assertThat(response.isLike()).isFalse(); } @Test - void 존재하지_않는_대학의_좋아요_여부를_조회하면_예외_응답을_반환한다() { + void 존재하지_않는_대학_지원_정보의_좋아요_여부를_조회하면_예외_응답을_반환한다() { // given - Long invalidUniversityId = 9999L; + Long invalidUnivApplyInfoId = 9999L; // when & then - assertThatCode(() -> universityLikeService.getIsLiked(user, invalidUniversityId)) + assertThatCode(() -> univApplyInfoLikeService.getIsLiked(user, invalidUnivApplyInfoId)) .isInstanceOf(CustomException.class) - .hasMessage(UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND.getMessage()); + .hasMessage(UNIV_APPLY_INFO_NOT_FOUND.getMessage()); } private void saveLikedUniversity(SiteUser siteUser, UnivApplyInfo univApplyInfo) { - LikedUniversity likedUniversity = LikedUniversity.builder() + LikedUnivApplyInfo likedUnivApplyInfo = LikedUnivApplyInfo.builder() .siteUser(siteUser) .univApplyInfo(univApplyInfo) .build(); - likedUniversityRepository.save(likedUniversity); + likedUnivApplyInfoRepository.save(likedUnivApplyInfo); } } diff --git a/src/test/java/com/example/solidconnection/university/service/UniversityQueryServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java similarity index 54% rename from src/test/java/com/example/solidconnection/university/service/UniversityQueryServiceTest.java rename to src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java index fbad2d174..1c58316dc 100644 --- a/src/test/java/com/example/solidconnection/university/service/UniversityQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java @@ -4,13 +4,13 @@ import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.UnivApplyInfo; -import com.example.solidconnection.university.dto.UniversityDetailResponse; -import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; -import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponses; +import com.example.solidconnection.university.dto.UnivApplyInfoDetailResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponses; import com.example.solidconnection.university.fixture.LanguageRequirementFixture; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; -import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; -import com.example.solidconnection.university.repository.custom.UniversityFilterRepository; +import com.example.solidconnection.university.repository.UnivApplyInfoRepository; +import com.example.solidconnection.university.repository.custom.UnivApplyInfoFilterRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -18,24 +18,24 @@ import java.util.List; -import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.times; @TestContainerSpringBootTest -@DisplayName("대학교 조회 서비스 테스트") -class UniversityQueryServiceTest { +@DisplayName("대학 지원 정보 조회 서비스 테스트") +class UnivApplyInfoQueryServiceTest { @Autowired - private UniversityQueryService universityQueryService; + private UnivApplyInfoQueryService univApplyInfoQueryService; @SpyBean - private UniversityFilterRepository universityFilterRepository; + private UnivApplyInfoFilterRepository univApplyInfoFilterRepository; @SpyBean - private UniversityInfoForApplyRepository universityInfoForApplyRepository; + private UnivApplyInfoRepository univApplyInfoRepository; @Autowired private UnivApplyInfoFixture univApplyInfoFixture; @@ -44,46 +44,46 @@ class UniversityQueryServiceTest { private LanguageRequirementFixture languageRequirementFixture; @Test - void 대학_상세정보를_정상_조회한다() { + void 대학_지원_정보를_상세_조회한다() { // given UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); // when - UniversityDetailResponse response = universityQueryService.getUniversityDetail(괌대학_A_지원_정보.getId()); + UnivApplyInfoDetailResponse response = univApplyInfoQueryService.getUnivApplyInfoDetail(괌대학_A_지원_정보.getId()); // then assertThat(response.id()).isEqualTo(괌대학_A_지원_정보.getId()); } @Test - void 대학_상세정보_조회시_캐시가_적용된다() { + void 대학_지원_정보_상세_조회시_캐시가_적용된다() { // given UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); // when - UniversityDetailResponse firstResponse = universityQueryService.getUniversityDetail(괌대학_A_지원_정보.getId()); - UniversityDetailResponse secondResponse = universityQueryService.getUniversityDetail(괌대학_A_지원_정보.getId()); + UnivApplyInfoDetailResponse firstResponse = univApplyInfoQueryService.getUnivApplyInfoDetail(괌대학_A_지원_정보.getId()); + UnivApplyInfoDetailResponse secondResponse = univApplyInfoQueryService.getUnivApplyInfoDetail(괌대학_A_지원_정보.getId()); // then assertThat(firstResponse).isEqualTo(secondResponse); - then(universityInfoForApplyRepository).should(times(1)).getUniversityInfoForApplyById(괌대학_A_지원_정보.getId()); + then(univApplyInfoRepository).should(times(1)).getUnivApplyInfoById(괌대학_A_지원_정보.getId()); } @Test - void 존재하지_않는_대학_상세정보를_조회하면_예외_응답을_반환한다() { + void 존재하지_않는_대학_지원_정보를_조회하면_예외_응답을_반환한다() { // given - Long invalidUniversityInfoForApplyId = 9999L; + Long invalidUnivApplyInfoId = 9999L; // when & then assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> universityQueryService.getUniversityDetail(invalidUniversityInfoForApplyId)) + .isThrownBy(() -> univApplyInfoQueryService.getUnivApplyInfoDetail(invalidUnivApplyInfoId)) .havingRootCause() .isInstanceOf(CustomException.class) - .withMessage(UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND.getMessage()); + .withMessage(UNIV_APPLY_INFO_NOT_FOUND.getMessage()); } @Test - void 전체_대학을_조회한다() { + void 전체_대학_지원_정보를_조회한다() { // given UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); UnivApplyInfo 괌대학_B_지원_정보 = univApplyInfoFixture.괌대학_B_지원_정보(); @@ -93,23 +93,23 @@ class UniversityQueryServiceTest { UnivApplyInfo 메이지대학_지원_정보 = univApplyInfoFixture.메이지대학_지원_정보(); // when - UniversityInfoForApplyPreviewResponses response = universityQueryService.searchUniversity( + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfo( null, List.of(), null, null); // then - assertThat(response.universityInfoForApplyPreviewResponses()) + assertThat(response.univApplyInfoPreviews()) .containsExactlyInAnyOrder( - UniversityInfoForApplyPreviewResponse.from(괌대학_A_지원_정보), - UniversityInfoForApplyPreviewResponse.from(괌대학_B_지원_정보), - UniversityInfoForApplyPreviewResponse.from(네바다주립대학_라스베이거스_지원_정보), - UniversityInfoForApplyPreviewResponse.from(서던덴마크대학교_지원_정보), - UniversityInfoForApplyPreviewResponse.from(그라츠대학_지원_정보), - UniversityInfoForApplyPreviewResponse.from(메이지대학_지원_정보) + UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보), + UnivApplyInfoPreviewResponse.from(괌대학_B_지원_정보), + UnivApplyInfoPreviewResponse.from(네바다주립대학_라스베이거스_지원_정보), + UnivApplyInfoPreviewResponse.from(서던덴마크대학교_지원_정보), + UnivApplyInfoPreviewResponse.from(그라츠대학_지원_정보), + UnivApplyInfoPreviewResponse.from(메이지대학_지원_정보) ); } @Test - void 대학_조회시_캐시가_적용된다() { + void 대학_지원_정보_조회시_캐시가_적용된다() { // given univApplyInfoFixture.괌대학_A_지원_정보(); String regionCode = "AMERICAS"; @@ -119,20 +119,20 @@ class UniversityQueryServiceTest { String term = "2024-1"; // when - UniversityInfoForApplyPreviewResponses firstResponse = - universityQueryService.searchUniversity(regionCode, keywords, testType, testScore); - UniversityInfoForApplyPreviewResponses secondResponse = - universityQueryService.searchUniversity(regionCode, keywords, testType, testScore); + UnivApplyInfoPreviewResponses firstResponse = + univApplyInfoQueryService.searchUnivApplyInfo(regionCode, keywords, testType, testScore); + UnivApplyInfoPreviewResponses secondResponse = + univApplyInfoQueryService.searchUnivApplyInfo(regionCode, keywords, testType, testScore); // then assertThat(firstResponse).isEqualTo(secondResponse); - then(universityFilterRepository).should(times(1)) - .findByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( + then(univApplyInfoFilterRepository).should(times(1)) + .findAllByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( regionCode, keywords, testType, testScore, term); } @Test - void 지역으로_대학을_필터링한다() { + void 지역으로_대학_지원_정보를_필터링한다() { // given UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); univApplyInfoFixture.코펜하겐IT대학_지원_정보(); @@ -140,35 +140,35 @@ class UniversityQueryServiceTest { univApplyInfoFixture.메이지대학_지원_정보(); // when - UniversityInfoForApplyPreviewResponses response = universityQueryService.searchUniversity( + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfo( "AMERICAS", List.of(), null, null); // then - assertThat(response.universityInfoForApplyPreviewResponses()) - .containsExactlyInAnyOrder(UniversityInfoForApplyPreviewResponse.from(괌대학_A_지원_정보)); + assertThat(response.univApplyInfoPreviews()) + .containsExactlyInAnyOrder(UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보)); } @Test - void 키워드로_대학을_필터링한다() { + void 키워드로_대학_지원_정보를_필터링한다() { // given univApplyInfoFixture.괌대학_A_지원_정보(); UnivApplyInfo 그라츠대학_지원_정보 = univApplyInfoFixture.그라츠대학_지원_정보(); UnivApplyInfo 메이지대학_지원_정보 = univApplyInfoFixture.메이지대학_지원_정보(); // when - UniversityInfoForApplyPreviewResponses response = universityQueryService.searchUniversity( + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfo( null, List.of("라", "일본"), null, null); // then - assertThat(response.universityInfoForApplyPreviewResponses()) + assertThat(response.univApplyInfoPreviews()) .containsExactlyInAnyOrder( - UniversityInfoForApplyPreviewResponse.from(그라츠대학_지원_정보), - UniversityInfoForApplyPreviewResponse.from(메이지대학_지원_정보) + UnivApplyInfoPreviewResponse.from(그라츠대학_지원_정보), + UnivApplyInfoPreviewResponse.from(메이지대학_지원_정보) ); } @Test - void 어학시험_조건으로_대학을_필터링한다() { + void 어학시험_조건으로_대학_지원_정보를_필터링한다() { // given UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); languageRequirementFixture.토플_80(괌대학_A_지원_정보); @@ -178,16 +178,16 @@ class UniversityQueryServiceTest { languageRequirementFixture.토익_900(괌대학_B_지원_정보); // when - UniversityInfoForApplyPreviewResponses response = universityQueryService.searchUniversity( + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfo( null, List.of(), LanguageTestType.TOEFL_IBT, "70"); // then - assertThat(response.universityInfoForApplyPreviewResponses()) - .containsExactlyInAnyOrder(UniversityInfoForApplyPreviewResponse.from(괌대학_B_지원_정보)); + assertThat(response.univApplyInfoPreviews()) + .containsExactlyInAnyOrder(UnivApplyInfoPreviewResponse.from(괌대학_B_지원_정보)); } @Test - void 모든_조건으로_대학을_필터링한다() { + void 모든_조건으로_대학_지원_정보를_필터링한다() { // given UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); languageRequirementFixture.토플_80(괌대학_A_지원_정보); @@ -196,11 +196,11 @@ class UniversityQueryServiceTest { languageRequirementFixture.토플_70(서던덴마크대학교_지원_정보); // when - UniversityInfoForApplyPreviewResponses response = universityQueryService.searchUniversity( + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfo( "EUROPE", List.of(), LanguageTestType.TOEFL_IBT, "70"); // then - assertThat(response.universityInfoForApplyPreviewResponses()) - .containsExactly(UniversityInfoForApplyPreviewResponse.from(서던덴마크대학교_지원_정보)); + assertThat(response.univApplyInfoPreviews()) + .containsExactly(UnivApplyInfoPreviewResponse.from(서던덴마크대학교_지원_정보)); } } diff --git a/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendServiceTest.java similarity index 59% rename from src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java rename to src/test/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendServiceTest.java index a1bfba10b..c6bf60b9b 100644 --- a/src/test/java/com/example/solidconnection/university/service/UniversityRecommendServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendServiceTest.java @@ -10,8 +10,8 @@ import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.university.domain.UnivApplyInfo; -import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; -import com.example.solidconnection.university.dto.UniversityRecommendsResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoRecommendsResponse; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -20,15 +20,15 @@ import java.util.List; -import static com.example.solidconnection.university.service.UniversityRecommendService.RECOMMEND_UNIVERSITY_NUM; +import static com.example.solidconnection.university.service.UnivApplyInfoRecommendService.RECOMMEND_UNIV_APPLY_INFO_NUM; import static org.assertj.core.api.Assertions.assertThat; @TestContainerSpringBootTest -@DisplayName("대학교 추천 서비스 테스트") -class UniversityRecommendServiceTest { +@DisplayName("대학 지원 정보 추천 서비스 테스트") +class UnivApplyInfoRecommendServiceTest { @Autowired - private UniversityRecommendService universityRecommendService; + private UnivApplyInfoRecommendService univApplyInfoRecommendService; @Autowired private InterestedRegionRepository interestedRegionRepository; @@ -37,7 +37,7 @@ class UniversityRecommendServiceTest { private InterestedCountryRepository interestedCountryRepository; @Autowired - private GeneralUniversityRecommendService generalUniversityRecommendService; + private GeneralUnivApplyInfoRecommendService generalUnivApplyInfoRecommendService; @Autowired private SiteUserFixture siteUserFixture; @@ -72,93 +72,93 @@ void setUp() { univApplyInfoFixture.그라츠공과대학_지원_정보(); univApplyInfoFixture.린츠_카톨릭대학_지원_정보(); univApplyInfoFixture.메이지대학_지원_정보(); - generalUniversityRecommendService.init(); + generalUnivApplyInfoRecommendService.init(); } @Test - void 관심_지역_설정한_사용자의_맞춤_추천_대학을_조회한다() { + void 관심_지역_설정한_사용자의_맞춤_추천_대학_지원_정보를_조회한다() { // given interestedRegionRepository.save(new InterestedRegion(user, regionFixture.영미권())); // when - UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(user); + UnivApplyInfoRecommendsResponse response = univApplyInfoRecommendService.getPersonalRecommends(user); // then assertThat(response.recommendedUniversities()) - .hasSize(RECOMMEND_UNIVERSITY_NUM) + .hasSize(RECOMMEND_UNIV_APPLY_INFO_NUM) .containsAll(List.of( - UniversityInfoForApplyPreviewResponse.from(괌대학_A_지원_정보), - UniversityInfoForApplyPreviewResponse.from(괌대학_B_지원_정보), - UniversityInfoForApplyPreviewResponse.from(메모리얼대학_세인트존스_A_지원_정보), - UniversityInfoForApplyPreviewResponse.from(네바다주립대학_라스베이거스_지원_정보) + UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보), + UnivApplyInfoPreviewResponse.from(괌대학_B_지원_정보), + UnivApplyInfoPreviewResponse.from(메모리얼대학_세인트존스_A_지원_정보), + UnivApplyInfoPreviewResponse.from(네바다주립대학_라스베이거스_지원_정보) )); } @Test - void 관심_국가_설정한_사용자의_맞춤_추천_대학을_조회한다() { + void 관심_국가_설정한_사용자의_맞춤_추천_대학_지원_정보를_조회한다() { // given interestedCountryRepository.save(new InterestedCountry(user, countryFixture.덴마크())); // when - UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(user); + UnivApplyInfoRecommendsResponse response = univApplyInfoRecommendService.getPersonalRecommends(user); // then assertThat(response.recommendedUniversities()) - .hasSize(RECOMMEND_UNIVERSITY_NUM) + .hasSize(RECOMMEND_UNIV_APPLY_INFO_NUM) .containsAll(List.of( - UniversityInfoForApplyPreviewResponse.from(서던덴마크대학교_지원_정보), - UniversityInfoForApplyPreviewResponse.from(코펜하겐IT대학_지원_정보) + UnivApplyInfoPreviewResponse.from(서던덴마크대학교_지원_정보), + UnivApplyInfoPreviewResponse.from(코펜하겐IT대학_지원_정보) )); } @Test - void 관심_지역과_국가_모두_설정한_사용자의_맞춤_추천_대학을_조회한다() { + void 관심_지역과_국가_모두_설정한_사용자의_맞춤_추천_대학_지원_정보를_조회한다() { // given interestedRegionRepository.save(new InterestedRegion(user, regionFixture.영미권())); interestedCountryRepository.save(new InterestedCountry(user, countryFixture.덴마크())); // when - UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(user); + UnivApplyInfoRecommendsResponse response = univApplyInfoRecommendService.getPersonalRecommends(user); // then assertThat(response.recommendedUniversities()) - .hasSize(RECOMMEND_UNIVERSITY_NUM) + .hasSize(RECOMMEND_UNIV_APPLY_INFO_NUM) .containsExactlyInAnyOrder( - UniversityInfoForApplyPreviewResponse.from(괌대학_A_지원_정보), - UniversityInfoForApplyPreviewResponse.from(괌대학_B_지원_정보), - UniversityInfoForApplyPreviewResponse.from(메모리얼대학_세인트존스_A_지원_정보), - UniversityInfoForApplyPreviewResponse.from(네바다주립대학_라스베이거스_지원_정보), - UniversityInfoForApplyPreviewResponse.from(서던덴마크대학교_지원_정보), - UniversityInfoForApplyPreviewResponse.from(코펜하겐IT대학_지원_정보) + UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보), + UnivApplyInfoPreviewResponse.from(괌대학_B_지원_정보), + UnivApplyInfoPreviewResponse.from(메모리얼대학_세인트존스_A_지원_정보), + UnivApplyInfoPreviewResponse.from(네바다주립대학_라스베이거스_지원_정보), + UnivApplyInfoPreviewResponse.from(서던덴마크대학교_지원_정보), + UnivApplyInfoPreviewResponse.from(코펜하겐IT대학_지원_정보) ); } @Test - void 관심사_미설정_사용자는_일반_추천_대학을_조회한다() { + void 관심사_미설정_사용자는_일반_추천_대학_지원_정보를_조회한다() { // when - UniversityRecommendsResponse response = universityRecommendService.getPersonalRecommends(user); + UnivApplyInfoRecommendsResponse response = univApplyInfoRecommendService.getPersonalRecommends(user); // then assertThat(response.recommendedUniversities()) - .hasSize(RECOMMEND_UNIVERSITY_NUM) + .hasSize(RECOMMEND_UNIV_APPLY_INFO_NUM) .containsExactlyInAnyOrderElementsOf( - generalUniversityRecommendService.getRecommendUniversities().stream() - .map(UniversityInfoForApplyPreviewResponse::from) + generalUnivApplyInfoRecommendService.getGeneralRecommends().stream() + .map(UnivApplyInfoPreviewResponse::from) .toList() ); } @Test - void 일반_추천_대학을_조회한다() { + void 일반_추천_대학_지원_정보를_조회한다() { // when - UniversityRecommendsResponse response = universityRecommendService.getGeneralRecommends(); + UnivApplyInfoRecommendsResponse response = univApplyInfoRecommendService.getGeneralRecommends(); // then assertThat(response.recommendedUniversities()) - .hasSize(RECOMMEND_UNIVERSITY_NUM) + .hasSize(RECOMMEND_UNIV_APPLY_INFO_NUM) .containsExactlyInAnyOrderElementsOf( - generalUniversityRecommendService.getRecommendUniversities().stream() - .map(UniversityInfoForApplyPreviewResponse::from) + generalUnivApplyInfoRecommendService.getGeneralRecommends().stream() + .map(UnivApplyInfoPreviewResponse::from) .toList() ); } From aa0ff59d3829fec667452fd03b246bcdf1f3cdaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Tue, 1 Jul 2025 17:01:36 +0900 Subject: [PATCH 28/90] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20DynamicFixture=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0=20(#355)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PostLikeCountConcurrencyTest.java | 44 +++++-------------- .../solidconnection/e2e/DynamicFixture.java | 18 -------- 2 files changed, 11 insertions(+), 51 deletions(-) delete mode 100644 src/test/java/com/example/solidconnection/e2e/DynamicFixture.java diff --git a/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java b/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java index 472cb3aca..7c2e61bb0 100644 --- a/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java +++ b/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java @@ -1,27 +1,25 @@ package com.example.solidconnection.concurrency; import com.example.solidconnection.community.board.domain.Board; -import com.example.solidconnection.community.board.repository.BoardRepository; +import com.example.solidconnection.community.board.fixture.BoardFixture; import com.example.solidconnection.community.post.domain.Post; import com.example.solidconnection.community.post.domain.PostCategory; +import com.example.solidconnection.community.post.fixture.PostFixture; import com.example.solidconnection.community.post.repository.PostRepository; import com.example.solidconnection.community.post.service.PostLikeService; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import static com.example.solidconnection.e2e.DynamicFixture.createSiteUserByEmailAndNickname; import static org.junit.jupiter.api.Assertions.assertEquals; @TestContainerSpringBootTest @@ -35,16 +33,13 @@ class PostLikeCountConcurrencyTest { private PostRepository postRepository; @Autowired - private BoardRepository boardRepository; + private SiteUserFixture siteUserFixture; @Autowired - private SiteUserRepository siteUserRepository; + private BoardFixture boardFixture; @Autowired - private SiteUserFixture siteUserFixture; - - @Value("${view.count.scheduling.delay}") - private int SCHEDULING_DELAY_MS; + private PostFixture postFixture; private int THREAD_NUMS = 1000; private int THREAD_POOL_SIZE = 200; @@ -56,30 +51,15 @@ class PostLikeCountConcurrencyTest { @BeforeEach void setUp() { - board = createBoard(); - boardRepository.save(board); + board = boardFixture.자유게시판(); user = siteUserFixture.사용자(); - post = createPost(board, user); - postRepository.save(post); - } - - private Board createBoard() { - return new Board( - "FREE", "자유게시판"); - } - - private Post createPost(Board board, SiteUser siteUser) { - Post post = new Post( + post = postFixture.게시글( "title", "content", false, - 0L, - 0L, - PostCategory.valueOf("자유") - ); - post.setBoardAndSiteUser(board, siteUser); - - return post; + PostCategory.자유, + board, + user); } @Test @@ -91,9 +71,8 @@ private Post createPost(Board board, SiteUser siteUser) { Long likeCount = postRepository.getById(post.getId()).getLikeCount(); for (int i = 0; i < THREAD_NUMS; i++) { - String email = "email" + i; String nickname = "nickname" + i; - SiteUser tmpSiteUser = siteUserRepository.save(createSiteUserByEmailAndNickname(email, nickname)); + SiteUser tmpSiteUser = siteUserFixture.사용자(i, nickname); executorService.submit(() -> { try { postLikeService.likePost(tmpSiteUser, post.getId()); @@ -103,7 +82,6 @@ private Post createPost(Board board, SiteUser siteUser) { } }); } - doneSignal.await(TIMEOUT_SECONDS, TimeUnit.SECONDS); executorService.shutdown(); boolean terminated = executorService.awaitTermination(TIMEOUT_SECONDS, TimeUnit.SECONDS); diff --git a/src/test/java/com/example/solidconnection/e2e/DynamicFixture.java b/src/test/java/com/example/solidconnection/e2e/DynamicFixture.java deleted file mode 100644 index 5187877d2..000000000 --- a/src/test/java/com/example/solidconnection/e2e/DynamicFixture.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.example.solidconnection.e2e; - -import com.example.solidconnection.siteuser.domain.PreparationStatus; -import com.example.solidconnection.siteuser.domain.Role; -import com.example.solidconnection.siteuser.domain.SiteUser; - -public class DynamicFixture { // todo: test fixture 개선 작업 이후, 이 클래스의 사용이 대체되면 삭제 필요 - - public static SiteUser createSiteUserByEmailAndNickname(String email, String nickname) { - return new SiteUser( - email, - nickname, - "profileImage", - PreparationStatus.CONSIDERING, - Role.MENTEE - ); - } -} From 5a006a4d0914adae285270b2f7270843a358a5e1 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Tue, 1 Jul 2025 17:15:19 +0900 Subject: [PATCH 29/90] =?UTF-8?q?refactor:=20=EA=B5=90=ED=99=98=ED=95=99?= =?UTF-8?q?=EC=83=9D=20=EC=A4=80=EB=B9=84=20=EC=83=81=ED=83=9C=EC=9D=98=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=BC=EB=AA=85=20=EB=B3=80=EA=B2=BD=20(#351)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: preparationStatus 필드 이름 변경 * refactor: 엔티티 필드명 변경 - PreparationStatus -> ExchangeStudentStatus chore: 컬럼명 변경 flyway 스크립트 추가 * chore: flyway 스크립트 버전 수정 --- .../solidconnection/auth/dto/SignUpRequest.java | 12 ++++++++---- ...{PreparationStatus.java => ExchangeStatus.java} | 6 ++++-- .../solidconnection/siteuser/domain/SiteUser.java | 14 +++++++------- .../migration/V18__rename_preparation_status.sql | 1 + src/main/resources/secret | 2 +- .../authentication/SiteUserAuthenticationTest.java | 4 ++-- .../siteuser/fixture/SiteUserFixtureBuilder.java | 4 ++-- .../repository/SiteUserRepositoryTest.java | 4 ++-- 8 files changed, 27 insertions(+), 20 deletions(-) rename src/main/java/com/example/solidconnection/siteuser/domain/{PreparationStatus.java => ExchangeStatus.java} (82%) create mode 100644 src/main/resources/db/migration/V18__rename_preparation_status.sql diff --git a/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java b/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java index b43671e82..8f72d25da 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java +++ b/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java @@ -1,9 +1,10 @@ package com.example.solidconnection.auth.dto; import com.example.solidconnection.siteuser.domain.AuthType; -import com.example.solidconnection.siteuser.domain.PreparationStatus; +import com.example.solidconnection.siteuser.domain.ExchangeStatus; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.NotBlank; import java.util.List; @@ -12,7 +13,10 @@ public record SignUpRequest( String signUpToken, List interestedRegions, List interestedCountries, - PreparationStatus preparationStatus, + + @JsonProperty("preparationStatus") + ExchangeStatus exchangeStatus, + String profileImageUrl, @NotBlank(message = "닉네임을 입력해주세요.") @@ -23,7 +27,7 @@ public SiteUser toOAuthSiteUser(String email, AuthType authType) { email, this.nickname, this.profileImageUrl, - this.preparationStatus, + this.exchangeStatus, Role.MENTEE, authType ); @@ -34,7 +38,7 @@ public SiteUser toEmailSiteUser(String email, String encodedPassword) { email, this.nickname, this.profileImageUrl, - this.preparationStatus, + this.exchangeStatus, Role.MENTEE, AuthType.EMAIL, encodedPassword diff --git a/src/main/java/com/example/solidconnection/siteuser/domain/PreparationStatus.java b/src/main/java/com/example/solidconnection/siteuser/domain/ExchangeStatus.java similarity index 82% rename from src/main/java/com/example/solidconnection/siteuser/domain/PreparationStatus.java rename to src/main/java/com/example/solidconnection/siteuser/domain/ExchangeStatus.java index 6f86853c0..6373f8729 100644 --- a/src/main/java/com/example/solidconnection/siteuser/domain/PreparationStatus.java +++ b/src/main/java/com/example/solidconnection/siteuser/domain/ExchangeStatus.java @@ -1,8 +1,10 @@ package com.example.solidconnection.siteuser.domain; -public enum PreparationStatus { +public enum ExchangeStatus { + CONSIDERING, // 교환학생 지원 고민 상태 PREPARING_FOR_DEPARTURE, // 교환학생 합격 후 파견 준비 상태 STUDYING_ABROAD, // 해외 학교에서 공부중인 상태 - AFTER_EXCHANGE + AFTER_EXCHANGE, + ; } diff --git a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java index 98c18b56a..7c065c8e1 100644 --- a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java +++ b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java @@ -64,7 +64,7 @@ public class SiteUser { @Column(nullable = false) @Enumerated(EnumType.STRING) - private PreparationStatus preparationStage; + private ExchangeStatus exchangeStatus; @Column(nullable = false) @Enumerated(EnumType.STRING) @@ -98,12 +98,12 @@ public SiteUser( String email, String nickname, String profileImageUrl, - PreparationStatus preparationStage, + ExchangeStatus exchangeStatus, Role role) { this.email = email; this.nickname = nickname; this.profileImageUrl = profileImageUrl; - this.preparationStage = preparationStage; + this.exchangeStatus = exchangeStatus; this.role = role; this.authType = AuthType.KAKAO; } @@ -112,13 +112,13 @@ public SiteUser( String email, String nickname, String profileImageUrl, - PreparationStatus preparationStage, + ExchangeStatus exchangeStatus, Role role, AuthType authType) { this.email = email; this.nickname = nickname; this.profileImageUrl = profileImageUrl; - this.preparationStage = preparationStage; + this.exchangeStatus = exchangeStatus; this.role = role; this.authType = authType; } @@ -128,14 +128,14 @@ public SiteUser( String email, String nickname, String profileImageUrl, - PreparationStatus preparationStage, + ExchangeStatus exchangeStatus, Role role, AuthType authType, String password) { this.email = email; this.nickname = nickname; this.profileImageUrl = profileImageUrl; - this.preparationStage = preparationStage; + this.exchangeStatus = exchangeStatus; this.role = role; this.authType = authType; this.password = password; diff --git a/src/main/resources/db/migration/V18__rename_preparation_status.sql b/src/main/resources/db/migration/V18__rename_preparation_status.sql new file mode 100644 index 000000000..0eba9ae66 --- /dev/null +++ b/src/main/resources/db/migration/V18__rename_preparation_status.sql @@ -0,0 +1 @@ +ALTER TABLE site_user RENAME COLUMN preparation_stage TO exchange_status; diff --git a/src/main/resources/secret b/src/main/resources/secret index 84002e866..5ddc9c656 160000 --- a/src/main/resources/secret +++ b/src/main/resources/secret @@ -1 +1 @@ -Subproject commit 84002e86670d380219f580c6605fb7c66ed7d977 +Subproject commit 5ddc9c656dc1e6c9b25ba9ea014aaa65edc74a1c diff --git a/src/test/java/com/example/solidconnection/security/authentication/SiteUserAuthenticationTest.java b/src/test/java/com/example/solidconnection/security/authentication/SiteUserAuthenticationTest.java index 8c3b9e216..29b47869d 100644 --- a/src/test/java/com/example/solidconnection/security/authentication/SiteUserAuthenticationTest.java +++ b/src/test/java/com/example/solidconnection/security/authentication/SiteUserAuthenticationTest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.security.authentication; import com.example.solidconnection.security.userdetails.SiteUserDetails; -import com.example.solidconnection.siteuser.domain.PreparationStatus; +import com.example.solidconnection.siteuser.domain.ExchangeStatus; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; import org.junit.jupiter.api.Test; @@ -63,7 +63,7 @@ private SiteUser createSiteUser() { "test@example.com", "nickname", "profileImageUrl", - PreparationStatus.CONSIDERING, + ExchangeStatus.CONSIDERING, Role.MENTEE ); } diff --git a/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixtureBuilder.java b/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixtureBuilder.java index 46db8bae4..901de4d6a 100644 --- a/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixtureBuilder.java @@ -1,7 +1,7 @@ package com.example.solidconnection.siteuser.fixture; import com.example.solidconnection.siteuser.domain.AuthType; -import com.example.solidconnection.siteuser.domain.PreparationStatus; +import com.example.solidconnection.siteuser.domain.ExchangeStatus; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; @@ -62,7 +62,7 @@ public SiteUser create() { email, nickname, profileImageUrl, - PreparationStatus.CONSIDERING, + ExchangeStatus.CONSIDERING, role, authType, passwordEncoder.encode(password) diff --git a/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java b/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java index b8c51c148..115e40e77 100644 --- a/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.siteuser.repository; import com.example.solidconnection.siteuser.domain.AuthType; -import com.example.solidconnection.siteuser.domain.PreparationStatus; +import com.example.solidconnection.siteuser.domain.ExchangeStatus; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.support.TestContainerDataJpaTest; @@ -83,7 +83,7 @@ private SiteUser createSiteUser(String email, String nickname, AuthType authType email, nickname, "profileImageUrl", - PreparationStatus.CONSIDERING, + ExchangeStatus.CONSIDERING, Role.MENTEE, authType ); From 68fc1a0a6e7dea1c01ea14d14a3454a60c91a152 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Tue, 1 Jul 2025 17:44:54 +0900 Subject: [PATCH 30/90] =?UTF-8?q?fix:=20=EC=84=9C=EB=B8=8C=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=ED=95=B4=EC=8B=9C=EB=A5=BC=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EB=B0=9C=EC=83=9D=20=EC=9D=B4=EC=A0=84=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=B5=EC=9B=90=20(#364)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/secret | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/secret b/src/main/resources/secret index 5ddc9c656..84002e866 160000 --- a/src/main/resources/secret +++ b/src/main/resources/secret @@ -1 +1 @@ -Subproject commit 5ddc9c656dc1e6c9b25ba9ea014aaa65edc74a1c +Subproject commit 84002e86670d380219f580c6605fb7c66ed7d977 From 985d2b25fa70592c92eef425c13bc5c8a6b2db0d Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Wed, 2 Jul 2025 00:02:28 +0900 Subject: [PATCH 31/90] =?UTF-8?q?feat:=20=EB=A9=98=ED=86=A0=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#348)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: flyway 스크립트 추가 * feat: 멘토 관련 엔티티 추가 * refactor: VerifyStatus 위치 변경 - application.domain 하위 -> common 하위 * refactor: 명시하지 않아도 될 AccessLevel.PUBLIC 옵션 제거 * refactor: Channel 엔티티에 유티크 제약조건 이름 명시 * refactor: 엔티티에서 컬럼명 매핑하는 방식 통일 * style: enum 컨벤션에 맞게 수정 * refactor: 논의된 내용대로 연관관계 설정 * refactor: 승인 상태 컬럼의 default 값 설정 * chore: flyway 스크립트 이름 변경 --- .../admin/dto/GpaScoreResponse.java | 2 +- .../admin/dto/GpaScoreStatusResponse.java | 2 +- .../admin/dto/GpaScoreUpdateRequest.java | 2 +- .../admin/dto/LanguageTestScoreResponse.java | 2 +- .../dto/LanguageTestScoreStatusResponse.java | 2 +- .../dto/LanguageTestScoreUpdateRequest.java | 2 +- .../admin/dto/ScoreSearchCondition.java | 2 +- .../admin/dto/ScoreUpdateRequest.java | 2 +- .../admin/service/AdminGpaScoreService.java | 2 +- .../AdminLanguageTestScoreService.java | 2 +- .../application/domain/Application.java | 3 +- .../application/domain/VerifyStatus.java | 5 -- .../validation/RejectedReasonValidator.java | 2 +- .../repository/ApplicationRepository.java | 2 +- .../service/ApplicationQueryService.java | 2 +- .../service/ApplicationSubmissionService.java | 2 +- .../solidconnection/common/VerifyStatus.java | 9 +++ .../mentor/domain/Channel.java | 47 ++++++++++++++ .../mentor/domain/ChannelType.java | 10 +++ .../solidconnection/mentor/domain/Mentor.java | 48 ++++++++++++++ .../mentor/domain/Mentoring.java | 63 +++++++++++++++++++ .../score/domain/GpaScore.java | 2 +- .../score/domain/LanguageTestScore.java | 2 +- .../score/dto/GpaScoreStatusResponse.java | 2 +- .../dto/LanguageTestScoreStatusResponse.java | 2 +- .../custom/GpaScoreFilterRepositoryImpl.java | 2 +- ...LanguageTestScoreFilterRepositoryImpl.java | 2 +- .../domain/LanguageRequirement.java | 2 +- .../university/domain/UnivApplyInfo.java | 2 +- .../university/domain/University.java | 2 +- .../V19__create_mentor_related_tables.sql | 40 ++++++++++++ .../RejectedReasonValidatorTest.java | 2 +- .../service/AdminGpaScoreServiceTest.java | 2 +- .../AdminLanguageTestScoreServiceTest.java | 2 +- .../fixture/ApplicationFixtureBuilder.java | 2 +- .../service/ApplicationQueryServiceTest.java | 2 +- .../ApplicationSubmissionServiceTest.java | 2 +- .../score/fixture/GpaScoreFixture.java | 2 +- .../score/fixture/GpaScoreFixtureBuilder.java | 2 +- .../fixture/LanguageTestScoreFixture.java | 3 +- .../LanguageTestScoreFixtureBuilder.java | 2 +- .../score/service/ScoreServiceTest.java | 2 +- 42 files changed, 253 insertions(+), 41 deletions(-) delete mode 100644 src/main/java/com/example/solidconnection/application/domain/VerifyStatus.java create mode 100644 src/main/java/com/example/solidconnection/common/VerifyStatus.java create mode 100644 src/main/java/com/example/solidconnection/mentor/domain/Channel.java create mode 100644 src/main/java/com/example/solidconnection/mentor/domain/ChannelType.java create mode 100644 src/main/java/com/example/solidconnection/mentor/domain/Mentor.java create mode 100644 src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java create mode 100644 src/main/resources/db/migration/V19__create_mentor_related_tables.sql diff --git a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreResponse.java b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreResponse.java index c90ba6a40..7da28252a 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreResponse.java @@ -1,6 +1,6 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.score.domain.GpaScore; public record GpaScoreResponse( diff --git a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreStatusResponse.java b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreStatusResponse.java index a9d1d0668..379d5567a 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreStatusResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreStatusResponse.java @@ -1,6 +1,6 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import java.time.ZonedDateTime; diff --git a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreUpdateRequest.java b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreUpdateRequest.java index c22979ea6..9393c1e7f 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreUpdateRequest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.application.dto.validation.RejectedReasonRequired; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreResponse.java b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreResponse.java index 978dcae82..f80f5e7cb 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreResponse.java @@ -1,6 +1,6 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.university.domain.LanguageTestType; diff --git a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreStatusResponse.java b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreStatusResponse.java index 3094ea18f..d060e0661 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreStatusResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreStatusResponse.java @@ -1,6 +1,6 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import java.time.ZonedDateTime; diff --git a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreUpdateRequest.java b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreUpdateRequest.java index c072111ec..51af55f79 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreUpdateRequest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.application.dto.validation.RejectedReasonRequired; import com.example.solidconnection.university.domain.LanguageTestType; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/com/example/solidconnection/admin/dto/ScoreSearchCondition.java b/src/main/java/com/example/solidconnection/admin/dto/ScoreSearchCondition.java index 1dc284be8..d968af5b2 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/ScoreSearchCondition.java +++ b/src/main/java/com/example/solidconnection/admin/dto/ScoreSearchCondition.java @@ -1,6 +1,6 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import java.time.LocalDate; diff --git a/src/main/java/com/example/solidconnection/admin/dto/ScoreUpdateRequest.java b/src/main/java/com/example/solidconnection/admin/dto/ScoreUpdateRequest.java index 7223b48e4..7299d6433 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/ScoreUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/admin/dto/ScoreUpdateRequest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; public interface ScoreUpdateRequest { VerifyStatus verifyStatus(); diff --git a/src/main/java/com/example/solidconnection/admin/service/AdminGpaScoreService.java b/src/main/java/com/example/solidconnection/admin/service/AdminGpaScoreService.java index eae555406..2f954d910 100644 --- a/src/main/java/com/example/solidconnection/admin/service/AdminGpaScoreService.java +++ b/src/main/java/com/example/solidconnection/admin/service/AdminGpaScoreService.java @@ -5,7 +5,7 @@ import com.example.solidconnection.admin.dto.GpaScoreUpdateRequest; import com.example.solidconnection.admin.dto.ScoreSearchCondition; import com.example.solidconnection.application.domain.Gpa; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.repository.GpaScoreRepository; diff --git a/src/main/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreService.java b/src/main/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreService.java index db89ae91c..b8770caa5 100644 --- a/src/main/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreService.java +++ b/src/main/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreService.java @@ -5,7 +5,7 @@ import com.example.solidconnection.admin.dto.LanguageTestScoreUpdateRequest; import com.example.solidconnection.admin.dto.ScoreSearchCondition; import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; diff --git a/src/main/java/com/example/solidconnection/application/domain/Application.java b/src/main/java/com/example/solidconnection/application/domain/Application.java index 5f982410e..0c6e3cc0a 100644 --- a/src/main/java/com/example/solidconnection/application/domain/Application.java +++ b/src/main/java/com/example/solidconnection/application/domain/Application.java @@ -1,5 +1,6 @@ package com.example.solidconnection.application.domain; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.persistence.Column; import jakarta.persistence.Embedded; @@ -19,7 +20,7 @@ import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; -import static com.example.solidconnection.application.domain.VerifyStatus.PENDING; +import static com.example.solidconnection.common.VerifyStatus.PENDING; @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) diff --git a/src/main/java/com/example/solidconnection/application/domain/VerifyStatus.java b/src/main/java/com/example/solidconnection/application/domain/VerifyStatus.java deleted file mode 100644 index e6f1afbe6..000000000 --- a/src/main/java/com/example/solidconnection/application/domain/VerifyStatus.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.example.solidconnection.application.domain; - -public enum VerifyStatus { - PENDING, REJECTED, APPROVED -} diff --git a/src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonValidator.java b/src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonValidator.java index 0f1837e0e..4b43673f3 100644 --- a/src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonValidator.java +++ b/src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonValidator.java @@ -1,7 +1,7 @@ package com.example.solidconnection.application.dto.validation; import com.example.solidconnection.admin.dto.ScoreUpdateRequest; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import io.micrometer.common.util.StringUtils; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; diff --git a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java index 9a7754896..7ab033f8b 100644 --- a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java +++ b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java @@ -1,7 +1,7 @@ package com.example.solidconnection.application.repository; import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java index 2d7adbc58..8ad3a9287 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java @@ -1,7 +1,7 @@ package com.example.solidconnection.application.service; import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.application.dto.ApplicationsResponse; import com.example.solidconnection.application.dto.ApplicantsResponse; import com.example.solidconnection.application.repository.ApplicationRepository; diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java index 2d307992b..37cc0ae19 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java @@ -1,7 +1,7 @@ package com.example.solidconnection.application.service; import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.application.dto.ApplicationSubmissionResponse; import com.example.solidconnection.application.dto.ApplyRequest; import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; diff --git a/src/main/java/com/example/solidconnection/common/VerifyStatus.java b/src/main/java/com/example/solidconnection/common/VerifyStatus.java new file mode 100644 index 000000000..d8848c1f0 --- /dev/null +++ b/src/main/java/com/example/solidconnection/common/VerifyStatus.java @@ -0,0 +1,9 @@ +package com.example.solidconnection.common; + +public enum VerifyStatus { + + PENDING, + REJECTED, + APPROVED, + ; +} diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Channel.java b/src/main/java/com/example/solidconnection/mentor/domain/Channel.java new file mode 100644 index 000000000..33c4f72c3 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/domain/Channel.java @@ -0,0 +1,47 @@ +package com.example.solidconnection.mentor.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(uniqueConstraints = { + @UniqueConstraint( + name = "uk_channel_mentor_id_sequence", + columnNames = {"mentor_id", "sequence"} + ) +}) +public class Channel { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column + private int sequence; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private ChannelType type; + + @Column(nullable = false, length = 500) + private String url; + + @ManyToOne(fetch = FetchType.LAZY) + private Mentor mentor; +} diff --git a/src/main/java/com/example/solidconnection/mentor/domain/ChannelType.java b/src/main/java/com/example/solidconnection/mentor/domain/ChannelType.java new file mode 100644 index 000000000..c12cf321f --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/domain/ChannelType.java @@ -0,0 +1,10 @@ +package com.example.solidconnection.mentor.domain; + +public enum ChannelType { + + BLOG, + INSTAGRAM, + YOUTUBE, + BRUNCH, + ; +} diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java new file mode 100644 index 000000000..81f9d6177 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java @@ -0,0 +1,48 @@ +package com.example.solidconnection.mentor.domain; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Mentor { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column + private int menteeCount = 0; + + @Column + private boolean hasBadge = false; + + @Column(length = 1000) + private String introduction; + + @Column(length = 1000) + private String passTip; + + @Column + private long siteUserId; + + @Column + private long universityId; + + @OneToMany(mappedBy = "mentor", cascade = CascadeType.ALL, orphanRemoval = true) + private List channels = new ArrayList<>(); +} diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java b/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java new file mode 100644 index 000000000..38811a014 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java @@ -0,0 +1,63 @@ +package com.example.solidconnection.mentor.domain; + +import com.example.solidconnection.common.VerifyStatus; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrePersist; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.DynamicInsert; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.ZonedDateTime; + +import static java.time.ZoneOffset.UTC; +import static java.time.temporal.ChronoUnit.MICROS; + +@Entity +@Getter +@EntityListeners(AuditingEntityListener.class) +@DynamicInsert +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Mentoring { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private ZonedDateTime createdAt; + + @Column + private ZonedDateTime confirmedAt; + + @Column + private ZonedDateTime checkedAt; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private VerifyStatus verifyStatus = VerifyStatus.PENDING; + + @Column(length = 500) + private String rejectedReason; + + @Column + private long mentorId; + + @Column + private long menteeId; + + @PrePersist + public void onPrePersist() { + this.createdAt = ZonedDateTime.now(UTC).truncatedTo(MICROS); // 나노초 6자리 까지만 저장 + } +} diff --git a/src/main/java/com/example/solidconnection/score/domain/GpaScore.java b/src/main/java/com/example/solidconnection/score/domain/GpaScore.java index 0156dae9a..45193e692 100644 --- a/src/main/java/com/example/solidconnection/score/domain/GpaScore.java +++ b/src/main/java/com/example/solidconnection/score/domain/GpaScore.java @@ -1,7 +1,7 @@ package com.example.solidconnection.score.domain; import com.example.solidconnection.application.domain.Gpa; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.BaseEntity; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.persistence.Column; diff --git a/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java b/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java index f2257561d..3ef773806 100644 --- a/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java +++ b/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java @@ -1,7 +1,7 @@ package com.example.solidconnection.score.domain; import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.BaseEntity; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.persistence.Column; diff --git a/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusResponse.java b/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusResponse.java index 4804d6874..b720e08f3 100644 --- a/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusResponse.java +++ b/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusResponse.java @@ -1,6 +1,6 @@ package com.example.solidconnection.score.dto; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.score.domain.GpaScore; public record GpaScoreStatusResponse( diff --git a/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusResponse.java b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusResponse.java index 4aa033d72..825ac63db 100644 --- a/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusResponse.java +++ b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusResponse.java @@ -1,6 +1,6 @@ package com.example.solidconnection.score.dto; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.score.domain.LanguageTestScore; public record LanguageTestScoreStatusResponse( diff --git a/src/main/java/com/example/solidconnection/score/repository/custom/GpaScoreFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/score/repository/custom/GpaScoreFilterRepositoryImpl.java index a90961c3d..efa12f8df 100644 --- a/src/main/java/com/example/solidconnection/score/repository/custom/GpaScoreFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/score/repository/custom/GpaScoreFilterRepositoryImpl.java @@ -5,7 +5,7 @@ import com.example.solidconnection.admin.dto.GpaScoreStatusResponse; import com.example.solidconnection.admin.dto.ScoreSearchCondition; import com.example.solidconnection.admin.dto.SiteUserResponse; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.querydsl.core.types.ConstructorExpression; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; diff --git a/src/main/java/com/example/solidconnection/score/repository/custom/LanguageTestScoreFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/score/repository/custom/LanguageTestScoreFilterRepositoryImpl.java index 28baaf521..9f677db09 100644 --- a/src/main/java/com/example/solidconnection/score/repository/custom/LanguageTestScoreFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/score/repository/custom/LanguageTestScoreFilterRepositoryImpl.java @@ -5,7 +5,7 @@ import com.example.solidconnection.admin.dto.LanguageTestScoreStatusResponse; import com.example.solidconnection.admin.dto.ScoreSearchCondition; import com.example.solidconnection.admin.dto.SiteUserResponse; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.querydsl.core.types.ConstructorExpression; import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; diff --git a/src/main/java/com/example/solidconnection/university/domain/LanguageRequirement.java b/src/main/java/com/example/solidconnection/university/domain/LanguageRequirement.java index ddb111fde..7cca13d9e 100644 --- a/src/main/java/com/example/solidconnection/university/domain/LanguageRequirement.java +++ b/src/main/java/com/example/solidconnection/university/domain/LanguageRequirement.java @@ -16,7 +16,7 @@ import lombok.NoArgsConstructor; @Getter -@AllArgsConstructor(access = AccessLevel.PUBLIC) +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity public class LanguageRequirement { diff --git a/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java b/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java index 47edd15c8..f54ffe5e7 100644 --- a/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java +++ b/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java @@ -22,7 +22,7 @@ @Getter @EqualsAndHashCode(of = "id") -@AllArgsConstructor(access = AccessLevel.PUBLIC) +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity @Table(name = "university_info_for_apply") diff --git a/src/main/java/com/example/solidconnection/university/domain/University.java b/src/main/java/com/example/solidconnection/university/domain/University.java index d010861d4..e3738ce0f 100644 --- a/src/main/java/com/example/solidconnection/university/domain/University.java +++ b/src/main/java/com/example/solidconnection/university/domain/University.java @@ -14,7 +14,7 @@ import lombok.NoArgsConstructor; @Entity -@AllArgsConstructor(access = AccessLevel.PUBLIC) +@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter public class University { diff --git a/src/main/resources/db/migration/V19__create_mentor_related_tables.sql b/src/main/resources/db/migration/V19__create_mentor_related_tables.sql new file mode 100644 index 000000000..5e554d7c1 --- /dev/null +++ b/src/main/resources/db/migration/V19__create_mentor_related_tables.sql @@ -0,0 +1,40 @@ +CREATE TABLE mentor +( + id BIGINT NOT NULL AUTO_INCREMENT, + university_id BIGINT NOT NULL, + site_user_id BIGINT NOT NULL, + mentee_count INT NOT NULL DEFAULT 0, + has_badge BOOLEAN NOT NULL DEFAULT FALSE, + introduction VARCHAR(1000) NULL, + pass_tip VARCHAR(1000) NULL, + PRIMARY KEY (id), + CONSTRAINT fk_mentor_university_id FOREIGN KEY (university_id) REFERENCES university (id), + CONSTRAINT fk_mentor_site_user_id FOREIGN KEY (site_user_id) REFERENCES site_user (id) +); + +CREATE TABLE mentoring +( + id BIGINT NOT NULL AUTO_INCREMENT, + mentor_id BIGINT NOT NULL, + mentee_id BIGINT NOT NULL, + created_at DATETIME(6) NOT NULL, + confirmed_at DATETIME(6) NULL, + checked_at DATETIME(6) NULL, + verify_status ENUM ('PENDING', 'REJECTED', 'APPROVED') NOT NULL DEFAULT 'PENDING', + rejected_reason VARCHAR(500) NULL, + PRIMARY KEY (id), + CONSTRAINT fk_mentoring_mentor_id FOREIGN KEY (mentor_id) REFERENCES mentor (id), + CONSTRAINT fk_mentoring_site_user_id FOREIGN KEY (mentee_id) REFERENCES site_user (id) +); + +CREATE TABLE channel +( + id BIGINT NOT NULL AUTO_INCREMENT, + mentor_id BIGINT NOT NULL, + sequence INT NOT NULL, + type ENUM ('BLOG', 'INSTAGRAM', 'YOUTUBE', 'BRUNCH') NOT NULL, + url VARCHAR(500) NOT NULL, + PRIMARY KEY (id), + CONSTRAINT fk_channel_mentor_id FOREIGN KEY (mentor_id) REFERENCES mentor (id), + CONSTRAINT uk_channel_mentor_id_sequence UNIQUE (mentor_id, sequence) +); diff --git a/src/test/java/com/example/solidconnection/admin/dto/validation/RejectedReasonValidatorTest.java b/src/test/java/com/example/solidconnection/admin/dto/validation/RejectedReasonValidatorTest.java index 8c9af55c6..eaaf529df 100644 --- a/src/test/java/com/example/solidconnection/admin/dto/validation/RejectedReasonValidatorTest.java +++ b/src/test/java/com/example/solidconnection/admin/dto/validation/RejectedReasonValidatorTest.java @@ -2,7 +2,7 @@ import com.example.solidconnection.admin.dto.GpaScoreUpdateRequest; import com.example.solidconnection.admin.dto.LanguageTestScoreUpdateRequest; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.university.domain.LanguageTestType; import jakarta.validation.ConstraintViolation; import jakarta.validation.Validation; diff --git a/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java b/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java index c649e1ba9..dd45e4f77 100644 --- a/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java @@ -4,7 +4,7 @@ import com.example.solidconnection.admin.dto.GpaScoreSearchResponse; import com.example.solidconnection.admin.dto.GpaScoreUpdateRequest; import com.example.solidconnection.admin.dto.ScoreSearchCondition; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.fixture.GpaScoreFixture; diff --git a/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java b/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java index 669c0b0be..439e9444c 100644 --- a/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java @@ -4,7 +4,7 @@ import com.example.solidconnection.admin.dto.LanguageTestScoreSearchResponse; import com.example.solidconnection.admin.dto.LanguageTestScoreUpdateRequest; import com.example.solidconnection.admin.dto.ScoreSearchCondition; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.fixture.LanguageTestScoreFixture; diff --git a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java index d860504e4..efe76bd4a 100644 --- a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java @@ -3,7 +3,7 @@ import com.example.solidconnection.application.domain.Application; import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.application.repository.ApplicationRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java index 488e85e63..3acb51d02 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.application.service; import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.application.dto.ApplicantResponse; import com.example.solidconnection.application.dto.ApplicationsResponse; import com.example.solidconnection.application.dto.ApplicantsResponse; diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java index b9389739f..a45e548ec 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.application.service; import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.application.dto.ApplicationSubmissionResponse; import com.example.solidconnection.application.dto.ApplyRequest; import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; diff --git a/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixture.java b/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixture.java index 4a61d1557..49938a284 100644 --- a/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixture.java +++ b/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixture.java @@ -1,7 +1,7 @@ package com.example.solidconnection.score.fixture; import com.example.solidconnection.application.domain.Gpa; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; diff --git a/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixtureBuilder.java b/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixtureBuilder.java index 7943008b4..51c2d0569 100644 --- a/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixtureBuilder.java @@ -1,7 +1,7 @@ package com.example.solidconnection.score.fixture; import com.example.solidconnection.application.domain.Gpa; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.repository.GpaScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; diff --git a/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixture.java b/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixture.java index 7805692e0..476783fe9 100644 --- a/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixture.java +++ b/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixture.java @@ -1,10 +1,9 @@ package com.example.solidconnection.score.fixture; import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.domain.LanguageTestType; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; diff --git a/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixtureBuilder.java b/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixtureBuilder.java index fd5a8d417..b2908bd93 100644 --- a/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixtureBuilder.java @@ -1,7 +1,7 @@ package com.example.solidconnection.score.fixture; import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; diff --git a/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java b/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java index 0b1aa0718..1f6dfad1e 100644 --- a/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java @@ -1,6 +1,6 @@ package com.example.solidconnection.score.service; -import com.example.solidconnection.application.domain.VerifyStatus; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.s3.domain.ImgType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; From bacdbbb731d1aa6bfd5111c87de206966ce14bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Wed, 2 Jul 2025 00:44:46 +0900 Subject: [PATCH 32/90] =?UTF-8?q?refactor:=20@Repository=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=95=88=EB=B6=99?= =?UTF-8?q?=EC=9D=B4=EB=8F=84=EB=A1=9D=20=ED=86=B5=EC=9D=BC=20(#360)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/repository/ApplicationRepository.java | 2 -- .../community/board/repository/BoardRepository.java | 2 -- .../community/post/repository/PostImageRepository.java | 2 -- .../community/post/repository/PostLikeRepository.java | 2 -- .../community/post/repository/PostRepository.java | 2 -- .../location/country/repository/CountryRepository.java | 2 -- .../country/repository/InterestedCountryRepository.java | 3 +-- .../location/region/repository/InterestedRegionRepository.java | 3 +-- .../location/region/repository/RegionRepository.java | 2 -- .../solidconnection/score/repository/GpaScoreRepository.java | 2 -- .../score/repository/LanguageTestScoreRepository.java | 2 -- .../siteuser/repository/SiteUserRepository.java | 3 --- .../university/repository/LanguageRequirementRepository.java | 2 -- .../repository/UniversityInfoForApplyRepository.java | 0 .../university/repository/UniversityRepository.java | 2 -- 15 files changed, 2 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java diff --git a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java index 7ab033f8b..d314c251e 100644 --- a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java +++ b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java @@ -7,14 +7,12 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; import java.util.List; import java.util.Optional; import static com.example.solidconnection.common.exception.ErrorCode.APPLICATION_NOT_FOUND; -@Repository public interface ApplicationRepository extends JpaRepository { boolean existsByNicknameForApply(String nicknameForApply); diff --git a/src/main/java/com/example/solidconnection/community/board/repository/BoardRepository.java b/src/main/java/com/example/solidconnection/community/board/repository/BoardRepository.java index 4c6f799d3..353a0d8dd 100644 --- a/src/main/java/com/example/solidconnection/community/board/repository/BoardRepository.java +++ b/src/main/java/com/example/solidconnection/community/board/repository/BoardRepository.java @@ -6,13 +6,11 @@ import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; import java.util.Optional; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_BOARD_CODE; -@Repository public interface BoardRepository extends JpaRepository { @EntityGraph(attributePaths = {"postList"}) diff --git a/src/main/java/com/example/solidconnection/community/post/repository/PostImageRepository.java b/src/main/java/com/example/solidconnection/community/post/repository/PostImageRepository.java index 54c43f375..096e47f20 100644 --- a/src/main/java/com/example/solidconnection/community/post/repository/PostImageRepository.java +++ b/src/main/java/com/example/solidconnection/community/post/repository/PostImageRepository.java @@ -2,8 +2,6 @@ import com.example.solidconnection.community.post.domain.PostImage; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -@Repository public interface PostImageRepository extends JpaRepository { } diff --git a/src/main/java/com/example/solidconnection/community/post/repository/PostLikeRepository.java b/src/main/java/com/example/solidconnection/community/post/repository/PostLikeRepository.java index fba377899..fbc8146cc 100644 --- a/src/main/java/com/example/solidconnection/community/post/repository/PostLikeRepository.java +++ b/src/main/java/com/example/solidconnection/community/post/repository/PostLikeRepository.java @@ -5,13 +5,11 @@ import com.example.solidconnection.community.post.domain.PostLike; import com.example.solidconnection.siteuser.domain.SiteUser; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; import java.util.Optional; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_LIKE; -@Repository public interface PostLikeRepository extends JpaRepository { Optional findPostLikeByPostAndSiteUser(Post post, SiteUser siteUser); diff --git a/src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java b/src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java index 8c1d256a6..2655e520c 100644 --- a/src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java +++ b/src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java @@ -7,13 +7,11 @@ import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; import java.util.Optional; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ID; -@Repository public interface PostRepository extends JpaRepository { @EntityGraph(attributePaths = {"postImageList", "board", "siteUser"}) diff --git a/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java b/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java index 4860e6015..4ac91c085 100644 --- a/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java +++ b/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java @@ -4,11 +4,9 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; import java.util.List; -@Repository public interface CountryRepository extends JpaRepository { @Query("SELECT c FROM Country c WHERE c.koreanName IN :names") diff --git a/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java b/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java index 2eacc2ca9..a24866ed6 100644 --- a/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java +++ b/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java @@ -3,11 +3,10 @@ import com.example.solidconnection.location.country.domain.InterestedCountry; import com.example.solidconnection.siteuser.domain.SiteUser; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; import java.util.List; -@Repository public interface InterestedCountryRepository extends JpaRepository { + List findAllBySiteUser(SiteUser siteUser); } diff --git a/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java b/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java index 90d860b3c..1e78b1803 100644 --- a/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java +++ b/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java @@ -3,11 +3,10 @@ import com.example.solidconnection.location.region.domain.InterestedRegion; import com.example.solidconnection.siteuser.domain.SiteUser; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; import java.util.List; -@Repository public interface InterestedRegionRepository extends JpaRepository { + List findAllBySiteUser(SiteUser siteUser); } diff --git a/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java b/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java index 094f19f5f..2cdb8c827 100644 --- a/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java +++ b/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java @@ -4,11 +4,9 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; import java.util.List; -@Repository public interface RegionRepository extends JpaRepository { @Query("SELECT r FROM Region r WHERE r.koreanName IN :names") diff --git a/src/main/java/com/example/solidconnection/score/repository/GpaScoreRepository.java b/src/main/java/com/example/solidconnection/score/repository/GpaScoreRepository.java index 5610c8de3..0114410a3 100644 --- a/src/main/java/com/example/solidconnection/score/repository/GpaScoreRepository.java +++ b/src/main/java/com/example/solidconnection/score/repository/GpaScoreRepository.java @@ -4,11 +4,9 @@ import com.example.solidconnection.score.repository.custom.GpaScoreFilterRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; import java.util.Optional; -@Repository public interface GpaScoreRepository extends JpaRepository, GpaScoreFilterRepository { Optional findGpaScoreBySiteUser(SiteUser siteUser); diff --git a/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java b/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java index 1934a0612..8eb00a014 100644 --- a/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java +++ b/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java @@ -5,11 +5,9 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.university.domain.LanguageTestType; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; import java.util.Optional; -@Repository public interface LanguageTestScoreRepository extends JpaRepository, LanguageTestScoreFilterRepository { Optional findLanguageTestScoreBySiteUserAndLanguageTest_LanguageTestType(SiteUser siteUser, LanguageTestType languageTestType); diff --git a/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java b/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java index 51cb410f6..c93ff0532 100644 --- a/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java +++ b/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java @@ -1,18 +1,15 @@ package com.example.solidconnection.siteuser.repository; - import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; import java.time.LocalDate; import java.util.List; import java.util.Optional; -@Repository public interface SiteUserRepository extends JpaRepository { Optional findByEmailAndAuthType(String email, AuthType authType); diff --git a/src/main/java/com/example/solidconnection/university/repository/LanguageRequirementRepository.java b/src/main/java/com/example/solidconnection/university/repository/LanguageRequirementRepository.java index 2cab8f14e..9a0e590e0 100644 --- a/src/main/java/com/example/solidconnection/university/repository/LanguageRequirementRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/LanguageRequirementRepository.java @@ -2,8 +2,6 @@ import com.example.solidconnection.university.domain.LanguageRequirement; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -@Repository public interface LanguageRequirementRepository extends JpaRepository { } diff --git a/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java b/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java b/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java index b5a9adb59..a499d003c 100644 --- a/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java @@ -5,13 +5,11 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import org.springframework.stereotype.Repository; import java.util.List; import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_NOT_FOUND; -@Repository public interface UniversityRepository extends JpaRepository { @Query("SELECT u FROM University u WHERE u.country.code IN :countryCodes OR u.region.code IN :regionCodes") From b4a5d09b3030675d05a7a22ceacf26af54153825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Wed, 2 Jul 2025 10:12:35 +0900 Subject: [PATCH 33/90] =?UTF-8?q?feat:=20=EC=86=8C=EC=8B=9D=EC=A7=80=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EA=B5=AC=ED=98=84=20(#353)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: News에 siteUserId FK 추가 * refactor: 권한 검사 로직을 역할 기반으로 유연하게 개선 - 기존 어드민 전용 어노테이션을 다중 역할 지원하도록 확장 - @RequireRoleAccess 어노테이션으로 여러 역할 조합 가능하게 변경 * feat: 소식지 생성 api 추가 * test: 소식지 생성 테스트 추가 - 소식지 관련 fixture 추가 * feat: 소식지 수정 api 추가 - default 이미지 URL 설정 어떻게 할지 논의 필요 * feat: 소식지 삭제 api 추가 - 어드민은 그냥 삭제 가능한지 논의 필요 * feat: 특정 유저의 소식지 목록 조회 api 추가 - 추후 Slice 적용 필요 * style: news_id에서 news-id로 변경 * style: 테스트 클래스에서 불필요한 public 제거 * refactor: @JsonProperty 제거 * refactor: defaultThumbnailUrl application-varaible.yml에서 가져오도록 변경 * refactor: RequireRoleAccess 어노테이션 roles 기본값 제거 * refactor: 권한 체크 로직 contains로 단순화하여 final 제거 * test: 권한별 나열식 테스트 제거, 요구 역할 유무에 따른 핵심 시나리오 중심으로 개선 * refactor: 소식지 수정 PATCH -> PUT으로 변경 * test: 테스트 메서드명을 멘토 → 사용자로 일반화 * style: NewsRepository 메서드 선언 개행 및 파라미터 타입 Long → long 변경 * refactor: 단일/목록 응답 클래스 네이밍 통일 * chore: 서브모듈 커밋 반영 * refactor: siteUserId 타입을 Long에서 long으로 통일 * test: assertAll로 테스트 그룹화 * refactor: private 메서드 위치를 호출부 아래로 이동 * chore: 서브모듈 커밋 반영 --- .../controller/ApplicationController.java | 5 +- .../common/exception/ErrorCode.java | 5 + .../news/config/NewsProperties.java | 9 + .../news/controller/NewsController.java | 80 +++++ .../solidconnection/news/domain/News.java | 32 +- .../news/dto/NewsCommandResponse.java | 13 + .../news/dto/NewsCreateRequest.java | 31 ++ .../news/dto/NewsListResponse.java | 11 + .../news/dto/NewsResponse.java | 25 ++ .../news/dto/NewsUpdateRequest.java | 23 ++ .../news/repository/NewsRepository.java | 11 + .../news/service/NewsCommandService.java | 103 ++++++ .../news/service/NewsQueryService.java | 27 ++ .../solidconnection/s3/domain/ImgType.java | 2 +- ...dminAccess.java => RequireRoleAccess.java} | 5 +- ...pect.java => RoleAuthorizationAspect.java} | 21 +- .../V20__add_site_user_id_fk_to_news.sql | 4 + src/main/resources/secret | 2 +- .../news/fixture/NewsFixture.java | 32 ++ .../news/fixture/NewsFixtureBuilder.java | 54 +++ .../news/service/NewsCommandServiceTest.java | 315 ++++++++++++++++++ .../news/service/NewsQueryServiceTest.java | 54 +++ .../aspect/AdminAuthorizationAspectTest.java | 88 ----- .../aspect/RoleAuthorizationAspectTest.java | 99 ++++++ .../siteuser/fixture/SiteUserFixture.java | 11 + src/test/resources/application.yml | 2 + 26 files changed, 962 insertions(+), 102 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/news/config/NewsProperties.java create mode 100644 src/main/java/com/example/solidconnection/news/controller/NewsController.java create mode 100644 src/main/java/com/example/solidconnection/news/dto/NewsCommandResponse.java create mode 100644 src/main/java/com/example/solidconnection/news/dto/NewsCreateRequest.java create mode 100644 src/main/java/com/example/solidconnection/news/dto/NewsListResponse.java create mode 100644 src/main/java/com/example/solidconnection/news/dto/NewsResponse.java create mode 100644 src/main/java/com/example/solidconnection/news/dto/NewsUpdateRequest.java create mode 100644 src/main/java/com/example/solidconnection/news/repository/NewsRepository.java create mode 100644 src/main/java/com/example/solidconnection/news/service/NewsCommandService.java create mode 100644 src/main/java/com/example/solidconnection/news/service/NewsQueryService.java rename src/main/java/com/example/solidconnection/security/annotation/{RequireAdminAccess.java => RequireRoleAccess.java} (71%) rename src/main/java/com/example/solidconnection/security/aspect/{AdminAuthorizationAspect.java => RoleAuthorizationAspect.java} (55%) create mode 100644 src/main/resources/db/migration/V20__add_site_user_id_fk_to_news.sql create mode 100644 src/test/java/com/example/solidconnection/news/fixture/NewsFixture.java create mode 100644 src/test/java/com/example/solidconnection/news/fixture/NewsFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java create mode 100644 src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java delete mode 100644 src/test/java/com/example/solidconnection/security/aspect/AdminAuthorizationAspectTest.java create mode 100644 src/test/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspectTest.java diff --git a/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java b/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java index 228c436ba..e79b4f8e3 100644 --- a/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java +++ b/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java @@ -6,7 +6,8 @@ import com.example.solidconnection.application.service.ApplicationQueryService; import com.example.solidconnection.application.service.ApplicationSubmissionService; import com.example.solidconnection.common.resolver.AuthorizedUser; -import com.example.solidconnection.security.annotation.RequireAdminAccess; +import com.example.solidconnection.security.annotation.RequireRoleAccess; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -39,7 +40,7 @@ public ResponseEntity apply( .body(applicationSubmissionResponse); } - @RequireAdminAccess + @RequireRoleAccess(roles = {Role.ADMIN}) @GetMapping public ResponseEntity getApplicants( @AuthorizedUser SiteUser siteUser, diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index ecacb1700..90a53dad3 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -42,6 +42,7 @@ public enum ErrorCode { COUNTRY_NOT_FOUND_BY_KOREAN_NAME(HttpStatus.NOT_FOUND.value(), "이름에 해당하는 국가를 찾을 수 없습니다."), GPA_SCORE_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 학점입니다."), LANGUAGE_TEST_SCORE_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 어학성적입니다."), + NEWS_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 소식지입니다."), // auth USER_ALREADY_SIGN_OUT(HttpStatus.UNAUTHORIZED.value(), "로그아웃 되었습니다."), @@ -96,6 +97,10 @@ public enum ErrorCode { USER_DO_NOT_HAVE_GPA(HttpStatus.BAD_REQUEST.value(), "해당 유저의 학점을 찾을 수 없음"), REJECTED_REASON_REQUIRED(HttpStatus.BAD_REQUEST.value(), "거절 사유가 필요합니다."), + // news + INVALID_NEWS_ACCESS(HttpStatus.BAD_REQUEST.value(), "자신의 소식지만 제어할 수 있습니다."), + + // database DATA_INTEGRITY_VIOLATION(HttpStatus.CONFLICT.value(), "데이터베이스 무결성 제약조건 위반이 발생했습니다."), diff --git a/src/main/java/com/example/solidconnection/news/config/NewsProperties.java b/src/main/java/com/example/solidconnection/news/config/NewsProperties.java new file mode 100644 index 000000000..ecd7c96b8 --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/config/NewsProperties.java @@ -0,0 +1,9 @@ +package com.example.solidconnection.news.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "news") +public record NewsProperties( + String defaultThumbnailUrl +) { +} diff --git a/src/main/java/com/example/solidconnection/news/controller/NewsController.java b/src/main/java/com/example/solidconnection/news/controller/NewsController.java new file mode 100644 index 000000000..51b739f8c --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/controller/NewsController.java @@ -0,0 +1,80 @@ +package com.example.solidconnection.news.controller; + +import com.example.solidconnection.common.resolver.AuthorizedUser; +import com.example.solidconnection.news.dto.NewsCommandResponse; +import com.example.solidconnection.news.dto.NewsCreateRequest; +import com.example.solidconnection.news.dto.NewsListResponse; +import com.example.solidconnection.news.dto.NewsUpdateRequest; +import com.example.solidconnection.news.service.NewsCommandService; +import com.example.solidconnection.news.service.NewsQueryService; +import com.example.solidconnection.security.annotation.RequireRoleAccess; +import com.example.solidconnection.siteuser.domain.Role; +import com.example.solidconnection.siteuser.domain.SiteUser; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/news") +public class NewsController { + + private final NewsQueryService newsQueryService; + private final NewsCommandService newsCommandService; + + // todo: 추후 Slice 적용 + @GetMapping + public ResponseEntity findNewsBySiteUserId( + @RequestParam(value = "site-user-id") Long siteUserId + ) { + NewsListResponse newsListResponse = newsQueryService.findNewsBySiteUserId(siteUserId); + return ResponseEntity.ok(newsListResponse); + } + + @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) + @PostMapping + public ResponseEntity createNews( + @AuthorizedUser SiteUser siteUser, + @Valid @RequestPart("newsCreateRequest") NewsCreateRequest newsCreateRequest, + @RequestParam(value = "file", required = false) MultipartFile imageFile + ) { + NewsCommandResponse newsCommandResponse = newsCommandService.createNews(siteUser.getId(), newsCreateRequest, imageFile); + return ResponseEntity.ok(newsCommandResponse); + } + + @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) + @PutMapping("/{news-id}") + public ResponseEntity updateNews( + @AuthorizedUser SiteUser siteUser, + @PathVariable("news-id") Long newsId, + @Valid @RequestPart(value = "newsUpdateRequest") NewsUpdateRequest newsUpdateRequest, + @RequestParam(value = "file", required = false) MultipartFile imageFile + ) { + NewsCommandResponse newsCommandResponse = newsCommandService.updateNews( + siteUser.getId(), + newsId, + newsUpdateRequest, + imageFile); + return ResponseEntity.ok(newsCommandResponse); + } + + @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) + @DeleteMapping("/{news-id}") + public ResponseEntity deleteNewsById( + @AuthorizedUser SiteUser siteUser, + @PathVariable("news-id") Long newsId + ) { + NewsCommandResponse newsCommandResponse = newsCommandService.deleteNewsById(siteUser, newsId); + return ResponseEntity.ok(newsCommandResponse); + } +} diff --git a/src/main/java/com/example/solidconnection/news/domain/News.java b/src/main/java/com/example/solidconnection/news/domain/News.java index 6a3bbdf1f..5443f65aa 100644 --- a/src/main/java/com/example/solidconnection/news/domain/News.java +++ b/src/main/java/com/example/solidconnection/news/domain/News.java @@ -6,13 +6,16 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; -@Getter @Entity -@NoArgsConstructor +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) @EqualsAndHashCode public class News extends BaseEntity { @@ -29,4 +32,29 @@ public class News extends BaseEntity { @Column(length = 500) private String url; + + private long siteUserId; + + public News( + String title, + String description, + String thumbnailUrl, + String url, + long siteUserId) { + this.title = title; + this.description = description; + this.thumbnailUrl = thumbnailUrl; + this.url = url; + this.siteUserId = siteUserId; + } + + public void updateNews(String title, String description, String url) { + this.title = title; + this.description = description; + this.url = url; + } + + public void updateThumbnailUrl(String thumbnailUrl) { + this.thumbnailUrl = thumbnailUrl; + } } diff --git a/src/main/java/com/example/solidconnection/news/dto/NewsCommandResponse.java b/src/main/java/com/example/solidconnection/news/dto/NewsCommandResponse.java new file mode 100644 index 000000000..fc0b8daf3 --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/dto/NewsCommandResponse.java @@ -0,0 +1,13 @@ +package com.example.solidconnection.news.dto; + +import com.example.solidconnection.news.domain.News; + +public record NewsCommandResponse( + long id +) { + public static NewsCommandResponse from(News news) { + return new NewsCommandResponse( + news.getId() + ); + } +} diff --git a/src/main/java/com/example/solidconnection/news/dto/NewsCreateRequest.java b/src/main/java/com/example/solidconnection/news/dto/NewsCreateRequest.java new file mode 100644 index 000000000..660611537 --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/dto/NewsCreateRequest.java @@ -0,0 +1,31 @@ +package com.example.solidconnection.news.dto; + +import com.example.solidconnection.news.domain.News; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import org.hibernate.validator.constraints.URL; + +public record NewsCreateRequest( + @NotBlank(message = "소식지 제목을 입력해주세요.") + @Size(max = 20, message = "소식지 제목은 20자 이하여야 합니다.") + String title, + + @NotBlank(message = "소식지 내용을 입력해주세요.") + @Size(max = 30, message = "소식지 내용은 30자 이하여야 합니다.") + String description, + + @NotBlank(message = "소식지 URL을 입력해주세요.") + @Size(max = 500, message = "소식지 URL은 500자 이하여야 합니다.") + @URL(message = "올바른 URL 형식이 아닙니다.") + String url +) { + public News toEntity(String thumbnailUrl, long siteUserId) { + return new News( + title, + description, + thumbnailUrl, + url, + siteUserId + ); + } +} diff --git a/src/main/java/com/example/solidconnection/news/dto/NewsListResponse.java b/src/main/java/com/example/solidconnection/news/dto/NewsListResponse.java new file mode 100644 index 000000000..b501b3810 --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/dto/NewsListResponse.java @@ -0,0 +1,11 @@ +package com.example.solidconnection.news.dto; + +import java.util.List; + +public record NewsListResponse( + List newsResponseList +) { + public static NewsListResponse from(List newsResponseList) { + return new NewsListResponse(newsResponseList); + } +} diff --git a/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java b/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java new file mode 100644 index 000000000..b39daffce --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java @@ -0,0 +1,25 @@ +package com.example.solidconnection.news.dto; + +import com.example.solidconnection.news.domain.News; + +import java.time.ZonedDateTime; + +public record NewsResponse( + long id, + String title, + String description, + String thumbnailUrl, + String url, + ZonedDateTime updatedAt +) { + public static NewsResponse from(News news) { + return new NewsResponse( + news.getId(), + news.getTitle(), + news.getDescription(), + news.getThumbnailUrl(), + news.getUrl(), + news.getUpdatedAt() + ); + } +} diff --git a/src/main/java/com/example/solidconnection/news/dto/NewsUpdateRequest.java b/src/main/java/com/example/solidconnection/news/dto/NewsUpdateRequest.java new file mode 100644 index 000000000..9d09001bd --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/dto/NewsUpdateRequest.java @@ -0,0 +1,23 @@ +package com.example.solidconnection.news.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import org.hibernate.validator.constraints.URL; + +public record NewsUpdateRequest( + @NotBlank(message = "소식지 제목을 입력해주세요.") + @Size(max = 20, message = "소식지 제목은 20자 이하여야 합니다.") + String title, + + @NotBlank(message = "소식지 내용을 입력해주세요.") + @Size(max = 30, message = "소식지 내용은 30자 이하여야 합니다.") + String description, + + @NotBlank(message = "소식지 URL을 입력해주세요.") + @Size(max = 500, message = "소식지 URL은 500자 이하여야 합니다.") + @URL(message = "올바른 URL 형식이 아닙니다.") + String url, + + Boolean resetToDefaultImage +) { +} diff --git a/src/main/java/com/example/solidconnection/news/repository/NewsRepository.java b/src/main/java/com/example/solidconnection/news/repository/NewsRepository.java new file mode 100644 index 000000000..3171bfcf8 --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/repository/NewsRepository.java @@ -0,0 +1,11 @@ +package com.example.solidconnection.news.repository; + +import com.example.solidconnection.news.domain.News; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface NewsRepository extends JpaRepository { + + List findAllBySiteUserIdOrderByUpdatedAtDesc(long siteUserId); +} diff --git a/src/main/java/com/example/solidconnection/news/service/NewsCommandService.java b/src/main/java/com/example/solidconnection/news/service/NewsCommandService.java new file mode 100644 index 000000000..a9d8c74a4 --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/service/NewsCommandService.java @@ -0,0 +1,103 @@ +package com.example.solidconnection.news.service; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.news.config.NewsProperties; +import com.example.solidconnection.news.domain.News; +import com.example.solidconnection.news.dto.NewsCommandResponse; +import com.example.solidconnection.news.dto.NewsCreateRequest; +import com.example.solidconnection.news.dto.NewsUpdateRequest; +import com.example.solidconnection.news.repository.NewsRepository; +import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; +import com.example.solidconnection.s3.service.S3Service; +import com.example.solidconnection.siteuser.domain.Role; +import com.example.solidconnection.siteuser.domain.SiteUser; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_NEWS_ACCESS; +import static com.example.solidconnection.common.exception.ErrorCode.NEWS_NOT_FOUND; + +@Service +@RequiredArgsConstructor +public class NewsCommandService { + + private final S3Service s3Service; + private final NewsProperties newsProperties; + private final NewsRepository newsRepository; + + @Transactional + public NewsCommandResponse createNews(long siteUserId,NewsCreateRequest newsCreateRequest, MultipartFile imageFile) { + String thumbnailUrl = getImageUrl(imageFile); + News news = newsCreateRequest.toEntity(thumbnailUrl, siteUserId); + News savedNews = newsRepository.save(news); + return NewsCommandResponse.from(savedNews); + } + + private String getImageUrl(MultipartFile imageFile) { + if (imageFile != null && !imageFile.isEmpty()) { + UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(imageFile, ImgType.NEWS); + return uploadedFile.fileUrl(); + } + return newsProperties.defaultThumbnailUrl(); + } + + @Transactional + public NewsCommandResponse updateNews( + long siteUserId, + Long newsId, + NewsUpdateRequest newsUpdateRequest, + MultipartFile imageFile) { + News news = newsRepository.findById(newsId) + .orElseThrow(() -> new CustomException(NEWS_NOT_FOUND)); + validateOwnership(news, siteUserId); + news.updateNews(newsUpdateRequest.title(), newsUpdateRequest.description(), newsUpdateRequest.url()); + updateThumbnail(news, imageFile, newsUpdateRequest.resetToDefaultImage()); + News savedNews = newsRepository.save(news); + return NewsCommandResponse.from(savedNews); + } + + private void validateOwnership(News news, long siteUserId) { + if (news.getSiteUserId() != siteUserId) { + throw new CustomException(INVALID_NEWS_ACCESS); + } + } + + private void updateThumbnail(News news, MultipartFile imageFile, Boolean resetToDefaultImage) { + if (Boolean.TRUE.equals(resetToDefaultImage)) { + deleteCustomImage(news.getThumbnailUrl()); + news.updateThumbnailUrl(newsProperties.defaultThumbnailUrl()); + } + else if (imageFile != null && !imageFile.isEmpty()) { + UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(imageFile, ImgType.NEWS); + deleteCustomImage(news.getThumbnailUrl()); + news.updateThumbnailUrl(uploadedFile.fileUrl()); + } + } + + @Transactional + public NewsCommandResponse deleteNewsById(SiteUser siteUser, Long newsId) { + News news = newsRepository.findById(newsId) + .orElseThrow(() -> new CustomException(NEWS_NOT_FOUND)); + validatePermission(siteUser, news); + deleteCustomImage(news.getThumbnailUrl()); + newsRepository.delete(news); + return NewsCommandResponse.from(news); + } + + private void validatePermission(SiteUser currentUser, News news) { + boolean isOwner = news.getSiteUserId() == currentUser.getId(); + boolean isAdmin = currentUser.getRole().equals(Role.ADMIN); + if (!isOwner && !isAdmin) { + throw new CustomException(INVALID_NEWS_ACCESS); + } + } + + private void deleteCustomImage(String imageUrl) { + if (!newsProperties.defaultThumbnailUrl().equals(imageUrl)) { + s3Service.deletePostImage(imageUrl); + } + } +} diff --git a/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java b/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java new file mode 100644 index 000000000..3b04e3b86 --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java @@ -0,0 +1,27 @@ +package com.example.solidconnection.news.service; + +import com.example.solidconnection.news.domain.News; +import com.example.solidconnection.news.dto.NewsResponse; +import com.example.solidconnection.news.dto.NewsListResponse; +import com.example.solidconnection.news.repository.NewsRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class NewsQueryService { + + private final NewsRepository newsRepository; + + @Transactional(readOnly = true) + public NewsListResponse findNewsBySiteUserId(long siteUserId) { + List newsList = newsRepository.findAllBySiteUserIdOrderByUpdatedAtDesc(siteUserId); + List newsResponseList = newsList.stream() + .map(NewsResponse::from) + .toList(); + return NewsListResponse.from(newsResponseList); + } +} diff --git a/src/main/java/com/example/solidconnection/s3/domain/ImgType.java b/src/main/java/com/example/solidconnection/s3/domain/ImgType.java index df881fe4b..7efedb1a5 100644 --- a/src/main/java/com/example/solidconnection/s3/domain/ImgType.java +++ b/src/main/java/com/example/solidconnection/s3/domain/ImgType.java @@ -4,7 +4,7 @@ @Getter public enum ImgType { - PROFILE("profile"), GPA("gpa"), LANGUAGE_TEST("language"), COMMUNITY("community"); + PROFILE("profile"), GPA("gpa"), LANGUAGE_TEST("language"), COMMUNITY("community"), NEWS("news"); private final String type; diff --git a/src/main/java/com/example/solidconnection/security/annotation/RequireAdminAccess.java b/src/main/java/com/example/solidconnection/security/annotation/RequireRoleAccess.java similarity index 71% rename from src/main/java/com/example/solidconnection/security/annotation/RequireAdminAccess.java rename to src/main/java/com/example/solidconnection/security/annotation/RequireRoleAccess.java index 682d5bdf8..aecef342d 100644 --- a/src/main/java/com/example/solidconnection/security/annotation/RequireAdminAccess.java +++ b/src/main/java/com/example/solidconnection/security/annotation/RequireRoleAccess.java @@ -1,5 +1,7 @@ package com.example.solidconnection.security.annotation; +import com.example.solidconnection.siteuser.domain.Role; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -7,5 +9,6 @@ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) -public @interface RequireAdminAccess { +public @interface RequireRoleAccess { + Role[] roles(); } diff --git a/src/main/java/com/example/solidconnection/security/aspect/AdminAuthorizationAspect.java b/src/main/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspect.java similarity index 55% rename from src/main/java/com/example/solidconnection/security/aspect/AdminAuthorizationAspect.java rename to src/main/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspect.java index 5ebba881f..b1b1f4223 100644 --- a/src/main/java/com/example/solidconnection/security/aspect/AdminAuthorizationAspect.java +++ b/src/main/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspect.java @@ -1,7 +1,8 @@ package com.example.solidconnection.security.aspect; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.security.annotation.RequireAdminAccess; +import com.example.solidconnection.security.annotation.RequireRoleAccess; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; import org.aspectj.lang.ProceedingJoinPoint; @@ -9,17 +10,18 @@ import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; +import java.util.Arrays; + import static com.example.solidconnection.common.exception.ErrorCode.ACCESS_DENIED; -import static com.example.solidconnection.siteuser.domain.Role.ADMIN; @Aspect @Component @RequiredArgsConstructor -public class AdminAuthorizationAspect { +public class RoleAuthorizationAspect { - @Around("@annotation(requireAdminAccess)") - public Object checkAdminAccess(ProceedingJoinPoint joinPoint, - RequireAdminAccess requireAdminAccess) throws Throwable { + // todo: 추후 siteUserId로 파라미터 변경 시 수정 필요 + @Around("@annotation(requireRoleAccess)") + public Object checkRoleAccess(ProceedingJoinPoint joinPoint, RequireRoleAccess requireRoleAccess) throws Throwable { SiteUser siteUser = null; for (Object arg : joinPoint.getArgs()) { if (arg instanceof SiteUser) { @@ -27,7 +29,12 @@ public Object checkAdminAccess(ProceedingJoinPoint joinPoint, break; } } - if (siteUser == null || !ADMIN.equals(siteUser.getRole())) { + if (siteUser == null) { + throw new CustomException(ACCESS_DENIED); + } + Role[] allowedRoles = requireRoleAccess.roles(); + boolean hasAccess = Arrays.asList(allowedRoles).contains(siteUser.getRole()); + if (!hasAccess) { throw new CustomException(ACCESS_DENIED); } return joinPoint.proceed(); diff --git a/src/main/resources/db/migration/V20__add_site_user_id_fk_to_news.sql b/src/main/resources/db/migration/V20__add_site_user_id_fk_to_news.sql new file mode 100644 index 000000000..84be3d84a --- /dev/null +++ b/src/main/resources/db/migration/V20__add_site_user_id_fk_to_news.sql @@ -0,0 +1,4 @@ +ALTER TABLE news + ADD COLUMN site_user_id BIGINT NOT NULL; +ALTER TABLE news + ADD CONSTRAINT fk_news_site_user_id FOREIGN KEY (site_user_id) REFERENCES site_user (id); diff --git a/src/main/resources/secret b/src/main/resources/secret index 84002e866..be52e6ce9 160000 --- a/src/main/resources/secret +++ b/src/main/resources/secret @@ -1 +1 @@ -Subproject commit 84002e86670d380219f580c6605fb7c66ed7d977 +Subproject commit be52e6ce9ca3d2c6eb51442108328b00a539510b diff --git a/src/test/java/com/example/solidconnection/news/fixture/NewsFixture.java b/src/test/java/com/example/solidconnection/news/fixture/NewsFixture.java new file mode 100644 index 000000000..44091a51b --- /dev/null +++ b/src/test/java/com/example/solidconnection/news/fixture/NewsFixture.java @@ -0,0 +1,32 @@ +package com.example.solidconnection.news.fixture; + +import com.example.solidconnection.news.domain.News; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class NewsFixture { + + private final NewsFixtureBuilder newsFixtureBuilder; + + public News 소식지(long siteUserId) { + return newsFixtureBuilder + .title("소식지 제목") + .description("소식지 설명") + .thumbnailUrl("news/5a02ba2f-38f5-4ae9-9a24-53d624a18233") + .url("https://youtu.be/test") + .siteUserId(siteUserId) + .create(); + } + + public News 소식지(long siteUserId, String thumbnailUrl) { + return newsFixtureBuilder + .title("소식지 제목") + .description("소식지 설명") + .thumbnailUrl(thumbnailUrl) + .url("https://youtu.be/test") + .siteUserId(siteUserId) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/news/fixture/NewsFixtureBuilder.java b/src/test/java/com/example/solidconnection/news/fixture/NewsFixtureBuilder.java new file mode 100644 index 000000000..5da97d93f --- /dev/null +++ b/src/test/java/com/example/solidconnection/news/fixture/NewsFixtureBuilder.java @@ -0,0 +1,54 @@ +package com.example.solidconnection.news.fixture; + +import com.example.solidconnection.news.domain.News; +import com.example.solidconnection.news.repository.NewsRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class NewsFixtureBuilder { + + private final NewsRepository newsRepository; + + private String title; + private String description; + private String thumbnailUrl; + private String url; + private long siteUserId; + + public NewsFixtureBuilder title(String title) { + this.title = title; + return this; + } + + public NewsFixtureBuilder description(String description) { + this.description = description; + return this; + } + + public NewsFixtureBuilder thumbnailUrl(String thumbnailUrl) { + this.thumbnailUrl = thumbnailUrl; + return this; + } + + public NewsFixtureBuilder url(String url) { + this.url = url; + return this; + } + + public NewsFixtureBuilder siteUserId(long siteUserId) { + this.siteUserId = siteUserId; + return this; + } + + public News create() { + News news = new News( + title, + description, + thumbnailUrl, + url, + siteUserId); + return newsRepository.save(news); + } +} diff --git a/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java b/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java new file mode 100644 index 000000000..681c2a959 --- /dev/null +++ b/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java @@ -0,0 +1,315 @@ +package com.example.solidconnection.news.service; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.news.config.NewsProperties; +import com.example.solidconnection.news.domain.News; +import com.example.solidconnection.news.dto.NewsCommandResponse; +import com.example.solidconnection.news.dto.NewsCreateRequest; +import com.example.solidconnection.news.dto.NewsUpdateRequest; +import com.example.solidconnection.news.fixture.NewsFixture; +import com.example.solidconnection.news.repository.NewsRepository; +import com.example.solidconnection.s3.domain.ImgType; +import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; +import com.example.solidconnection.s3.service.S3Service; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_NEWS_ACCESS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.never; +import static org.mockito.BDDMockito.then; + +@TestContainerSpringBootTest +@DisplayName("소식지 생성/수정/삭제 서비스 테스트") +class NewsCommandServiceTest { + + @Autowired + private NewsCommandService newsCommandService; + + @Autowired + private NewsProperties newsProperties; + + @MockBean + private S3Service s3Service; + + @Autowired + private NewsRepository newsRepository; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private NewsFixture newsFixture; + + private SiteUser user; + + @BeforeEach + void setUp() { + user = siteUserFixture.멘토(1, "mentor"); + } + + @Nested + class 소식지_생성_테스트 { + + @Test + void 소식지를_성공적으로_생성한다() { + // given + NewsCreateRequest request = createNewsCreateRequest(); + MultipartFile imageFile = createImageFile(); + String expectedImageUrl = "news/5a02ba2f-38f5-4ae9-9a24-53d624a18233"; + given(s3Service.uploadFile(any(), eq(ImgType.NEWS))) + .willReturn(new UploadedFileUrlResponse(expectedImageUrl)); + + // when + NewsCommandResponse response = newsCommandService.createNews(user.getId(), request, imageFile); + + // then + News savedNews = newsRepository.findById(response.id()).orElseThrow(); + assertThat(response.id()).isEqualTo(savedNews.getId()); + } + } + + private NewsCreateRequest createNewsCreateRequest() { + return new NewsCreateRequest("제목", "설명", "https://youtu.be/test"); + } + + @Nested + class 소식지_수정_테스트 { + + private static final String CUSTOM_IMAGE_URL = "news/custom-image-url"; + + private News originNews; + + @Nested + class 기본_필드_수정_테스트 { + + @BeforeEach + void setUp() { + originNews = newsFixture.소식지(user.getId()); + } + + @Test + void 소식지를_성공적으로_수정한다() { + // given + String expectedTitle = "제목 수정"; + String expectedDescription = "설명 수정"; + String expectedUrl = "https://youtu.be/test-edit"; + MultipartFile expectedFile = createImageFile(); + String expectedNewImageUrl = "news/5a02ba2f-38f5-4ae9-9a24-53d624a18233-edit"; + given(s3Service.uploadFile(any(), eq(ImgType.NEWS))) + .willReturn(new UploadedFileUrlResponse(expectedNewImageUrl)); + NewsUpdateRequest request = createNewsUpdateRequest( + expectedTitle, + expectedDescription, + expectedUrl, + null); + + // when + NewsCommandResponse response = newsCommandService.updateNews( + user.getId(), + originNews.getId(), + request, + expectedFile); + + // then + News savedNews = newsRepository.findById(response.id()).orElseThrow(); + assertAll( + () -> assertThat(savedNews.getTitle()).isEqualTo(expectedTitle), + () -> assertThat(savedNews.getDescription()).isEqualTo(expectedDescription), + () -> assertThat(savedNews.getThumbnailUrl()).isEqualTo(expectedNewImageUrl), + () -> assertThat(savedNews.getUrl()).isEqualTo(expectedUrl) + ); + } + + @Test + void 다른_사용자의_소식지를_수정하면_예외_응답을_반환한다() { + // given + SiteUser anotherUser = siteUserFixture.멘토(2, "anotherMentor"); + NewsUpdateRequest request = createNewsUpdateRequest( + "제목 수정", + null, + null, + null); + + // when & then + assertThatCode(() -> newsCommandService.updateNews( + anotherUser.getId(), + originNews.getId(), + request, + null)) + .isInstanceOf(CustomException.class) + .hasMessage(INVALID_NEWS_ACCESS.getMessage()); + } + } + + @Nested + class 커스텀_이미지_관련_수정_테스트 { + + @BeforeEach + void setUp() { + originNews = newsFixture.소식지(user.getId(), CUSTOM_IMAGE_URL); + } + + @Test + void 기본_이미지로_변경_요청시_기존_커스텀_이미지를_삭제하고_기본_이미지로_변경한다() { + // given + NewsUpdateRequest request = createNewsUpdateRequest( + null, + null, + null, + true); + + // when + NewsCommandResponse response = newsCommandService.updateNews( + user.getId(), + originNews.getId(), + request, + null); + + // then + News savedNews = newsRepository.findById(response.id()).orElseThrow(); + assertAll( + () -> assertThat(savedNews.getThumbnailUrl()).isEqualTo(newsProperties.defaultThumbnailUrl()), + () -> then(s3Service).should().deletePostImage(CUSTOM_IMAGE_URL), + () -> then(s3Service).should(never()).uploadFile(null, ImgType.NEWS) + ); + } + + @Test + void 새_이미지_업로드시_기존_커스텀_이미지를_삭제하고_새_이미지로_변경한다() { + // given + MultipartFile newImageFile = createImageFile(); + String newImageUrl = "news/new-image-url"; + given(s3Service.uploadFile(newImageFile, ImgType.NEWS)) + .willReturn(new UploadedFileUrlResponse(newImageUrl)); + NewsUpdateRequest request = createNewsUpdateRequest( + null, + null, + null, + null); + + // when + NewsCommandResponse response = newsCommandService.updateNews( + user.getId(), + originNews.getId(), + request, + newImageFile); + + // then + News savedNews = newsRepository.findById(response.id()).orElseThrow(); + assertAll( + () -> assertThat(savedNews.getThumbnailUrl()).isEqualTo(newImageUrl), + () -> then(s3Service).should().deletePostImage(CUSTOM_IMAGE_URL), + () -> then(s3Service).should().uploadFile(any(), any()) + ); + } + } + + @Nested + class 기본_이미지_관련_수정_테스트 { + + @BeforeEach + void setUp() { + originNews = newsFixture.소식지(user.getId(), newsProperties.defaultThumbnailUrl()); + } + + @Test + void 기본_이미지에서_기본_이미지로_변경_요청시_삭제_호출되지_않는다() { + // given + NewsUpdateRequest request = createNewsUpdateRequest( + null, + null, + null, + true); + + // when + newsCommandService.updateNews( + user.getId(), + originNews.getId(), + request, + null); + + // then + News savedNews = newsRepository.findById(originNews.getId()).orElseThrow(); + assertAll( + () -> assertThat(savedNews.getThumbnailUrl()).isEqualTo(newsProperties.defaultThumbnailUrl()), + () -> then(s3Service).should(never()).deletePostImage(newsProperties.defaultThumbnailUrl()), + () -> then(s3Service).should(never()).uploadFile(null, ImgType.NEWS) + ); + } + + @Test + void 기본_이미지에서_새_이미지_업로드시_삭제_호출되지_않고_새_이미지로_변경한다() { + // given + MultipartFile newImageFile = createImageFile(); + String newImageUrl = "news/new-image-url"; + given(s3Service.uploadFile(newImageFile, ImgType.NEWS)) + .willReturn(new UploadedFileUrlResponse(newImageUrl)); + NewsUpdateRequest request = createNewsUpdateRequest(null, null, null, null); + + // when + newsCommandService.updateNews( + user.getId(), + originNews.getId(), + request, + newImageFile); + + // then + News savedNews = newsRepository.findById(originNews.getId()).orElseThrow(); + assertAll( + () -> assertThat(savedNews.getThumbnailUrl()).isEqualTo(newImageUrl), + () -> then(s3Service).should(never()).deletePostImage(newsProperties.defaultThumbnailUrl()), + () -> then(s3Service).should().uploadFile(any(), any()) + ); + } + } + } + + private NewsUpdateRequest createNewsUpdateRequest(String title, String description, String url, Boolean resetToDefaultImage) { + return new NewsUpdateRequest(title, description, url, resetToDefaultImage); + } + + private MockMultipartFile createImageFile() { + return new MockMultipartFile( + "image", + "test.jpg", + "image/jpeg", + "test image content".getBytes() + ); + } + + @Nested + class 소식지_삭제_테스트 { + + @Test + void 소식지를_성공적으로_삭제한다() { + // given + News originNews = newsFixture.소식지(user.getId()); + String expectedImageUrl = originNews.getThumbnailUrl(); + + // when + NewsCommandResponse response = newsCommandService.deleteNewsById(user, originNews.getId()); + + // then + assertAll( + () -> assertThat(response.id()).isEqualTo(originNews.getId()), + () -> assertThat(newsRepository.findById(originNews.getId())).isEmpty(), + () -> then(s3Service).should().deletePostImage(expectedImageUrl) + ); + } + } +} diff --git a/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java b/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java new file mode 100644 index 000000000..73926a6dc --- /dev/null +++ b/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java @@ -0,0 +1,54 @@ +package com.example.solidconnection.news.service; + +import com.example.solidconnection.news.domain.News; +import com.example.solidconnection.news.dto.NewsResponse; +import com.example.solidconnection.news.dto.NewsListResponse; +import com.example.solidconnection.news.fixture.NewsFixture; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.Comparator; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +@TestContainerSpringBootTest +@DisplayName("소식지 조회 서비스 테스트") +class NewsQueryServiceTest { + + @Autowired + private NewsQueryService newsQueryService; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private NewsFixture newsFixture; + + @Test + void 특정_사용자의_소식지_목록을_성공적으로_조회한다() { + // given + SiteUser user1 = siteUserFixture.멘토(1, "mentor1"); + SiteUser user2 = siteUserFixture.멘토(2, "mentor2"); + News news1 = newsFixture.소식지(user1.getId()); + News news2 = newsFixture.소식지(user1.getId()); + newsFixture.소식지(user2.getId()); + List newsList = List.of(news1, news2); + + // when + NewsListResponse response = newsQueryService.findNewsBySiteUserId(user1.getId()); + + // then + assertAll( + () -> assertThat(response.newsResponseList()).hasSize(newsList.size()), + () -> assertThat(response.newsResponseList()) + .extracting(NewsResponse::updatedAt) + .isSortedAccordingTo(Comparator.reverseOrder()) + ); + } +} diff --git a/src/test/java/com/example/solidconnection/security/aspect/AdminAuthorizationAspectTest.java b/src/test/java/com/example/solidconnection/security/aspect/AdminAuthorizationAspectTest.java deleted file mode 100644 index 343bbbc30..000000000 --- a/src/test/java/com/example/solidconnection/security/aspect/AdminAuthorizationAspectTest.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.example.solidconnection.security.aspect; - -import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.security.annotation.RequireAdminAccess; -import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.fixture.SiteUserFixture; -import com.example.solidconnection.support.TestContainerSpringBootTest; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.stereotype.Component; - -import static com.example.solidconnection.common.exception.ErrorCode.ACCESS_DENIED; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; - -@TestContainerSpringBootTest -@DisplayName("어드민 권한 검사 Aspect 테스트") -class AdminAuthorizationAspectTest { - - @Autowired - private TestService testService; - - @Autowired - private SiteUserFixture siteUserFixture; - - @Test - void 어드민_사용자는_어드민_전용_메소드에_접근할_수_있다() { - // given - SiteUser admin = siteUserFixture.관리자(); - - // when - boolean response = testService.adminOnlyMethod(admin); - - // then - assertThat(response).isTrue(); - } - - @Test - void 일반_사용자가_어드민_전용_메소드에_접근하면_예외_응답을_반환한다() { - // given - SiteUser user = siteUserFixture.사용자(); - - // when & then - assertThatCode(() -> testService.adminOnlyMethod(user)) - .isInstanceOf(CustomException.class) - .hasMessage(ACCESS_DENIED.getMessage()); - } - - @Test - void 어드민_어노테이션이_없는_메소드는_모두_접근_가능하다() { - // given - SiteUser user = siteUserFixture.사용자(); - SiteUser admin = siteUserFixture.관리자(); - - // when - boolean menteeResponse = testService.publicMethod(user); - boolean adminResponse = testService.publicMethod(admin); - - // then - assertThat(menteeResponse).isTrue(); - assertThat(adminResponse).isTrue(); - } - - @TestConfiguration - static class TestConfig { - - @Bean - public TestService testService() { - return new TestService(); - } - } - - @Component - static class TestService { - - @RequireAdminAccess - public boolean adminOnlyMethod(SiteUser siteUser) { - return true; - } - - public boolean publicMethod(SiteUser siteUser) { - return true; - } - } -} diff --git a/src/test/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspectTest.java b/src/test/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspectTest.java new file mode 100644 index 000000000..a3a1333e8 --- /dev/null +++ b/src/test/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspectTest.java @@ -0,0 +1,99 @@ +package com.example.solidconnection.security.aspect; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.security.annotation.RequireRoleAccess; +import com.example.solidconnection.siteuser.domain.Role; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +import static com.example.solidconnection.common.exception.ErrorCode.ACCESS_DENIED; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + +@TestContainerSpringBootTest +@DisplayName("권한 검사 Aspect 테스트") +class RoleAuthorizationAspectTest { + + @Autowired + private TestService testService; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Test + void 요구하는_역할을_가진_사용자는_메서드를_정상적으로_호출할_수_있다() { + // given + SiteUser admin = siteUserFixture.관리자(); + SiteUser mentor = siteUserFixture.멘토(1, "mentor"); + + // when & then + assertAll( + () -> assertThatCode(() -> testService.adminOnlyMethod(admin)) + .doesNotThrowAnyException(), + () -> assertThatCode(() -> testService.mentorOrAdminMethod(mentor)) + .doesNotThrowAnyException() + ); + } + + @Test + void 요구하는_역할이_없는_사용자가_메서드를_호출하면_예외가_발생한다() { + // given + SiteUser user = siteUserFixture.사용자(); + + // when & then + assertThatCode(() -> testService.mentorOrAdminMethod(user)) + .isInstanceOf(CustomException.class) + .hasMessage(ACCESS_DENIED.getMessage()); + } + + @Test + void 역할을_요구하지_않는_메서드는_누구나_호출할_수_있다() { + // given + SiteUser admin = siteUserFixture.관리자(); + SiteUser mentor = siteUserFixture.멘토(1, "mentor"); + SiteUser user = siteUserFixture.사용자(); + + // when & then + assertAll( + () -> assertThatCode(() -> testService.publicMethod(admin)) + .doesNotThrowAnyException(), + () -> assertThatCode(() -> testService.publicMethod(mentor)) + .doesNotThrowAnyException(), + () -> assertThatCode(() -> testService.publicMethod(user)) + .doesNotThrowAnyException() + ); + } + + @TestConfiguration + static class TestConfig { + @Bean + public TestService testService() { + return new TestService(); + } + } + + @Component + static class TestService { + + @RequireRoleAccess(roles = {Role.ADMIN}) + public boolean adminOnlyMethod(SiteUser siteUser) { + return true; + } + + @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) + public boolean mentorOrAdminMethod(SiteUser siteUser) { + return true; + } + + public boolean publicMethod(SiteUser siteUser) { + return true; + } + } +} diff --git a/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixture.java b/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixture.java index 664a727c1..9c2eb12bc 100644 --- a/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixture.java +++ b/src/test/java/com/example/solidconnection/siteuser/fixture/SiteUserFixture.java @@ -56,6 +56,17 @@ public class SiteUserFixture { .create(); } + public SiteUser 멘토(int index, String nickname) { + return siteUserFixtureBuilder.siteUser() + .email("mentor" + index + "@example.com") + .authType(AuthType.EMAIL) + .nickname(nickname) + .profileImageUrl("profileImageUrl") + .role(Role.MENTOR) + .password("mentor123") + .create(); + } + public SiteUser 관리자() { return siteUserFixtureBuilder.siteUser() .email("admin@example.com") diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 7c6f83171..83fe6e8cf 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -71,3 +71,5 @@ jwt: cors: allowed-origins: - "http://localhost:8080" +news: + default-thumbnail-url: "default-thumbnail-url" From fe7aefc911099c29e3166f1f31798380a45436ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Thu, 3 Jul 2025 00:07:52 +0900 Subject: [PATCH 34/90] =?UTF-8?q?feat:=20=EC=86=8C=EC=8B=9D=EC=A7=80=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=97=94=ED=8B=B0=ED=8B=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#359)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: flyway 스크립트 추가 * feat: 소식지 좋아요 엔티티 추가 --- .../news/domain/LikedNews.java | 36 +++++++++++++++++++ .../V21__create_liked_news_table.sql | 9 +++++ 2 files changed, 45 insertions(+) create mode 100644 src/main/java/com/example/solidconnection/news/domain/LikedNews.java create mode 100644 src/main/resources/db/migration/V21__create_liked_news_table.sql diff --git a/src/main/java/com/example/solidconnection/news/domain/LikedNews.java b/src/main/java/com/example/solidconnection/news/domain/LikedNews.java new file mode 100644 index 000000000..a50e31659 --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/domain/LikedNews.java @@ -0,0 +1,36 @@ +package com.example.solidconnection.news.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(uniqueConstraints = { + @UniqueConstraint( + name = "uk_liked_news_site_user_id_news_id", + columnNames = {"site_user_id", "news_id"} + ) +}) +public class LikedNews { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "news_id") + private long newsId; + + @Column(name = "site_user_id") + private long siteUserId; +} diff --git a/src/main/resources/db/migration/V21__create_liked_news_table.sql b/src/main/resources/db/migration/V21__create_liked_news_table.sql new file mode 100644 index 000000000..e80f65fc1 --- /dev/null +++ b/src/main/resources/db/migration/V21__create_liked_news_table.sql @@ -0,0 +1,9 @@ +CREATE TABLE liked_news ( + id BIGINT NOT NULL AUTO_INCREMENT, + news_id BIGINT NOT NULL, + site_user_id BIGINT NOT NULL, + PRIMARY KEY (id), + CONSTRAINT uk_liked_news_site_user_id_news_id UNIQUE (site_user_id, news_id), + CONSTRAINT fk_liked_news_news_id FOREIGN KEY (news_id) REFERENCES news(id), + CONSTRAINT fk_liked_news_site_user_id FOREIGN KEY (site_user_id) REFERENCES site_user(id) +); From 7ef425aa1adbf00b78d58d38b7d59f94a65808d4 Mon Sep 17 00:00:00 2001 From: seonghyeok cho <65901319+whqtker@users.noreply.github.com> Date: Mon, 7 Jul 2025 21:59:33 +0900 Subject: [PATCH 35/90] =?UTF-8?q?feat:=20=EB=A9=98=ED=86=A0=EB=A7=81=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#362)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 멘토링 신청 구현 * feat: 멘토링 요청 목록 조회, 멘토링 수락/거절 구현 * feat: 멘토링 확인 구현 * feat: 멘토링 신규 신청 건수 조회 구현 * chore: 전반적으로 로직 수정 - 통일성 있게 .ok(response) 사용 - 단일 객체는 단수형(response) 사용 * chore: 불필요한 개행 제거 * chore: rebase 시 반영되지 않는 로직 추가 * chore: 멘토 관련 로직에 권한 부여 * test: 멘토 관련 픽스처 생성 * test: 멘토링 조회 서비스 테스트 구현 * fix: 이미 멘토인 사용자 검증 로직 수정 - 파라미터는 siteUserId이므로 이를 통해 멘토 존재 여부를 구해야 함. * chore: 멘토가 자기 자신에 대해 멘토링을 신청하는 경우 검증 삭제 - 멘토링을 신청하는 사용자가 멘토인지 여부로 검증 가능 * chore: 미사용 테스트 메서드 삭제 * test: 멘토링 CUD 테스트 구현 * refactor: List 자체를 Response로 리턴하도록 수정 * refactor: Long -> long으로 변경 * refactor: 개행 추가 * refactor: 처음 개행 추가, Long -> long * refactor: 메서드 간 개행 추가 * chore: 불필요한 테스트 코드 제거 * refactor: 테스트 코드 리팩터링 - import 문 개행 추가 - 테스트 이름 컨벤션 준수하도록 변경 * refactor: 컨벤션에 맞게 코드 수정 - private 함수는 마지막으로 사용되는 public 함수 아래에 작성한다. - Long -> long * refactor: 비즈니스 로직 흐름에 맞게 순서 변경 * chore: 코드 컨벤션 따르도록 수정 - DTO 첫 개행 제거 - import 문 개행 수정 * refactor: 멘토 여부 검증을 서비스 말고 컨트롤러에서 처리하도록 수정 - RequireRoleAccess 어노테이션을 사용하여 멘티 여부 검증 * chore: 컨벤션 관련 수정 - Long -> long - 의미에 맞도록 서비스 계층의 validate 함수명 변경 * refactor: API 명세에 맞게 응답 수정 * chore: 오타 수정 * refactor: 빌더 패턴 제거 * chore: validateMentoringOwnership 메서드 바디 수정 - 주석의 내용을 잘 대변하도록 변경 * chore: 코딩 컨벤션에 맞도록 수정 - 파라미터 3개인 경우 개행하지 않는다 - 적절한 이름으로 컨트롤러 메서드명 수정 * chore: 코딩 컨벤션에 맞도록 수정 - 적절한 이름으로 컨트롤러 메서드명 수정 * chore: 코딩 컨벤션에 맞도록 수정 - 불필요한 개행 제거 --- .../common/exception/ErrorCode.java | 7 + .../controller/MentoringController.java | 83 +++++++ .../solidconnection/mentor/domain/Mentor.java | 4 + .../mentor/domain/Mentoring.java | 20 ++ .../mentor/dto/MentoringApplyRequest.java | 9 + .../mentor/dto/MentoringApplyResponse.java | 12 + .../mentor/dto/MentoringCheckResponse.java | 10 + .../mentor/dto/MentoringConfirmRequest.java | 12 + .../mentor/dto/MentoringConfirmResponse.java | 11 + .../mentor/dto/MentoringCountResponse.java | 10 + .../mentor/dto/MentoringListResponse.java | 11 + .../mentor/dto/MentoringResponse.java | 24 ++ .../mentor/repository/MentorRepository.java | 13 + .../repository/MentoringRepository.java | 13 + .../service/MentoringCommandService.java | 90 +++++++ .../mentor/service/MentoringQueryService.java | 57 +++++ .../mentor/fixture/MentorFixture.java | 21 ++ .../mentor/fixture/MentorFixtureBuilder.java | 68 +++++ .../mentor/fixture/MentoringFixture.java | 60 +++++ .../fixture/MentoringFixtureBuilder.java | 77 ++++++ .../service/MentoringCommandServiceTest.java | 232 ++++++++++++++++++ .../service/MentoringQueryServiceTest.java | 113 +++++++++ 22 files changed, 957 insertions(+) create mode 100644 src/main/java/com/example/solidconnection/mentor/controller/MentoringController.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentoringApplyRequest.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentoringApplyResponse.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentoringCheckResponse.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmRequest.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmResponse.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentoringCountResponse.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentoringListResponse.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentoringResponse.java create mode 100644 src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java create mode 100644 src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java create mode 100644 src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java create mode 100644 src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java create mode 100644 src/test/java/com/example/solidconnection/mentor/fixture/MentorFixture.java create mode 100644 src/test/java/com/example/solidconnection/mentor/fixture/MentorFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java create mode 100644 src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java create mode 100644 src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 90a53dad3..ee3f8e112 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -104,6 +104,13 @@ public enum ErrorCode { // database DATA_INTEGRITY_VIOLATION(HttpStatus.CONFLICT.value(), "데이터베이스 무결성 제약조건 위반이 발생했습니다."), + // mentor + ALREADY_MENTOR(HttpStatus.BAD_REQUEST.value(), "이미 멘토로 등록된 사용자입니다."), + MENTOR_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "해당 사용자는 멘토로 등록되어 있지 않습니다."), + MENTORING_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "해당 멘토링 신청을 찾을 수 없습니다."), + UNAUTHORIZED_MENTORING(HttpStatus.FORBIDDEN.value(), "멘토링 권한이 없습니다."), + MENTORING_ALREADY_CONFIRMED(HttpStatus.BAD_REQUEST.value(), "이미 승인 또는 거절된 멘토링입니다."), + // general JSON_PARSING_FAILED(HttpStatus.BAD_REQUEST.value(), "JSON 파싱을 할 수 없습니다."), JWT_EXCEPTION(HttpStatus.BAD_REQUEST.value(), "JWT 토큰을 처리할 수 없습니다."), diff --git a/src/main/java/com/example/solidconnection/mentor/controller/MentoringController.java b/src/main/java/com/example/solidconnection/mentor/controller/MentoringController.java new file mode 100644 index 000000000..9df73e41c --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/controller/MentoringController.java @@ -0,0 +1,83 @@ +package com.example.solidconnection.mentor.controller; + +import com.example.solidconnection.common.resolver.AuthorizedUser; +import com.example.solidconnection.mentor.dto.MentoringApplyRequest; +import com.example.solidconnection.mentor.dto.MentoringApplyResponse; +import com.example.solidconnection.mentor.dto.MentoringCheckResponse; +import com.example.solidconnection.mentor.dto.MentoringConfirmRequest; +import com.example.solidconnection.mentor.dto.MentoringConfirmResponse; +import com.example.solidconnection.mentor.dto.MentoringCountResponse; +import com.example.solidconnection.mentor.dto.MentoringListResponse; +import com.example.solidconnection.mentor.service.MentoringCommandService; +import com.example.solidconnection.mentor.service.MentoringQueryService; +import com.example.solidconnection.security.annotation.RequireRoleAccess; +import com.example.solidconnection.siteuser.domain.Role; +import com.example.solidconnection.siteuser.domain.SiteUser; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/mentorings") +public class MentoringController { + + private final MentoringCommandService mentoringCommandService; + private final MentoringQueryService mentoringQueryService; + + @RequireRoleAccess(roles = Role.MENTEE) + @PostMapping("/apply") + public ResponseEntity applyMentoring( + @AuthorizedUser SiteUser siteUser, + @Valid @RequestBody MentoringApplyRequest mentoringApplyRequest + ) { + MentoringApplyResponse response = mentoringCommandService.applyMentoring(siteUser.getId(), mentoringApplyRequest); + return ResponseEntity.ok(response); + } + + @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) + @GetMapping("/apply") + public ResponseEntity getMentorings( + @AuthorizedUser SiteUser siteUser + ) { + MentoringListResponse responses = mentoringQueryService.getMentorings(siteUser.getId()); + return ResponseEntity.ok(responses); + } + + @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) + @PatchMapping("/{mentoring-id}/apply") + public ResponseEntity confirmMentoring( + @AuthorizedUser SiteUser siteUser, + @PathVariable("mentoring-id") Long mentoringId, + @Valid @RequestBody MentoringConfirmRequest mentoringConfirmRequest + ) { + MentoringConfirmResponse response = mentoringCommandService.confirmMentoring(siteUser.getId(), mentoringId, mentoringConfirmRequest); + return ResponseEntity.ok(response); + } + + @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) + @PatchMapping("/{mentoring-id}/check") + public ResponseEntity checkMentoring( + @AuthorizedUser SiteUser siteUser, + @PathVariable("mentoring-id") Long mentoringId + ) { + MentoringCheckResponse response = mentoringCommandService.checkMentoring(siteUser.getId(), mentoringId); + return ResponseEntity.ok(response); + } + + @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) + @GetMapping("/check") + public ResponseEntity getUncheckedMentoringsCount( + @AuthorizedUser SiteUser siteUser + ) { + MentoringCountResponse response = mentoringQueryService.getNewMentoringsCount(siteUser.getId()); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java index 81f9d6177..87fe963a1 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java @@ -45,4 +45,8 @@ public class Mentor { @OneToMany(mappedBy = "mentor", cascade = CascadeType.ALL, orphanRemoval = true) private List channels = new ArrayList<>(); + + public void increaseMenteeCount() { + this.menteeCount++; + } } diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java b/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java index 38811a014..dd2353df6 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java @@ -56,8 +56,28 @@ public class Mentoring { @Column private long menteeId; + public Mentoring(long mentorId, long menteeId, VerifyStatus verifyStatus) { + this.mentorId = mentorId; + this.menteeId = menteeId; + this.verifyStatus = verifyStatus; + } + @PrePersist public void onPrePersist() { this.createdAt = ZonedDateTime.now(UTC).truncatedTo(MICROS); // 나노초 6자리 까지만 저장 } + + public void confirm(VerifyStatus status, String rejectedReason) { + this.verifyStatus = status; + this.rejectedReason = rejectedReason; + this.confirmedAt = ZonedDateTime.now(UTC).truncatedTo(MICROS); + + if (this.checkedAt == null) { + this.checkedAt = this.confirmedAt; + } + } + + public void check() { + this.checkedAt = ZonedDateTime.now(UTC).truncatedTo(MICROS); + } } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringApplyRequest.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringApplyRequest.java new file mode 100644 index 000000000..27d03afae --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringApplyRequest.java @@ -0,0 +1,9 @@ +package com.example.solidconnection.mentor.dto; + +import jakarta.validation.constraints.NotNull; + +public record MentoringApplyRequest( + @NotNull(message = "멘토 id를 입력해주세요.") + Long mentorId +) { +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringApplyResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringApplyResponse.java new file mode 100644 index 000000000..d77523aa7 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringApplyResponse.java @@ -0,0 +1,12 @@ +package com.example.solidconnection.mentor.dto; + +import com.example.solidconnection.mentor.domain.Mentoring; + +public record MentoringApplyResponse( + long mentoringId +) { + + public static MentoringApplyResponse from(Mentoring mentoring) { + return new MentoringApplyResponse(mentoring.getId()); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringCheckResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringCheckResponse.java new file mode 100644 index 000000000..581ddd141 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringCheckResponse.java @@ -0,0 +1,10 @@ +package com.example.solidconnection.mentor.dto; + +public record MentoringCheckResponse( + long mentoringId +) { + + public static MentoringCheckResponse from(long mentoringId) { + return new MentoringCheckResponse(mentoringId); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmRequest.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmRequest.java new file mode 100644 index 000000000..8436f263e --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmRequest.java @@ -0,0 +1,12 @@ +package com.example.solidconnection.mentor.dto; + +import com.example.solidconnection.common.VerifyStatus; +import jakarta.validation.constraints.NotNull; + +public record MentoringConfirmRequest( + @NotNull(message = "승인 상태를 설정해주세요.") + VerifyStatus status, + + String rejectedReason +) { +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmResponse.java new file mode 100644 index 000000000..79ad48bf4 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmResponse.java @@ -0,0 +1,11 @@ +package com.example.solidconnection.mentor.dto; + +import com.example.solidconnection.mentor.domain.Mentoring; + +public record MentoringConfirmResponse( + long mentoringId +) { + public static MentoringConfirmResponse from(Mentoring mentoring) { + return new MentoringConfirmResponse(mentoring.getId()); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringCountResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringCountResponse.java new file mode 100644 index 000000000..428b0b7f3 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringCountResponse.java @@ -0,0 +1,10 @@ +package com.example.solidconnection.mentor.dto; + +public record MentoringCountResponse( + int uncheckedCount +) { + + public static MentoringCountResponse from(int uncheckedCount) { + return new MentoringCountResponse(uncheckedCount); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringListResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringListResponse.java new file mode 100644 index 000000000..d943db618 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringListResponse.java @@ -0,0 +1,11 @@ +package com.example.solidconnection.mentor.dto; + +import java.util.List; + +public record MentoringListResponse( + List requests +) { + public static MentoringListResponse from(List requests) { + return new MentoringListResponse(requests); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringResponse.java new file mode 100644 index 000000000..595f28b7b --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringResponse.java @@ -0,0 +1,24 @@ +package com.example.solidconnection.mentor.dto; + +import com.example.solidconnection.mentor.domain.Mentoring; +import com.example.solidconnection.siteuser.domain.SiteUser; + +import java.time.ZonedDateTime; + +public record MentoringResponse( + long mentoringId, + String profileImageUrl, + String nickname, + boolean isChecked, + ZonedDateTime createdAt +) { + public static MentoringResponse from(Mentoring mentoring, SiteUser mentee) { + return new MentoringResponse( + mentoring.getId(), + mentee.getProfileImageUrl(), + mentee.getNickname(), + mentoring.getCheckedAt() != null, + mentoring.getCreatedAt() + ); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java new file mode 100644 index 000000000..a8d6c91c3 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java @@ -0,0 +1,13 @@ +package com.example.solidconnection.mentor.repository; + +import com.example.solidconnection.mentor.domain.Mentor; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface MentorRepository extends JpaRepository { + + Optional findBySiteUserId(long siteUserId); + + boolean existsBySiteUserId(long siteUserId); +} diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java new file mode 100644 index 000000000..d0bb648eb --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java @@ -0,0 +1,13 @@ +package com.example.solidconnection.mentor.repository; + +import com.example.solidconnection.mentor.domain.Mentoring; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface MentoringRepository extends JpaRepository { + + List findAllByMentorId(long mentorId); + + int countByMentorIdAndCheckedAtIsNull(long mentorId); +} diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java b/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java new file mode 100644 index 000000000..f7a9495ad --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java @@ -0,0 +1,90 @@ +package com.example.solidconnection.mentor.service; + +import com.example.solidconnection.common.VerifyStatus; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.domain.Mentoring; +import com.example.solidconnection.mentor.dto.MentoringApplyRequest; +import com.example.solidconnection.mentor.dto.MentoringApplyResponse; +import com.example.solidconnection.mentor.dto.MentoringCheckResponse; +import com.example.solidconnection.mentor.dto.MentoringConfirmRequest; +import com.example.solidconnection.mentor.dto.MentoringConfirmResponse; +import com.example.solidconnection.mentor.repository.MentorRepository; +import com.example.solidconnection.mentor.repository.MentoringRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static com.example.solidconnection.common.exception.ErrorCode.MENTORING_ALREADY_CONFIRMED; +import static com.example.solidconnection.common.exception.ErrorCode.MENTORING_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.REJECTED_REASON_REQUIRED; +import static com.example.solidconnection.common.exception.ErrorCode.UNAUTHORIZED_MENTORING; + +@Service +@RequiredArgsConstructor +public class MentoringCommandService { + + private final MentoringRepository mentoringRepository; + private final MentorRepository mentorRepository; + + @Transactional + public MentoringApplyResponse applyMentoring(long siteUserId, MentoringApplyRequest mentoringApplyRequest) { + Mentoring mentoring = new Mentoring(mentoringApplyRequest.mentorId(), siteUserId, VerifyStatus.PENDING); + + return MentoringApplyResponse.from(mentoringRepository.save(mentoring)); + } + + @Transactional + public MentoringConfirmResponse confirmMentoring(long siteUserId, long mentoringId, MentoringConfirmRequest mentoringConfirmRequest) { + Mentoring mentoring = mentoringRepository.findById(mentoringId) + .orElseThrow(() -> new CustomException(MENTORING_NOT_FOUND)); + + Mentor mentor = mentorRepository.findBySiteUserId(siteUserId) + .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); + + validateMentoringOwnership(mentor, mentoring); + validateMentoringNotConfirmed(mentoring); + + if (mentoringConfirmRequest.status() == VerifyStatus.REJECTED + && (mentoringConfirmRequest.rejectedReason() == null || mentoringConfirmRequest.rejectedReason().isBlank())) { + throw new CustomException(REJECTED_REASON_REQUIRED); + } + + mentoring.confirm(mentoringConfirmRequest.status(), mentoringConfirmRequest.rejectedReason()); + + if (mentoringConfirmRequest.status() == VerifyStatus.APPROVED) { + mentor.increaseMenteeCount(); + } + + return MentoringConfirmResponse.from(mentoring); + } + + private void validateMentoringNotConfirmed(Mentoring mentoring) { + if (mentoring.getVerifyStatus() != VerifyStatus.PENDING) { + throw new CustomException(MENTORING_ALREADY_CONFIRMED); + } + } + + @Transactional + public MentoringCheckResponse checkMentoring(long siteUserId, long mentoringId) { + Mentoring mentoring = mentoringRepository.findById(mentoringId) + .orElseThrow(() -> new CustomException(MENTORING_NOT_FOUND)); + + Mentor mentor = mentorRepository.findBySiteUserId(siteUserId) + .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); + + validateMentoringOwnership(mentor, mentoring); + + mentoring.check(); + + return MentoringCheckResponse.from(mentoring.getId()); + } + + // 멘토는 본인의 멘토링에 대해 confirm 및 check해야 한다. + private void validateMentoringOwnership(Mentor mentor, Mentoring mentoring) { + if (mentoring.getMentorId() != mentor.getId()) { + throw new CustomException(UNAUTHORIZED_MENTORING); + } + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java new file mode 100644 index 000000000..cf40b73a5 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java @@ -0,0 +1,57 @@ +package com.example.solidconnection.mentor.service; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.domain.Mentoring; +import com.example.solidconnection.mentor.dto.MentoringCountResponse; +import com.example.solidconnection.mentor.dto.MentoringListResponse; +import com.example.solidconnection.mentor.dto.MentoringResponse; +import com.example.solidconnection.mentor.repository.MentorRepository; +import com.example.solidconnection.mentor.repository.MentoringRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; + +@Service +@RequiredArgsConstructor +public class MentoringQueryService { + + private final MentoringRepository mentoringRepository; + private final MentorRepository mentorRepository; + private final SiteUserRepository siteUserRepository; + + @Transactional(readOnly = true) + public MentoringListResponse getMentorings(long siteUserId) { + Mentor mentor = mentorRepository.findBySiteUserId(siteUserId) + .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); + + List mentorings = mentoringRepository.findAllByMentorId(mentor.getId()); + List mentoringResponses = mentorings.stream() + .map(mentoring -> { + SiteUser mentee = siteUserRepository.findById(mentoring.getMenteeId()) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); + + return MentoringResponse.from(mentoring, mentee); + }) + .toList(); + + return MentoringListResponse.from(mentoringResponses); + } + + @Transactional(readOnly = true) + public MentoringCountResponse getNewMentoringsCount(long siteUserId) { + Mentor mentor = mentorRepository.findBySiteUserId(siteUserId) + .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); + + int count = mentoringRepository.countByMentorIdAndCheckedAtIsNull(mentor.getId()); + + return MentoringCountResponse.from(count); + } +} diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixture.java b/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixture.java new file mode 100644 index 000000000..40718b2da --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixture.java @@ -0,0 +1,21 @@ +package com.example.solidconnection.mentor.fixture; + +import com.example.solidconnection.mentor.domain.Mentor; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class MentorFixture { + + private final MentorFixtureBuilder mentorFixtureBuilder; + + public Mentor 멘토(long siteUserId, long universityId) { + return mentorFixtureBuilder.mentor() + .siteUserId(siteUserId) + .universityId(universityId) + .introduction("멘토 소개") + .passTip("멘토 팁") + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixtureBuilder.java b/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixtureBuilder.java new file mode 100644 index 000000000..d499ecc2a --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixtureBuilder.java @@ -0,0 +1,68 @@ +package com.example.solidconnection.mentor.fixture; + +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.repository.MentorRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class MentorFixtureBuilder { + + private final MentorRepository mentorRepository; + + private int menteeCount = 0; + private boolean hasBadge = false; + private String introduction; + private String passTip; + private long siteUserId; + private long universityId; + + public MentorFixtureBuilder mentor() { + return new MentorFixtureBuilder(mentorRepository); + } + + public MentorFixtureBuilder menteeCount(int menteeCount) { + this.menteeCount = menteeCount; + return this; + } + + public MentorFixtureBuilder hasBadge(boolean hasBadge) { + this.hasBadge = hasBadge; + return this; + } + + public MentorFixtureBuilder introduction(String introduction) { + this.introduction = introduction; + return this; + } + + public MentorFixtureBuilder passTip(String passTip) { + this.passTip = passTip; + return this; + } + + public MentorFixtureBuilder siteUserId(Long siteUserId) { + this.siteUserId = siteUserId; + return this; + } + + public MentorFixtureBuilder universityId(Long universityId) { + this.universityId = universityId; + return this; + } + + public Mentor create() { + Mentor mentor = new Mentor( + null, + menteeCount, + hasBadge, + introduction, + passTip, + siteUserId, + universityId, + null + ); + return mentorRepository.save(mentor); + } +} diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java new file mode 100644 index 000000000..3d5896662 --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java @@ -0,0 +1,60 @@ +package com.example.solidconnection.mentor.fixture; + +import com.example.solidconnection.common.VerifyStatus; +import com.example.solidconnection.mentor.domain.Mentoring; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +import java.time.ZonedDateTime; + +import static java.time.ZoneOffset.UTC; +import static java.time.temporal.ChronoUnit.MICROS; + +@TestComponent +@RequiredArgsConstructor +public class MentoringFixture { + + private final MentoringFixtureBuilder mentoringFixtureBuilder; + + public Mentoring 대기중_멘토링(long mentorId, long menteeId) { + return mentoringFixtureBuilder.mentoring() + .mentorId(mentorId) + .menteeId(menteeId) + .create(); + } + + public Mentoring 승인된_멘토링(long mentorId, long menteeId) { + ZonedDateTime now = getCurrentTime(); + return mentoringFixtureBuilder.mentoring() + .mentorId(mentorId) + .menteeId(menteeId) + .verifyStatus(VerifyStatus.APPROVED) + .confirmedAt(now) + .checkedAt(now) + .create(); + } + + public Mentoring 거절된_멘토링(long mentorId, long menteeId, String rejectedReason) { + ZonedDateTime now = getCurrentTime(); + return mentoringFixtureBuilder.mentoring() + .mentorId(mentorId) + .menteeId(menteeId) + .verifyStatus(VerifyStatus.REJECTED) + .rejectedReason(rejectedReason) + .confirmedAt(now) + .checkedAt(now) + .create(); + } + + public Mentoring 확인되지_않은_멘토링(long mentorId, long menteeId) { + return mentoringFixtureBuilder.mentoring() + .mentorId(mentorId) + .menteeId(menteeId) + .checkedAt(null) + .create(); + } + + private ZonedDateTime getCurrentTime() { + return ZonedDateTime.now(UTC).truncatedTo(MICROS); + } +} diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java new file mode 100644 index 000000000..0c579de5b --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java @@ -0,0 +1,77 @@ +package com.example.solidconnection.mentor.fixture; + +import com.example.solidconnection.common.VerifyStatus; +import com.example.solidconnection.mentor.domain.Mentoring; +import com.example.solidconnection.mentor.repository.MentoringRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +import java.time.ZonedDateTime; + +@TestComponent +@RequiredArgsConstructor +public class MentoringFixtureBuilder { + + private final MentoringRepository mentoringRepository; + + private ZonedDateTime createdAt; + private ZonedDateTime confirmedAt; + private ZonedDateTime checkedAt; + private VerifyStatus verifyStatus = VerifyStatus.PENDING; + private String rejectedReason; + private long mentorId; + private long menteeId; + + public MentoringFixtureBuilder mentoring() { + return new MentoringFixtureBuilder(mentoringRepository); + } + + public MentoringFixtureBuilder createdAt(ZonedDateTime createdAt) { + this.createdAt = createdAt; + return this; + } + + public MentoringFixtureBuilder confirmedAt(ZonedDateTime confirmedAt) { + this.confirmedAt = confirmedAt; + return this; + } + + public MentoringFixtureBuilder checkedAt(ZonedDateTime checkedAt) { + this.checkedAt = checkedAt; + return this; + } + + public MentoringFixtureBuilder verifyStatus(VerifyStatus verifyStatus) { + this.verifyStatus = verifyStatus; + return this; + } + + public MentoringFixtureBuilder rejectedReason(String rejectedReason) { + this.rejectedReason = rejectedReason; + return this; + } + + public MentoringFixtureBuilder mentorId(long mentorId) { + this.mentorId = mentorId; + return this; + } + + public MentoringFixtureBuilder menteeId(long menteeId) { + this.menteeId = menteeId; + return this; + } + + public Mentoring create() { + Mentoring mentoring = new Mentoring( + null, + createdAt, + confirmedAt, + checkedAt, + verifyStatus, + rejectedReason, + mentorId, + menteeId + ); + return mentoringRepository.save(mentoring); + } +} diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java new file mode 100644 index 000000000..e9d218716 --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java @@ -0,0 +1,232 @@ +package com.example.solidconnection.mentor.service; + +import com.example.solidconnection.common.VerifyStatus; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.domain.Mentoring; +import com.example.solidconnection.mentor.dto.MentoringApplyRequest; +import com.example.solidconnection.mentor.dto.MentoringApplyResponse; +import com.example.solidconnection.mentor.dto.MentoringCheckResponse; +import com.example.solidconnection.mentor.dto.MentoringConfirmRequest; +import com.example.solidconnection.mentor.dto.MentoringConfirmResponse; +import com.example.solidconnection.mentor.fixture.MentorFixture; +import com.example.solidconnection.mentor.fixture.MentoringFixture; +import com.example.solidconnection.mentor.repository.MentorRepository; +import com.example.solidconnection.mentor.repository.MentoringRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static com.example.solidconnection.common.exception.ErrorCode.MENTORING_ALREADY_CONFIRMED; +import static com.example.solidconnection.common.exception.ErrorCode.MENTORING_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.REJECTED_REASON_REQUIRED; +import static com.example.solidconnection.common.exception.ErrorCode.UNAUTHORIZED_MENTORING; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +@TestContainerSpringBootTest +@DisplayName("멘토링 CUD 서비스 테스트") +class MentoringCommandServiceTest { + + @Autowired + private MentoringCommandService mentoringCommandService; + + @Autowired + private MentorRepository mentorRepository; + + @Autowired + private MentoringRepository mentoringRepository; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private MentorFixture mentorFixture; + + @Autowired + private MentoringFixture mentoringFixture; + + private SiteUser mentorUser1; + private SiteUser mentorUser2; + + private SiteUser menteeUser; + private Mentor mentor1; + private Mentor mentor2; + + @BeforeEach + void setUp() { + mentorUser1 = siteUserFixture.멘토(1, "mentor1"); + menteeUser = siteUserFixture.사용자(2, "mentee1"); + mentorUser2 = siteUserFixture.멘토(3, "mentor2"); + + mentor1 = mentorFixture.멘토(mentorUser1.getId(), 1L); + mentor2 = mentorFixture.멘토(mentorUser2.getId(), 2L); + } + + @Nested + class 멘토링_신청_테스트 { + + @Test + void 멘토링을_성공적으로_신청한다() { + // given + MentoringApplyRequest request = new MentoringApplyRequest(mentor1.getId()); + + // when + MentoringApplyResponse response = mentoringCommandService.applyMentoring(menteeUser.getId(), request); + + // then + Mentoring mentoring = mentoringRepository.findById(response.mentoringId()).orElseThrow(); + + assertAll( + () -> assertThat(mentoring.getMentorId()).isEqualTo(mentor1.getId()), + () -> assertThat(mentoring.getMenteeId()).isEqualTo(menteeUser.getId()), + () -> assertThat(mentoring.getVerifyStatus()).isEqualTo(VerifyStatus.PENDING) + ); + } + } + + @Nested + class 멘토링_승인_거절_테스트 { + + @Test + void 멘토링을_성공적으로_승인한다() { + // given + Mentoring mentoring = mentoringFixture.대기중_멘토링(mentor1.getId(), menteeUser.getId()); + MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.APPROVED, null); + int beforeMenteeCount = mentor1.getMenteeCount(); + + // when + MentoringConfirmResponse response = mentoringCommandService.confirmMentoring(mentorUser1.getId(), mentoring.getId(), request); + + // then + Mentoring confirmedMentoring = mentoringRepository.findById(response.mentoringId()).orElseThrow(); + Mentor mentor = mentorRepository.findById(mentor1.getId()).orElseThrow(); + + assertAll( + () -> assertThat(confirmedMentoring.getVerifyStatus()).isEqualTo(VerifyStatus.APPROVED), + () -> assertThat(confirmedMentoring.getConfirmedAt()).isNotNull(), + () -> assertThat(confirmedMentoring.getCheckedAt()).isNotNull(), + () -> assertThat(mentor.getMenteeCount()).isEqualTo(beforeMenteeCount + 1) + ); + } + + @Test + void 멘토링을_성공적으로_거절한다() { + // given + Mentoring mentoring = mentoringFixture.대기중_멘토링(mentor1.getId(), menteeUser.getId()); + String rejectedReason = "멘토링 거절 사유"; + MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.REJECTED, rejectedReason); + int beforeMenteeCount = mentor1.getMenteeCount(); + + // when + MentoringConfirmResponse response = mentoringCommandService.confirmMentoring(mentorUser1.getId(), mentoring.getId(), request); + + // then + Mentoring confirmedMentoring = mentoringRepository.findById(response.mentoringId()).orElseThrow(); + Mentor mentor = mentorRepository.findById(mentor1.getId()).orElseThrow(); + + assertAll( + () -> assertThat(confirmedMentoring.getVerifyStatus()).isEqualTo(VerifyStatus.REJECTED), + () -> assertThat(confirmedMentoring.getRejectedReason()).isEqualTo(rejectedReason), + () -> assertThat(confirmedMentoring.getConfirmedAt()).isNotNull(), + () -> assertThat(confirmedMentoring.getCheckedAt()).isNotNull(), + () -> assertThat(mentor.getMenteeCount()).isEqualTo(beforeMenteeCount) + ); + } + + @Test + void 거절_시_사유가_없으면_예외_응답을_반환한다() { + // given + Mentoring mentoring = mentoringFixture.대기중_멘토링(mentor1.getId(), menteeUser.getId()); + MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.REJECTED, null); + + // when & then + assertThatThrownBy(() -> + mentoringCommandService.confirmMentoring(mentorUser1.getId(), mentoring.getId(), request)) + .isInstanceOf(CustomException.class) + .hasMessage(REJECTED_REASON_REQUIRED.getMessage()); + } + + @Test + void 다른_멘토의_멘토링을_승인할_수_없다() { + // given + Mentoring mentoring = mentoringFixture.대기중_멘토링(mentor1.getId(), menteeUser.getId()); + MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.APPROVED, null); + + // when & then + assertThatThrownBy(() -> mentoringCommandService.confirmMentoring(mentorUser2.getId(), mentoring.getId(), request)) + .isInstanceOf(CustomException.class) + .hasMessage(UNAUTHORIZED_MENTORING.getMessage()); + } + + @Test + void 이미_처리된_멘토링은_다시_승인할_수_없다() { + // given + Mentoring mentoring = mentoringFixture.승인된_멘토링(mentor1.getId(), menteeUser.getId()); + MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.APPROVED, null); + + // when & then + assertThatThrownBy(() -> mentoringCommandService.confirmMentoring(mentorUser1.getId(), mentoring.getId(), request)) + .isInstanceOf(CustomException.class) + .hasMessage(MENTORING_ALREADY_CONFIRMED.getMessage()); + } + + @Test + void 존재하지_않는_멘토링_아이디로_요청시_예외_응답을_반환한다() { + // given + MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.APPROVED, null); + long invalidMentoringId = 9999L; + + // when & then + assertThatThrownBy(() -> mentoringCommandService.confirmMentoring(mentorUser1.getId(), invalidMentoringId, request)) + .isInstanceOf(CustomException.class) + .hasMessage(MENTORING_NOT_FOUND.getMessage()); + } + } + + @Nested + class 멘토링_확인_테스트 { + + @Test + void 멘토링을_성공적으로_확인_처리한다() { + // given + Mentoring mentoring = mentoringFixture.확인되지_않은_멘토링(mentor1.getId(), menteeUser.getId()); + + // when + MentoringCheckResponse response = mentoringCommandService.checkMentoring(mentorUser1.getId(), mentoring.getId()); + + // then + Mentoring checked = mentoringRepository.findById(response.mentoringId()).orElseThrow(); + + assertThat(checked.getCheckedAt()).isNotNull(); + } + + @Test + void 다른_멘토의_멘토링은_확인할_수_없다() { + // given + Mentoring mentoring = mentoringFixture.확인되지_않은_멘토링(mentor1.getId(), menteeUser.getId()); + + // when & then + assertThatThrownBy(() -> mentoringCommandService.checkMentoring(mentorUser2.getId(), mentoring.getId())) + .isInstanceOf(CustomException.class) + .hasMessage(UNAUTHORIZED_MENTORING.getMessage()); + } + + @Test + void 존재하지_않는_멘토링_아이디로_요청시_예외_응답을_반환한다() { + // given + long invalidMentoringId = 9999L; + + // when & then + assertThatThrownBy(() -> mentoringCommandService.checkMentoring(mentorUser1.getId(), invalidMentoringId)) + .isInstanceOf(CustomException.class) + .hasMessage(MENTORING_NOT_FOUND.getMessage()); + } + } +} diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java new file mode 100644 index 000000000..869de8a3c --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java @@ -0,0 +1,113 @@ +package com.example.solidconnection.mentor.service; + +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.domain.Mentoring; +import com.example.solidconnection.mentor.dto.MentoringCountResponse; +import com.example.solidconnection.mentor.dto.MentoringListResponse; +import com.example.solidconnection.mentor.dto.MentoringResponse; +import com.example.solidconnection.mentor.fixture.MentorFixture; +import com.example.solidconnection.mentor.fixture.MentoringFixture; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +@TestContainerSpringBootTest +@DisplayName("멘토링 조회 서비스 테스트") +class MentoringQueryServiceTest { + + @Autowired + private MentoringQueryService mentoringQueryService; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private MentorFixture mentorFixture; + + @Autowired + private MentoringFixture mentoringFixture; + + private SiteUser mentorUser; + private SiteUser menteeUser; + private Mentor mentor; + + @BeforeEach + void setUp() { + mentorUser = siteUserFixture.멘토(1, "mentor1"); + menteeUser = siteUserFixture.사용자(2, "mentee1"); + mentor = mentorFixture.멘토(mentorUser.getId(), 1L); + } + + @Nested + class 멘토링_목록_조회_테스트 { + + @Test + void 멘토의_모든_멘토링을_조회한다() { + // given + Mentoring mentoring1 = mentoringFixture.대기중_멘토링(mentor.getId(), menteeUser.getId()); + Mentoring mentoring2 = mentoringFixture.승인된_멘토링(mentor.getId(), menteeUser.getId()); + Mentoring mentoring3 = mentoringFixture.거절된_멘토링(mentor.getId(), menteeUser.getId(), "거절 사유"); + + // when + MentoringListResponse responses = mentoringQueryService.getMentorings(mentorUser.getId()); + + // then + assertAll( + () -> assertThat(responses.requests()).hasSize(3), + () -> assertThat(responses.requests()).extracting(MentoringResponse::mentoringId) + .containsExactlyInAnyOrder( + mentoring1.getId(), + mentoring2.getId(), + mentoring3.getId() + ) + ); + } + + @Test + void 멘토링이_없는_경우_빈_리스트를_반환한다() { + // when + MentoringListResponse responses = mentoringQueryService.getMentorings(mentorUser.getId()); + + // then + assertThat(responses.requests()).isEmpty(); + } + } + + @Nested + class 새_멘토링_개수_조회_테스트 { + + @Test + void 확인되지_않은_멘토링_개수를_반환한다() { + // given + mentoringFixture.확인되지_않은_멘토링(mentor.getId(), menteeUser.getId()); + mentoringFixture.확인되지_않은_멘토링(mentor.getId(), menteeUser.getId()); + mentoringFixture.승인된_멘토링(mentor.getId(), menteeUser.getId()); + + // when + MentoringCountResponse response = mentoringQueryService.getNewMentoringsCount(mentorUser.getId()); + + // then + assertThat(response.uncheckedCount()).isEqualTo(2); + } + + @Test + void 확인되지_않은_멘토링이_없으면_0을_반환한다() { + // given + mentoringFixture.승인된_멘토링(mentor.getId(), menteeUser.getId()); + + // when + MentoringCountResponse response = mentoringQueryService.getNewMentoringsCount(mentorUser.getId()); + + // then + assertThat(response.uncheckedCount()).isZero(); + } + } +} From b6408833b356dd3c0d49b34478f5851ae1150c36 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Wed, 9 Jul 2025 12:02:07 +0900 Subject: [PATCH 36/90] =?UTF-8?q?feat:=20=EB=A9=98=ED=86=A0=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#370)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 멘토 단일 조회 응답 dto 생성 * feat: 멘토, 멘토링, 채널 레포지토리 생성 * feat: 멘토 단일 조회 서비스 함수 생성 * feat: 멘토 단일 조회 컨트롤러 생성 * test: 멘토 관련 픽스처 생성 * refactor: channel 연관관계 편의 메서드 추가 * test: channel 픽스쳐 생성 * test: 멘토 단일 조회 테스트 코드 작성 * feat: 멘토 미리보기 목록 조회 dto 생성 * feat: 멘토 미리보기 Batch 조회를 위한 레포지토리 생성 - Mentor와 SiteUser는 id 만 참조하는 관계이다. - Mentor와 Mentoring도 id 만 참조하는 관계이다. - "멘토 목록"에 대해서 매번 siteUser, mentoring과 join 하면 N+1 이 발생한다. - 이를 해결하기 위해, 한번에 조회하고 매핑하여 1번의 쿼리로 해결한다. * feat: 멘토 미리보기 목록 서비스 함수 생성 * feat: 멘토 미리보기 목록 컨트롤러 생성 * test: 멘토 미리보기 목록 조회 테스트 코드 작성 * test: 멘토 배치 조회 레포지토리 테스트 코드 작성 * style: 개행 삭제 * refactor: Page가 아니라 Slice를 반환받도록 - Page를 사용할 시, 추가로 발생하는 count 쿼리 방지 * refactor: Channel N+1 문제 해결 * refactor: SliceResponse로 대체 * refactor: 멘토 목록 조회 정렬 정책 구체화 - 기획팀께 답변 받은 내용 적용 * feat: 채널 응답을 sequence 오름차순으로 정렬하는 기능 구현 * refactor: SliceResponse 정적 팩터리 메서드 사용하도록 * refactor: 자연스럽게 읽히도록 파라미터 순서 변경 * refactor: 함수 이름이 의미를 드러내도록 이름 변경 * refactor: JPA 표준 따르도록 함수이름 변경 - findBy -> findAllBy * refactor: 멘토 객체에서 채널의 순서를 정렬해 가지고 있도록 - test도 더 정확하게 수정 --------- Co-authored-by: seonghyeok --- .../common/dto/SliceResponse.java | 21 ++ .../common/exception/ErrorCode.java | 2 +- .../mentor/controller/MentorController.java | 55 +++++ .../mentor/domain/Channel.java | 4 + .../solidconnection/mentor/domain/Mentor.java | 4 + .../mentor/dto/ChannelResponse.java | 17 ++ .../mentor/dto/MentorDetailResponse.java | 40 ++++ .../mentor/dto/MentorPreviewResponse.java | 38 ++++ .../mentor/repository/ChannelRepository.java | 7 + .../MentorBatchQueryRepository.java | 56 +++++ .../mentor/repository/MentorRepository.java | 6 +- .../repository/MentoringRepository.java | 12 +- .../mentor/service/MentorQueryService.java | 67 ++++++ .../mentor/fixture/ChannelFixture.java | 23 +++ .../mentor/fixture/ChannelFixtureBuilder.java | 56 +++++ .../mentor/fixture/MentorFixture.java | 2 +- .../MentorBatchQueryRepositoryTest.java | 80 ++++++++ .../service/MentorQueryServiceTest.java | 192 ++++++++++++++++++ 18 files changed, 678 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/common/dto/SliceResponse.java create mode 100644 src/main/java/com/example/solidconnection/mentor/controller/MentorController.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/ChannelResponse.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java create mode 100644 src/main/java/com/example/solidconnection/mentor/repository/ChannelRepository.java create mode 100644 src/main/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepository.java create mode 100644 src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java create mode 100644 src/test/java/com/example/solidconnection/mentor/fixture/ChannelFixture.java create mode 100644 src/test/java/com/example/solidconnection/mentor/fixture/ChannelFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java create mode 100644 src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java diff --git a/src/main/java/com/example/solidconnection/common/dto/SliceResponse.java b/src/main/java/com/example/solidconnection/common/dto/SliceResponse.java new file mode 100644 index 000000000..3f91dc7c4 --- /dev/null +++ b/src/main/java/com/example/solidconnection/common/dto/SliceResponse.java @@ -0,0 +1,21 @@ +package com.example.solidconnection.common.dto; + +import org.springframework.data.domain.Slice; + +import java.util.List; + +public record SliceResponse( + List content, + int nextPageNumber +) { + + private static final int NO_NEXT_PAGE = -1; + private static final int BASE_NUMBER = 1; // 1-based + + public static SliceResponse of(List content, Slice slice) { + int nextPageNumber = slice.hasNext() + ? slice.getNumber() + BASE_NUMBER + 1 + : NO_NEXT_PAGE; + return new SliceResponse<>(content, nextPageNumber); + } +} diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index ee3f8e112..3b851ef77 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -43,6 +43,7 @@ public enum ErrorCode { GPA_SCORE_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 학점입니다."), LANGUAGE_TEST_SCORE_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 어학성적입니다."), NEWS_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 소식지입니다."), + MENTOR_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 멘토입니다."), // auth USER_ALREADY_SIGN_OUT(HttpStatus.UNAUTHORIZED.value(), "로그아웃 되었습니다."), @@ -106,7 +107,6 @@ public enum ErrorCode { // mentor ALREADY_MENTOR(HttpStatus.BAD_REQUEST.value(), "이미 멘토로 등록된 사용자입니다."), - MENTOR_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "해당 사용자는 멘토로 등록되어 있지 않습니다."), MENTORING_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "해당 멘토링 신청을 찾을 수 없습니다."), UNAUTHORIZED_MENTORING(HttpStatus.FORBIDDEN.value(), "멘토링 권한이 없습니다."), MENTORING_ALREADY_CONFIRMED(HttpStatus.BAD_REQUEST.value(), "이미 승인 또는 거절된 멘토링입니다."), diff --git a/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java b/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java new file mode 100644 index 000000000..ea60b180c --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java @@ -0,0 +1,55 @@ +package com.example.solidconnection.mentor.controller; + +import com.example.solidconnection.common.dto.SliceResponse; +import com.example.solidconnection.common.resolver.AuthorizedUser; +import com.example.solidconnection.mentor.dto.MentorDetailResponse; +import com.example.solidconnection.mentor.dto.MentorPreviewResponse; +import com.example.solidconnection.mentor.service.MentorQueryService; +import com.example.solidconnection.siteuser.domain.SiteUser; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.data.web.SortDefault; +import org.springframework.data.web.SortDefault.SortDefaults; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import static org.springframework.data.domain.Sort.Direction.DESC; + +@RequiredArgsConstructor +@RequestMapping("/mentors") +@RestController +public class MentorController { + + private final MentorQueryService mentorQueryService; + + @GetMapping("/{mentor-id}") + public ResponseEntity getMentorDetails( + @AuthorizedUser SiteUser siteUser, + @PathVariable("mentor-id") Long mentorId + ) { + MentorDetailResponse response = mentorQueryService.getMentorDetails(mentorId, siteUser); + return ResponseEntity.ok(response); + } + + @GetMapping + public ResponseEntity> getMentorPreviews( + @AuthorizedUser SiteUser siteUser, + @RequestParam("region") String region, + + @PageableDefault(size = 3, sort = "menteeCount", direction = DESC) + @SortDefaults({ + @SortDefault(sort = "menteeCount", direction = Sort.Direction.DESC), + @SortDefault(sort = "id", direction = Sort.Direction.ASC) + }) + Pageable pageable + ) { + SliceResponse response = mentorQueryService.getMentorPreviews(region, siteUser, pageable); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Channel.java b/src/main/java/com/example/solidconnection/mentor/domain/Channel.java index 33c4f72c3..846b8e625 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Channel.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Channel.java @@ -44,4 +44,8 @@ public class Channel { @ManyToOne(fetch = FetchType.LAZY) private Mentor mentor; + + public void updateMentor(Mentor mentor) { + this.mentor = mentor; + } } diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java index 87fe963a1..542972b9e 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java @@ -7,10 +7,12 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderBy; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.BatchSize; import java.util.ArrayList; import java.util.List; @@ -43,6 +45,8 @@ public class Mentor { @Column private long universityId; + @BatchSize(size = 10) + @OrderBy("sequence ASC") @OneToMany(mappedBy = "mentor", cascade = CascadeType.ALL, orphanRemoval = true) private List channels = new ArrayList<>(); diff --git a/src/main/java/com/example/solidconnection/mentor/dto/ChannelResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/ChannelResponse.java new file mode 100644 index 000000000..cc6de7c71 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/ChannelResponse.java @@ -0,0 +1,17 @@ +package com.example.solidconnection.mentor.dto; + +import com.example.solidconnection.mentor.domain.Channel; +import com.example.solidconnection.mentor.domain.ChannelType; + +public record ChannelResponse( + ChannelType type, + String url +) { + + public static ChannelResponse from(Channel channel) { + return new ChannelResponse( + channel.getType(), + channel.getUrl() + ); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java new file mode 100644 index 000000000..cf0fbc98a --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java @@ -0,0 +1,40 @@ +package com.example.solidconnection.mentor.dto; + +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.siteuser.domain.ExchangeStatus; +import com.example.solidconnection.siteuser.domain.SiteUser; + +import java.util.List; + +public record MentorDetailResponse( + long id, + String nickname, + String profileImageUrl, + ExchangeStatus exchangeStatus, + String country, + String universityName, + int menteeCount, + boolean hasBadge, + String introduction, + List channels, + String passTip, + boolean isApplied +) { + + public static MentorDetailResponse of(Mentor mentor, SiteUser mentorUser, boolean isApplied) { + return new MentorDetailResponse( + mentor.getId(), + mentorUser.getNickname(), + mentorUser.getProfileImageUrl(), + mentorUser.getExchangeStatus(), + "국가", // todo: 교환학생 기록이 인증되면 추가 + "대학 이름", // todo: 교환학생 기록이 인증되면 추가 + mentor.getMenteeCount(), + mentor.isHasBadge(), + mentor.getIntroduction(), + mentor.getChannels().stream().map(ChannelResponse::from).toList(), + mentor.getPassTip(), + isApplied + ); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java new file mode 100644 index 000000000..bba3010f4 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java @@ -0,0 +1,38 @@ +package com.example.solidconnection.mentor.dto; + +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.siteuser.domain.ExchangeStatus; +import com.example.solidconnection.siteuser.domain.SiteUser; + +import java.util.List; + +public record MentorPreviewResponse( + long id, + String nickname, + String profileImageUrl, + ExchangeStatus exchangeStatus, + String country, + String universityName, + int menteeCount, + boolean hasBadge, + String introduction, + List channels, + boolean isApplied +) { + + public static MentorPreviewResponse of(Mentor mentor, SiteUser mentorUser, boolean isApplied) { + return new MentorPreviewResponse( + mentor.getId(), + mentorUser.getNickname(), + mentorUser.getProfileImageUrl(), + mentorUser.getExchangeStatus(), + "국가", // todo: 교환학생 기록이 인증되면 추가 + "대학 이름", // todo: 교환학생 기록이 인증되면 추가 + mentor.getMenteeCount(), + mentor.isHasBadge(), + mentor.getIntroduction(), + mentor.getChannels().stream().map(ChannelResponse::from).toList(), + isApplied + ); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/repository/ChannelRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/ChannelRepository.java new file mode 100644 index 000000000..571e4fb21 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/repository/ChannelRepository.java @@ -0,0 +1,7 @@ +package com.example.solidconnection.mentor.repository; + +import com.example.solidconnection.mentor.domain.Channel; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ChannelRepository extends JpaRepository { +} diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepository.java new file mode 100644 index 000000000..026c7aef5 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepository.java @@ -0,0 +1,56 @@ +package com.example.solidconnection.mentor.repository; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.domain.Mentoring; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static com.example.solidconnection.common.exception.ErrorCode.DATA_INTEGRITY_VIOLATION; + +@Repository +@RequiredArgsConstructor +public class MentorBatchQueryRepository { // 연관관계가 설정되지 않은 엔티티들을 N+1 없이 하나의 쿼리로 조회 + + private final SiteUserRepository siteUserRepository; + private final MentoringRepository mentoringRepository; + + public Map getMentorIdToSiteUserMap(List mentors) { + List mentorUserIds = mentors.stream().map(Mentor::getSiteUserId).toList(); + List mentorUsers = siteUserRepository.findAllById(mentorUserIds); + Map mentorUserIdToSiteUserMap = mentorUsers.stream() + .collect(Collectors.toMap(SiteUser::getId, Function.identity())); + + return mentors.stream().collect(Collectors.toMap( + Mentor::getId, + mentor -> { + SiteUser mentorUser = mentorUserIdToSiteUserMap.get(mentor.getSiteUserId()); + if (mentorUser == null) { // site_user.id == mentor.site_user_id 에 해당하는게 없으면 정합성 문제가 발생한 것 + throw new CustomException(DATA_INTEGRITY_VIOLATION, "mentor에 해당하는 siteUser 존재하지 않음"); + } + return mentorUser; + } + )); + } + + public Map getMentorIdToIsApplied(List mentors, long currentUserId) { + List mentorIds = mentors.stream().map(Mentor::getId).toList(); + List appliedMentorings = mentoringRepository.findAllByMentorIdInAndMenteeId(mentorIds, currentUserId); + Set appliedMentorIds = appliedMentorings.stream() + .map(Mentoring::getMentorId) + .collect(Collectors.toSet()); + + return mentors.stream().collect(Collectors.toMap( + Mentor::getId, + mentor -> appliedMentorIds.contains(mentor.getId()) + )); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java index a8d6c91c3..aa8328ba7 100644 --- a/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java @@ -1,13 +1,17 @@ package com.example.solidconnection.mentor.repository; import com.example.solidconnection.mentor.domain.Mentor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface MentorRepository extends JpaRepository { + boolean existsBySiteUserId(long siteUserId); + Optional findBySiteUserId(long siteUserId); - boolean existsBySiteUserId(long siteUserId); + Slice findAllBy(Pageable pageable); } diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java index d0bb648eb..76a3758f2 100644 --- a/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java @@ -2,12 +2,22 @@ import com.example.solidconnection.mentor.domain.Mentoring; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; public interface MentoringRepository extends JpaRepository { + int countByMentorIdAndCheckedAtIsNull(long mentorId); + + boolean existsByMentorIdAndMenteeId(long mentorId, long menteeId); + List findAllByMentorId(long mentorId); - int countByMentorIdAndCheckedAtIsNull(long mentorId); + @Query(""" + SELECT m FROM Mentoring m + WHERE m.mentorId IN :mentorIds AND m.menteeId = :menteeId + """) + List findAllByMentorIdInAndMenteeId(@Param("mentorIds") List mentorIds, @Param("menteeId") long menteeId); } diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java new file mode 100644 index 000000000..a7c46e285 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java @@ -0,0 +1,67 @@ +package com.example.solidconnection.mentor.service; + +import com.example.solidconnection.common.dto.SliceResponse; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.dto.MentorDetailResponse; +import com.example.solidconnection.mentor.dto.MentorPreviewResponse; +import com.example.solidconnection.mentor.repository.MentorBatchQueryRepository; +import com.example.solidconnection.mentor.repository.MentorRepository; +import com.example.solidconnection.mentor.repository.MentoringRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; + +@RequiredArgsConstructor +@Service +public class MentorQueryService { + + private final MentorRepository mentorRepository; + private final MentoringRepository mentoringRepository; + private final SiteUserRepository siteUserRepository; + private final MentorBatchQueryRepository mentorBatchQueryRepository; + + @Transactional(readOnly = true) + public MentorDetailResponse getMentorDetails(long mentorId, SiteUser currentUser) { + Mentor mentor = mentorRepository.findById(mentorId) + .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); + SiteUser mentorUser = siteUserRepository.findById(mentor.getSiteUserId()) + .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); + boolean isApplied = mentoringRepository.existsByMentorIdAndMenteeId(mentorId, currentUser.getId()); + + return MentorDetailResponse.of(mentor, mentorUser, isApplied); + } + + @Transactional(readOnly = true) + public SliceResponse getMentorPreviews(String region, SiteUser siteUser, Pageable pageable) { // todo: 멘토의 '인증' 작업 후 region 필터링 추가 + Slice mentorSlice = mentorRepository.findAllBy(pageable); + List mentors = mentorSlice.toList(); + List content = getMentorPreviewResponses(mentors, siteUser); + + return SliceResponse.of(content, mentorSlice); + } + + private List getMentorPreviewResponses(List mentors, SiteUser siteUser) { + Map mentorIdToSiteUser = mentorBatchQueryRepository.getMentorIdToSiteUserMap(mentors); + Map mentorIdToIsApplied = mentorBatchQueryRepository.getMentorIdToIsApplied(mentors, siteUser.getId()); + + List mentorPreviews = new ArrayList<>(); + for (Mentor mentor : mentors) { + SiteUser mentorUser = mentorIdToSiteUser.get(mentor.getId()); + boolean isApplied = mentorIdToIsApplied.get(mentor.getId()); + MentorPreviewResponse response = MentorPreviewResponse.of(mentor, mentorUser, isApplied); + mentorPreviews.add(response); + } + return mentorPreviews; + } +} diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/ChannelFixture.java b/src/test/java/com/example/solidconnection/mentor/fixture/ChannelFixture.java new file mode 100644 index 000000000..d3c27c114 --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/fixture/ChannelFixture.java @@ -0,0 +1,23 @@ +package com.example.solidconnection.mentor.fixture; + +import com.example.solidconnection.mentor.domain.Channel; +import com.example.solidconnection.mentor.domain.ChannelType; +import com.example.solidconnection.mentor.domain.Mentor; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ChannelFixture { + + private final ChannelFixtureBuilder channelFixtureBuilder; + + public Channel 채널(int sequence, Mentor mentor) { + return channelFixtureBuilder.channel() + .sequence(sequence) + .type(ChannelType.YOUTUBE) + .url("https://www.youtube.com/channel" + sequence) + .mentor(mentor) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/ChannelFixtureBuilder.java b/src/test/java/com/example/solidconnection/mentor/fixture/ChannelFixtureBuilder.java new file mode 100644 index 000000000..670b0e8b4 --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/fixture/ChannelFixtureBuilder.java @@ -0,0 +1,56 @@ +package com.example.solidconnection.mentor.fixture; + +import com.example.solidconnection.mentor.domain.Channel; +import com.example.solidconnection.mentor.domain.ChannelType; +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.repository.ChannelRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ChannelFixtureBuilder { + + private final ChannelRepository channelRepository; + + private int sequence; + private ChannelType type; + private String url; + private Mentor mentor; + + public ChannelFixtureBuilder channel() { + return new ChannelFixtureBuilder(channelRepository); + } + + public ChannelFixtureBuilder sequence(int sequence) { + this.sequence = sequence; + return this; + } + + public ChannelFixtureBuilder type(ChannelType type) { + this.type = type; + return this; + } + + public ChannelFixtureBuilder url(String url) { + this.url = url; + return this; + } + + public ChannelFixtureBuilder mentor(Mentor mentor) { + this.mentor = mentor; + return this; + } + + public Channel create() { + Channel channel = new Channel( + null, + sequence, + type, + url, + null + ); + channel.updateMentor(mentor); + return channelRepository.save(channel); + } +} diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixture.java b/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixture.java index 40718b2da..b612a9417 100644 --- a/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixture.java +++ b/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixture.java @@ -15,7 +15,7 @@ public class MentorFixture { .siteUserId(siteUserId) .universityId(universityId) .introduction("멘토 소개") - .passTip("멘토 팁") + .passTip("합격 팁") .create(); } } diff --git a/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java b/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java new file mode 100644 index 000000000..0ef517c34 --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java @@ -0,0 +1,80 @@ +package com.example.solidconnection.mentor.repository; + +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.domain.Mentoring; +import com.example.solidconnection.mentor.fixture.MentorFixture; +import com.example.solidconnection.mentor.fixture.MentoringFixture; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +@DisplayName("멘토 배치 조회 레포지토리 테스트") +@TestContainerSpringBootTest +class MentorBatchQueryRepositoryTest { + + @Autowired + private MentorBatchQueryRepository mentorBatchQueryRepository; + + @Autowired + private MentorFixture mentorFixture; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private MentoringFixture mentoringFixture; + + private long universityId = 1L; // todo: 멘토 인증 기능 추가 변경 필요 + private Mentor mentor1, mentor2; + private SiteUser mentorUser1, mentorUser2, currentUser; + + @BeforeEach + void setUp() { + currentUser = siteUserFixture.사용자(1, "사용자"); + mentorUser1 = siteUserFixture.사용자(2, "멘토1"); + mentorUser2 = siteUserFixture.사용자(3, "멘토2"); + mentor1 = mentorFixture.멘토(mentorUser1.getId(), universityId); + mentor2 = mentorFixture.멘토(mentorUser2.getId(), universityId); + } + + @Test + void 멘토_ID_와_멘토_사용자를_매핑한다() { + // given + List mentors = List.of(mentor1, mentor2); + + // when + Map mentorIdToSiteUser = mentorBatchQueryRepository.getMentorIdToSiteUserMap(mentors); + + // then + assertAll( + () -> assertThat(mentorIdToSiteUser.get(mentor1.getId()).getId()).isEqualTo(mentorUser1.getId()), + () -> assertThat(mentorIdToSiteUser.get(mentor2.getId()).getId()).isEqualTo(mentorUser2.getId()) + ); + } + + @Test + void 멘토_ID_와_현재_사용자의_지원_여부를_매핑한다() { + // given + Mentoring 대기중_멘토링 = mentoringFixture.대기중_멘토링(mentor1.getId(), currentUser.getId()); + List mentors = List.of(mentor1, mentor2); + + // when + Map mentorIdToIsApplied = mentorBatchQueryRepository.getMentorIdToIsApplied(mentors, currentUser.getId()); + + // then + assertAll( + () -> assertThat(mentorIdToIsApplied.get(mentor1.getId())).isTrue(), + () -> assertThat(mentorIdToIsApplied.get(mentor2.getId())).isFalse() + ); + } +} diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java new file mode 100644 index 000000000..421d24a12 --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java @@ -0,0 +1,192 @@ +package com.example.solidconnection.mentor.service; + +import com.example.solidconnection.common.dto.SliceResponse; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; +import com.example.solidconnection.mentor.domain.Channel; +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.dto.ChannelResponse; +import com.example.solidconnection.mentor.dto.MentorDetailResponse; +import com.example.solidconnection.mentor.dto.MentorPreviewResponse; +import com.example.solidconnection.mentor.fixture.ChannelFixture; +import com.example.solidconnection.mentor.fixture.MentorFixture; +import com.example.solidconnection.mentor.fixture.MentoringFixture; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; + +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + +@DisplayName("멘토 조회 서비스 테스트") +@TestContainerSpringBootTest +class MentorQueryServiceTest { + + @Autowired + private MentorQueryService mentorQueryService; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private MentorFixture mentorFixture; + + @Autowired + private MentoringFixture mentoringFixture; + + @Autowired + private ChannelFixture channelFixture; + + private long universityId = 1L; // todo: 멘토 인증 기능 추가 변경 필요 + private String region = "아시아"; + + @Nested + class 멘토_단일_조회_성공 { + + @Test + void 멘토_정보를_조회한다() { + // given + SiteUser siteUser = siteUserFixture.사용자(); + SiteUser mentorUser = siteUserFixture.사용자(1, "멘토"); + Mentor mentor = mentorFixture.멘토(mentorUser.getId(), universityId); + Channel channel1 = channelFixture.채널(1, mentor); + Channel channel2 = channelFixture.채널(2, mentor); + + // when + MentorDetailResponse response = mentorQueryService.getMentorDetails(mentor.getId(), siteUser); + + // then + assertAll( + () -> assertThat(response.id()).isEqualTo(mentor.getId()), + () -> assertThat(response.nickname()).isEqualTo(mentorUser.getNickname()), + () -> assertThat(response.channels()).extracting(ChannelResponse::url) + .containsExactly(channel1.getUrl(), channel2.getUrl()) + ); + } + + @Test + void 멘토에_대한_나의_멘토링_신청_여부를_조회한다() { + // given + SiteUser mentorUser = siteUserFixture.사용자(1, "멘토"); + Mentor mentor = mentorFixture.멘토(mentorUser.getId(), universityId); + + SiteUser notAppliedUser = siteUserFixture.사용자(2, "멘토링 지원 안한 사용자"); + SiteUser appliedUser = siteUserFixture.사용자(3, "멘토링 지원한 사용자"); + mentoringFixture.대기중_멘토링(mentor.getId(), appliedUser.getId()); + + // when + MentorDetailResponse notAppliedResponse = mentorQueryService.getMentorDetails(mentor.getId(), notAppliedUser); + MentorDetailResponse appliedResponse = mentorQueryService.getMentorDetails(mentor.getId(), appliedUser); + + // then + assertAll( + () -> assertThat(notAppliedResponse.isApplied()).isFalse(), + () -> assertThat(appliedResponse.isApplied()).isTrue() + ); + } + } + + @Nested + class 멘토_단일_조회_실패 { + + @Test + void 존재하지_않는_멘토를_조회하면_예외_응답을_반환한다() { + // given + long notExistingMentorId = 999L; + + // when & then + assertThatCode(() -> mentorQueryService.getMentorDetails(notExistingMentorId, siteUserFixture.사용자())) + .isInstanceOf(CustomException.class) + .hasMessageContaining(ErrorCode.MENTOR_NOT_FOUND.getMessage()); + } + } + + @Nested + class 멘토_미리보기_목록_조회 { + + private static final int NO_NEXT_PAGE_NUMBER = -1; + + private Mentor mentor1, mentor2; + private SiteUser mentorUser1, mentorUser2, currentUser; + + @BeforeEach + void setUp() { + currentUser = siteUserFixture.사용자(1, "사용자1"); + mentorUser1 = siteUserFixture.사용자(2, "멘토1"); + mentorUser2 = siteUserFixture.사용자(3, "멘토2"); + mentor1 = mentorFixture.멘토(mentorUser1.getId(), universityId); + mentor2 = mentorFixture.멘토(mentorUser2.getId(), universityId); + } + + @Test + void 멘토_미리보기_목록의_정보를_조회한다() { + // given + Channel channel1 = channelFixture.채널(1, mentor1); + Channel channel2 = channelFixture.채널(2, mentor2); + + // when + SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 10)); + + // then + Map mentorPreviewMap = response.content().stream() + .collect(Collectors.toMap(MentorPreviewResponse::id, Function.identity())); + + assertAll( + () -> assertThat(mentorPreviewMap.get(mentor1.getId())).extracting(MentorPreviewResponse::nickname) + .isEqualTo(mentorUser1.getNickname()), + () -> assertThat(mentorPreviewMap.get(mentor1.getId()).channels()).extracting(ChannelResponse::url) + .containsOnly(channel1.getUrl()), + () -> assertThat(mentorPreviewMap.get(mentor2.getId())).extracting(MentorPreviewResponse::nickname) + .isEqualTo(mentorUser2.getNickname()), + () -> assertThat(mentorPreviewMap.get(mentor2.getId()).channels()).extracting(ChannelResponse::url) + .containsOnly(channel2.getUrl()) + ); + } + + @Test + void 멘토들에_대한_나의_멘토링_지원_여부를_조회한다() { + // given + mentoringFixture.대기중_멘토링(mentor1.getId(), currentUser.getId()); + + // when + SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 10)); + + // then + Map mentorPreviewMap = response.content().stream() + .collect(Collectors.toMap(MentorPreviewResponse::id, Function.identity())); + assertAll( + () -> assertThat(mentorPreviewMap.get(mentor1.getId()).isApplied()).isTrue(), + () -> assertThat(mentorPreviewMap.get(mentor2.getId()).isApplied()).isFalse() + ); + } + + @Test + void 다음_페이지_번호를_응답한다() { + // given + SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 1)); + + // then + assertThat(response.nextPageNumber()).isEqualTo(2); + } + + @Test + void 다음_페이지가_없으면_페이지_없음을_의미하는_값을_응답한다() { + // given + SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 10)); + + // then + assertThat(response.nextPageNumber()).isEqualTo(NO_NEXT_PAGE_NUMBER); + } + } +} From d6733658315998c65e34b3e16237e777350185d8 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Thu, 10 Jul 2025 01:55:36 +0900 Subject: [PATCH 37/90] =?UTF-8?q?feat:=20=EB=A9=98=ED=86=A0=20=EB=A7=88?= =?UTF-8?q?=EC=9D=B4=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A1=B0=ED=9A=8C/?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#375)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 멘토 미리보기 목록 조회 dto 생성 * feat: 멘토 미리보기 목록 서비스 함수 생성 * feat: 멘토 미리보기 목록 컨트롤러 생성 * feat: 멘토 마이페이지 응답 dto 생성 * feat: 멘토 마이페이지 응답 서비스 함수 구현 * feat: 멘토 마이페이지 컨트롤러 생성 * test: 멘토 마이페이지 조회 테스트 코드 작성 * feat: 멘토 마이페이지 수정 요청 dto 생성 * feat: 멘토 마이페이지 수정 서비스 함수 구현 * feat: 멘토 마이페이지 수정 컨트롤러 구현 * test: 멘토 마이페이지 수정 테스트 코드 작성 * style: 불필요한 중괄호 제거 * chore: 사용되지 않는 변수 제거 * refactor: 변수명 단순화 * refactor: 최대 채널 등록 갯수 검증 추가, 함수 분리 * style: 개행 추가 * refactor: 멘토 마이 페이지에서 사용자 정보를 수정하지 않도록 * refactor: 누락한 검증 메세지 추가 * style: 불필요한 중괄호 삭제 * refactor: 누락한 RequestBody 어노테이션 추가 * refactor: 불필요한 빈 주입 제거 * style: 사용되지 않는 import 제거 * refactor: 잘못된 반환 타입 수정 * fix: 테스트 깨지는 곳 수정 --- .../common/exception/ErrorCode.java | 3 + .../controller/MentorMyPageController.java | 44 ++++++ .../mentor/domain/Channel.java | 6 + .../solidconnection/mentor/domain/Mentor.java | 19 +++ .../mentor/dto/ChannelRequest.java | 16 +++ .../mentor/dto/MentorMyPageResponse.java | 38 ++++++ .../mentor/dto/MentorMyPageUpdateRequest.java | 18 +++ .../mentor/dto/MentorPreviewsResponse.java | 9 ++ .../mentor/service/MentorMyPageService.java | 62 +++++++++ .../repository/ChannelRepositoryForTest.java | 11 ++ .../MentorBatchQueryRepositoryTest.java | 7 +- .../service/MentorMyPageServiceTest.java | 126 ++++++++++++++++++ 12 files changed, 355 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/mentor/controller/MentorMyPageController.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/ChannelRequest.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageUpdateRequest.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java create mode 100644 src/main/java/com/example/solidconnection/mentor/service/MentorMyPageService.java create mode 100644 src/test/java/com/example/solidconnection/mentor/repository/ChannelRepositoryForTest.java create mode 100644 src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 3b851ef77..0ea4b29a5 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -101,6 +101,9 @@ public enum ErrorCode { // news INVALID_NEWS_ACCESS(HttpStatus.BAD_REQUEST.value(), "자신의 소식지만 제어할 수 있습니다."), + // mentor + CHANNEL_SEQUENCE_NOT_UNIQUE(HttpStatus.BAD_REQUEST.value(), "채널의 순서가 중복되었습니다."), + CHANNEL_REGISTRATION_LIMIT_EXCEEDED(HttpStatus.BAD_REQUEST.value(), "등록 가능한 채널 수를 초과하였습니다."), // database DATA_INTEGRITY_VIOLATION(HttpStatus.CONFLICT.value(), "데이터베이스 무결성 제약조건 위반이 발생했습니다."), diff --git a/src/main/java/com/example/solidconnection/mentor/controller/MentorMyPageController.java b/src/main/java/com/example/solidconnection/mentor/controller/MentorMyPageController.java new file mode 100644 index 000000000..ba8bc0911 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/controller/MentorMyPageController.java @@ -0,0 +1,44 @@ +package com.example.solidconnection.mentor.controller; + +import com.example.solidconnection.common.resolver.AuthorizedUser; +import com.example.solidconnection.mentor.dto.MentorMyPageResponse; +import com.example.solidconnection.mentor.dto.MentorMyPageUpdateRequest; +import com.example.solidconnection.mentor.service.MentorMyPageService; +import com.example.solidconnection.security.annotation.RequireRoleAccess; +import com.example.solidconnection.siteuser.domain.Role; +import com.example.solidconnection.siteuser.domain.SiteUser; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RequestMapping("/mentor/my") +@RestController +public class MentorMyPageController { + + private final MentorMyPageService mentorMyPageService; + + @RequireRoleAccess(roles = Role.MENTOR) + @GetMapping + public ResponseEntity getMentorMyPage( + @AuthorizedUser SiteUser siteUser + ) { + MentorMyPageResponse mentorMyPageResponse = mentorMyPageService.getMentorMyPage(siteUser); + return ResponseEntity.ok(mentorMyPageResponse); + } + + @RequireRoleAccess(roles = Role.MENTOR) + @PutMapping + public ResponseEntity updateMentorMyPage( + @AuthorizedUser SiteUser siteUser, + @Valid @RequestBody MentorMyPageUpdateRequest mentorMyPageUpdateRequest + ) { + mentorMyPageService.updateMentorMyPage(siteUser, mentorMyPageUpdateRequest); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Channel.java b/src/main/java/com/example/solidconnection/mentor/domain/Channel.java index 846b8e625..b3c12bff7 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Channel.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Channel.java @@ -45,6 +45,12 @@ public class Channel { @ManyToOne(fetch = FetchType.LAZY) private Mentor mentor; + public Channel(int sequence, ChannelType type, String url) { + this.sequence = sequence; + this.type = type; + this.url = url; + } + public void updateMentor(Mentor mentor) { this.mentor = mentor; } diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java index 542972b9e..253bf666d 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java @@ -53,4 +53,23 @@ public class Mentor { public void increaseMenteeCount() { this.menteeCount++; } + + public void updateIntroduction(String introduction) { + this.introduction = introduction; + } + + public void updatePassTip(String passTip) { + this.passTip = passTip; + } + + public void updateChannels(List channels) { + this.channels.clear(); + if (channels == null || channels.isEmpty()) { + return; + } + for (Channel channel : channels) { + channel.updateMentor(this); + this.channels.add(channel); + } + } } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/ChannelRequest.java b/src/main/java/com/example/solidconnection/mentor/dto/ChannelRequest.java new file mode 100644 index 000000000..9172262ae --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/ChannelRequest.java @@ -0,0 +1,16 @@ +package com.example.solidconnection.mentor.dto; + +import com.example.solidconnection.mentor.domain.ChannelType; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import org.hibernate.validator.constraints.URL; + +public record ChannelRequest( + @NotNull(message = "채널 종류를 입력해주세요.") + ChannelType type, + + @NotBlank(message = "채널 URL을 입력해주세요.") + @URL + String url +) { +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java new file mode 100644 index 000000000..91077051f --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java @@ -0,0 +1,38 @@ +package com.example.solidconnection.mentor.dto; + +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.siteuser.domain.ExchangeStatus; +import com.example.solidconnection.siteuser.domain.SiteUser; + +import java.util.List; + +public record MentorMyPageResponse( + long id, + String profileImageUrl, + String nickname, + ExchangeStatus exchangeStatus, + String country, + String universityName, + int menteeCount, + boolean hasBadge, + String introduction, + List channels +) { + + public static MentorMyPageResponse of(Mentor mentor, SiteUser siteUser) { + return new MentorMyPageResponse( + mentor.getId(), + siteUser.getProfileImageUrl(), + siteUser.getNickname(), + siteUser.getExchangeStatus(), + "국가", // todo: 교환학생 기록이 인증되면 추가 + "대학 이름", + mentor.getMenteeCount(), + mentor.isHasBadge(), + mentor.getIntroduction(), + mentor.getChannels().stream() + .map(ChannelResponse::from) + .toList() + ); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageUpdateRequest.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageUpdateRequest.java new file mode 100644 index 000000000..5a6790aeb --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageUpdateRequest.java @@ -0,0 +1,18 @@ +package com.example.solidconnection.mentor.dto; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; + +import java.util.List; + +public record MentorMyPageUpdateRequest( + @NotBlank(message = "자기소개를 입력해주세요.") + String introduction, + + @NotBlank(message = "합격 레시피를 입력해주세요.") + String passTip, + + @Valid + List channels +) { +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java new file mode 100644 index 000000000..cb49ba602 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java @@ -0,0 +1,9 @@ +package com.example.solidconnection.mentor.dto; + +import java.util.List; + +public record MentorPreviewsResponse( + List content, + int nextPageNumber +) { +} diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorMyPageService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorMyPageService.java new file mode 100644 index 000000000..6a2d47ded --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorMyPageService.java @@ -0,0 +1,62 @@ +package com.example.solidconnection.mentor.service; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.mentor.domain.Channel; +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.dto.ChannelRequest; +import com.example.solidconnection.mentor.dto.MentorMyPageResponse; +import com.example.solidconnection.mentor.dto.MentorMyPageUpdateRequest; +import com.example.solidconnection.mentor.repository.MentorRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +import static com.example.solidconnection.common.exception.ErrorCode.CHANNEL_REGISTRATION_LIMIT_EXCEEDED; +import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; + +@RequiredArgsConstructor +@Service +public class MentorMyPageService { + + private static final int CHANNEL_REGISTRATION_LIMIT = 4; + private static final int CHANNEL_SEQUENCE_START_NUMBER = 1; + + private final MentorRepository mentorRepository; + + @Transactional(readOnly = true) + public MentorMyPageResponse getMentorMyPage(SiteUser siteUser) { + Mentor mentor = mentorRepository.findBySiteUserId(siteUser.getId()) + .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); + return MentorMyPageResponse.of(mentor, siteUser); + } + + @Transactional + public void updateMentorMyPage(SiteUser siteUser, MentorMyPageUpdateRequest request) { + validateChannelRegistrationLimit(request.channels()); + Mentor mentor = mentorRepository.findBySiteUserId(siteUser.getId()) + .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); + + mentor.updateIntroduction(request.introduction()); + mentor.updatePassTip(request.passTip()); + updateChannel(request.channels(), mentor); + } + + private void validateChannelRegistrationLimit(List channelRequests) { + if (channelRequests.size() > CHANNEL_REGISTRATION_LIMIT) { + throw new CustomException(CHANNEL_REGISTRATION_LIMIT_EXCEEDED); + } + } + + private void updateChannel(List channelRequests, Mentor mentor) { + int sequence = CHANNEL_SEQUENCE_START_NUMBER; + List newChannels = new ArrayList<>(); + for (ChannelRequest request : channelRequests) { + newChannels.add(new Channel(sequence++, request.type(), request.url())); + } + mentor.updateChannels(newChannels); + } +} diff --git a/src/test/java/com/example/solidconnection/mentor/repository/ChannelRepositoryForTest.java b/src/test/java/com/example/solidconnection/mentor/repository/ChannelRepositoryForTest.java new file mode 100644 index 000000000..9e6fee1de --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/repository/ChannelRepositoryForTest.java @@ -0,0 +1,11 @@ +package com.example.solidconnection.mentor.repository; + +import com.example.solidconnection.mentor.domain.Channel; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ChannelRepositoryForTest extends JpaRepository { + + List findAllByMentorId(long mentorId); +} diff --git a/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java b/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java index 0ef517c34..c68deedb7 100644 --- a/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java +++ b/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java @@ -1,7 +1,6 @@ package com.example.solidconnection.mentor.repository; import com.example.solidconnection.mentor.domain.Mentor; -import com.example.solidconnection.mentor.domain.Mentoring; import com.example.solidconnection.mentor.fixture.MentorFixture; import com.example.solidconnection.mentor.fixture.MentoringFixture; import com.example.solidconnection.siteuser.domain.SiteUser; @@ -29,10 +28,10 @@ class MentorBatchQueryRepositoryTest { private MentorFixture mentorFixture; @Autowired - private SiteUserFixture siteUserFixture; + private MentoringFixture mentoringFixture; @Autowired - private MentoringFixture mentoringFixture; + private SiteUserFixture siteUserFixture; private long universityId = 1L; // todo: 멘토 인증 기능 추가 변경 필요 private Mentor mentor1, mentor2; @@ -65,7 +64,7 @@ void setUp() { @Test void 멘토_ID_와_현재_사용자의_지원_여부를_매핑한다() { // given - Mentoring 대기중_멘토링 = mentoringFixture.대기중_멘토링(mentor1.getId(), currentUser.getId()); + mentoringFixture.대기중_멘토링(mentor1.getId(), currentUser.getId()); List mentors = List.of(mentor1, mentor2); // when diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java new file mode 100644 index 000000000..b7c5724a7 --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java @@ -0,0 +1,126 @@ +package com.example.solidconnection.mentor.service; + +import com.example.solidconnection.mentor.domain.Channel; +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.dto.ChannelRequest; +import com.example.solidconnection.mentor.dto.ChannelResponse; +import com.example.solidconnection.mentor.dto.MentorMyPageResponse; +import com.example.solidconnection.mentor.dto.MentorMyPageUpdateRequest; +import com.example.solidconnection.mentor.fixture.ChannelFixture; +import com.example.solidconnection.mentor.fixture.MentorFixture; +import com.example.solidconnection.mentor.repository.ChannelRepositoryForTest; +import com.example.solidconnection.mentor.repository.MentorRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; + +import static com.example.solidconnection.mentor.domain.ChannelType.BLOG; +import static com.example.solidconnection.mentor.domain.ChannelType.INSTAGRAM; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +@TestContainerSpringBootTest +@DisplayName("멘토 마이페이지 서비스 테스트") +class MentorMyPageServiceTest { + + @Autowired + private MentorMyPageService mentorMyPageService; + + @Autowired + private MentorFixture mentorFixture; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private ChannelFixture channelFixture; + + @Autowired + private MentorRepository mentorRepository; + + @Autowired + private ChannelRepositoryForTest channelRepositoryForTest; + + private SiteUser mentorUser; + private Mentor mentor; + private long universityId = 1L; + + @BeforeEach + void setUp() { + mentorUser = siteUserFixture.멘토(1, "멘토"); + mentor = mentorFixture.멘토(mentorUser.getId(), universityId); + } + + @Nested + class 멘토의_마이_페이지를_조회한다 { + + @Test + void 성공적으로_조회한다() { + // given + Channel channel1 = channelFixture.채널(1, mentor); + Channel channel2 = channelFixture.채널(2, mentor); + + // when + MentorMyPageResponse response = mentorMyPageService.getMentorMyPage(mentorUser); + + // then + assertAll( + () -> assertThat(response.id()).isEqualTo(mentor.getId()), + () -> assertThat(response.nickname()).isEqualTo(mentorUser.getNickname()), + () -> assertThat(response.channels()).extracting(ChannelResponse::url) + .containsExactly(channel1.getUrl(), channel2.getUrl()) + ); + } + } + + @Nested + class 멘토의_마이_페이지를_수정한다 { + + @Test + void 멘토_정보를_수정한다() { + // given + String newIntroduction = "새로운 자기소개"; + String newPassTip = "새로운 합격 팁"; + MentorMyPageUpdateRequest request = new MentorMyPageUpdateRequest(newIntroduction, newPassTip, List.of()); + + // when + mentorMyPageService.updateMentorMyPage(mentorUser, request); + + // then + Mentor updatedMentor = mentorRepository.findById(mentor.getId()).get(); + assertAll( + () -> assertThat(updatedMentor.getIntroduction()).isEqualTo(newIntroduction), + () -> assertThat(updatedMentor.getPassTip()).isEqualTo(newPassTip) + ); + } + + @Test + void 채널_정보를_수정한다() { + // given + List newChannels = List.of( + new ChannelRequest(BLOG, "https://blog.com"), + new ChannelRequest(INSTAGRAM, "https://instagram.com") + ); + MentorMyPageUpdateRequest request = new MentorMyPageUpdateRequest("introduction", "passTip", newChannels); + + // when + mentorMyPageService.updateMentorMyPage(mentorUser, request); + + // then + List updatedChannels = channelRepositoryForTest.findAllByMentorId(mentor.getId()); + assertAll( + () -> assertThat(updatedChannels).extracting(Channel::getType) + .containsExactly(BLOG, INSTAGRAM), + () -> assertThat(updatedChannels).extracting(Channel::getUrl) + .containsExactly("https://blog.com", "https://instagram.com") + ); + } + } +} From 163d0a3d528e13e047309d798b95186503296feb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Thu, 10 Jul 2025 14:39:54 +0900 Subject: [PATCH 38/90] =?UTF-8?q?feat:=20=EC=86=8C=EC=8B=9D=EC=A7=80=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#377)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 소식지 좋아요 응답 dto 생성 * feat: 소식지 좋아요 Repository 생성 * feat: 소식지 좋아요 Service 생성 * feat: 소식지 좋아요 Controller 생성 * test: 소식지 좋아요 테스트 코드 작성 * feat: 소식지 좋아요 취소 Service 생성 * feat: 소식지 좋아요 취소 Controller 생성 * test: 소식지 좋아요 취소 테스트 코드 작성 * feat: 소식지 좋아요 상태 확인 Service 생성 * feat: 소식지 좋아요 상태 확인 Controller 생성 * test: 소식지 좋아요 상태 확인 테스트 코드 작성 * refactor: newsId Long -> long으로 변경 * refactor: 좋아요 성공 및 취소 시 200 상태코드만 주도록 변경 * refactor: 좋아요 상태 확인 응답 dto에 id 제거 * style: 소식지 좋아요 상태 관련 테스트명 변경 * refactor: return 타입 ResponseEntity로 변경 * style: 테스트명에 공백 추가 * refactor: 좋아요 상태 확인 함수명 isNewsLiked로 변경 --- .../common/exception/ErrorCode.java | 10 +- .../news/controller/NewsController.java | 30 +++++ .../news/domain/LikedNews.java | 5 + .../news/dto/LikedNewsResponse.java | 10 ++ .../news/repository/LikedNewsRepository.java | 13 ++ .../news/service/NewsLikeService.java | 53 ++++++++ .../news/service/NewsLikeServiceTest.java | 119 ++++++++++++++++++ 7 files changed, 235 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/news/dto/LikedNewsResponse.java create mode 100644 src/main/java/com/example/solidconnection/news/repository/LikedNewsRepository.java create mode 100644 src/main/java/com/example/solidconnection/news/service/NewsLikeService.java create mode 100644 src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 0ea4b29a5..75fbf5b0f 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -100,20 +100,20 @@ public enum ErrorCode { // news INVALID_NEWS_ACCESS(HttpStatus.BAD_REQUEST.value(), "자신의 소식지만 제어할 수 있습니다."), + ALREADY_LIKED_NEWS(HttpStatus.BAD_REQUEST.value(), "이미 좋아요한 소식지입니다."), + NOT_LIKED_NEWS(HttpStatus.BAD_REQUEST.value(), "좋아요하지 않은 소식지입니다."), // mentor CHANNEL_SEQUENCE_NOT_UNIQUE(HttpStatus.BAD_REQUEST.value(), "채널의 순서가 중복되었습니다."), CHANNEL_REGISTRATION_LIMIT_EXCEEDED(HttpStatus.BAD_REQUEST.value(), "등록 가능한 채널 수를 초과하였습니다."), - - // database - DATA_INTEGRITY_VIOLATION(HttpStatus.CONFLICT.value(), "데이터베이스 무결성 제약조건 위반이 발생했습니다."), - - // mentor ALREADY_MENTOR(HttpStatus.BAD_REQUEST.value(), "이미 멘토로 등록된 사용자입니다."), MENTORING_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "해당 멘토링 신청을 찾을 수 없습니다."), UNAUTHORIZED_MENTORING(HttpStatus.FORBIDDEN.value(), "멘토링 권한이 없습니다."), MENTORING_ALREADY_CONFIRMED(HttpStatus.BAD_REQUEST.value(), "이미 승인 또는 거절된 멘토링입니다."), + // database + DATA_INTEGRITY_VIOLATION(HttpStatus.CONFLICT.value(), "데이터베이스 무결성 제약조건 위반이 발생했습니다."), + // general JSON_PARSING_FAILED(HttpStatus.BAD_REQUEST.value(), "JSON 파싱을 할 수 없습니다."), JWT_EXCEPTION(HttpStatus.BAD_REQUEST.value(), "JWT 토큰을 처리할 수 없습니다."), diff --git a/src/main/java/com/example/solidconnection/news/controller/NewsController.java b/src/main/java/com/example/solidconnection/news/controller/NewsController.java index 51b739f8c..dd522fb47 100644 --- a/src/main/java/com/example/solidconnection/news/controller/NewsController.java +++ b/src/main/java/com/example/solidconnection/news/controller/NewsController.java @@ -1,11 +1,13 @@ package com.example.solidconnection.news.controller; import com.example.solidconnection.common.resolver.AuthorizedUser; +import com.example.solidconnection.news.dto.LikedNewsResponse; import com.example.solidconnection.news.dto.NewsCommandResponse; import com.example.solidconnection.news.dto.NewsCreateRequest; import com.example.solidconnection.news.dto.NewsListResponse; import com.example.solidconnection.news.dto.NewsUpdateRequest; import com.example.solidconnection.news.service.NewsCommandService; +import com.example.solidconnection.news.service.NewsLikeService; import com.example.solidconnection.news.service.NewsQueryService; import com.example.solidconnection.security.annotation.RequireRoleAccess; import com.example.solidconnection.siteuser.domain.Role; @@ -31,6 +33,7 @@ public class NewsController { private final NewsQueryService newsQueryService; private final NewsCommandService newsCommandService; + private final NewsLikeService newsLikeService; // todo: 추후 Slice 적용 @GetMapping @@ -77,4 +80,31 @@ public ResponseEntity deleteNewsById( NewsCommandResponse newsCommandResponse = newsCommandService.deleteNewsById(siteUser, newsId); return ResponseEntity.ok(newsCommandResponse); } + + @GetMapping("/{news-id}/like") + public ResponseEntity isNewsLiked( + @AuthorizedUser SiteUser siteUser, + @PathVariable("news-id") Long newsId + ) { + LikedNewsResponse likedNewsResponse = newsLikeService.isNewsLiked(siteUser.getId(), newsId); + return ResponseEntity.ok(likedNewsResponse); + } + + @PostMapping("/{news-id}/like") + public ResponseEntity addNewsLike( + @AuthorizedUser SiteUser siteUser, + @PathVariable("news-id") Long newsId + ) { + newsLikeService.addNewsLike(siteUser.getId(), newsId); + return ResponseEntity.ok().build(); + } + + @DeleteMapping("/{news-id}/like") + public ResponseEntity cancelNewsLike( + @AuthorizedUser SiteUser siteUser, + @PathVariable("news-id") Long newsId + ) { + newsLikeService.cancelNewsLike(siteUser.getId(), newsId); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/com/example/solidconnection/news/domain/LikedNews.java b/src/main/java/com/example/solidconnection/news/domain/LikedNews.java index a50e31659..9b7affad7 100644 --- a/src/main/java/com/example/solidconnection/news/domain/LikedNews.java +++ b/src/main/java/com/example/solidconnection/news/domain/LikedNews.java @@ -33,4 +33,9 @@ public class LikedNews { @Column(name = "site_user_id") private long siteUserId; + + public LikedNews(long newsId, long siteUserId) { + this.newsId = newsId; + this.siteUserId = siteUserId; + } } diff --git a/src/main/java/com/example/solidconnection/news/dto/LikedNewsResponse.java b/src/main/java/com/example/solidconnection/news/dto/LikedNewsResponse.java new file mode 100644 index 000000000..b854b9bf0 --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/dto/LikedNewsResponse.java @@ -0,0 +1,10 @@ +package com.example.solidconnection.news.dto; + +public record LikedNewsResponse( + boolean isLike +) { + + public static LikedNewsResponse of(boolean isLike) { + return new LikedNewsResponse(isLike); + } +} diff --git a/src/main/java/com/example/solidconnection/news/repository/LikedNewsRepository.java b/src/main/java/com/example/solidconnection/news/repository/LikedNewsRepository.java new file mode 100644 index 000000000..6c9a51585 --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/repository/LikedNewsRepository.java @@ -0,0 +1,13 @@ +package com.example.solidconnection.news.repository; + +import com.example.solidconnection.news.domain.LikedNews; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface LikedNewsRepository extends JpaRepository { + + boolean existsByNewsIdAndSiteUserId(long newsId, long siteUserId); + + Optional findByNewsIdAndSiteUserId(long newsId, long siteUserId); +} diff --git a/src/main/java/com/example/solidconnection/news/service/NewsLikeService.java b/src/main/java/com/example/solidconnection/news/service/NewsLikeService.java new file mode 100644 index 000000000..0ca88992f --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/service/NewsLikeService.java @@ -0,0 +1,53 @@ +package com.example.solidconnection.news.service; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.news.domain.LikedNews; +import com.example.solidconnection.news.dto.LikedNewsResponse; +import com.example.solidconnection.news.repository.LikedNewsRepository; +import com.example.solidconnection.news.repository.NewsRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_NEWS; +import static com.example.solidconnection.common.exception.ErrorCode.NEWS_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_NEWS; + +@RequiredArgsConstructor +@Service +public class NewsLikeService { + + private final NewsRepository newsRepository; + private final LikedNewsRepository likedNewsRepository; + + @Transactional(readOnly = true) + public LikedNewsResponse isNewsLiked(long siteUserId, long newsId) { + if (!newsRepository.existsById(newsId)) { + throw new CustomException(NEWS_NOT_FOUND); + } + boolean isLike = likedNewsRepository.existsByNewsIdAndSiteUserId(newsId, siteUserId); + return LikedNewsResponse.of(isLike); + } + + @Transactional + public void addNewsLike(long siteUserId, long newsId) { + if (!newsRepository.existsById(newsId)) { + throw new CustomException(NEWS_NOT_FOUND); + } + if (likedNewsRepository.existsByNewsIdAndSiteUserId(newsId, siteUserId)) { + throw new CustomException(ALREADY_LIKED_NEWS); + } + LikedNews likedNews = new LikedNews(newsId, siteUserId); + likedNewsRepository.save(likedNews); + } + + @Transactional + public void cancelNewsLike(long siteUserId, long newsId) { + if (!newsRepository.existsById(newsId)) { + throw new CustomException(NEWS_NOT_FOUND); + } + LikedNews likedNews = likedNewsRepository.findByNewsIdAndSiteUserId(newsId, siteUserId) + .orElseThrow(() -> new CustomException(NOT_LIKED_NEWS)); + likedNewsRepository.delete(likedNews); + } +} diff --git a/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java b/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java new file mode 100644 index 000000000..1bf26bc3c --- /dev/null +++ b/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java @@ -0,0 +1,119 @@ +package com.example.solidconnection.news.service; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.news.domain.News; +import com.example.solidconnection.news.dto.LikedNewsResponse; +import com.example.solidconnection.news.fixture.NewsFixture; +import com.example.solidconnection.news.repository.LikedNewsRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_NEWS; +import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_NEWS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; + +@TestContainerSpringBootTest +@DisplayName("소식지 좋아요 서비스 테스트") +class NewsLikeServiceTest { + + @Autowired + private NewsLikeService newsLikeService; + + @Autowired + private LikedNewsRepository likedNewsRepository; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private NewsFixture newsFixture; + + private SiteUser user; + private News news; + + @BeforeEach + void setUp() { + user = siteUserFixture.사용자(); + news = newsFixture.소식지(siteUserFixture.멘토(1, "mentor").getId()); + } + + @Nested + class 소식지_좋아요_상태를_조회한다 { + + @Test + void 좋아요한_소식지의_좋아요_상태를_조회한다() { + // given + newsLikeService.addNewsLike(user.getId(), news.getId()); + + // when + LikedNewsResponse response = newsLikeService.isNewsLiked(user.getId(), news.getId()); + + // then + assertThat(response.isLike()).isTrue(); + } + + @Test + void 좋아요하지_않은_소식지의_좋아요_상태를_조회한다() { + // when + LikedNewsResponse response = newsLikeService.isNewsLiked(user.getId(), news.getId()); + + // then + assertThat(response.isLike()).isFalse(); + } + } + + @Nested + class 소식지_좋아요를_등록한다 { + + @Test + void 성공적으로_좋아요를_등록한다() { + // when + newsLikeService.addNewsLike(user.getId(), news.getId()); + + // then + assertThat(likedNewsRepository.existsByNewsIdAndSiteUserId(news.getId(), user.getId())).isTrue(); + } + + @Test + void 이미_좋아요했으면_예외_응답을_반환한다() { + // given + newsLikeService.addNewsLike(user.getId(), news.getId()); + + // when & then + assertThatCode(() -> newsLikeService.addNewsLike(user.getId(), news.getId())) + .isInstanceOf(CustomException.class) + .hasMessage(ALREADY_LIKED_NEWS.getMessage()); + } + } + + @Nested + class 소식지_좋아요를_취소한다 { + + @Test + void 성공적으로_좋아요를_취소한다() { + // given + newsLikeService.addNewsLike(user.getId(), news.getId()); + + // when + newsLikeService.cancelNewsLike(user.getId(), news.getId()); + + // then + assertThat(likedNewsRepository.existsByNewsIdAndSiteUserId(news.getId(), user.getId())).isFalse(); + } + + @Test + void 좋아요하지_않았으면_예외_응답을_반환한다() { + // when & then + assertThatCode(() -> newsLikeService.cancelNewsLike(user.getId(), news.getId())) + .isInstanceOf(CustomException.class) + .hasMessage(NOT_LIKED_NEWS.getMessage()); + } + } +} From bfa7ba7863f2885aa2c9a70bd2b1c1b110e2b097 Mon Sep 17 00:00:00 2001 From: Yeon <84384499+lsy1307@users.noreply.github.com> Date: Thu, 10 Jul 2025 19:20:23 +0900 Subject: [PATCH 39/90] =?UTF-8?q?refactor:=20Community=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EC=A0=9C=EC=99=B8=20=EC=97=B0=EA=B4=80?= =?UTF-8?q?=EA=B4=80=EA=B3=84=20=EC=82=AD=EC=A0=9C=20(#363)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: community 도메인 제외 연관관계 삭제 - Application, Country, GpaScore, InterestedCountry/Region, LanguageTestScore, LikedUniversity. SiteUser, UnivApplyInfo 도메인 수정 - 연관관계 삭제 및 생성자 parameter 수정으로 인한 쿼리 수정 다수 - test코드 수정 * fix: application, university 테스트 코드 오류 수정 - 연관관계 변경에 따른 transaction 문제 해결 - 쿼리 일부 수정, 테스트 코드 일부 수정 - GeneralUniversityRecommendService 로직 수정 * refactor: 불필요한 siteUser 재조회 수정 - ScoreService에서 siteUser정보를 다시 불러오는 로직 삭제 * refactor: 수정사항 반영 - 컨벤션, 타입수정 다수 * fix: conflict merge 과정에서 생긴 더미 파일 삭제 * refactor: 불필요한 import문 삭제 * fix: 서브모듈 참조 오류 수정 * refactor: 코드리뷰 수정사항 반영 - Long타입 long으로 수정 - collect -> toList단독으로 변경 - 컨벤션 수정 - Recomment 변경사항 원복 - getGeneralRecommendsExcludingSelected함수 OutOfRange 방지 추가 * refactor: 코드리뷰 수정사항 반영 - UnivApplyInfo University fetchType LAZY로 변경 - 컨벤션 수정 - findRandomByTerm함수 nativeQuery제거 및 Pageable로 LIMIT 구현 * refactor: Parameter 명칭 변경 - siteUser -> siteUserId로 변경 * refactor: Parameter 타입 변경 - Long -> long으로 변경 --- .../application/domain/Application.java | 12 ++-- .../application/dto/ApplicantsResponse.java | 3 +- .../repository/ApplicationRepository.java | 10 ++- .../service/ApplicationQueryService.java | 4 +- .../service/ApplicationSubmissionService.java | 6 +- .../location/country/domain/Country.java | 10 ++- .../country/domain/InterestedCountry.java | 14 ++-- .../InterestedCountryRepository.java | 3 +- .../region/domain/InterestedRegion.java | 14 ++-- .../InterestedRegionRepository.java | 2 +- .../score/domain/GpaScore.java | 14 +--- .../score/domain/LanguageTestScore.java | 14 +--- .../score/repository/GpaScoreRepository.java | 8 ++- .../LanguageTestScoreRepository.java | 8 ++- .../custom/GpaScoreFilterRepositoryImpl.java | 4 +- ...LanguageTestScoreFilterRepositoryImpl.java | 4 +- .../score/service/ScoreService.java | 60 +++++------------ .../siteuser/domain/SiteUser.java | 8 --- .../siteuser/service/MyPageService.java | 11 ++-- .../university/domain/LikedUnivApplyInfo.java | 13 ++-- .../university/domain/UnivApplyInfo.java | 5 +- .../LikedUnivApplyInfoRepository.java | 19 ++++-- .../repository/UnivApplyInfoRepository.java | 65 ++++++++++--------- .../UniversityInfoForApplyRepository.java | 0 .../UnivApplyInfoFilterRepositoryImpl.java | 11 ++-- .../GeneralUnivApplyInfoRecommendService.java | 5 +- .../service/UnivApplyInfoLikeService.java | 10 +-- .../UnivApplyInfoRecommendService.java | 5 +- src/main/resources/data.sql | 2 +- .../ApplicationSubmissionServiceTest.java | 2 +- .../fixture/CountryFixtureBuilder.java | 2 +- .../score/fixture/GpaScoreFixtureBuilder.java | 1 - .../LanguageTestScoreFixtureBuilder.java | 1 - .../siteuser/service/MyPageServiceTest.java | 8 +-- .../LikedUnivApplyInfoRepositoryTest.java | 4 +- .../service/UnivApplyInfoLikeServiceTest.java | 12 ++-- .../UnivApplyInfoRecommendServiceTest.java | 4 +- 37 files changed, 167 insertions(+), 211 deletions(-) delete mode 100644 src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java diff --git a/src/main/java/com/example/solidconnection/application/domain/Application.java b/src/main/java/com/example/solidconnection/application/domain/Application.java index 0c6e3cc0a..32c7a971c 100644 --- a/src/main/java/com/example/solidconnection/application/domain/Application.java +++ b/src/main/java/com/example/solidconnection/application/domain/Application.java @@ -7,12 +7,10 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; -import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Index; -import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.Getter; import lombok.NoArgsConstructor; @@ -75,15 +73,15 @@ public class Application { @Column(name = "third_choice_university_info_for_apply_id") private Long thirdChoiceUnivApplyInfoId; - @ManyToOne(fetch = FetchType.LAZY) - private SiteUser siteUser; + @Column(name = "site_user_id") + private long siteUserId; public Application( SiteUser siteUser, Gpa gpa, LanguageTest languageTest, String term) { - this.siteUser = siteUser; + this.siteUserId = siteUser.getId(); this.gpa = gpa; this.languageTest = languageTest; this.term = term; @@ -101,7 +99,7 @@ public Application( Long secondChoiceUnivApplyInfoId, Long thirdChoiceUnivApplyInfoId, String nicknameForApply) { - this.siteUser = siteUser; + this.siteUserId = siteUser.getId(); this.gpa = gpa; this.languageTest = languageTest; this.term = term; @@ -122,7 +120,7 @@ public Application( Long secondChoiceUnivApplyInfoId, Long thirdChoiceUnivApplyInfoId, String nicknameForApply) { - this.siteUser = siteUser; + this.siteUserId = siteUser.getId(); this.gpa = gpa; this.languageTest = languageTest; this.term = term; diff --git a/src/main/java/com/example/solidconnection/application/dto/ApplicantsResponse.java b/src/main/java/com/example/solidconnection/application/dto/ApplicantsResponse.java index 72fbc9c22..b92f699a7 100644 --- a/src/main/java/com/example/solidconnection/application/dto/ApplicantsResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/ApplicantsResponse.java @@ -5,6 +5,7 @@ import com.example.solidconnection.university.domain.UnivApplyInfo; import java.util.List; +import java.util.Objects; public record ApplicantsResponse( String koreanName, @@ -24,6 +25,6 @@ public static ApplicantsResponse of(UnivApplyInfo univApplyInfo, List @Query(""" SELECT a FROM Application a - JOIN FETCH a.siteUser WHERE (a.firstChoiceUnivApplyInfoId IN :univApplyInfoIds OR a.secondChoiceUnivApplyInfoId IN :univApplyInfoIds OR a.thirdChoiceUnivApplyInfoId IN :univApplyInfoIds) @@ -33,14 +31,14 @@ public interface ApplicationRepository extends JpaRepository @Query(""" SELECT a FROM Application a - WHERE a.siteUser = :siteUser + WHERE a.siteUserId = :siteUserId AND a.term = :term AND a.isDelete = false """) - Optional findBySiteUserAndTerm(@Param("siteUser") SiteUser siteUser, @Param("term") String term); + Optional findBySiteUserIdAndTerm(@Param("siteUserId") long siteUserId, @Param("term") String term); - default Application getApplicationBySiteUserAndTerm(SiteUser siteUser, String term) { - return findBySiteUserAndTerm(siteUser, term) + default Application getApplicationBySiteUserIdAndTerm(long siteUserId, String term) { + return findBySiteUserIdAndTerm(siteUserId, term) .orElseThrow(() -> new CustomException(APPLICATION_NOT_FOUND)); } } diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java index 8ad3a9287..f3dbc827c 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java @@ -56,7 +56,7 @@ public ApplicationsResponse getApplicants(SiteUser siteUser, String regionCode, @Transactional(readOnly = true) public ApplicationsResponse getApplicantsByUserApplications(SiteUser siteUser) { - Application userLatestApplication = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term); + Application userLatestApplication = applicationRepository.getApplicationBySiteUserIdAndTerm(siteUser.getId(), term); List univApplyInfoIds = Stream.of( userLatestApplication.getFirstChoiceUnivApplyInfoId(), @@ -120,7 +120,7 @@ private List createUniversityApplicantsResponses( @Transactional(readOnly = true) public void validateSiteUserCanViewApplicants(SiteUser siteUser) { - VerifyStatus verifyStatus = applicationRepository.getApplicationBySiteUserAndTerm(siteUser, term).getVerifyStatus(); + VerifyStatus verifyStatus = applicationRepository.getApplicationBySiteUserIdAndTerm(siteUser.getId(), term).getVerifyStatus(); if (verifyStatus != VerifyStatus.APPROVED) { throw new CustomException(APPLICATION_NOT_APPROVED); } diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java index 37cc0ae19..e811a48a0 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java @@ -50,7 +50,7 @@ public ApplicationSubmissionResponse apply(SiteUser siteUser, ApplyRequest apply Long secondChoiceUnivApplyInfoId = univApplyInfoChoiceRequest.secondChoiceUnivApplyInfoId(); Long thirdChoiceUnivApplyInfoId = univApplyInfoChoiceRequest.thirdChoiceUnivApplyInfoId(); - Optional existingApplication = applicationRepository.findBySiteUserAndTerm(siteUser, term); + Optional existingApplication = applicationRepository.findBySiteUserIdAndTerm(siteUser.getId(), term); int updateCount = existingApplication .map(application -> { validateUpdateLimitNotExceed(application); @@ -78,7 +78,7 @@ public ApplicationSubmissionResponse apply(SiteUser siteUser, ApplyRequest apply } private GpaScore getValidGpaScore(SiteUser siteUser, Long gpaScoreId) { - GpaScore gpaScore = gpaScoreRepository.findGpaScoreBySiteUserAndId(siteUser, gpaScoreId) + GpaScore gpaScore = gpaScoreRepository.findGpaScoreBySiteUserIdAndId(siteUser.getId(), gpaScoreId) .orElseThrow(() -> new CustomException(GPA_SCORE_NOT_FOUND)); if (gpaScore.getVerifyStatus() != VerifyStatus.APPROVED) { throw new CustomException(INVALID_GPA_SCORE_STATUS); @@ -88,7 +88,7 @@ private GpaScore getValidGpaScore(SiteUser siteUser, Long gpaScoreId) { private LanguageTestScore getValidLanguageTestScore(SiteUser siteUser, Long languageTestScoreId) { LanguageTestScore languageTestScore = languageTestScoreRepository - .findLanguageTestScoreBySiteUserAndId(siteUser, languageTestScoreId) + .findLanguageTestScoreBySiteUserIdAndId(siteUser.getId(), languageTestScoreId) .orElseThrow(() -> new CustomException(INVALID_LANGUAGE_TEST_SCORE)); if (languageTestScore.getVerifyStatus() != VerifyStatus.APPROVED) { throw new CustomException(INVALID_LANGUAGE_TEST_SCORE_STATUS); diff --git a/src/main/java/com/example/solidconnection/location/country/domain/Country.java b/src/main/java/com/example/solidconnection/location/country/domain/Country.java index 5a14fef86..90ce8fe4f 100644 --- a/src/main/java/com/example/solidconnection/location/country/domain/Country.java +++ b/src/main/java/com/example/solidconnection/location/country/domain/Country.java @@ -1,10 +1,8 @@ package com.example.solidconnection.location.country.domain; -import com.example.solidconnection.location.region.domain.Region; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; -import jakarta.persistence.ManyToOne; import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -23,12 +21,12 @@ public class Country { @Column(nullable = false, length = 100) private String koreanName; - @ManyToOne - private Region region; + @Column(name="region_code") + private String regionCode; - public Country(String code, String koreanName, Region region) { + public Country(String code, String koreanName, String regionCode) { this.code = code; this.koreanName = koreanName; - this.region = region; + this.regionCode = regionCode; } } diff --git a/src/main/java/com/example/solidconnection/location/country/domain/InterestedCountry.java b/src/main/java/com/example/solidconnection/location/country/domain/InterestedCountry.java index edc68ba79..bfad77e10 100644 --- a/src/main/java/com/example/solidconnection/location/country/domain/InterestedCountry.java +++ b/src/main/java/com/example/solidconnection/location/country/domain/InterestedCountry.java @@ -1,11 +1,11 @@ package com.example.solidconnection.location.country.domain; import com.example.solidconnection.siteuser.domain.SiteUser; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import jakarta.persistence.UniqueConstraint; import lombok.AccessLevel; @@ -27,14 +27,14 @@ public class InterestedCountry { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne - private SiteUser siteUser; + @Column(name="site_user_id") + private long siteUserId; - @ManyToOne - private Country country; + @Column(name="country_code") + private String countryCode; public InterestedCountry(SiteUser siteUser, Country country) { - this.siteUser = siteUser; - this.country = country; + this.siteUserId = siteUser.getId(); + this.countryCode = country.getCode(); } } diff --git a/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java b/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java index a24866ed6..9e80a84d2 100644 --- a/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java +++ b/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java @@ -1,12 +1,11 @@ package com.example.solidconnection.location.country.repository; import com.example.solidconnection.location.country.domain.InterestedCountry; -import com.example.solidconnection.siteuser.domain.SiteUser; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface InterestedCountryRepository extends JpaRepository { - List findAllBySiteUser(SiteUser siteUser); + List findAllBySiteUserId(long siteUserId); } diff --git a/src/main/java/com/example/solidconnection/location/region/domain/InterestedRegion.java b/src/main/java/com/example/solidconnection/location/region/domain/InterestedRegion.java index 35214a95c..29f6bcaa8 100644 --- a/src/main/java/com/example/solidconnection/location/region/domain/InterestedRegion.java +++ b/src/main/java/com/example/solidconnection/location/region/domain/InterestedRegion.java @@ -1,11 +1,11 @@ package com.example.solidconnection.location.region.domain; import com.example.solidconnection.siteuser.domain.SiteUser; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import jakarta.persistence.UniqueConstraint; import lombok.AccessLevel; @@ -27,14 +27,14 @@ public class InterestedRegion { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne - private SiteUser siteUser; + @Column(name="site_user_id") + private long siteUserId; - @ManyToOne - private Region region; + @Column(name="region_code") + private String regionCode; public InterestedRegion(SiteUser siteUser, Region region) { - this.siteUser = siteUser; - this.region = region; + this.siteUserId = siteUser.getId(); + this.regionCode = region.getCode(); } } diff --git a/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java b/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java index 1e78b1803..8e06e8411 100644 --- a/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java +++ b/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java @@ -8,5 +8,5 @@ public interface InterestedRegionRepository extends JpaRepository { - List findAllBySiteUser(SiteUser siteUser); + List findAllBySiteUserId(long siteUserId); } diff --git a/src/main/java/com/example/solidconnection/score/domain/GpaScore.java b/src/main/java/com/example/solidconnection/score/domain/GpaScore.java index 45193e692..8c2b42e02 100644 --- a/src/main/java/com/example/solidconnection/score/domain/GpaScore.java +++ b/src/main/java/com/example/solidconnection/score/domain/GpaScore.java @@ -12,7 +12,6 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.ManyToOne; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @@ -38,24 +37,15 @@ public class GpaScore extends BaseEntity { private String rejectedReason; - @ManyToOne - private SiteUser siteUser; + private long siteUserId; public GpaScore(Gpa gpa, SiteUser siteUser) { this.gpa = gpa; - this.siteUser = siteUser; + this.siteUserId = siteUser.getId(); this.verifyStatus = VerifyStatus.PENDING; this.rejectedReason = null; } - public void setSiteUser(SiteUser siteUser) { - if (this.siteUser != null) { - this.siteUser.getGpaScoreList().remove(this); - } - this.siteUser = siteUser; - siteUser.getGpaScoreList().add(this); - } - public void updateGpaScore(Gpa gpa, VerifyStatus verifyStatus, String rejectedReason) { this.gpa = gpa; this.verifyStatus = verifyStatus; diff --git a/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java b/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java index 3ef773806..2bb87e707 100644 --- a/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java +++ b/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java @@ -12,7 +12,6 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.ManyToOne; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -38,21 +37,12 @@ public class LanguageTestScore extends BaseEntity { private String rejectedReason; - @ManyToOne - private SiteUser siteUser; + private long siteUserId; public LanguageTestScore(LanguageTest languageTest, SiteUser siteUser) { this.languageTest = languageTest; this.verifyStatus = VerifyStatus.PENDING; - this.siteUser = siteUser; - } - - public void setSiteUser(SiteUser siteUser) { - if (this.siteUser != null) { - this.siteUser.getLanguageTestScoreList().remove(this); - } - this.siteUser = siteUser; - siteUser.getLanguageTestScoreList().add(this); + this.siteUserId = siteUser.getId(); } public void updateLanguageTestScore(LanguageTest languageTest, VerifyStatus verifyStatus, String rejectedReason) { diff --git a/src/main/java/com/example/solidconnection/score/repository/GpaScoreRepository.java b/src/main/java/com/example/solidconnection/score/repository/GpaScoreRepository.java index 0114410a3..a37fbfd1c 100644 --- a/src/main/java/com/example/solidconnection/score/repository/GpaScoreRepository.java +++ b/src/main/java/com/example/solidconnection/score/repository/GpaScoreRepository.java @@ -2,14 +2,16 @@ import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.repository.custom.GpaScoreFilterRepository; -import com.example.solidconnection.siteuser.domain.SiteUser; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.Optional; public interface GpaScoreRepository extends JpaRepository, GpaScoreFilterRepository { - Optional findGpaScoreBySiteUser(SiteUser siteUser); + Optional findGpaScoreBySiteUserId(long siteUserId); - Optional findGpaScoreBySiteUserAndId(SiteUser siteUser, Long id); + Optional findGpaScoreBySiteUserIdAndId(long siteUserId, Long id); + + List findBySiteUserId(long siteUserId); } diff --git a/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java b/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java index 8eb00a014..2f4268c06 100644 --- a/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java +++ b/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java @@ -2,15 +2,17 @@ import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.repository.custom.LanguageTestScoreFilterRepository; -import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.university.domain.LanguageTestType; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.Optional; public interface LanguageTestScoreRepository extends JpaRepository, LanguageTestScoreFilterRepository { - Optional findLanguageTestScoreBySiteUserAndLanguageTest_LanguageTestType(SiteUser siteUser, LanguageTestType languageTestType); + Optional findLanguageTestScoreBySiteUserIdAndLanguageTest_LanguageTestType(long siteUserId, LanguageTestType languageTestType); - Optional findLanguageTestScoreBySiteUserAndId(SiteUser siteUser, Long id); + Optional findLanguageTestScoreBySiteUserIdAndId(long siteUserId, Long id); + + List findBySiteUserId(long siteUserId); } diff --git a/src/main/java/com/example/solidconnection/score/repository/custom/GpaScoreFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/score/repository/custom/GpaScoreFilterRepositoryImpl.java index efa12f8df..dad2fa25e 100644 --- a/src/main/java/com/example/solidconnection/score/repository/custom/GpaScoreFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/score/repository/custom/GpaScoreFilterRepositoryImpl.java @@ -70,7 +70,7 @@ public Page searchGpaScores(ScoreSearchCondition conditi List content = queryFactory .select(GPA_SCORE_SEARCH_RESPONSE_PROJECTION) .from(gpaScore) - .join(gpaScore.siteUser, siteUser) + .join(siteUser).on(gpaScore.siteUserId.eq(siteUser.id)) .where( verifyStatusEq(condition.verifyStatus()), nicknameContains(condition.nickname()), @@ -84,7 +84,7 @@ public Page searchGpaScores(ScoreSearchCondition conditi Long totalCount = queryFactory .select(gpaScore.count()) .from(gpaScore) - .join(gpaScore.siteUser, siteUser) + .join(siteUser).on(gpaScore.siteUserId.eq(siteUser.id)) .where( verifyStatusEq(condition.verifyStatus()), nicknameContains(condition.nickname()), diff --git a/src/main/java/com/example/solidconnection/score/repository/custom/LanguageTestScoreFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/score/repository/custom/LanguageTestScoreFilterRepositoryImpl.java index 9f677db09..8d958d12a 100644 --- a/src/main/java/com/example/solidconnection/score/repository/custom/LanguageTestScoreFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/score/repository/custom/LanguageTestScoreFilterRepositoryImpl.java @@ -70,7 +70,7 @@ public Page searchLanguageTestScores(ScoreSearc List content = queryFactory .select(LANGUAGE_TEST_SCORE_SEARCH_RESPONSE_PROJECTION) .from(languageTestScore) - .join(languageTestScore.siteUser, siteUser) + .join(siteUser).on(languageTestScore.siteUserId.eq(siteUser.id)) .where( verifyStatusEq(condition.verifyStatus()), nicknameContains(condition.nickname()), @@ -84,7 +84,7 @@ public Page searchLanguageTestScores(ScoreSearc Long totalCount = queryFactory .select(languageTestScore.count()) .from(languageTestScore) - .join(languageTestScore.siteUser, siteUser) + .join(siteUser).on(languageTestScore.siteUserId.eq(siteUser.id)) .where( verifyStatusEq(condition.verifyStatus()), nicknameContains(condition.nickname()), diff --git a/src/main/java/com/example/solidconnection/score/service/ScoreService.java b/src/main/java/com/example/solidconnection/score/service/ScoreService.java index 5a6cf9801..8b56461e9 100644 --- a/src/main/java/com/example/solidconnection/score/service/ScoreService.java +++ b/src/main/java/com/example/solidconnection/score/service/ScoreService.java @@ -2,7 +2,6 @@ import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.s3.domain.ImgType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; @@ -17,19 +16,14 @@ import com.example.solidconnection.score.repository.GpaScoreRepository; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; -import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; - @Service @RequiredArgsConstructor public class ScoreService { @@ -37,23 +31,14 @@ public class ScoreService { private final GpaScoreRepository gpaScoreRepository; private final S3Service s3Service; private final LanguageTestScoreRepository languageTestScoreRepository; - private final SiteUserRepository siteUserRepository; @Transactional public Long submitGpaScore(SiteUser siteUser, GpaScoreRequest gpaScoreRequest, MultipartFile file) { UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(file, ImgType.GPA); Gpa gpa = new Gpa(gpaScoreRequest.gpa(), gpaScoreRequest.gpaCriteria(), uploadedFile.fileUrl()); - - /* - * todo: siteUser를 영속 상태로 만들 수 있도록 컨트롤러에서 siteUserId 를 넘겨줄 것인지, - * siteUser 에 gpaScoreList 를 FetchType.EAGER 로 설정할 것인지, - * gpa 와 siteUser 사이의 양방향을 끊을 것인지 생각해봐야한다. - */ - SiteUser siteUser1 = siteUserRepository.findById(siteUser.getId()).orElseThrow(() -> new CustomException(USER_NOT_FOUND)); - GpaScore newGpaScore = new GpaScore(gpa, siteUser1); - newGpaScore.setSiteUser(siteUser1); - GpaScore savedNewGpaScore = gpaScoreRepository.save(newGpaScore); // 저장 후 반환된 객체 - return savedNewGpaScore.getId(); // 저장된 GPA Score의 ID 반환 + GpaScore newGpaScore = new GpaScore(gpa, siteUser); + GpaScore savedNewGpaScore = gpaScoreRepository.save(newGpaScore); + return savedNewGpaScore.getId(); } @Transactional @@ -61,42 +46,31 @@ public Long submitLanguageTestScore(SiteUser siteUser, LanguageTestScoreRequest UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(file, ImgType.LANGUAGE_TEST); LanguageTest languageTest = new LanguageTest(languageTestScoreRequest.languageTestType(), languageTestScoreRequest.languageTestScore(), uploadedFile.fileUrl()); - - /* - * todo: siteUser를 영속 상태로 만들 수 있도록 컨트롤러에서 siteUserId 를 넘겨줄 것인지, - * siteUser 에 languageTestScoreList 를 FetchType.EAGER 로 설정할 것인지, - * languageTest 와 siteUser 사이의 양방향을 끊을 것인지 생각해봐야한다. - */ - SiteUser siteUser1 = siteUserRepository.findById(siteUser.getId()).orElseThrow(() -> new CustomException(USER_NOT_FOUND)); - LanguageTestScore newScore = new LanguageTestScore(languageTest, siteUser1); - newScore.setSiteUser(siteUser1); - LanguageTestScore savedNewScore = languageTestScoreRepository.save(newScore); // 새로 저장한 객체 - return savedNewScore.getId(); // 저장된 객체의 ID 반환 + LanguageTestScore newScore = new LanguageTestScore(languageTest, siteUser); + LanguageTestScore savedNewScore = languageTestScoreRepository.save(newScore); + return savedNewScore.getId(); } @Transactional(readOnly = true) public GpaScoreStatusesResponse getGpaScoreStatus(SiteUser siteUser) { - // todo: ditto - SiteUser siteUser1 = siteUserRepository.findById(siteUser.getId()).orElseThrow(() -> new CustomException(USER_NOT_FOUND)); List gpaScoreStatusResponseList = - Optional.ofNullable(siteUser1.getGpaScoreList()) - .map(scores -> scores.stream() - .map(GpaScoreStatusResponse::from) - .collect(Collectors.toList())) - .orElse(Collections.emptyList()); + gpaScoreRepository.findBySiteUserId(siteUser.getId()) + .stream() + .map(GpaScoreStatusResponse::from) + .toList(); + return GpaScoreStatusesResponse.from(gpaScoreStatusResponseList); } @Transactional(readOnly = true) public LanguageTestScoreStatusesResponse getLanguageTestScoreStatus(SiteUser siteUser) { - // todo: ditto - SiteUser siteUser1 = siteUserRepository.findById(siteUser.getId()).orElseThrow(() -> new CustomException(USER_NOT_FOUND)); + List languageTestScores = languageTestScoreRepository.findBySiteUserId(siteUser.getId()); + List languageTestScoreStatusResponseList = - Optional.ofNullable(siteUser1.getLanguageTestScoreList()) - .map(scores -> scores.stream() - .map(LanguageTestScoreStatusResponse::from) - .collect(Collectors.toList())) - .orElse(Collections.emptyList()); + languageTestScores.stream() + .map(LanguageTestScoreStatusResponse::from) + .collect(Collectors.toList()); + return LanguageTestScoreStatusesResponse.from(languageTestScoreStatusResponseList); } } diff --git a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java index 7c065c8e1..596801a97 100644 --- a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java +++ b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java @@ -3,8 +3,6 @@ import com.example.solidconnection.community.comment.domain.Comment; import com.example.solidconnection.community.post.domain.Post; import com.example.solidconnection.community.post.domain.PostLike; -import com.example.solidconnection.score.domain.GpaScore; -import com.example.solidconnection.score.domain.LanguageTestScore; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -88,12 +86,6 @@ public class SiteUser { @OneToMany(mappedBy = "siteUser", cascade = CascadeType.ALL, orphanRemoval = true) private List postLikeList = new ArrayList<>(); - @OneToMany(mappedBy = "siteUser", cascade = CascadeType.ALL, orphanRemoval = true) - private List languageTestScoreList = new ArrayList<>(); - - @OneToMany(mappedBy = "siteUser", cascade = CascadeType.ALL, orphanRemoval = true) - private List gpaScoreList = new ArrayList<>(); - public SiteUser( String email, String nickname, diff --git a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java index c524487e8..03db85fbc 100644 --- a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java +++ b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java @@ -8,7 +8,7 @@ import com.example.solidconnection.siteuser.dto.MyPageResponse; import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.university.domain.LikedUnivApplyInfo; +import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -39,7 +39,7 @@ public class MyPageService { * */ @Transactional(readOnly = true) public MyPageResponse getMyPageInfo(SiteUser siteUser) { - int likedUnivApplyInfoCount = likedUnivApplyInfoRepository.countBySiteUser_Id(siteUser.getId()); + int likedUnivApplyInfoCount = likedUnivApplyInfoRepository.countBySiteUserId(siteUser.getId()); return MyPageResponse.of(siteUser, likedUnivApplyInfoCount); } @@ -95,9 +95,10 @@ private boolean isDefaultProfileImage(String profileImageUrl) { * */ @Transactional(readOnly = true) public List getWishUnivApplyInfo(SiteUser siteUser) { - List likedUnivApplyInfos = likedUnivApplyInfoRepository.findAllBySiteUser_Id(siteUser.getId()); - return likedUnivApplyInfos.stream() - .map(likedUnivApplyInfo -> UnivApplyInfoPreviewResponse.from(likedUnivApplyInfo.getUnivApplyInfo())) + List univApplyInfos = likedUnivApplyInfoRepository.findUnivApplyInfosBySiteUserId(siteUser.getId()); + + return univApplyInfos.stream() + .map(UnivApplyInfoPreviewResponse::from) .toList(); } } diff --git a/src/main/java/com/example/solidconnection/university/domain/LikedUnivApplyInfo.java b/src/main/java/com/example/solidconnection/university/domain/LikedUnivApplyInfo.java index 08612fa42..0fc24ae3c 100644 --- a/src/main/java/com/example/solidconnection/university/domain/LikedUnivApplyInfo.java +++ b/src/main/java/com/example/solidconnection/university/domain/LikedUnivApplyInfo.java @@ -1,12 +1,10 @@ package com.example.solidconnection.university.domain; -import com.example.solidconnection.siteuser.domain.SiteUser; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import jakarta.persistence.UniqueConstraint; import lombok.AllArgsConstructor; @@ -33,10 +31,9 @@ public class LikedUnivApplyInfo { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne - @JoinColumn(name = "university_info_for_apply_id") - private UnivApplyInfo univApplyInfo; + @Column(name="university_info_for_apply_id") + private long univApplyInfoId; - @ManyToOne - private SiteUser siteUser; + @Column(name="site_user_id") + private long siteUserId; } diff --git a/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java b/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java index f54ffe5e7..9287f5bd4 100644 --- a/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java +++ b/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java @@ -11,6 +11,7 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; +import jakarta.persistence.CascadeType; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; @@ -76,10 +77,10 @@ public class UnivApplyInfo { @Column(length = 1000) private String details; - @OneToMany(mappedBy = "univApplyInfo", fetch = FetchType.EAGER) + @OneToMany(mappedBy = "univApplyInfo", cascade = CascadeType.ALL, orphanRemoval = true) private Set languageRequirements = new HashSet<>(); - @ManyToOne(fetch = FetchType.EAGER) + @ManyToOne(fetch = FetchType.LAZY) private University university; public void addLanguageRequirements(LanguageRequirement languageRequirements) { diff --git a/src/main/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepository.java b/src/main/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepository.java index bca7a7eef..fe4f93676 100644 --- a/src/main/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepository.java @@ -1,18 +1,29 @@ package com.example.solidconnection.university.repository; -import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.university.domain.LikedUnivApplyInfo; import com.example.solidconnection.university.domain.UnivApplyInfo; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; import java.util.Optional; public interface LikedUnivApplyInfoRepository extends JpaRepository { - List findAllBySiteUser_Id(long siteUserId); + List findAllBySiteUserId(long siteUserId); - int countBySiteUser_Id(long siteUserId); + int countBySiteUserId(long siteUserId); - Optional findBySiteUserAndUnivApplyInfo(SiteUser siteUser, UnivApplyInfo univApplyInfo); + Optional findBySiteUserIdAndUnivApplyInfoId(long siteUserId, long univApplyInfoId); + + @Query(""" + SELECT u + FROM UnivApplyInfo u + JOIN LikedUnivApplyInfo l ON u.id = l.univApplyInfoId + WHERE l.siteUserId = :siteUserId + """) + List findUnivApplyInfosBySiteUserId(@Param("siteUserId") long siteUserId); + + boolean existsBySiteUserIdAndUnivApplyInfoId(long siteUserId, long univApplyInfoId); } diff --git a/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java b/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java index 389f66531..c99ac01eb 100644 --- a/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java @@ -1,9 +1,9 @@ package com.example.solidconnection.university.repository; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.repository.custom.UnivApplyInfoFilterRepository; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -17,33 +17,37 @@ public interface UnivApplyInfoRepository extends JpaRepository, UnivApplyInfoFilterRepository { @Query(""" - SELECT uai - FROM UnivApplyInfo uai - JOIN University u ON uai.university = u - WHERE (u.country.code IN ( - SELECT c.code - FROM InterestedCountry ic - JOIN ic.country c - WHERE ic.siteUser = :siteUser - ) - OR u.region.code IN ( - SELECT r.code - FROM InterestedRegion ir - JOIN ir.region r - WHERE ir.siteUser = :siteUser - )) - AND uai.term = :term - """) - List findAllBySiteUsersInterestedCountryOrRegionAndTerm(@Param("siteUser") SiteUser siteUser, @Param("term") String term); + SELECT DISTINCT uai + FROM UnivApplyInfo uai + LEFT JOIN FETCH uai.languageRequirements lr + JOIN FETCH uai.university u + LEFT JOIN FETCH u.country c + LEFT JOIN FETCH u.region r + WHERE (c.code IN ( + SELECT ic.countryCode + FROM InterestedCountry ic + WHERE ic.siteUserId = :siteUserId + ) + OR r.code IN ( + SELECT ir.regionCode + FROM InterestedRegion ir + WHERE ir.siteUserId = :siteUserId + )) + AND uai.term = :term + """) + List findAllBySiteUsersInterestedCountryOrRegionAndTerm(@Param("siteUserId") Long siteUserId, @Param("term") String term); - @Query(value = """ - SELECT * - FROM university_info_for_apply - WHERE term = :term - ORDER BY RAND() - LIMIT :limitNum - """, nativeQuery = true) - List findRandomByTerm(@Param("term") String term, @Param("limitNum") int limitNum); + @Query(""" + SELECT uai + FROM UnivApplyInfo uai + LEFT JOIN FETCH uai.languageRequirements lr + LEFT JOIN FETCH uai.university u + LEFT JOIN FETCH u.country c + LEFT JOIN FETCH u.region r + WHERE uai.term = :term + ORDER BY FUNCTION('RAND') + """) + List findRandomByTerm(@Param("term") String term, Pageable pageable); // JPA에서 LIMIT 사용이 불가하므로 Pageable을 통해 0page에서 정해진 개수 만큼 가져오는 방식으로 구현 default UnivApplyInfo getUnivApplyInfoById(Long id) { return findById(id) @@ -53,9 +57,10 @@ default UnivApplyInfo getUnivApplyInfoById(Long id) { @Query(""" SELECT DISTINCT uai FROM UnivApplyInfo uai - JOIN FETCH uai.university u - JOIN FETCH u.country c - JOIN FETCH u.region r + LEFT JOIN FETCH uai.languageRequirements lr + LEFT JOIN FETCH uai.university u + LEFT JOIN FETCH u.country c + LEFT JOIN FETCH u.region r WHERE uai.id IN :ids """) List findAllByIds(@Param("ids") List ids); diff --git a/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java b/src/main/java/com/example/solidconnection/university/repository/UniversityInfoForApplyRepository.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java index 50c9bcf67..cf8f185d4 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java @@ -32,29 +32,26 @@ public List findAllByRegionCodeAndKeywords(String regionCode, Lis QUnivApplyInfo univApplyInfo = QUnivApplyInfo.univApplyInfo; QUniversity university = QUniversity.university; QCountry country = QCountry.country; - QRegion region = QRegion.region; QLanguageRequirement languageRequirement = QLanguageRequirement.languageRequirement; return queryFactory .selectFrom(univApplyInfo) .join(univApplyInfo.university, university).fetchJoin() .join(university.country, country).fetchJoin() - .join(country.region, region).fetchJoin() .leftJoin(univApplyInfo.languageRequirements, languageRequirement).fetchJoin() .where( - regionCodeEq(region, regionCode) + regionCodeEq(country, regionCode) .and(countryOrUniversityContainsKeyword(country, university, keywords)) ) .distinct() .fetch(); } - - private BooleanExpression regionCodeEq(QRegion region, String regionCode) { + private BooleanExpression regionCodeEq(QCountry country, String regionCode) { if (regionCode == null || regionCode.isEmpty()) { return Expressions.asBoolean(true).isTrue(); } - return region.code.eq(regionCode); + return country.regionCode.eq(regionCode); } private BooleanExpression countryOrUniversityContainsKeyword(QCountry country, QUniversity university, List keywords) { @@ -86,7 +83,7 @@ public List findAllByRegionCodeAndKeywordsAndLanguageTestTypeAndT .join(univApplyInfo.university, university) .join(university.country, country) .join(university.region, region) - .where(regionCodeEq(region, regionCode) + .where(regionCodeEq(country, regionCode) .and(countryOrUniversityContainsKeyword(country, university, keywords)) .and(univApplyInfo.term.eq(term))) .fetch(); diff --git a/src/main/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendService.java b/src/main/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendService.java index 1835f4ed0..07c2ef653 100644 --- a/src/main/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendService.java +++ b/src/main/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendService.java @@ -7,6 +7,8 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.util.List; @@ -30,6 +32,7 @@ public class GeneralUnivApplyInfoRecommendService { @EventListener(ApplicationReadyEvent.class) public void init() { - generalRecommends = univApplyInfoRepository.findRandomByTerm(term, RECOMMEND_UNIV_APPLY_INFO_NUM); + Pageable page = PageRequest.of(0, RECOMMEND_UNIV_APPLY_INFO_NUM); + generalRecommends = univApplyInfoRepository.findRandomByTerm(term, page); } } diff --git a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoLikeService.java b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoLikeService.java index 0d3fc4d96..4aa70eac4 100644 --- a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoLikeService.java +++ b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoLikeService.java @@ -38,14 +38,14 @@ public class UnivApplyInfoLikeService { public LikeResultResponse likeUnivApplyInfo(SiteUser siteUser, Long univApplyInfoId) { UnivApplyInfo univApplyInfo = univApplyInfoRepository.getUnivApplyInfoById(univApplyInfoId); - Optional optionalLikedUnivApplyInfo = likedUnivApplyInfoRepository.findBySiteUserAndUnivApplyInfo(siteUser, univApplyInfo); + Optional optionalLikedUnivApplyInfo = likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId(siteUser.getId(), univApplyInfo.getId()); if (optionalLikedUnivApplyInfo.isPresent()) { throw new CustomException(ALREADY_LIKED_UNIV_APPLY_INFO); } LikedUnivApplyInfo likedUnivApplyInfo = LikedUnivApplyInfo.builder() - .univApplyInfo(univApplyInfo) - .siteUser(siteUser) + .univApplyInfoId(univApplyInfo.getId()) + .siteUserId(siteUser.getId()) .build(); likedUnivApplyInfoRepository.save(likedUnivApplyInfo); return new LikeResultResponse(LIKE_SUCCESS_MESSAGE); @@ -58,7 +58,7 @@ public LikeResultResponse likeUnivApplyInfo(SiteUser siteUser, Long univApplyInf public LikeResultResponse cancelLikeUnivApplyInfo(SiteUser siteUser, long univApplyInfoId) { UnivApplyInfo univApplyInfo = univApplyInfoRepository.getUnivApplyInfoById(univApplyInfoId); - Optional optionalLikedUnivApplyInfo = likedUnivApplyInfoRepository.findBySiteUserAndUnivApplyInfo(siteUser, univApplyInfo); + Optional optionalLikedUnivApplyInfo = likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId(siteUser.getId(), univApplyInfo.getId()); if (optionalLikedUnivApplyInfo.isEmpty()) { throw new CustomException(NOT_LIKED_UNIV_APPLY_INFO); } @@ -73,7 +73,7 @@ public LikeResultResponse cancelLikeUnivApplyInfo(SiteUser siteUser, long univAp @Transactional(readOnly = true) public IsLikeResponse getIsLiked(SiteUser siteUser, Long univApplyInfoId) { UnivApplyInfo univApplyInfo = univApplyInfoRepository.getUnivApplyInfoById(univApplyInfoId); - boolean isLike = likedUnivApplyInfoRepository.findBySiteUserAndUnivApplyInfo(siteUser, univApplyInfo).isPresent(); + boolean isLike = likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId(siteUser.getId(), univApplyInfo.getId()).isPresent(); return new IsLikeResponse(isLike); } } diff --git a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendService.java b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendService.java index aa6f34f79..eaff6eec9 100644 --- a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendService.java +++ b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendService.java @@ -37,7 +37,7 @@ public class UnivApplyInfoRecommendService { public UnivApplyInfoRecommendsResponse getPersonalRecommends(SiteUser siteUser) { // 맞춤 추천 대학교를 불러온다. List personalRecommends = univApplyInfoRepository - .findAllBySiteUsersInterestedCountryOrRegionAndTerm(siteUser, term); + .findAllBySiteUsersInterestedCountryOrRegionAndTerm(siteUser.getId(), term); List trimmedRecommends = personalRecommends.subList(0, Math.min(RECOMMEND_UNIV_APPLY_INFO_NUM, personalRecommends.size())); Collections.shuffle(trimmedRecommends); @@ -56,7 +56,8 @@ private List getGeneralRecommendsExcludingSelected(List generalRecommend = new ArrayList<>(generalUnivApplyInfoRecommendService.getGeneralRecommends()); generalRecommend.removeAll(alreadyPicked); Collections.shuffle(generalRecommend); - return generalRecommend.subList(0, RECOMMEND_UNIV_APPLY_INFO_NUM - alreadyPicked.size()); + int needed = RECOMMEND_UNIV_APPLY_INFO_NUM - alreadyPicked.size(); + return generalRecommend.subList(0, Math.min(needed, generalRecommend.size())); } /* diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index b1beb524b..433ccb209 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -41,7 +41,7 @@ VALUES ('BN', '브루나이', 'ASIA'), ('MY', '말레이시아', 'ASIA'), ('RU', '러시아', 'EUROPE'); -INSERT INTO site_user (email, nickname, profile_image_url, preparation_stage, role, password, auth_type) +INSERT INTO site_user (email, nickname, profile_image_url, exchange_status, role, password, auth_type) VALUES ('test@test.email', 'yonso', 'https://github.com/nayonsoso.png', 'CONSIDERING', 'MENTEE', '$2a$10$psmwlxPfqWnIlq9JrlQJkuXr1XtjRNsyVOgcTWYZub5jFfn0TML76', 'EMAIL'); -- 12341234 diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java index a45e548ec..d0340931d 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java @@ -84,7 +84,7 @@ void setUp() { ApplicationSubmissionResponse response = applicationSubmissionService.apply(user, request); // then - Application savedApplication = applicationRepository.findBySiteUserAndTerm(user, term).orElseThrow(); + Application savedApplication = applicationRepository.findBySiteUserIdAndTerm(user.getId(), term).orElseThrow(); assertAll( () -> assertThat(response.applyCount()) .isEqualTo(savedApplication.getUpdateCount()), diff --git a/src/test/java/com/example/solidconnection/location/country/fixture/CountryFixtureBuilder.java b/src/test/java/com/example/solidconnection/location/country/fixture/CountryFixtureBuilder.java index 85fdb466f..2f6ce7011 100644 --- a/src/test/java/com/example/solidconnection/location/country/fixture/CountryFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/location/country/fixture/CountryFixtureBuilder.java @@ -37,6 +37,6 @@ public CountryFixtureBuilder region(Region region) { public Country findOrCreate() { return countryRepositoryForTest.findByCode(code) - .orElseGet(() -> countryRepositoryForTest.save(new Country(code, koreanName, region))); + .orElseGet(() -> countryRepositoryForTest.save(new Country(code, koreanName, region.getCode()))); } } diff --git a/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixtureBuilder.java b/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixtureBuilder.java index 51c2d0569..f95f31e2c 100644 --- a/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixtureBuilder.java @@ -39,7 +39,6 @@ public GpaScoreFixtureBuilder siteUser(SiteUser siteUser) { public GpaScore create() { GpaScore gpaScore = new GpaScore(gpa, siteUser); - gpaScore.setSiteUser(siteUser); gpaScore.setVerifyStatus(verifyStatus); return gpaScoreRepository.save(gpaScore); } diff --git a/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixtureBuilder.java b/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixtureBuilder.java index b2908bd93..aa7c4bdf5 100644 --- a/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixtureBuilder.java @@ -39,7 +39,6 @@ public LanguageTestScoreFixtureBuilder siteUser(SiteUser siteUser) { public LanguageTestScore create() { LanguageTestScore languageTestScore = new LanguageTestScore(languageTest, siteUser); - languageTestScore.setSiteUser(siteUser); languageTestScore.setVerifyStatus(verifyStatus); return languageTestScoreRepository.save(languageTestScore); } diff --git a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java index 62732f1c1..85fdfd4cf 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java @@ -193,14 +193,14 @@ void setUp() { } private int createLikedUnivApplyInfos(SiteUser testUser) { - LikedUnivApplyInfo likedUnivApplyInfo1 = new LikedUnivApplyInfo(null, univApplyInfoFixture.괌대학_A_지원_정보(), testUser); - LikedUnivApplyInfo likedUnivApplyInfo2 = new LikedUnivApplyInfo(null, univApplyInfoFixture.메이지대학_지원_정보(), testUser); - LikedUnivApplyInfo likedUnivApplyInfo3 = new LikedUnivApplyInfo(null, univApplyInfoFixture.코펜하겐IT대학_지원_정보(), testUser); + LikedUnivApplyInfo likedUnivApplyInfo1 = new LikedUnivApplyInfo(null, univApplyInfoFixture.괌대학_A_지원_정보().getId(), testUser.getId()); + LikedUnivApplyInfo likedUnivApplyInfo2 = new LikedUnivApplyInfo(null, univApplyInfoFixture.메이지대학_지원_정보().getId(), testUser.getId()); + LikedUnivApplyInfo likedUnivApplyInfo3 = new LikedUnivApplyInfo(null, univApplyInfoFixture.코펜하겐IT대학_지원_정보().getId(), testUser.getId()); likedUnivApplyInfoRepository.save(likedUnivApplyInfo1); likedUnivApplyInfoRepository.save(likedUnivApplyInfo2); likedUnivApplyInfoRepository.save(likedUnivApplyInfo3); - return likedUnivApplyInfoRepository.countBySiteUser_Id(testUser.getId()); + return likedUnivApplyInfoRepository.countBySiteUserId(testUser.getId()); } private MockMultipartFile createValidImageFile() { diff --git a/src/test/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepositoryTest.java b/src/test/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepositoryTest.java index 1d3094e31..59b09d628 100644 --- a/src/test/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepositoryTest.java +++ b/src/test/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepositoryTest.java @@ -81,8 +81,8 @@ class 사용자와_좋아요한_대학은_복합_유니크_제약조건을_갖 private LikedUnivApplyInfo createLikedUnivApplyInfo(SiteUser siteUser, UnivApplyInfo univApplyInfo) { return LikedUnivApplyInfo.builder() - .siteUser(siteUser) - .univApplyInfo(univApplyInfo) + .siteUserId(siteUser.getId()) + .univApplyInfoId(univApplyInfo.getId()) .build(); } } diff --git a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoLikeServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoLikeServiceTest.java index 548ddd9f5..8867639e5 100644 --- a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoLikeServiceTest.java @@ -61,8 +61,8 @@ class 대학_지원_정보_좋아요를_등록한다 { // then assertAll( () -> assertThat(response.result()).isEqualTo(LIKE_SUCCESS_MESSAGE), - () -> assertThat(likedUnivApplyInfoRepository.findBySiteUserAndUnivApplyInfo( - user, 괌대학_A_지원_정보 + () -> assertThat(likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId( + user.getId(), 괌대학_A_지원_정보.getId() )).isPresent() ); } @@ -93,8 +93,8 @@ class 대학_지원_정보_좋아요를_취소한다 { // then assertAll( () -> assertThat(response.result()).isEqualTo(LIKE_CANCELED_MESSAGE), - () -> assertThat(likedUnivApplyInfoRepository.findBySiteUserAndUnivApplyInfo( - user, 괌대학_A_지원_정보 + () -> assertThat(likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId( + user.getId(), 괌대학_A_지원_정보.getId() )).isEmpty() ); } @@ -153,8 +153,8 @@ class 대학_지원_정보_좋아요를_취소한다 { private void saveLikedUniversity(SiteUser siteUser, UnivApplyInfo univApplyInfo) { LikedUnivApplyInfo likedUnivApplyInfo = LikedUnivApplyInfo.builder() - .siteUser(siteUser) - .univApplyInfo(univApplyInfo) + .siteUserId(siteUser.getId()) + .univApplyInfoId(univApplyInfo.getId()) .build(); likedUnivApplyInfoRepository.save(likedUnivApplyInfo); } diff --git a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendServiceTest.java index c6bf60b9b..96e8bf81d 100644 --- a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendServiceTest.java @@ -143,11 +143,9 @@ void setUp() { .hasSize(RECOMMEND_UNIV_APPLY_INFO_NUM) .containsExactlyInAnyOrderElementsOf( generalUnivApplyInfoRecommendService.getGeneralRecommends().stream() - .map(UnivApplyInfoPreviewResponse::from) - .toList() + .map(UnivApplyInfoPreviewResponse::from).toList() ); } - @Test void 일반_추천_대학_지원_정보를_조회한다() { // when From cfe8489d111f0af4cfa1db97b6544cbf7d9735be Mon Sep 17 00:00:00 2001 From: Yeon <84384499+lsy1307@users.noreply.github.com> Date: Fri, 11 Jul 2025 01:30:17 +0900 Subject: [PATCH 40/90] =?UTF-8?q?refactor:=20Community=20=EC=97=B0?= =?UTF-8?q?=EA=B4=80=EA=B4=80=EA=B3=84=20=EC=88=98=EC=A0=95/=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20(#372)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: community 도메인 제외 연관관계 삭제 - Application, Country, GpaScore, InterestedCountry/Region, LanguageTestScore, LikedUniversity. SiteUser, UnivApplyInfo 도메인 수정 - 연관관계 삭제 및 생성자 parameter 수정으로 인한 쿼리 수정 다수 - test코드 수정 * fix: application, university 테스트 코드 오류 수정 - 연관관계 변경에 따른 transaction 문제 해결 - 쿼리 일부 수정, 테스트 코드 일부 수정 - GeneralUniversityRecommendService 로직 수정 * refactor: 불필요한 siteUser 재조회 수정 - ScoreService에서 siteUser정보를 다시 불러오는 로직 삭제 * refactor: 수정사항 반영 - 컨벤션, 타입수정 다수 * fix: conflict merge 과정에서 생긴 더미 파일 삭제 * refactor: 불필요한 import문 삭제 * refactor: Community 연관관계 매핑 수정/삭제 - Board, Comment, Post, PostLike 연관관계 수정/삭제 - 연관관계 수정에 따른 Repository및 Service 레이어 수정 - 연관관계 수정에 따른 테스트 코드 수정 * refactor: 사용하지 않는 import문 삭제 * fix: likedPostCount 테스트 코드 주석처리 및 todo 추가 * fix: 서브모듈 참조 오류 수정 * refactor: 코드리뷰 수정사항 반영 - Long타입 long으로 수정 - collect -> toList단독으로 변경 - 컨벤션 수정 - Recomment 변경사항 원복 - getGeneralRecommendsExcludingSelected함수 OutOfRange 방지 추가 * refactor: 코드리뷰 수정사항 반영 - UnivApplyInfo University fetchType LAZY로 변경 - 컨벤션 수정 - findRandomByTerm함수 nativeQuery제거 및 Pageable로 LIMIT 구현 * refactor: Parameter 명칭 변경 - siteUser -> siteUserId로 변경 * refactor: 코드리뷰 수정사항 반영 - 메서드명 각자 기능에 맞게 수정 * refactor: 코드리뷰 수정사항 반영 - 사용하지 않는 import문 제거 - 코드 컨벤션 수정 - postLikeList에서 의미없는 BatchSize 삭제 * fix: SiteUser 결함 수정 - password nullable true -> false로 수정 * fix: 직전 커밋 revert - password nullable 변경사항 취소 This reverts commit 6fcc903720043b2ba99b2ec18006007baa00156e. * refactor: 사용하지 않는 import문 제거 --- .../community/board/domain/Board.java | 9 ---- .../board/repository/BoardRepository.java | 2 - .../community/comment/domain/Comment.java | 28 ++++--------- .../comment/dto/CommentCreateRequest.java | 4 +- .../comment/dto/PostFindCommentResponse.java | 5 ++- .../comment/service/CommentService.java | 24 +++++------ .../community/post/domain/Post.java | 42 ++++--------------- .../community/post/domain/PostLike.java | 21 ++-------- .../community/post/dto/PostCreateRequest.java | 2 +- .../post/repository/PostLikeRepository.java | 7 ++-- .../post/repository/PostRepository.java | 5 ++- .../post/service/PostCommandService.java | 16 ++----- .../post/service/PostLikeService.java | 18 ++------ .../post/service/PostQueryService.java | 23 ++++++---- .../siteuser/domain/SiteUser.java | 16 ------- .../board/fixture/BoardFixtureBuilder.java | 2 +- .../repository/BoardRepositoryForTest.java | 4 +- .../fixture/CommentFixtureBuilder.java | 6 +-- .../comment/service/CommentServiceTest.java | 6 +-- .../post/fixture/PostFixtureBuilder.java | 2 +- .../post/service/PostLikeServiceTest.java | 4 +- .../post/service/PostQueryServiceTest.java | 4 +- .../PostViewCountConcurrencyTest.java | 2 +- .../siteuser/service/MyPageServiceTest.java | 4 +- 24 files changed, 83 insertions(+), 173 deletions(-) diff --git a/src/main/java/com/example/solidconnection/community/board/domain/Board.java b/src/main/java/com/example/solidconnection/community/board/domain/Board.java index fbf13b44d..4f93e9801 100644 --- a/src/main/java/com/example/solidconnection/community/board/domain/Board.java +++ b/src/main/java/com/example/solidconnection/community/board/domain/Board.java @@ -1,17 +1,11 @@ package com.example.solidconnection.community.board.domain; -import com.example.solidconnection.community.post.domain.Post; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.ArrayList; -import java.util.List; - @Entity @Getter @NoArgsConstructor @@ -24,9 +18,6 @@ public class Board { @Column(nullable = false, length = 20) private String koreanName; - @OneToMany(mappedBy = "board", cascade = CascadeType.ALL, orphanRemoval = true) - private List postList = new ArrayList<>(); - public Board(String code, String koreanName) { this.code = code; this.koreanName = koreanName; diff --git a/src/main/java/com/example/solidconnection/community/board/repository/BoardRepository.java b/src/main/java/com/example/solidconnection/community/board/repository/BoardRepository.java index 353a0d8dd..b81f9a15a 100644 --- a/src/main/java/com/example/solidconnection/community/board/repository/BoardRepository.java +++ b/src/main/java/com/example/solidconnection/community/board/repository/BoardRepository.java @@ -3,7 +3,6 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.community.board.domain.Board; -import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.query.Param; @@ -13,7 +12,6 @@ public interface BoardRepository extends JpaRepository { - @EntityGraph(attributePaths = {"postList"}) Optional findBoardByCode(@Param("code") String code); default Board getByCodeUsingEntityGraph(String code) { diff --git a/src/main/java/com/example/solidconnection/community/comment/domain/Comment.java b/src/main/java/com/example/solidconnection/community/comment/domain/Comment.java index 9bb2dccd9..90aa1c6ec 100644 --- a/src/main/java/com/example/solidconnection/community/comment/domain/Comment.java +++ b/src/main/java/com/example/solidconnection/community/comment/domain/Comment.java @@ -2,7 +2,6 @@ import com.example.solidconnection.common.BaseEntity; import com.example.solidconnection.community.post.domain.Post; -import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -45,9 +44,8 @@ public class Comment extends BaseEntity { @JoinColumn(name = "post_id") private Post post; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "site_user_id") - private SiteUser siteUser; + @Column + private long siteUserId; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parent_id") @@ -60,7 +58,7 @@ public Comment(String content) { this.content = content; } - public void setParentCommentAndPostAndSiteUser(Comment parentComment, Post post, SiteUser siteUser) { + public void setParentCommentAndPostAndSiteUserId(Comment parentComment, Post post, long siteUserId) { if (this.parentComment != null) { this.parentComment.getCommentList().remove(this); @@ -74,14 +72,10 @@ public void setParentCommentAndPostAndSiteUser(Comment parentComment, Post post, this.post = post; post.getCommentList().add(this); - if (this.siteUser != null) { - this.siteUser.getCommentList().remove(this); - } - this.siteUser = siteUser; - siteUser.getCommentList().add(this); + this.siteUserId = siteUserId; } - public void setPostAndSiteUser(Post post, SiteUser siteUser) { + public void setPostAndSiteUserId(Post post, long siteUserId) { if (this.post != null) { this.post.getCommentList().remove(this); @@ -89,22 +83,14 @@ public void setPostAndSiteUser(Post post, SiteUser siteUser) { this.post = post; post.getCommentList().add(this); - if (this.siteUser != null) { - this.siteUser.getCommentList().remove(this); - } - this.siteUser = siteUser; - siteUser.getCommentList().add(this); + this.siteUserId = siteUserId; } - public void resetPostAndSiteUserAndParentComment() { + public void resetPostAndParentComment() { if (this.post != null) { this.post.getCommentList().remove(this); this.post = null; } - if (this.siteUser != null) { - this.siteUser.getCommentList().remove(this); - this.siteUser = null; - } if (this.parentComment != null) { this.parentComment.getCommentList().remove(this); this.parentComment = null; diff --git a/src/main/java/com/example/solidconnection/community/comment/dto/CommentCreateRequest.java b/src/main/java/com/example/solidconnection/community/comment/dto/CommentCreateRequest.java index 13c512a0c..dd25516eb 100644 --- a/src/main/java/com/example/solidconnection/community/comment/dto/CommentCreateRequest.java +++ b/src/main/java/com/example/solidconnection/community/comment/dto/CommentCreateRequest.java @@ -24,9 +24,9 @@ public Comment toEntity(SiteUser siteUser, Post post, Comment parentComment) { ); if (parentComment == null) { - comment.setPostAndSiteUser(post, siteUser); + comment.setPostAndSiteUserId(post, siteUser.getId()); } else { - comment.setParentCommentAndPostAndSiteUser(parentComment, post, siteUser); + comment.setParentCommentAndPostAndSiteUserId(parentComment, post, siteUser.getId()); } return comment; } diff --git a/src/main/java/com/example/solidconnection/community/comment/dto/PostFindCommentResponse.java b/src/main/java/com/example/solidconnection/community/comment/dto/PostFindCommentResponse.java index f1fd78ad0..dcfcdcfa5 100644 --- a/src/main/java/com/example/solidconnection/community/comment/dto/PostFindCommentResponse.java +++ b/src/main/java/com/example/solidconnection/community/comment/dto/PostFindCommentResponse.java @@ -1,6 +1,7 @@ package com.example.solidconnection.community.comment.dto; import com.example.solidconnection.community.comment.domain.Comment; +import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.PostFindSiteUserResponse; import java.time.ZonedDateTime; @@ -15,7 +16,7 @@ public record PostFindCommentResponse( PostFindSiteUserResponse postFindSiteUserResponse ) { - public static PostFindCommentResponse from(Boolean isOwner, Comment comment) { + public static PostFindCommentResponse from(Boolean isOwner, Comment comment, SiteUser siteUser) { return new PostFindCommentResponse( comment.getId(), getParentCommentId(comment), @@ -23,7 +24,7 @@ public static PostFindCommentResponse from(Boolean isOwner, Comment comment) { isOwner, comment.getCreatedAt(), comment.getUpdatedAt(), - PostFindSiteUserResponse.from(comment.getSiteUser()) + PostFindSiteUserResponse.from(siteUser) ); } diff --git a/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java b/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java index 6a2760cf0..08e458810 100644 --- a/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java +++ b/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java @@ -18,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPDATE_DEPRECATED_COMMENT; @@ -35,14 +36,16 @@ public class CommentService { @Transactional(readOnly = true) public List findCommentsByPostId(SiteUser siteUser, Long postId) { + SiteUser commentOwner = siteUserRepository.findById(siteUser.getId()) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); return commentRepository.findCommentTreeByPostId(postId) .stream() - .map(comment -> PostFindCommentResponse.from(isOwner(comment, siteUser), comment)) + .map(comment -> PostFindCommentResponse.from(isOwner(comment, siteUser), comment, siteUser)) .collect(Collectors.toList()); } private Boolean isOwner(Comment comment, SiteUser siteUser) { - return comment.getSiteUser().getId().equals(siteUser.getId()); + return Objects.equals(comment.getSiteUserId(), siteUser.getId()); } @Transactional @@ -54,14 +57,7 @@ public CommentCreateResponse createComment(SiteUser siteUser, CommentCreateReque parentComment = commentRepository.getById(commentCreateRequest.parentId()); validateCommentDepth(parentComment); } - - /* - * todo: siteUser를 영속 상태로 만들 수 있도록 컨트롤러에서 siteUserId 를 넘겨줄 것인지, - * siteUser 에 postList 를 FetchType.EAGER 로 설정할 것인지, - * post 와 siteUser 사이의 양방향을 끊을 것인지 생각해봐야한다. - */ - SiteUser siteUser1 = siteUserRepository.findById(siteUser.getId()).orElseThrow(() -> new CustomException(USER_NOT_FOUND)); - Comment comment = commentCreateRequest.toEntity(siteUser1, post, parentComment); + Comment comment = commentCreateRequest.toEntity(siteUser, post, parentComment); Comment createdComment = commentRepository.save(comment); return CommentCreateResponse.from(createdComment); @@ -100,18 +96,18 @@ public CommentDeleteResponse deleteCommentById(SiteUser siteUser, Long commentId // 대댓글인 경우 Comment parentComment = comment.getParentComment(); // 대댓글을 삭제합니다. - comment.resetPostAndSiteUserAndParentComment(); + comment.resetPostAndParentComment(); commentRepository.deleteById(commentId); // 대댓글 삭제 이후, 부모댓글이 무의미하다면 이역시 삭제합니다. if (parentComment.getCommentList().isEmpty() && parentComment.getContent() == null) { - parentComment.resetPostAndSiteUserAndParentComment(); + parentComment.resetPostAndParentComment(); commentRepository.deleteById(parentComment.getId()); } } else { // 댓글인 경우 if (comment.getCommentList().isEmpty()) { // 대댓글이 없는 경우 - comment.resetPostAndSiteUserAndParentComment(); + comment.resetPostAndParentComment(); commentRepository.deleteById(commentId); } else { // 대댓글이 있는 경우 @@ -122,7 +118,7 @@ public CommentDeleteResponse deleteCommentById(SiteUser siteUser, Long commentId } private void validateOwnership(Comment comment, SiteUser siteUser) { - if (!comment.getSiteUser().getId().equals(siteUser.getId())) { + if (!Objects.equals(comment.getSiteUserId(), siteUser.getId())) { throw new CustomException(INVALID_POST_ACCESS); } } diff --git a/src/main/java/com/example/solidconnection/community/post/domain/Post.java b/src/main/java/com/example/solidconnection/community/post/domain/Post.java index 613abbb4b..7da358d43 100644 --- a/src/main/java/com/example/solidconnection/community/post/domain/Post.java +++ b/src/main/java/com/example/solidconnection/community/post/domain/Post.java @@ -1,21 +1,16 @@ package com.example.solidconnection.community.post.domain; import com.example.solidconnection.common.BaseEntity; -import com.example.solidconnection.community.board.domain.Board; import com.example.solidconnection.community.comment.domain.Comment; import com.example.solidconnection.community.post.dto.PostUpdateRequest; -import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; -import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -50,13 +45,12 @@ public class Post extends BaseEntity { @Enumerated(EnumType.STRING) private PostCategory category; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "board_code") - private Board board; + @Column + private String boardCode; + + @Column + private long siteUserId; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "site_user_id") - private SiteUser siteUser; @BatchSize(size = 20) @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) @@ -78,29 +72,9 @@ public Post(String title, String content, Boolean isQuestion, Long likeCount, Lo this.category = category; } - public void setBoardAndSiteUser(Board board, SiteUser siteUser) { - if (this.board != null) { - this.board.getPostList().remove(this); - } - this.board = board; - board.getPostList().add(this); - - if (this.siteUser != null) { - this.siteUser.getPostList().remove(this); - } - this.siteUser = siteUser; - siteUser.getPostList().add(this); - } - - public void resetBoardAndSiteUser() { - if (this.board != null) { - this.board.getPostList().remove(this); - this.board = null; - } - if (this.siteUser != null) { - this.siteUser.getPostList().remove(this); - this.siteUser = null; - } + public void setBoardAndSiteUserId(String boardCode, long siteUserId) { + this.boardCode = boardCode; + this.siteUserId = siteUserId; } public void update(PostUpdateRequest postUpdateRequest) { diff --git a/src/main/java/com/example/solidconnection/community/post/domain/PostLike.java b/src/main/java/com/example/solidconnection/community/post/domain/PostLike.java index bbe1ff361..6aa75f6a5 100644 --- a/src/main/java/com/example/solidconnection/community/post/domain/PostLike.java +++ b/src/main/java/com/example/solidconnection/community/post/domain/PostLike.java @@ -1,6 +1,5 @@ package com.example.solidconnection.community.post.domain; -import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -26,33 +25,21 @@ public class PostLike { @JoinColumn(name = "post_id") private Post post; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "site_user_id") - private SiteUser siteUser; + private long siteUserId; - public void setPostAndSiteUser(Post post, SiteUser siteUser) { + public void setPostAndSiteUserId(Post post, long siteUserId) { if (this.post != null) { this.post.getPostLikeList().remove(this); } this.post = post; post.getPostLikeList().add(this); - - if (this.siteUser != null) { - this.siteUser.getPostLikeList().remove(this); - } - this.siteUser = siteUser; - siteUser.getPostLikeList().add(this); + this.siteUserId = siteUserId; } - public void resetPostAndSiteUser() { + public void resetPost() { if (this.post != null) { this.post.getPostLikeList().remove(this); } this.post = null; - - if (this.siteUser != null) { - this.siteUser.getPostLikeList().remove(this); - } - this.siteUser = null; } } diff --git a/src/main/java/com/example/solidconnection/community/post/dto/PostCreateRequest.java b/src/main/java/com/example/solidconnection/community/post/dto/PostCreateRequest.java index 0cc3afc1f..485897510 100644 --- a/src/main/java/com/example/solidconnection/community/post/dto/PostCreateRequest.java +++ b/src/main/java/com/example/solidconnection/community/post/dto/PostCreateRequest.java @@ -36,7 +36,7 @@ public Post toEntity(SiteUser siteUser, Board board) { 0L, PostCategory.valueOf(this.postCategory) ); - post.setBoardAndSiteUser(board, siteUser); + post.setBoardAndSiteUserId(board.getCode(), siteUser.getId()); return post; } } diff --git a/src/main/java/com/example/solidconnection/community/post/repository/PostLikeRepository.java b/src/main/java/com/example/solidconnection/community/post/repository/PostLikeRepository.java index fbc8146cc..a3b96e826 100644 --- a/src/main/java/com/example/solidconnection/community/post/repository/PostLikeRepository.java +++ b/src/main/java/com/example/solidconnection/community/post/repository/PostLikeRepository.java @@ -3,7 +3,6 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.post.domain.Post; import com.example.solidconnection.community.post.domain.PostLike; -import com.example.solidconnection.siteuser.domain.SiteUser; import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; @@ -12,10 +11,10 @@ public interface PostLikeRepository extends JpaRepository { - Optional findPostLikeByPostAndSiteUser(Post post, SiteUser siteUser); + Optional findPostLikeByPostAndSiteUserId(Post post, long siteUserId); - default PostLike getByPostAndSiteUser(Post post, SiteUser siteUser) { - return findPostLikeByPostAndSiteUser(post, siteUser) + default PostLike getByPostAndSiteUserId(Post post, long siteUserId) { + return findPostLikeByPostAndSiteUserId(post, siteUserId) .orElseThrow(() -> new CustomException(INVALID_POST_LIKE)); } } diff --git a/src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java b/src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java index 2655e520c..3a6d50d3d 100644 --- a/src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java +++ b/src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java @@ -8,13 +8,16 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.List; import java.util.Optional; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ID; public interface PostRepository extends JpaRepository { - @EntityGraph(attributePaths = {"postImageList", "board", "siteUser"}) + List findByBoardCode(String boardCode); + + @EntityGraph(attributePaths = {"postImageList"}) Optional findPostById(Long id); @Modifying(clearAutomatically = true, flushAutomatically = true) diff --git a/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java b/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java index cb553fd0c..a76543407 100644 --- a/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java +++ b/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java @@ -16,7 +16,6 @@ import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.util.RedisUtils; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.EnumUtils; @@ -25,12 +24,12 @@ import org.springframework.web.multipart.MultipartFile; import java.util.List; +import java.util.Objects; import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_DELETE_OR_UPDATE_QUESTION; import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ACCESS; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_CATEGORY; -import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; @Service @RequiredArgsConstructor @@ -41,7 +40,6 @@ public class PostCommandService { private final S3Service s3Service; private final RedisService redisService; private final RedisUtils redisUtils; - private final SiteUserRepository siteUserRepository; @Transactional public PostCreateResponse createPost(SiteUser siteUser, PostCreateRequest postCreateRequest, @@ -52,13 +50,8 @@ public PostCreateResponse createPost(SiteUser siteUser, PostCreateRequest postCr // 객체 생성 Board board = boardRepository.getByCode(postCreateRequest.boardCode()); - /* - * todo: siteUser를 영속 상태로 만들 수 있도록 컨트롤러에서 siteUserId 를 넘겨줄 것인지, - * siteUser 에 postList 를 FetchType.EAGER 로 설정할 것인지, - * post 와 siteUser 사이의 양방향을 끊을 것인지 생각해봐야한다. - */ - SiteUser siteUser1 = siteUserRepository.findById(siteUser.getId()).orElseThrow(() -> new CustomException(USER_NOT_FOUND)); - Post post = postCreateRequest.toEntity(siteUser1, board); + Post post = postCreateRequest.toEntity(siteUser, board); + // 이미지 처리 savePostImages(imageFile, post); Post createdPost = postRepository.save(post); @@ -103,7 +96,6 @@ public PostDeleteResponse deletePostById(SiteUser siteUser, Long postId) { validateQuestion(post); removePostImages(post); - post.resetBoardAndSiteUser(); // cache out redisService.deleteKey(redisUtils.getPostViewCountRedisKey(postId)); postRepository.deleteById(post.getId()); @@ -112,7 +104,7 @@ public PostDeleteResponse deletePostById(SiteUser siteUser, Long postId) { } private void validateOwnership(Post post, SiteUser siteUser) { - if (!post.getSiteUser().getId().equals(siteUser.getId())) { + if (!Objects.equals(post.getSiteUserId(), siteUser.getId())) { throw new CustomException(INVALID_POST_ACCESS); } } diff --git a/src/main/java/com/example/solidconnection/community/post/service/PostLikeService.java b/src/main/java/com/example/solidconnection/community/post/service/PostLikeService.java index e0921e295..4ea99ad0f 100644 --- a/src/main/java/com/example/solidconnection/community/post/service/PostLikeService.java +++ b/src/main/java/com/example/solidconnection/community/post/service/PostLikeService.java @@ -8,14 +8,12 @@ import com.example.solidconnection.community.post.repository.PostLikeRepository; import com.example.solidconnection.community.post.repository.PostRepository; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_POST_LIKE; -import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; @Service @RequiredArgsConstructor @@ -23,21 +21,13 @@ public class PostLikeService { private final PostRepository postRepository; private final PostLikeRepository postLikeRepository; - private final SiteUserRepository siteUserRepository; @Transactional(isolation = Isolation.READ_COMMITTED) public PostLikeResponse likePost(SiteUser siteUser, Long postId) { Post post = postRepository.getById(postId); validateDuplicatePostLike(post, siteUser); PostLike postLike = new PostLike(); - - /* - * todo: siteUser를 영속 상태로 만들 수 있도록 컨트롤러에서 siteUserId 를 넘겨줄 것인지, - * siteUser 에 postList 를 FetchType.EAGER 로 설정할 것인지, - * post 와 siteUser 사이의 양방향을 끊을 것인지 생각해봐야한다. - */ - SiteUser siteUser1 = siteUserRepository.findById(siteUser.getId()).orElseThrow(() -> new CustomException(USER_NOT_FOUND)); - postLike.setPostAndSiteUser(post, siteUser1); + postLike.setPostAndSiteUserId(post, siteUser.getId()); postLikeRepository.save(postLike); postRepository.increaseLikeCount(post.getId()); @@ -48,8 +38,8 @@ public PostLikeResponse likePost(SiteUser siteUser, Long postId) { public PostDislikeResponse dislikePost(SiteUser siteUser, Long postId) { Post post = postRepository.getById(postId); - PostLike postLike = postLikeRepository.getByPostAndSiteUser(post, siteUser); - postLike.resetPostAndSiteUser(); + PostLike postLike = postLikeRepository.getByPostAndSiteUserId(post, siteUser.getId()); + postLike.resetPost(); postLikeRepository.deleteById(postLike.getId()); postRepository.decreaseLikeCount(post.getId()); @@ -57,7 +47,7 @@ public PostDislikeResponse dislikePost(SiteUser siteUser, Long postId) { } private void validateDuplicatePostLike(Post post, SiteUser siteUser) { - if (postLikeRepository.findPostLikeByPostAndSiteUser(post, siteUser).isPresent()) { + if (postLikeRepository.findPostLikeByPostAndSiteUserId(post, siteUser.getId()).isPresent()) { throw new CustomException(DUPLICATE_POST_LIKE); } } diff --git a/src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java b/src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java index b8d07b54f..4ac06424b 100644 --- a/src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java +++ b/src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java @@ -16,6 +16,7 @@ import com.example.solidconnection.community.post.repository.PostRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.PostFindSiteUserResponse; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.util.RedisUtils; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.EnumUtils; @@ -23,10 +24,12 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_BOARD_CODE; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_CATEGORY; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; @Service @RequiredArgsConstructor @@ -35,6 +38,7 @@ public class PostQueryService { private final BoardRepository boardRepository; private final PostRepository postRepository; private final PostLikeRepository postLikeRepository; + private final SiteUserRepository siteUserRepository; private final CommentService commentService; private final RedisService redisService; private final RedisUtils redisUtils; @@ -44,11 +48,10 @@ public List findPostsByCodeAndPostCategory(String code, String String boardCode = validateCode(code); PostCategory postCategory = validatePostCategory(category); + boardRepository.getByCode(boardCode); + List postList = postRepository.findByBoardCode(boardCode); - Board board = boardRepository.getByCodeUsingEntityGraph(boardCode); - List postList = getPostListByPostCategory(board.getPostList(), postCategory); - - return PostListResponse.from(postList); + return PostListResponse.from(getPostListByPostCategory(postList, postCategory)); } @Transactional(readOnly = true) @@ -57,8 +60,12 @@ public PostFindResponse findPostById(SiteUser siteUser, Long postId) { Boolean isOwner = getIsOwner(post, siteUser); Boolean isLiked = getIsLiked(post, siteUser); - PostFindBoardResponse boardPostFindResultDTO = PostFindBoardResponse.from(post.getBoard()); - PostFindSiteUserResponse siteUserPostFindResultDTO = PostFindSiteUserResponse.from(post.getSiteUser()); + Board board = boardRepository.findBoardByCode(post.getBoardCode()) + .orElseThrow(()->new CustomException(INVALID_BOARD_CODE)); + SiteUser postAuthor = siteUserRepository.findById(post.getSiteUserId()) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); + PostFindBoardResponse boardPostFindResultDTO = PostFindBoardResponse.from(board); + PostFindSiteUserResponse siteUserPostFindResultDTO = PostFindSiteUserResponse.from(postAuthor); List postImageFindResultDTOList = PostFindPostImageResponse.from(post.getPostImageList()); List commentFindResultDTOList = commentService.findCommentsByPostId(siteUser, postId); @@ -80,11 +87,11 @@ private String validateCode(String code) { } private Boolean getIsOwner(Post post, SiteUser siteUser) { - return post.getSiteUser().getId().equals(siteUser.getId()); + return Objects.equals(post.getSiteUserId(), siteUser.getId()); } private Boolean getIsLiked(Post post, SiteUser siteUser) { - return postLikeRepository.findPostLikeByPostAndSiteUser(post, siteUser) + return postLikeRepository.findPostLikeByPostAndSiteUserId(post, siteUser.getId()) .isPresent(); } diff --git a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java index 596801a97..c7d30e7ae 100644 --- a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java +++ b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java @@ -1,9 +1,5 @@ package com.example.solidconnection.siteuser.domain; -import com.example.solidconnection.community.comment.domain.Comment; -import com.example.solidconnection.community.post.domain.Post; -import com.example.solidconnection.community.post.domain.PostLike; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -11,7 +7,6 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.persistence.UniqueConstraint; import lombok.AccessLevel; @@ -22,8 +17,6 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -77,15 +70,6 @@ public class SiteUser { @Column(nullable = true) private String password; - @OneToMany(mappedBy = "siteUser", cascade = CascadeType.ALL, orphanRemoval = true) - private List postList = new ArrayList<>(); - - @OneToMany(mappedBy = "siteUser", cascade = CascadeType.ALL) - private List commentList = new ArrayList<>(); - - @OneToMany(mappedBy = "siteUser", cascade = CascadeType.ALL, orphanRemoval = true) - private List postLikeList = new ArrayList<>(); - public SiteUser( String email, String nickname, diff --git a/src/test/java/com/example/solidconnection/community/board/fixture/BoardFixtureBuilder.java b/src/test/java/com/example/solidconnection/community/board/fixture/BoardFixtureBuilder.java index 1b7eff6ba..0a50b0930 100644 --- a/src/test/java/com/example/solidconnection/community/board/fixture/BoardFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/community/board/fixture/BoardFixtureBuilder.java @@ -25,7 +25,7 @@ public BoardFixtureBuilder koreanName(String koreanName) { } public Board findOrCreate() { - return boardRepositoryForTest.findByCodeWithPosts(code) + return boardRepositoryForTest.findByCode(code) .orElseGet(() -> boardRepositoryForTest.save(new Board(code, koreanName))); } } diff --git a/src/test/java/com/example/solidconnection/community/board/repository/BoardRepositoryForTest.java b/src/test/java/com/example/solidconnection/community/board/repository/BoardRepositoryForTest.java index 0d94e8d41..bc6470e2c 100644 --- a/src/test/java/com/example/solidconnection/community/board/repository/BoardRepositoryForTest.java +++ b/src/test/java/com/example/solidconnection/community/board/repository/BoardRepositoryForTest.java @@ -9,6 +9,6 @@ public interface BoardRepositoryForTest extends JpaRepository { - @Query("SELECT b FROM Board b LEFT JOIN FETCH b.postList WHERE b.code = :code") - Optional findByCodeWithPosts(@Param("code") String code); + @Query("SELECT b FROM Board b WHERE b.code = :code") + Optional findByCode(@Param("code") String code); } diff --git a/src/test/java/com/example/solidconnection/community/comment/fixture/CommentFixtureBuilder.java b/src/test/java/com/example/solidconnection/community/comment/fixture/CommentFixtureBuilder.java index f5dc10ac2..02a8ba889 100644 --- a/src/test/java/com/example/solidconnection/community/comment/fixture/CommentFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/community/comment/fixture/CommentFixtureBuilder.java @@ -40,14 +40,14 @@ public CommentFixtureBuilder parentComment(Comment parentComment) { public Comment createParent() { Comment comment = new Comment(content); - comment.setPostAndSiteUser(post, siteUser); + comment.setPostAndSiteUserId(post, siteUser.getId()); return commentRepository.save(comment); } public Comment createChild() { Comment comment = new Comment(content); - comment.setPostAndSiteUser(post, siteUser); - comment.setParentCommentAndPostAndSiteUser(parentComment, post, siteUser); + comment.setPostAndSiteUserId(post, siteUser.getId()); + comment.setParentCommentAndPostAndSiteUserId(parentComment, post, siteUser.getId()); return commentRepository.save(comment); } } diff --git a/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java b/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java index 8e5e5c995..02501d7f1 100644 --- a/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java @@ -128,7 +128,7 @@ class 댓글_생성_테스트 { () -> assertThat(savedComment.getContent()).isEqualTo(request.content()), () -> assertThat(savedComment.getParentComment()).isNull(), () -> assertThat(savedComment.getPost().getId()).isEqualTo(post.getId()), - () -> assertThat(savedComment.getSiteUser().getId()).isEqualTo(user1.getId()) + () -> assertThat(savedComment.getSiteUserId()).isEqualTo(user1.getId()) ); } @@ -148,7 +148,7 @@ class 댓글_생성_테스트 { () -> assertThat(savedComment.getContent()).isEqualTo(request.content()), () -> assertThat(savedComment.getParentComment().getId()).isEqualTo(parentComment.getId()), () -> assertThat(savedComment.getPost().getId()).isEqualTo(post.getId()), - () -> assertThat(savedComment.getSiteUser().getId()).isEqualTo(user2.getId()) + () -> assertThat(savedComment.getSiteUserId()).isEqualTo(user2.getId()) ); } @@ -205,7 +205,7 @@ class 댓글_수정_테스트 { () -> assertThat(updatedComment.getContent()).isEqualTo(request.content()), () -> assertThat(updatedComment.getParentComment()).isNull(), () -> assertThat(updatedComment.getPost().getId()).isEqualTo(post.getId()), - () -> assertThat(updatedComment.getSiteUser().getId()).isEqualTo(user1.getId()) + () -> assertThat(updatedComment.getSiteUserId()).isEqualTo(user1.getId()) ); } diff --git a/src/test/java/com/example/solidconnection/community/post/fixture/PostFixtureBuilder.java b/src/test/java/com/example/solidconnection/community/post/fixture/PostFixtureBuilder.java index ac3440b42..3473d61e2 100644 --- a/src/test/java/com/example/solidconnection/community/post/fixture/PostFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/community/post/fixture/PostFixtureBuilder.java @@ -71,7 +71,7 @@ public Post create() { likeCount, viewCount, postCategory); - post.setBoardAndSiteUser(board, siteUser); + post.setBoardAndSiteUserId(board.getCode(), siteUser.getId()); return postRepository.save(post); } } diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java index 9e36edeb9..3f5833006 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java @@ -79,7 +79,7 @@ class 게시글_좋아요_테스트 { () -> assertThat(response.likeCount()).isEqualTo(beforeLikeCount + 1), () -> assertThat(response.isLiked()).isTrue(), () -> assertThat(likedPost.getLikeCount()).isEqualTo(beforeLikeCount + 1), - () -> assertThat(postLikeRepository.findPostLikeByPostAndSiteUser(likedPost, user)).isPresent() + () -> assertThat(postLikeRepository.findPostLikeByPostAndSiteUserId(likedPost, user.getId())).isPresent() ); } @@ -117,7 +117,7 @@ class 게시글_좋아요_취소_테스트 { () -> assertThat(response.likeCount()).isEqualTo(beforeLikeCount - 1), () -> assertThat(response.isLiked()).isFalse(), () -> assertThat(unlikedPost.getLikeCount()).isEqualTo(beforeLikeCount - 1), - () -> assertThat(postLikeRepository.findPostLikeByPostAndSiteUser(unlikedPost, user)).isEmpty() + () -> assertThat(postLikeRepository.findPostLikeByPostAndSiteUserId(unlikedPost, user.getId())).isEmpty() ); } diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java index dc1e80746..3d198251a 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java @@ -93,7 +93,7 @@ void setUp() { List posts = List.of(post1, post2, post3); List expectedPosts = posts.stream() .filter(post -> post.getCategory().equals(PostCategory.자유) - && post.getBoard().getCode().equals(BoardCode.FREE.name())) + && post.getBoardCode().equals(BoardCode.FREE.name())) .toList(); List expectedResponses = PostListResponse.from(expectedPosts); @@ -115,7 +115,7 @@ void setUp() { // given List posts = List.of(post1, post2, post3); List expectedPosts = posts.stream() - .filter(post -> post.getBoard().getCode().equals(BoardCode.FREE.name())) + .filter(post -> post.getBoardCode().equals(BoardCode.FREE.name())) .toList(); List expectedResponses = PostListResponse.from(expectedPosts); diff --git a/src/test/java/com/example/solidconnection/concurrency/PostViewCountConcurrencyTest.java b/src/test/java/com/example/solidconnection/concurrency/PostViewCountConcurrencyTest.java index 5f2b70d37..8f61f0e75 100644 --- a/src/test/java/com/example/solidconnection/concurrency/PostViewCountConcurrencyTest.java +++ b/src/test/java/com/example/solidconnection/concurrency/PostViewCountConcurrencyTest.java @@ -77,7 +77,7 @@ private Post createPost(Board board, SiteUser siteUser) { 0L, PostCategory.valueOf("자유") ); - post.setBoardAndSiteUser(board, siteUser); + post.setBoardAndSiteUserId(board.getCode(), siteUser.getId()); return post; } diff --git a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java index 85fdfd4cf..677c6a3c4 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java @@ -80,13 +80,15 @@ void setUp() { // when MyPageResponse response = myPageService.getMyPageInfo(user); + // then Assertions.assertAll( () -> assertThat(response.nickname()).isEqualTo(user.getNickname()), () -> assertThat(response.profileImageUrl()).isEqualTo(user.getProfileImageUrl()), () -> assertThat(response.role()).isEqualTo(user.getRole()), () -> assertThat(response.email()).isEqualTo(user.getEmail()), - () -> assertThat(response.likedPostCount()).isEqualTo(user.getPostLikeList().size()), + // () -> assertThat(response.likedPostCount()).isEqualTo(user.getLikedPostList().size()), + // todo : 좋아요한 게시물 수 반환 기능 추가와 함께 수정요망 () -> assertThat(response.likedUnivApplyInfoCount()).isEqualTo(likedUnivApplyInfoCount) ); } From cda7a6809038769fff1ee0703a434283b43124be Mon Sep 17 00:00:00 2001 From: Yeon <84384499+lsy1307@users.noreply.github.com> Date: Sat, 12 Jul 2025 00:49:19 +0900 Subject: [PATCH 41/90] =?UTF-8?q?refactor:=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=20=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20(#373)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: community 도메인 제외 연관관계 삭제 - Application, Country, GpaScore, InterestedCountry/Region, LanguageTestScore, LikedUniversity. SiteUser, UnivApplyInfo 도메인 수정 - 연관관계 삭제 및 생성자 parameter 수정으로 인한 쿼리 수정 다수 - test코드 수정 * fix: application, university 테스트 코드 오류 수정 - 연관관계 변경에 따른 transaction 문제 해결 - 쿼리 일부 수정, 테스트 코드 일부 수정 - GeneralUniversityRecommendService 로직 수정 * refactor: 불필요한 siteUser 재조회 수정 - ScoreService에서 siteUser정보를 다시 불러오는 로직 삭제 * refactor: 수정사항 반영 - 컨벤션, 타입수정 다수 * fix: conflict merge 과정에서 생긴 더미 파일 삭제 * refactor: 불필요한 import문 삭제 * refactor: Community 연관관계 매핑 수정/삭제 - Board, Comment, Post, PostLike 연관관계 수정/삭제 - 연관관계 수정에 따른 Repository및 Service 레이어 수정 - 연관관계 수정에 따른 테스트 코드 수정 * refactor: 사용하지 않는 import문 삭제 * refactor: 댓글 정책 변경에 따른 수정 - is_deleted Column추가 - 해당 Column추가에 따른 Repository, Service, DTO 수정 - 댓글 삭제 시 content 보존하도록 변경 - 테스트 코드 수정 * fix: likedPostCount 테스트 코드 주석처리 및 todo 추가 * fix: 서브모듈 참조 오류 수정 * refactor: 코드리뷰 수정사항 반영 - Long타입 long으로 수정 - collect -> toList단독으로 변경 - 컨벤션 수정 - Recomment 변경사항 원복 - getGeneralRecommendsExcludingSelected함수 OutOfRange 방지 추가 * refactor: 코드리뷰 수정사항 반영 - UnivApplyInfo University fetchType LAZY로 변경 - 컨벤션 수정 - findRandomByTerm함수 nativeQuery제거 및 Pageable로 LIMIT 구현 * refactor: Parameter 명칭 변경 - siteUser -> siteUserId로 변경 * refactor: 코드리뷰 수정사항 반영 - 메서드명 각자 기능에 맞게 수정 * refactor: 코드리뷰 수정사항 반영 - 사용하지 않는 import문 제거 - 코드 컨벤션 수정 - postLikeList에서 의미없는 BatchSize 삭제 * fix: SiteUser 결함 수정 - password nullable true -> false로 수정 * fix: 직전 커밋 revert - password nullable 변경사항 취소 This reverts commit 6fcc903720043b2ba99b2ec18006007baa00156e. * refactor: 코드 리뷰 수정사항 반영 - 조건문에서 isDeleted사용 - 코드 컨벤션 수정 - 부모 댓글이 삭제된 경우에 자식 댓글이 있는 경우 테스트 코드 추가 * refactor: flyway script 추가 * fix: CommentService 수정 - CommentService에서 현재 접속중인 User정보가 아닌 댓글의 User정보를 반환하도록 수정 * refactor: 삭제된 댓글에 대한 사용자 정보를 반환하지 않도록 수정 - DTO에서 구분하여 isDeleted true면 null로 반환 - 테스트 코드 추가 * refactor: 테스트 코드 수정 - 디버깅용 코드 삭제 - 꼭 필요한 부분만 검증하도록 테스트 검증 간소화 --- .../community/comment/domain/Comment.java | 5 +- .../comment/dto/PostFindCommentResponse.java | 13 ++- .../comment/repository/CommentRepository.java | 4 +- .../comment/service/CommentService.java | 46 +++++++- .../V22_add_is_deleted_to_comment.sql | 2 + .../comment/service/CommentServiceTest.java | 104 ++++++++++++++---- 6 files changed, 142 insertions(+), 32 deletions(-) create mode 100644 src/main/resources/db/migration/V22_add_is_deleted_to_comment.sql diff --git a/src/main/java/com/example/solidconnection/community/comment/domain/Comment.java b/src/main/java/com/example/solidconnection/community/comment/domain/Comment.java index 90aa1c6ec..5d47bd591 100644 --- a/src/main/java/com/example/solidconnection/community/comment/domain/Comment.java +++ b/src/main/java/com/example/solidconnection/community/comment/domain/Comment.java @@ -40,6 +40,9 @@ public class Comment extends BaseEntity { @Column(length = 255) private String content; + @Column(name = "is_deleted", columnDefinition = "boolean default false", nullable = false) + private boolean isDeleted = false; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "post_id") private Post post; @@ -102,6 +105,6 @@ public void updateContent(String content) { } public void deprecateComment() { - this.content = null; + this.isDeleted = true; } } diff --git a/src/main/java/com/example/solidconnection/community/comment/dto/PostFindCommentResponse.java b/src/main/java/com/example/solidconnection/community/comment/dto/PostFindCommentResponse.java index dcfcdcfa5..0d9f5f295 100644 --- a/src/main/java/com/example/solidconnection/community/comment/dto/PostFindCommentResponse.java +++ b/src/main/java/com/example/solidconnection/community/comment/dto/PostFindCommentResponse.java @@ -20,11 +20,11 @@ public static PostFindCommentResponse from(Boolean isOwner, Comment comment, Sit return new PostFindCommentResponse( comment.getId(), getParentCommentId(comment), - comment.getContent(), + getDisplayContent(comment), isOwner, comment.getCreatedAt(), comment.getUpdatedAt(), - PostFindSiteUserResponse.from(siteUser) + getDisplaySiteUserResponse(comment, siteUser) ); } @@ -34,4 +34,13 @@ private static Long getParentCommentId(Comment comment) { } return null; } + + private static String getDisplayContent(Comment comment) + { + return comment.isDeleted() ? "" : comment.getContent(); + } + + private static PostFindSiteUserResponse getDisplaySiteUserResponse(Comment comment, SiteUser siteUser) { + return comment.isDeleted() ? null : PostFindSiteUserResponse.from(siteUser); + } } diff --git a/src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java b/src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java index cd2ae72ae..a17565bee 100644 --- a/src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java +++ b/src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java @@ -16,14 +16,14 @@ public interface CommentRepository extends JpaRepository { WITH RECURSIVE CommentTree AS ( SELECT id, parent_id, post_id, site_user_id, content, - created_at, updated_at, + created_at, updated_at, is_deleted, 0 AS level, CAST(id AS CHAR(255)) AS path FROM comment WHERE post_id = :postId AND parent_id IS NULL UNION ALL SELECT c.id, c.parent_id, c.post_id, c.site_user_id, c.content, - c.created_at, c.updated_at, + c.created_at, c.updated_at, c.is_deleted, ct.level + 1, CONCAT(ct.path, '->', c.id) FROM comment c INNER JOIN CommentTree ct ON c.parent_id = ct.id diff --git a/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java b/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java index 08e458810..38a61b60d 100644 --- a/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java +++ b/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java @@ -17,8 +17,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPDATE_DEPRECATED_COMMENT; @@ -36,14 +39,47 @@ public class CommentService { @Transactional(readOnly = true) public List findCommentsByPostId(SiteUser siteUser, Long postId) { - SiteUser commentOwner = siteUserRepository.findById(siteUser.getId()) - .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); - return commentRepository.findCommentTreeByPostId(postId) + List allComments = commentRepository.findCommentTreeByPostId(postId); + List filteredComments = filterCommentsByDeletionRules(allComments); + + Set userIds = filteredComments.stream() + .map(Comment::getSiteUserId) + .collect(Collectors.toSet()); + + Map userMap = siteUserRepository.findAllById(userIds) .stream() - .map(comment -> PostFindCommentResponse.from(isOwner(comment, siteUser), comment, siteUser)) + .collect(Collectors.toMap(SiteUser::getId, user -> user)); + + return filteredComments.stream() + .map(comment -> PostFindCommentResponse.from( + isOwner(comment, siteUser), comment, userMap.get(comment.getSiteUserId()))) .collect(Collectors.toList()); } + private List filterCommentsByDeletionRules(List comments) { + Map> commentsByParent = comments.stream() + .filter(comment -> comment.getParentComment() != null) + .collect(Collectors.groupingBy(comment -> comment.getParentComment().getId())); + + List result = new ArrayList<>(); + + List parentComments = comments.stream() + .filter(comment -> comment.getParentComment() == null) + .toList(); + for (Comment parent : parentComments) { + List children = commentsByParent.getOrDefault(parent.getId(), List.of()); + boolean allDeleted = parent.isDeleted() && + children.stream().allMatch(Comment::isDeleted); + if (!allDeleted) { + result.add(parent); + result.addAll(children.stream() + .filter(child -> !child.isDeleted()) + .toList()); + } + } + return result; + } + private Boolean isOwner(Comment comment, SiteUser siteUser) { return Objects.equals(comment.getSiteUserId(), siteUser.getId()); } @@ -99,7 +135,7 @@ public CommentDeleteResponse deleteCommentById(SiteUser siteUser, Long commentId comment.resetPostAndParentComment(); commentRepository.deleteById(commentId); // 대댓글 삭제 이후, 부모댓글이 무의미하다면 이역시 삭제합니다. - if (parentComment.getCommentList().isEmpty() && parentComment.getContent() == null) { + if (parentComment.getCommentList().isEmpty() && parentComment.isDeleted()) { parentComment.resetPostAndParentComment(); commentRepository.deleteById(parentComment.getId()); } diff --git a/src/main/resources/db/migration/V22_add_is_deleted_to_comment.sql b/src/main/resources/db/migration/V22_add_is_deleted_to_comment.sql new file mode 100644 index 000000000..a479fbaa2 --- /dev/null +++ b/src/main/resources/db/migration/V22_add_is_deleted_to_comment.sql @@ -0,0 +1,2 @@ +ALTER TABLE comment + ADD COLUMN is_deleted BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java b/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java index 02501d7f1..136500502 100644 --- a/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java @@ -15,6 +15,7 @@ import com.example.solidconnection.community.post.domain.PostCategory; import com.example.solidconnection.community.post.fixture.PostFixture; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.dto.PostFindSiteUserResponse; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; import jakarta.transaction.Transactional; @@ -108,6 +109,85 @@ class 댓글_조회_테스트 { )) ); } + + @Test + void 부모댓글과_대댓글이_모두_삭제되면_응답에서_제외한다() { + // given + Comment parentComment = commentFixture.부모_댓글("부모 댓글", post, user1); + Comment childComment1 = commentFixture.자식_댓글("자식 댓글1", post, user2, parentComment); + Comment childComment2 = commentFixture.자식_댓글("자식 댓글2", post, user2, parentComment); + + parentComment.deprecateComment(); + childComment1.deprecateComment(); + childComment2.deprecateComment(); + commentRepository.saveAll(List.of(parentComment, childComment1, childComment2)); + + // when + List responses = commentService.findCommentsByPostId(user1, post.getId()); + + // then + assertAll( + () -> assertThat(responses).isEmpty() + ); + } + + @Test + void 부모댓글이_삭제된_경우에도_자식댓글이_존재하면_자식댓글의_내용만_반환한다() { + // given + Comment parentComment = commentFixture.부모_댓글("부모 댓글", post, user1); + Comment childComment1 = commentFixture.자식_댓글("자식 댓글1", post, user2, parentComment); + Comment childComment2 = commentFixture.자식_댓글("자식 댓글2", post, user2, parentComment); + + parentComment.deprecateComment(); + commentRepository.saveAll(List.of(parentComment, childComment1, childComment2)); + + // when + List responses = commentService.findCommentsByPostId(user1, post.getId()); + + // then + assertAll( + () -> assertThat(responses).hasSize(3), + () -> assertThat(responses) + .extracting(PostFindCommentResponse::id) + .containsExactlyInAnyOrder(parentComment.getId(), childComment1.getId(), childComment2.getId()), + () -> assertThat(responses) + .filteredOn(response -> response.id().equals(parentComment.getId())) + .extracting(PostFindCommentResponse::content) + .containsExactly(""), + () -> assertThat(responses) + .filteredOn(response -> !response.id().equals(parentComment.getId())) + .extracting(PostFindCommentResponse::content) + .containsExactlyInAnyOrder("자식 댓글1", "자식 댓글2") + ); + } + + @Test + void 부모댓글이_삭제된_경우_부모댓글의_사용자정보는_null이고_자식댓글의_사용자정보는_정상적으로_반환한다() { + // given + Comment parentComment = commentFixture.부모_댓글("부모 댓글", post, user1); + Comment childComment1 = commentFixture.자식_댓글("자식 댓글1", post, user2, parentComment); + Comment childComment2 = commentFixture.자식_댓글("자식 댓글2", post, user2, parentComment); + + parentComment.deprecateComment(); + commentRepository.saveAll(List.of(parentComment, childComment1, childComment2)); + + // when + List responses = commentService.findCommentsByPostId(user1, post.getId()); + + // then + assertAll( + () -> assertThat(responses) + .filteredOn(response -> response.id().equals(parentComment.getId())) + .extracting(PostFindCommentResponse::postFindSiteUserResponse) + .containsExactly((PostFindSiteUserResponse) null), + () -> assertThat(responses) + .filteredOn(response -> !response.id().equals(parentComment.getId())) + .extracting(PostFindCommentResponse::postFindSiteUserResponse) + .isNotNull() + .extracting(PostFindSiteUserResponse::id) + .containsExactlyInAnyOrder(user2.getId(), user2.getId()) + ); + } } @Nested @@ -281,7 +361,8 @@ class 댓글_삭제_테스트 { // then Comment deletedComment = commentRepository.findById(response.id()).orElseThrow(); assertAll( - () -> assertThat(deletedComment.getContent()).isNull(), + () -> assertThat(deletedComment.getContent()).isEqualTo("부모 댓글"), + () -> assertThat(deletedComment.isDeleted()).isTrue(), () -> assertThat(deletedComment.getCommentList()) .extracting(Comment::getId) .containsExactlyInAnyOrder(childComment.getId()), @@ -316,27 +397,6 @@ class 댓글_삭제_테스트 { ); } - @Test - @Transactional - void 대댓글을_삭제하고_부모댓글이_삭제된_상태면_부모댓글도_삭제된다() { - // given - Comment parentComment = commentFixture.부모_댓글("부모 댓글", post, user1); - Comment childComment = commentFixture.자식_댓글("자식 댓글", post, user2, parentComment); - List comments = post.getCommentList(); - int expectedCommentsCount = comments.size() - 2; - parentComment.deprecateComment(); - - // when - CommentDeleteResponse response = commentService.deleteCommentById(user2, childComment.getId()); - - // then - assertAll( - () -> assertThat(commentRepository.findById(response.id())).isEmpty(), - () -> assertThat(commentRepository.findById(parentComment.getId())).isEmpty(), - () -> assertThat(post.getCommentList()).hasSize(expectedCommentsCount) - ); - } - @Test void 다른_사용자의_댓글을_삭제하면_예외_응답을_반환한다() { // given From 1ca612690e272c901f74bdb3ea96e3beab4cfda6 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Sun, 13 Jul 2025 16:26:32 +0900 Subject: [PATCH 42/90] =?UTF-8?q?refactor:=20'=EC=A2=8B=EC=95=84=EC=9A=94'?= =?UTF-8?q?=EC=9A=A9=EC=96=B4=20=ED=86=B5=EC=9D=BC=20=EB=B0=8F=20=ED=9A=8C?= =?UTF-8?q?=EC=9D=98=EC=97=90=EC=84=9C=20=EB=85=BC=EC=9D=98=EB=90=9C=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EC=A0=81=EC=9A=A9=20(#383)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 함수명 통일 wish -> liked * refactor: 대학지원정보 좋아요 시, 200만 반환하도록 - 회의에서 논의된 내용대로 통일 * refactor: 누락된 용어 통일 수정 * refactor: 좋아요한 대학지원정보 목록 조회 함수 이동 - MyPageService에서 UnivApplyInfoLikeService 로 * refactor: 대학 지원 정보 좋아요 서비스 이름 변경 --- .../siteuser/service/MyPageService.java | 17 +---- .../controller/UnivApplyInfoController.java | 29 ++++---- ...ce.java => LikedUnivApplyInfoService.java} | 29 +++++--- .../siteuser/service/MyPageServiceTest.java | 16 +---- ...ava => LikedUnivApplyInfoServiceTest.java} | 71 +++++++++++-------- 5 files changed, 73 insertions(+), 89 deletions(-) rename src/main/java/com/example/solidconnection/university/service/{UnivApplyInfoLikeService.java => LikedUnivApplyInfoService.java} (78%) rename src/test/java/com/example/solidconnection/university/service/{UnivApplyInfoLikeServiceTest.java => LikedUnivApplyInfoServiceTest.java} (62%) diff --git a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java index 03db85fbc..ccabb0565 100644 --- a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java +++ b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java @@ -6,10 +6,8 @@ import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.MyPageResponse; -import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.university.domain.UnivApplyInfo; -import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; +import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,7 +15,6 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.List; import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; import static com.example.solidconnection.common.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; @@ -89,16 +86,4 @@ private boolean isDefaultProfileImage(String profileImageUrl) { String prefix = "profile/"; return profileImageUrl == null || !profileImageUrl.startsWith(prefix); } - - /* - * 관심 대학교 목록을 조회한다. - * */ - @Transactional(readOnly = true) - public List getWishUnivApplyInfo(SiteUser siteUser) { - List univApplyInfos = likedUnivApplyInfoRepository.findUnivApplyInfosBySiteUserId(siteUser.getId()); - - return univApplyInfos.stream() - .map(UnivApplyInfoPreviewResponse::from) - .toList(); - } } diff --git a/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java b/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java index d39518e5d..d93e1524b 100644 --- a/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java +++ b/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java @@ -2,14 +2,12 @@ import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.service.MyPageService; import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.dto.IsLikeResponse; -import com.example.solidconnection.university.dto.LikeResultResponse; import com.example.solidconnection.university.dto.UnivApplyInfoDetailResponse; import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; import com.example.solidconnection.university.dto.UnivApplyInfoRecommendsResponse; -import com.example.solidconnection.university.service.UnivApplyInfoLikeService; +import com.example.solidconnection.university.service.LikedUnivApplyInfoService; import com.example.solidconnection.university.service.UnivApplyInfoQueryService; import com.example.solidconnection.university.service.UnivApplyInfoRecommendService; import lombok.RequiredArgsConstructor; @@ -30,9 +28,8 @@ public class UnivApplyInfoController { private final UnivApplyInfoQueryService univApplyInfoQueryService; - private final UnivApplyInfoLikeService univApplyInfoLikeService; + private final LikedUnivApplyInfoService likedUnivApplyInfoService; private final UnivApplyInfoRecommendService univApplyInfoRecommendService; - private final MyPageService myPageService; @GetMapping("/recommend") public ResponseEntity getUnivApplyInfoRecommends( @@ -47,38 +44,38 @@ public ResponseEntity getUnivApplyInfoRecommend // todo: return 타입 UnivApplyInfoPreviewResponses 같이 객체로 묶어서 반환하는 것으로 변경 필요 @GetMapping("/like") - public ResponseEntity> getMyWishUnivApplyInfo( /* todo: wish 가 아니라 liked 로 변경 필요 - 코드 용어 통일 */ + public ResponseEntity> getLikedUnivApplyInfos( @AuthorizedUser SiteUser siteUser ) { - List wishUniversities = myPageService.getWishUnivApplyInfo(siteUser); - return ResponseEntity.ok(wishUniversities); + List likedUnivApplyInfos = likedUnivApplyInfoService.getLikedUnivApplyInfos(siteUser); + return ResponseEntity.ok(likedUnivApplyInfos); } @GetMapping("/{univ-apply-info-id}/like") - public ResponseEntity getIsLiked( + public ResponseEntity isUnivApplyInfoLiked( @AuthorizedUser SiteUser siteUser, @PathVariable("univ-apply-info-id") Long univApplyInfoId ) { - IsLikeResponse isLiked = univApplyInfoLikeService.getIsLiked(siteUser, univApplyInfoId); + IsLikeResponse isLiked = likedUnivApplyInfoService.isUnivApplyInfoLiked(siteUser, univApplyInfoId); return ResponseEntity.ok(isLiked); } @PostMapping("/{univ-apply-info-id}/like") - public ResponseEntity addWishUnivApplyInfo( + public ResponseEntity addUnivApplyInfoLike( @AuthorizedUser SiteUser siteUser, @PathVariable("univ-apply-info-id") Long univApplyInfoId ) { - LikeResultResponse likeResultResponse = univApplyInfoLikeService.likeUnivApplyInfo(siteUser, univApplyInfoId); - return ResponseEntity.ok(likeResultResponse); + likedUnivApplyInfoService.addUnivApplyInfoLike(siteUser, univApplyInfoId); + return ResponseEntity.ok().build(); } @DeleteMapping("/{univ-apply-info-id}/like") - public ResponseEntity cancelWishUnivApplyInfo( + public ResponseEntity cancelUnivApplyInfoLike( @AuthorizedUser SiteUser siteUser, @PathVariable("univ-apply-info-id") Long univApplyInfoId ) { - LikeResultResponse likeResultResponse = univApplyInfoLikeService.cancelLikeUnivApplyInfo(siteUser, univApplyInfoId); - return ResponseEntity.ok(likeResultResponse); + likedUnivApplyInfoService.cancelUnivApplyInfoLike(siteUser, univApplyInfoId); + return ResponseEntity.ok().build(); } @GetMapping("/{univ-apply-info-id}") diff --git a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoLikeService.java b/src/main/java/com/example/solidconnection/university/service/LikedUnivApplyInfoService.java similarity index 78% rename from src/main/java/com/example/solidconnection/university/service/UnivApplyInfoLikeService.java rename to src/main/java/com/example/solidconnection/university/service/LikedUnivApplyInfoService.java index 4aa70eac4..2ac9ceb3d 100644 --- a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoLikeService.java +++ b/src/main/java/com/example/solidconnection/university/service/LikedUnivApplyInfoService.java @@ -2,17 +2,18 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import com.example.solidconnection.university.domain.LikedUnivApplyInfo; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.dto.IsLikeResponse; -import com.example.solidconnection.university.dto.LikeResultResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; +import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; import java.util.Optional; import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_UNIV_APPLY_INFO; @@ -20,10 +21,7 @@ @RequiredArgsConstructor @Service -public class UnivApplyInfoLikeService { - - public static final String LIKE_SUCCESS_MESSAGE = "LIKE_SUCCESS"; - public static final String LIKE_CANCELED_MESSAGE = "LIKE_CANCELED"; +public class LikedUnivApplyInfoService { private final UnivApplyInfoRepository univApplyInfoRepository; private final LikedUnivApplyInfoRepository likedUnivApplyInfoRepository; @@ -31,11 +29,22 @@ public class UnivApplyInfoLikeService { @Value("${university.term}") public String term; + /* + * '좋아요'한 대학교 목록을 조회한다. + * */ + @Transactional(readOnly = true) + public List getLikedUnivApplyInfos(SiteUser siteUser) { + List univApplyInfos = likedUnivApplyInfoRepository.findUnivApplyInfosBySiteUserId(siteUser.getId()); + return univApplyInfos.stream() + .map(UnivApplyInfoPreviewResponse::from) + .toList(); + } + /* * 대학교를 '좋아요' 한다. * */ @Transactional - public LikeResultResponse likeUnivApplyInfo(SiteUser siteUser, Long univApplyInfoId) { + public void addUnivApplyInfoLike(SiteUser siteUser, Long univApplyInfoId) { UnivApplyInfo univApplyInfo = univApplyInfoRepository.getUnivApplyInfoById(univApplyInfoId); Optional optionalLikedUnivApplyInfo = likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId(siteUser.getId(), univApplyInfo.getId()); @@ -48,14 +57,13 @@ public LikeResultResponse likeUnivApplyInfo(SiteUser siteUser, Long univApplyInf .siteUserId(siteUser.getId()) .build(); likedUnivApplyInfoRepository.save(likedUnivApplyInfo); - return new LikeResultResponse(LIKE_SUCCESS_MESSAGE); } /* * 대학교 '좋아요'를 취소한다. * */ @Transactional - public LikeResultResponse cancelLikeUnivApplyInfo(SiteUser siteUser, long univApplyInfoId) { + public void cancelUnivApplyInfoLike(SiteUser siteUser, long univApplyInfoId) { UnivApplyInfo univApplyInfo = univApplyInfoRepository.getUnivApplyInfoById(univApplyInfoId); Optional optionalLikedUnivApplyInfo = likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId(siteUser.getId(), univApplyInfo.getId()); @@ -64,14 +72,13 @@ public LikeResultResponse cancelLikeUnivApplyInfo(SiteUser siteUser, long univAp } likedUnivApplyInfoRepository.delete(optionalLikedUnivApplyInfo.get()); - return new LikeResultResponse(LIKE_CANCELED_MESSAGE); } /* * '좋아요'한 대학교인지 확인한다. * */ @Transactional(readOnly = true) - public IsLikeResponse getIsLiked(SiteUser siteUser, Long univApplyInfoId) { + public IsLikeResponse isUnivApplyInfoLiked(SiteUser siteUser, Long univApplyInfoId) { UnivApplyInfo univApplyInfo = univApplyInfoRepository.getUnivApplyInfoById(univApplyInfoId); boolean isLike = likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId(siteUser.getId(), univApplyInfo.getId()).isPresent(); return new IsLikeResponse(isLike); diff --git a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java index 677c6a3c4..d317ed542 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java @@ -10,12 +10,11 @@ import com.example.solidconnection.siteuser.dto.MyPageResponse; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.siteuser.fixture.SiteUserFixtureBuilder; -import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.university.domain.LikedUnivApplyInfo; -import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; +import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -26,7 +25,6 @@ import org.springframework.mock.web.MockMultipartFile; import java.time.LocalDateTime; -import java.util.List; import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; import static com.example.solidconnection.siteuser.service.MyPageService.MIN_DAYS_BETWEEN_NICKNAME_CHANGES; @@ -93,18 +91,6 @@ void setUp() { ); } - @Test - void 관심_대학_지원_정보_목록을_조회한다() { - // given - int likedUnivApplyInfo = createLikedUnivApplyInfos(user); - - // when - List response = myPageService.getWishUnivApplyInfo(user); - - // then - assertThat(response).hasSize(likedUnivApplyInfo); - } - @Nested class 프로필_이미지_수정_테스트 { diff --git a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoLikeServiceTest.java b/src/test/java/com/example/solidconnection/university/service/LikedUnivApplyInfoServiceTest.java similarity index 62% rename from src/test/java/com/example/solidconnection/university/service/UnivApplyInfoLikeServiceTest.java rename to src/test/java/com/example/solidconnection/university/service/LikedUnivApplyInfoServiceTest.java index 8867639e5..d8d4c0b9b 100644 --- a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/LikedUnivApplyInfoServiceTest.java @@ -3,34 +3,33 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; -import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.university.domain.LikedUnivApplyInfo; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.dto.IsLikeResponse; -import com.example.solidconnection.university.dto.LikeResultResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; +import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import java.util.List; + import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_UNIV_APPLY_INFO; import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_UNIV_APPLY_INFO; import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND; -import static com.example.solidconnection.university.service.UnivApplyInfoLikeService.LIKE_CANCELED_MESSAGE; -import static com.example.solidconnection.university.service.UnivApplyInfoLikeService.LIKE_SUCCESS_MESSAGE; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; @TestContainerSpringBootTest @DisplayName("대학 지원 정보 좋아요 서비스 테스트") -class UnivApplyInfoLikeServiceTest { +class LikedUnivApplyInfoServiceTest { @Autowired - private UnivApplyInfoLikeService univApplyInfoLikeService; + private LikedUnivApplyInfoService likedUnivApplyInfoService; @Autowired private LikedUnivApplyInfoRepository likedUnivApplyInfoRepository; @@ -50,30 +49,43 @@ void setUp() { 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); } + @Test + void 관심_대학_지원_정보_목록을_조회한다() { + // given + UnivApplyInfo 메이지대학_지원_정보 = univApplyInfoFixture.메이지대학_지원_정보(); + UnivApplyInfo 그라츠대학_지원_정보 = univApplyInfoFixture.그라츠대학_지원_정보(); + saveLikedUnivApplyInfo(user, 메이지대학_지원_정보); + saveLikedUnivApplyInfo(user, 그라츠대학_지원_정보); + + // when + List response = likedUnivApplyInfoService.getLikedUnivApplyInfos(user); + + // then + assertThat(response).extracting(UnivApplyInfoPreviewResponse::id) + .containsExactlyInAnyOrder(메이지대학_지원_정보.getId(), 그라츠대학_지원_정보.getId()); + } + @Nested class 대학_지원_정보_좋아요를_등록한다 { @Test void 성공적으로_좋아요를_등록한다() { // when - LikeResultResponse response = univApplyInfoLikeService.likeUnivApplyInfo(user, 괌대학_A_지원_정보.getId()); + likedUnivApplyInfoService.addUnivApplyInfoLike(user, 괌대학_A_지원_정보.getId()); // then - assertAll( - () -> assertThat(response.result()).isEqualTo(LIKE_SUCCESS_MESSAGE), - () -> assertThat(likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId( - user.getId(), 괌대학_A_지원_정보.getId() - )).isPresent() - ); + assertThat( + likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId(user.getId(), 괌대학_A_지원_정보.getId()) + ).isPresent(); } @Test void 이미_좋아요했으면_예외_응답을_반환한다() { // given - saveLikedUniversity(user, 괌대학_A_지원_정보); + saveLikedUnivApplyInfo(user, 괌대학_A_지원_정보); // when & then - assertThatCode(() -> univApplyInfoLikeService.likeUnivApplyInfo(user, 괌대학_A_지원_정보.getId())) + assertThatCode(() -> likedUnivApplyInfoService.addUnivApplyInfoLike(user, 괌대학_A_지원_정보.getId())) .isInstanceOf(CustomException.class) .hasMessage(ALREADY_LIKED_UNIV_APPLY_INFO.getMessage()); } @@ -85,24 +97,21 @@ class 대학_지원_정보_좋아요를_취소한다 { @Test void 성공적으로_좋아요를_취소한다() { // given - saveLikedUniversity(user, 괌대학_A_지원_정보); + saveLikedUnivApplyInfo(user, 괌대학_A_지원_정보); // when - LikeResultResponse response = univApplyInfoLikeService.cancelLikeUnivApplyInfo(user, 괌대학_A_지원_정보.getId()); + likedUnivApplyInfoService.cancelUnivApplyInfoLike(user, 괌대학_A_지원_정보.getId()); // then - assertAll( - () -> assertThat(response.result()).isEqualTo(LIKE_CANCELED_MESSAGE), - () -> assertThat(likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId( - user.getId(), 괌대학_A_지원_정보.getId() - )).isEmpty() - ); + assertThat( + likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId(user.getId(), 괌대학_A_지원_정보.getId()) + ).isEmpty(); } @Test void 좋아요하지_않았으면_예외_응답을_반환한다() { // when & then - assertThatCode(() -> univApplyInfoLikeService.cancelLikeUnivApplyInfo(user, 괌대학_A_지원_정보.getId())) + assertThatCode(() -> likedUnivApplyInfoService.cancelUnivApplyInfoLike(user, 괌대학_A_지원_정보.getId())) .isInstanceOf(CustomException.class) .hasMessage(NOT_LIKED_UNIV_APPLY_INFO.getMessage()); } @@ -114,7 +123,7 @@ class 대학_지원_정보_좋아요를_취소한다 { Long invalidUnivApplyInfoId = 9999L; // when & then - assertThatCode(() -> univApplyInfoLikeService.likeUnivApplyInfo(user, invalidUnivApplyInfoId)) + assertThatCode(() -> likedUnivApplyInfoService.addUnivApplyInfoLike(user, invalidUnivApplyInfoId)) .isInstanceOf(CustomException.class) .hasMessage(UNIV_APPLY_INFO_NOT_FOUND.getMessage()); } @@ -122,10 +131,10 @@ class 대학_지원_정보_좋아요를_취소한다 { @Test void 좋아요한_대학_지원_정보인지_확인한다() { // given - saveLikedUniversity(user, 괌대학_A_지원_정보); + saveLikedUnivApplyInfo(user, 괌대학_A_지원_정보); // when - IsLikeResponse response = univApplyInfoLikeService.getIsLiked(user, 괌대학_A_지원_정보.getId()); + IsLikeResponse response = likedUnivApplyInfoService.isUnivApplyInfoLiked(user, 괌대학_A_지원_정보.getId()); // then assertThat(response.isLike()).isTrue(); @@ -134,7 +143,7 @@ class 대학_지원_정보_좋아요를_취소한다 { @Test void 좋아요하지_않은_대학_지원_정보인지_확인한다() { // when - IsLikeResponse response = univApplyInfoLikeService.getIsLiked(user, 괌대학_A_지원_정보.getId()); + IsLikeResponse response = likedUnivApplyInfoService.isUnivApplyInfoLiked(user, 괌대학_A_지원_정보.getId()); // then assertThat(response.isLike()).isFalse(); @@ -146,12 +155,12 @@ class 대학_지원_정보_좋아요를_취소한다 { Long invalidUnivApplyInfoId = 9999L; // when & then - assertThatCode(() -> univApplyInfoLikeService.getIsLiked(user, invalidUnivApplyInfoId)) + assertThatCode(() -> likedUnivApplyInfoService.isUnivApplyInfoLiked(user, invalidUnivApplyInfoId)) .isInstanceOf(CustomException.class) .hasMessage(UNIV_APPLY_INFO_NOT_FOUND.getMessage()); } - private void saveLikedUniversity(SiteUser siteUser, UnivApplyInfo univApplyInfo) { + private void saveLikedUnivApplyInfo(SiteUser siteUser, UnivApplyInfo univApplyInfo) { LikedUnivApplyInfo likedUnivApplyInfo = LikedUnivApplyInfo.builder() .siteUserId(siteUser.getId()) .univApplyInfoId(univApplyInfo.getId()) From efc32cc83f4a15ef507c2a8fdcef4353c883d78b Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Mon, 14 Jul 2025 18:02:24 +0900 Subject: [PATCH 43/90] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=ED=95=A8=EC=88=98=EB=AA=85=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#387)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 예외 관련 테스트코드 함수명 수정 - 레이어드 아키텍쳐의 구조에 맞게, 컨트롤러 테스트가 아닌 테스트 코드에서 "응답"이라는 단어를 쓰지 않는다. * refactor: 누락한 용어 변경 반영 --- README.md | Bin 1114 -> 1104 bytes .../RejectedReasonValidatorTest.java | 4 ++-- .../service/AdminGpaScoreServiceTest.java | 2 +- .../AdminLanguageTestScoreServiceTest.java | 2 +- .../ApplicationSubmissionServiceTest.java | 6 +++--- .../auth/service/AuthServiceTest.java | 2 +- .../auth/service/EmailSignInServiceTest.java | 4 ++-- .../auth/service/JwtTokenProviderTest.java | 4 ++-- .../oauth/OAuthSignUpTokenProviderTest.java | 10 +++++----- .../CustomAccessDeniedHandlerTest.java | 2 +- .../CustomAuthenticationEntryPointTest.java | 2 +- .../resolver/AuthorizedUserResolverTest.java | 2 +- .../comment/service/CommentServiceTest.java | 10 +++++----- .../post/service/PostCommandServiceTest.java | 16 ++++++++-------- .../post/service/PostLikeServiceTest.java | 4 ++-- .../InterestedCountryRepositoryTest.java | 2 +- .../InterestedRegionRepositoryTest.java | 2 +- .../service/MentorQueryServiceTest.java | 2 +- .../service/MentoringCommandServiceTest.java | 6 +++--- .../news/service/NewsCommandServiceTest.java | 2 +- .../news/service/NewsLikeServiceTest.java | 4 ++-- .../filter/ExceptionHandlerFilterTest.java | 2 +- .../filter/SignOutCheckFilterTest.java | 2 +- .../SiteUserAuthenticationProviderTest.java | 8 ++++---- .../SiteUserDetailsServiceTest.java | 8 ++++---- .../repository/SiteUserRepositoryTest.java | 6 +++--- .../siteuser/service/MyPageServiceTest.java | 2 +- ...ValidUnivApplyInfoChoiceValidatorTest.java | 6 +++--- .../LikedUnivApplyInfoRepositoryTest.java | 2 +- .../LikedUnivApplyInfoServiceTest.java | 8 ++++---- .../UnivApplyInfoQueryServiceTest.java | 2 +- 31 files changed, 67 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 5f4bcca59e9278564cbc50a3068a7f104325fce2..6cd3ba1a32280817bbc6c7f9d635085403aa298e 100644 GIT binary patch delta 49 zcmcb`ae-q44+~4g)hU}Mi?AptFszAZklAzhK+M%Ao0J%0b|^4xy;yS`#M5KoW#D1} E0Ox5FPXGV_ delta 59 zcmcb>af@RE4-1C^Ln?z25Kk6hQBaF#2t59ElgsgV28lfp*J7?d*`&k}vqOPl>&2Sm MAf6rrF9R0?07qgK2mk;8 diff --git a/src/test/java/com/example/solidconnection/admin/dto/validation/RejectedReasonValidatorTest.java b/src/test/java/com/example/solidconnection/admin/dto/validation/RejectedReasonValidatorTest.java index eaaf529df..a09db4de7 100644 --- a/src/test/java/com/example/solidconnection/admin/dto/validation/RejectedReasonValidatorTest.java +++ b/src/test/java/com/example/solidconnection/admin/dto/validation/RejectedReasonValidatorTest.java @@ -52,7 +52,7 @@ class GPA_점수_거절_사유_검증 { } @Test - void 거절_상태일_때_거절사유가_없으면_예외_응답을_반환한다() { + void 거절_상태일_때_거절사유가_없으면_예외가_발생한다() { // given GpaScoreUpdateRequest request = new GpaScoreUpdateRequest( 3.0, @@ -92,7 +92,7 @@ class 어학_점수_거절_사유_검증 { } @Test - void 거절_상태일_때_거절사유가_없으면_예외_응답을_반환한다() { + void 거절_상태일_때_거절사유가_없으면_예외가_발생한다() { // given LanguageTestScoreUpdateRequest request = new LanguageTestScoreUpdateRequest( LanguageTestType.TOEIC, diff --git a/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java b/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java index dd45e4f77..070ec9e6f 100644 --- a/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java @@ -163,7 +163,7 @@ class GPA_점수_검증_및_수정 { } @Test - void 존재하지_않는_GPA_수정_시_예외_응답을_반환한다() { + void 존재하지_않는_GPA_수정_시_예외가_발생한다() { // given long invalidGpaScoreId = 9999L; GpaScoreUpdateRequest request = new GpaScoreUpdateRequest( diff --git a/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java b/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java index 439e9444c..c662f72d8 100644 --- a/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java @@ -164,7 +164,7 @@ class 어학점수_검증_및_수정 { } @Test - void 존재하지_않는_어학점수_수정_시_예외_응답을_반환한다() { + void 존재하지_않는_어학점수_수정_시_예외가_발생한다() { // given long invalidLanguageTestScoreId = 9999L; LanguageTestScoreUpdateRequest request = new LanguageTestScoreUpdateRequest( diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java index d0340931d..891adc9a9 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java @@ -102,7 +102,7 @@ void setUp() { } @Test - void 미승인된_GPA_성적으로_지원하면_예외_응답을_반환한다() { + void 미승인된_GPA_성적으로_지원하면_예외가_발생한다() { // given GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.PENDING, user); LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user); @@ -122,7 +122,7 @@ void setUp() { } @Test - void 미승인된_어학성적으로_지원하면_예외_응답을_반환한다() { + void 미승인된_어학성적으로_지원하면_예외가_발생한다() { // given GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user); LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.PENDING, user); @@ -142,7 +142,7 @@ void setUp() { } @Test - void 지원서_수정_횟수를_초과하면_예외_응답을_반환한다() { + void 지원서_수정_횟수를_초과하면_예외가_발생한다() { // given GpaScore gpaScore = gpaScoreFixture.GPA_점수(VerifyStatus.APPROVED, user); LanguageTestScore languageTestScore = languageTestScoreFixture.어학_점수(VerifyStatus.APPROVED, user); diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java index c4c2a1708..b4053fa93 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -96,7 +96,7 @@ class 토큰을_재발급한다 { } @Test - void 요청의_리프레시_토큰이_저장되어있지_않다면_예외_응답을_반환한다() { + void 요청의_리프레시_토큰이_저장되어있지_않다면_예외가_발생한다() { // given String invalidRefreshToken = authTokenProvider.generateAccessToken(new Subject("subject")).token(); ReissueRequest reissueRequest = new ReissueRequest(invalidRefreshToken); diff --git a/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java index 84c0f362b..faa1c42b0 100644 --- a/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java @@ -48,7 +48,7 @@ class EmailSignInServiceTest { class 로그인에_실패한다 { @Test - void 이메일과_일치하는_사용자가_없으면_예외_응답을_반환한다() { + void 이메일과_일치하는_사용자가_없으면_예외가_발생한다() { // given EmailSignInRequest signInRequest = new EmailSignInRequest("이메일", "비밀번호"); @@ -59,7 +59,7 @@ class 로그인에_실패한다 { } @Test - void 비밀번호가_일치하지_않으면_예외_응답을_반환한다() { + void 비밀번호가_일치하지_않으면_예외가_발생한다() { // given String email = "testEmail"; siteUserFixture.사용자(email, "testPassword"); diff --git a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java index c36a0bb39..ed8738a9a 100644 --- a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java @@ -90,7 +90,7 @@ class 토큰으로부터_subject_를_추출한다 { } @Test - void 유효하지_않은_토큰의_subject_를_추출하면_예외_응답을_반환한다() { + void 유효하지_않은_토큰의_subject_를_추출하면_예외가_발생한다() { // given String subject = "subject123"; String token = createExpiredToken(subject); @@ -125,7 +125,7 @@ class 토큰으로부터_claim_을_추출한다 { } @Test - void 유효하지_않은_토큰의_claim_을_추출하면_예외_응답을_반환한다() { + void 유효하지_않은_토큰의_claim_을_추출하면_예외가_발생한다() { // given String subject = "subject"; Claims expectedClaims = Jwts.claims().setSubject(subject); diff --git a/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java index c748987c1..c623285e4 100644 --- a/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java @@ -80,7 +80,7 @@ class 주어진_회원가입_토큰을_검증한다 { } @Test - void 만료되었으면_예외_응답을_반환한다() { + void 만료되었으면_예외가_발생한다() { // given String expiredToken = createExpiredToken(); @@ -91,7 +91,7 @@ class 주어진_회원가입_토큰을_검증한다 { } @Test - void 정해진_형식에_맞지_않으면_예외_응답을_반환한다_jwt_가_아닌_토큰() { + void 정해진_형식에_맞지_않으면_예외가_발생한다_jwt_가_아닌_토큰() { // given String notJwt = "not jwt"; @@ -102,7 +102,7 @@ class 주어진_회원가입_토큰을_검증한다 { } @Test - void 정해진_형식에_맞지_않으면_예외_응답을_반환한다_authType_클래스_불일치() { + void 정해진_형식에_맞지_않으면_예외가_발생한다_authType_클래스_불일치() { // given Map wrongClaim = new HashMap<>(Map.of(AUTH_TYPE_CLAIM_KEY, "카카오")); String wrongAuthType = createBaseJwtBuilder().addClaims(wrongClaim).compact(); @@ -114,7 +114,7 @@ class 주어진_회원가입_토큰을_검증한다 { } @Test - void 정해진_형식에_맞지_않으면_예외_응답을_반환한다_subject_누락() { + void 정해진_형식에_맞지_않으면_예외가_발생한다_subject_누락() { // given Map claim = new HashMap<>(Map.of(AUTH_TYPE_CLAIM_KEY, AuthType.APPLE)); String noSubject = createBaseJwtBuilder().addClaims(claim).compact(); @@ -126,7 +126,7 @@ class 주어진_회원가입_토큰을_검증한다 { } @Test - void 우리_서버에_발급된_토큰이_아니면_예외_응답을_반환한다() { + void 우리_서버에_발급된_토큰이_아니면_예외가_발생한다() { // given Map validClaim = new HashMap<>(Map.of(AUTH_TYPE_CLAIM_KEY, AuthType.APPLE)); String signUpToken = createBaseJwtBuilder().addClaims(validClaim).setSubject("email").compact(); diff --git a/src/test/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandlerTest.java b/src/test/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandlerTest.java index 977733785..6b993633a 100644 --- a/src/test/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandlerTest.java +++ b/src/test/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandlerTest.java @@ -36,7 +36,7 @@ void setUp() { } @Test - void 권한이_없는_사용자_접근시_403_예외_응답을_반환한다() throws IOException { + void 권한이_없는_사용자_접근시_403_예외가_발생한다() throws IOException { // given AccessDeniedException accessDeniedException = new AccessDeniedException(ACCESS_DENIED.getMessage()); diff --git a/src/test/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPointTest.java b/src/test/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPointTest.java index a10fed5df..121cc3768 100644 --- a/src/test/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPointTest.java +++ b/src/test/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPointTest.java @@ -37,7 +37,7 @@ void setUp() { } @Test - void 인증되지_않은_사용자_접근시_401_예외_응답을_반환한다() throws IOException { + void 인증되지_않은_사용자_접근시_401_예외가_발생한다() throws IOException { // given AuthenticationException authException = new AuthenticationServiceException(AUTHENTICATION_FAILED.getMessage()); diff --git a/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java b/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java index 7eb144214..08cc483b6 100644 --- a/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java +++ b/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java @@ -59,7 +59,7 @@ void setUp() { class security_context_에_저장된_사용자가_없는_경우 { @Test - void required_가_true_이면_예외_응답을_반환한다() { + void required_가_true_이면_예외가_발생한다() { // given MethodParameter parameter = mock(MethodParameter.class); AuthorizedUser authorizedUser = mock(AuthorizedUser.class); diff --git a/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java b/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java index 136500502..da5372b82 100644 --- a/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java @@ -233,7 +233,7 @@ class 댓글_생성_테스트 { } @Test - void 대대댓글_생성_시도하면_예외_응답을_반환한다() { + void 대대댓글_생성_시도하면_예외가_발생한다() { // given Comment parentComment = commentFixture.부모_댓글("부모 댓글", post, user1); Comment childComment = commentFixture.자식_댓글("자식 댓글", post, user2, parentComment); @@ -250,7 +250,7 @@ class 댓글_생성_테스트 { } @Test - void 존재하지_않는_부모댓글로_대댓글_작성시_예외를_반환한다() { + void 존재하지_않는_부모댓글로_대댓글_작성시_예외가_빌생한다() { // given long invalidCommentId = 9999L; CommentCreateRequest request = new CommentCreateRequest(post.getId(), "자식 댓글", invalidCommentId); @@ -290,7 +290,7 @@ class 댓글_수정_테스트 { } @Test - void 다른_사용자의_댓글을_수정하면_예외_응답을_반환한다() { + void 다른_사용자의_댓글을_수정하면_예외가_발생한다() { // given Comment comment = commentFixture.부모_댓글("원본 댓글", post, user1); CommentUpdateRequest request = new CommentUpdateRequest("수정된 댓글"); @@ -307,7 +307,7 @@ class 댓글_수정_테스트 { } @Test - void 삭제된_댓글을_수정하면_예외_응답을_반환한다() { + void 삭제된_댓글을_수정하면_예외가_발생한다() { // given Comment comment = commentFixture.부모_댓글(null, post, user1); CommentUpdateRequest request = new CommentUpdateRequest("수정된 댓글"); @@ -398,7 +398,7 @@ class 댓글_삭제_테스트 { } @Test - void 다른_사용자의_댓글을_삭제하면_예외_응답을_반환한다() { + void 다른_사용자의_댓글을_삭제하면_예외가_발생한다() { // given Comment comment = commentFixture.부모_댓글("부모 댓글", post, user1); diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java index 2cf253cf7..405ac0d54 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java @@ -128,7 +128,7 @@ class 게시글_생성_테스트 { } @Test - void 전체_카테고리로_생성하면_예외_응답을_반환한다() { + void 전체_카테고리로_생성하면_예외가_발생한다() { // given PostCreateRequest request = createPostCreateRequest(PostCategory.전체.name()); List imageFiles = List.of(); @@ -141,7 +141,7 @@ class 게시글_생성_테스트 { } @Test - void 존재하지_않는_카테고리로_생성하면_예외_응답을_반환한다() { + void 존재하지_않는_카테고리로_생성하면_예외가_발생한다() { // given PostCreateRequest request = createPostCreateRequest("INVALID_CATEGORY"); List imageFiles = List.of(); @@ -154,7 +154,7 @@ class 게시글_생성_테스트 { } @Test - void 이미지를_5개_초과하여_업로드하면_예외_응답을_반환한다() { + void 이미지를_5개_초과하여_업로드하면_예외가_발생한다() { // given PostCreateRequest request = createPostCreateRequest(PostCategory.자유.name()); List imageFiles = createSixImageFiles(); @@ -204,7 +204,7 @@ class 게시글_수정_테스트 { } @Test - void 다른_사용자의_게시글을_수정하면_예외_응답을_반환한다() { + void 다른_사용자의_게시글을_수정하면_예외가_발생한다() { // given SiteUser user2 = siteUserFixture.사용자(2, "test2"); PostUpdateRequest request = createPostUpdateRequest(); @@ -223,7 +223,7 @@ class 게시글_수정_테스트 { } @Test - void 질문_게시글을_수정하면_예외_응답을_반환한다() { + void 질문_게시글을_수정하면_예외가_발생한다() { // given PostUpdateRequest request = createPostUpdateRequest(); List imageFiles = List.of(); @@ -241,7 +241,7 @@ class 게시글_수정_테스트 { } @Test - void 이미지를_5개_초과하여_수정하면_예외_응답을_반환한다() { + void 이미지를_5개_초과하여_수정하면_예외가_발생한다() { // given PostUpdateRequest request = createPostUpdateRequest(); List imageFiles = createSixImageFiles(); @@ -283,7 +283,7 @@ class 게시글_삭제_테스트 { } @Test - void 다른_사용자의_게시글을_삭제하면_예외_응답을_반환한다() { + void 다른_사용자의_게시글을_삭제하면_예외가_발생한다() { // given SiteUser user2 = siteUserFixture.사용자(2, "test2"); @@ -298,7 +298,7 @@ class 게시글_삭제_테스트 { } @Test - void 질문_게시글을_삭제하면_예외_응답을_반환한다() { + void 질문_게시글을_삭제하면_예외가_발생한다() { // when & then assertThatThrownBy(() -> postCommandService.deletePostById( diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java index 3f5833006..d214d5a04 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java @@ -84,7 +84,7 @@ class 게시글_좋아요_테스트 { } @Test - void 이미_좋아요한_게시글을_다시_좋아요하면_예외_응답을_반환한다() { + void 이미_좋아요한_게시글을_다시_좋아요하면_예외가_발생한다() { // given postLikeService.likePost(user, post.getId()); @@ -122,7 +122,7 @@ class 게시글_좋아요_취소_테스트 { } @Test - void 좋아요하지_않은_게시글을_좋아요_취소하면_예외_응답을_반환한다() { + void 좋아요하지_않은_게시글을_좋아요_취소하면_예외가_발생한다() { // when & then assertThatThrownBy(() -> postLikeService.dislikePost( diff --git a/src/test/java/com/example/solidconnection/location/country/repository/InterestedCountryRepositoryTest.java b/src/test/java/com/example/solidconnection/location/country/repository/InterestedCountryRepositoryTest.java index 4daeb55e1..78f0a27e5 100644 --- a/src/test/java/com/example/solidconnection/location/country/repository/InterestedCountryRepositoryTest.java +++ b/src/test/java/com/example/solidconnection/location/country/repository/InterestedCountryRepositoryTest.java @@ -30,7 +30,7 @@ public class InterestedCountryRepositoryTest { class 사용자와_나라는_복합_유니크_제약_조건을_가진다 { @Test - void 같은_사용자가_같은_나라에_관심_표시를_하면_예외_응답을_반환한다() { + void 같은_사용자가_같은_나라에_관심_표시를_하면_예외가_발생한다() { // given SiteUser user = siteUserFixture.사용자(); Country country = countryFixture.미국(); diff --git a/src/test/java/com/example/solidconnection/location/region/repository/InterestedRegionRepositoryTest.java b/src/test/java/com/example/solidconnection/location/region/repository/InterestedRegionRepositoryTest.java index a9594e0f8..0fb36cf5d 100644 --- a/src/test/java/com/example/solidconnection/location/region/repository/InterestedRegionRepositoryTest.java +++ b/src/test/java/com/example/solidconnection/location/region/repository/InterestedRegionRepositoryTest.java @@ -30,7 +30,7 @@ public class InterestedRegionRepositoryTest { class 사용자와_지역은_복합_유니크_제약_조건을_가진다 { @Test - void 같은_사용자가_같은_지역에_관심_표시를_하면_예외_응답을_반환한다() { + void 같은_사용자가_같은_지역에_관심_표시를_하면_예외가_발생한다() { // given SiteUser user = siteUserFixture.사용자(); Region region = regionFixture.영미권(); diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java index 421d24a12..cb1410aee 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java @@ -101,7 +101,7 @@ class 멘토_단일_조회_성공 { class 멘토_단일_조회_실패 { @Test - void 존재하지_않는_멘토를_조회하면_예외_응답을_반환한다() { + void 존재하지_않는_멘토를_조회하면_예외가_발생한다() { // given long notExistingMentorId = 999L; diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java index e9d218716..5e0ef04c7 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java @@ -141,7 +141,7 @@ class 멘토링_승인_거절_테스트 { } @Test - void 거절_시_사유가_없으면_예외_응답을_반환한다() { + void 거절_시_사유가_없으면_예외가_발생한다() { // given Mentoring mentoring = mentoringFixture.대기중_멘토링(mentor1.getId(), menteeUser.getId()); MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.REJECTED, null); @@ -178,7 +178,7 @@ class 멘토링_승인_거절_테스트 { } @Test - void 존재하지_않는_멘토링_아이디로_요청시_예외_응답을_반환한다() { + void 존재하지_않는_멘토링_아이디로_요청시_예외가_발생한다() { // given MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.APPROVED, null); long invalidMentoringId = 9999L; @@ -219,7 +219,7 @@ class 멘토링_확인_테스트 { } @Test - void 존재하지_않는_멘토링_아이디로_요청시_예외_응답을_반환한다() { + void 존재하지_않는_멘토링_아이디로_요청시_예외가_발생한다() { // given long invalidMentoringId = 9999L; diff --git a/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java b/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java index 681c2a959..f9ec5de6d 100644 --- a/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java @@ -136,7 +136,7 @@ void setUp() { } @Test - void 다른_사용자의_소식지를_수정하면_예외_응답을_반환한다() { + void 다른_사용자의_소식지를_수정하면_예외가_발생한다() { // given SiteUser anotherUser = siteUserFixture.멘토(2, "anotherMentor"); NewsUpdateRequest request = createNewsUpdateRequest( diff --git a/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java b/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java index 1bf26bc3c..d8a51d7d9 100644 --- a/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java @@ -82,7 +82,7 @@ class 소식지_좋아요를_등록한다 { } @Test - void 이미_좋아요했으면_예외_응답을_반환한다() { + void 이미_좋아요했으면_예외가_발생한다() { // given newsLikeService.addNewsLike(user.getId(), news.getId()); @@ -109,7 +109,7 @@ class 소식지_좋아요를_취소한다 { } @Test - void 좋아요하지_않았으면_예외_응답을_반환한다() { + void 좋아요하지_않았으면_예외가_발생한다() { // when & then assertThatCode(() -> newsLikeService.cancelNewsLike(user.getId(), news.getId())) .isInstanceOf(CustomException.class) diff --git a/src/test/java/com/example/solidconnection/security/filter/ExceptionHandlerFilterTest.java b/src/test/java/com/example/solidconnection/security/filter/ExceptionHandlerFilterTest.java index 4fa01a234..086104a24 100644 --- a/src/test/java/com/example/solidconnection/security/filter/ExceptionHandlerFilterTest.java +++ b/src/test/java/com/example/solidconnection/security/filter/ExceptionHandlerFilterTest.java @@ -73,7 +73,7 @@ void setUp() { @ParameterizedTest @MethodSource("provideException") - void 필터_체인에서_예외가_발생하면_예외_응답을_반환한다(Throwable throwable) throws Exception { + void 필터_체인에서_예외가_발생하면_예외가_발생한다(Throwable throwable) throws Exception { // given willThrow(throwable).given(filterChain).doFilter(request, response); diff --git a/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java b/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java index 80e927203..188e09f2c 100644 --- a/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java +++ b/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java @@ -55,7 +55,7 @@ void setUp() { } @Test - void 로그아웃한_토큰이면_예외를_응답한다() { + void 로그아웃한_토큰이면_예외가_발생한다() { // given String token = createToken(subject); request = createRequest(token); diff --git a/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java b/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java index 0bbb6677f..0c56d302a 100644 --- a/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java +++ b/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java @@ -76,10 +76,10 @@ void setUp() { } @Nested - class 예외_응답을_반환하다 { + class 예외가_발생한다 { @Test - void 유효하지_않은_토큰이면_예외_응답을_반환한다() { + void 유효하지_않은_토큰이면_예외가_발생한다() { // given SiteUserAuthentication expiredAuth = new SiteUserAuthentication(createExpiredToken()); @@ -90,7 +90,7 @@ class 예외_응답을_반환하다 { } @Test - void 사용자_정보의_형식이_다르면_예외_응답을_반환한다() { + void 사용자_정보의_형식이_다르면_예외가_발생한다() { // given SiteUserAuthentication wrongSubjectTypeAuth = new SiteUserAuthentication(createWrongSubjectTypeToken()); @@ -101,7 +101,7 @@ class 예외_응답을_반환하다 { } @Test - void 유효한_토큰이지만_해당되는_사용자가_없으면_예외_응답을_반환한다() { + void 유효한_토큰이지만_해당되는_사용자가_없으면_예외가_발생한다() { // given long notExistingUserId = user.getId() + 100; String token = createValidToken(notExistingUserId); diff --git a/src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsServiceTest.java b/src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsServiceTest.java index c292203af..babdd9196 100644 --- a/src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsServiceTest.java +++ b/src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsServiceTest.java @@ -48,10 +48,10 @@ class SiteUserDetailsServiceTest { } @Nested - class 예외_응답을_반환한다 { + class 예외가_발생한다 { @Test - void 지정되지_않은_형식의_식별자가_주어지면_예외_응답을_반환한다() { + void 지정되지_않은_형식의_식별자가_주어지면_예외가_발생한다() { // given String username = "notNumber"; @@ -62,7 +62,7 @@ class 예외_응답을_반환한다 { } @Test - void 식별자에_해당하는_사용자가_없으면_예외_응답을_반환한다() { + void 식별자에_해당하는_사용자가_없으면_예외가_발생한다() { // given String username = "1234"; @@ -73,7 +73,7 @@ class 예외_응답을_반환한다 { } @Test - void 탈퇴한_사용자이면_예외_응답을_반환한다() { + void 탈퇴한_사용자이면_예외가_발생한다() { // given SiteUser user = siteUserFixture.사용자(); user.setQuitedAt(LocalDate.now()); diff --git a/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java b/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java index 115e40e77..b7bf16472 100644 --- a/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java @@ -22,7 +22,7 @@ class SiteUserRepositoryTest { class 이메일과_인증_유형이_동일한_사용자는_저장할_수_없다 { @Test - void 이메일과_인증_유형이_동일한_사용자를_저장하면_예외_응답을_반환한다() { + void 이메일과_인증_유형이_동일한_사용자를_저장하면_예외가_발생한다() { // given SiteUser user1 = createSiteUser("email", "nickname1", AuthType.KAKAO); SiteUser user2 = createSiteUser("email", "nickname2", AuthType.KAKAO); @@ -50,7 +50,7 @@ class 이메일과_인증_유형이_동일한_사용자는_저장할_수_없다 class 닉네임은_중복될_수_없다 { @Test - void 중복된_닉네임으로_사용자를_저장하면_예외_응답을_반환한다() { + void 중복된_닉네임으로_사용자를_저장하면_예외가_발생한다() { // given SiteUser user1 = createSiteUser("email1", "nickname", AuthType.KAKAO); SiteUser user2 = createSiteUser("email2", "nickname", AuthType.KAKAO); @@ -62,7 +62,7 @@ class 닉네임은_중복될_수_없다 { } @Test - void 중복된_닉네임으로_변경하면_예외_응답을_반환한다() { + void 중복된_닉네임으로_변경하면_예외가_발생한다() { // given SiteUser user1 = createSiteUser("email1", "nickname1", AuthType.KAKAO); SiteUser user2 = createSiteUser("email2", "nickname2", AuthType.KAKAO); diff --git a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java index d317ed542..e93ba3278 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java @@ -166,7 +166,7 @@ void setUp() { } @Test - void 최소_대기기간이_지나지_않은_상태에서_변경하면_예외_응답을_반환한다() { + void 최소_대기기간이_지나지_않은_상태에서_변경하면_예외가_발생한다() { // given MockMultipartFile imageFile = createValidImageFile(); LocalDateTime modifiedAt = LocalDateTime.now().minusDays(MIN_DAYS_BETWEEN_NICKNAME_CHANGES - 1); diff --git a/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java b/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java index 84a41d801..8daacdd66 100644 --- a/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java +++ b/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java @@ -54,7 +54,7 @@ void setUp() { } @Test - void 두_번째_지망_없이_세_번째_지망을_선택하면_예외_응답을_반환한다() { + void 두_번째_지망_없이_세_번째_지망을_선택하면_예외가_발생한다() { // given UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(1L, null, 3L); @@ -68,7 +68,7 @@ void setUp() { } @Test - void 첫_번째_지망을_선택하지_않으면_예외_응답을_반환한다() { + void 첫_번째_지망을_선택하지_않으면_예외가_발생한다() { // given UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(null, 2L, 3L); @@ -83,7 +83,7 @@ void setUp() { } @Test - void 대학을_중복_선택하면_예외_응답을_반환한다() { + void 대학을_중복_선택하면_예외가_발생한다() { // given UnivApplyInfoChoiceRequest request = new UnivApplyInfoChoiceRequest(1L, 1L, 2L); diff --git a/src/test/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepositoryTest.java b/src/test/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepositoryTest.java index 59b09d628..72e3610a6 100644 --- a/src/test/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepositoryTest.java +++ b/src/test/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepositoryTest.java @@ -31,7 +31,7 @@ public class LikedUnivApplyInfoRepositoryTest { class 사용자와_좋아요한_대학은_복합_유니크_제약조건을_갖는다 { @Test - void 같은_사용자가_같은_대학에_중복으로_좋아요하면_예외_응답을_반환한다() { + void 같은_사용자가_같은_대학에_중복으로_좋아요하면_예외가_발생한다() { // given SiteUser user = siteUserFixture.사용자(); UnivApplyInfo univApplyInfo = univApplyInfoFixture.괌대학_A_지원_정보(); diff --git a/src/test/java/com/example/solidconnection/university/service/LikedUnivApplyInfoServiceTest.java b/src/test/java/com/example/solidconnection/university/service/LikedUnivApplyInfoServiceTest.java index d8d4c0b9b..cc5220756 100644 --- a/src/test/java/com/example/solidconnection/university/service/LikedUnivApplyInfoServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/LikedUnivApplyInfoServiceTest.java @@ -80,7 +80,7 @@ class 대학_지원_정보_좋아요를_등록한다 { } @Test - void 이미_좋아요했으면_예외_응답을_반환한다() { + void 이미_좋아요했으면_예외가_발생한다() { // given saveLikedUnivApplyInfo(user, 괌대학_A_지원_정보); @@ -109,7 +109,7 @@ class 대학_지원_정보_좋아요를_취소한다 { } @Test - void 좋아요하지_않았으면_예외_응답을_반환한다() { + void 좋아요하지_않았으면_예외가_발생한다() { // when & then assertThatCode(() -> likedUnivApplyInfoService.cancelUnivApplyInfoLike(user, 괌대학_A_지원_정보.getId())) .isInstanceOf(CustomException.class) @@ -118,7 +118,7 @@ class 대학_지원_정보_좋아요를_취소한다 { } @Test - void 존재하지_않는_지원_정보에_좋아요_시도하면_예외_응답을_반환한다() { + void 존재하지_않는_지원_정보에_좋아요_시도하면_예외가_발생한다() { // given Long invalidUnivApplyInfoId = 9999L; @@ -150,7 +150,7 @@ class 대학_지원_정보_좋아요를_취소한다 { } @Test - void 존재하지_않는_대학_지원_정보의_좋아요_여부를_조회하면_예외_응답을_반환한다() { + void 존재하지_않는_대학_지원_정보의_좋아요_여부를_조회하면_예외가_발생한다() { // given Long invalidUnivApplyInfoId = 9999L; diff --git a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java index 1c58316dc..949cbfc75 100644 --- a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java @@ -70,7 +70,7 @@ class UnivApplyInfoQueryServiceTest { } @Test - void 존재하지_않는_대학_지원_정보를_조회하면_예외_응답을_반환한다() { + void 존재하지_않는_대학_지원_정보를_조회하면_예외가_발생한다() { // given Long invalidUnivApplyInfoId = 9999L; From 0388f97ab7e66e751d8ce5829c3e3a1a47d4e497 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Wed, 16 Jul 2025 21:09:18 +0900 Subject: [PATCH 44/90] =?UTF-8?q?fix:=20flyway=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=20=EC=9D=B4=EB=A6=84=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#389)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ..._deleted_to_comment.sql => V22__add_is_deleted_to_comment.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/resources/db/migration/{V22_add_is_deleted_to_comment.sql => V22__add_is_deleted_to_comment.sql} (100%) diff --git a/src/main/resources/db/migration/V22_add_is_deleted_to_comment.sql b/src/main/resources/db/migration/V22__add_is_deleted_to_comment.sql similarity index 100% rename from src/main/resources/db/migration/V22_add_is_deleted_to_comment.sql rename to src/main/resources/db/migration/V22__add_is_deleted_to_comment.sql From 187e9e7ac9f1957d950c60f9d2fefde2f58fcba4 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Thu, 17 Jul 2025 17:09:41 +0900 Subject: [PATCH 45/90] =?UTF-8?q?style:=20=ED=8C=80=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=20=EC=BB=A8=EB=B2=A4=EC=85=98=20=EC=A0=81=EC=9A=A9=20(#385)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: 프로덕션 코드에 style 적용 * style: 테스트 코드에 style 적용 * chore: 코드 스타일 xml 파일 업로드 --- .../solid-connection-intellij-scheme.xml | 595 ++++++++++++++++++ .../admin/dto/GpaResponse.java | 1 + .../admin/dto/GpaScoreResponse.java | 1 + .../admin/dto/GpaScoreSearchResponse.java | 1 + .../admin/dto/GpaScoreStatusResponse.java | 2 +- .../admin/dto/GpaScoreUpdateRequest.java | 3 +- .../admin/dto/LanguageTestResponse.java | 1 + .../admin/dto/LanguageTestScoreResponse.java | 1 + .../dto/LanguageTestScoreSearchResponse.java | 1 + .../dto/LanguageTestScoreStatusResponse.java | 2 +- .../dto/LanguageTestScoreUpdateRequest.java | 3 +- .../admin/dto/ScoreSearchCondition.java | 2 +- .../admin/dto/ScoreUpdateRequest.java | 2 + .../admin/dto/SiteUserResponse.java | 1 + .../admin/service/AdminGpaScoreService.java | 4 +- .../AdminLanguageTestScoreService.java | 4 +- .../application/domain/Application.java | 16 +- .../application/dto/ApplicantsResponse.java | 6 +- .../dto/ApplicationSubmissionResponse.java | 1 + .../application/dto/ApplicationsResponse.java | 1 + .../application/dto/ApplyRequest.java | 1 + .../dto/UnivApplyInfoChoiceRequest.java | 1 + .../validation/RejectedReasonRequired.java | 3 +- .../validation/RejectedReasonValidator.java | 4 +- .../repository/ApplicationRepository.java | 39 +- .../service/ApplicationQueryService.java | 17 +- .../service/ApplicationSubmissionService.java | 17 +- .../application/service/NicknameCreator.java | 3 +- .../auth/client/AppleOAuthClient.java | 11 +- .../AppleOAuthClientSecretProvider.java | 11 +- .../auth/client/ApplePublicKeyProvider.java | 37 +- .../auth/client/KakaoOAuthClient.java | 11 +- .../config/AppleOAuthClientProperties.java | 1 + .../config/KakaoOAuthClientProperties.java | 1 + .../auth/dto/EmailSignInRequest.java | 1 + .../auth/dto/EmailSignUpTokenRequest.java | 1 + .../auth/dto/EmailSignUpTokenResponse.java | 1 + .../auth/dto/ReissueRequest.java | 1 + .../auth/dto/SignUpRequest.java | 1 - .../auth/dto/oauth/AppleTokenDto.java | 1 + .../auth/dto/oauth/AppleUserInfoDto.java | 8 +- .../auth/dto/oauth/KakaoTokenDto.java | 1 + .../auth/dto/oauth/OAuthCodeRequest.java | 1 + .../auth/dto/oauth/OAuthResponse.java | 1 + .../auth/dto/oauth/OAuthSignInResponse.java | 1 + .../auth/service/AuthService.java | 7 +- .../auth/service/AuthTokenProvider.java | 3 +- .../service/CommonSignUpTokenProvider.java | 6 +- .../auth/service/EmailSignInService.java | 7 +- .../auth/service/EmailSignUpService.java | 4 +- .../service/EmailSignUpTokenProvider.java | 15 +- .../auth/service/SignUpService.java | 7 +- .../solidconnection/auth/service/Subject.java | 1 + .../auth/service/oauth/OAuthService.java | 4 +- .../service/oauth/OAuthSignUpService.java | 4 +- .../oauth/OAuthSignUpTokenProvider.java | 13 +- .../auth/token/JwtTokenProvider.java | 9 +- .../auth/token/TokenBlackListService.java | 4 +- .../auth/token/config/JwtProperties.java | 1 + .../cache/CacheUpdateListener.java | 3 +- .../cache/CompletableFutureManager.java | 3 +- .../cache/ThunderingHerdCachingAspect.java | 25 +- .../annotation/ThunderingHerdCaching.java | 1 + .../cache/manager/CustomCacheManager.java | 5 +- .../solidconnection/common/BaseEntity.java | 9 +- .../config/client/RestTemplateConfig.java | 3 +- .../common/config/redis/RedisConfig.java | 4 +- .../common/config/web/WebMvcConfig.java | 3 +- .../common/dto/SliceResponse.java | 3 +- .../exception/CustomAccessDeniedHandler.java | 3 +- .../CustomAuthenticationEntryPoint.java | 3 +- .../exception/CustomExceptionHandler.java | 17 +- .../common/exception/ErrorCode.java | 6 +- .../common/resolver/AuthorizedUser.java | 1 + .../resolver/AuthorizedUserResolver.java | 4 +- .../common/response/PageResponse.java | 4 +- .../board/controller/BoardController.java | 5 +- .../board/dto/PostFindBoardResponse.java | 1 + .../board/repository/BoardRepository.java | 7 +- .../community/board/service/BoardService.java | 1 + .../community/comment/domain/Comment.java | 5 +- .../comment/dto/CommentCreateRequest.java | 1 + .../comment/dto/CommentDeleteResponse.java | 1 + .../comment/dto/CommentUpdateRequest.java | 1 + .../comment/dto/PostFindCommentResponse.java | 4 +- .../comment/repository/CommentRepository.java | 43 +- .../comment/service/CommentService.java | 20 +- .../post/controller/PostController.java | 5 +- .../community/post/domain/Post.java | 5 +- .../post/dto/PostDeleteResponse.java | 1 + .../post/dto/PostDislikeResponse.java | 1 + .../post/dto/PostFindPostImageResponse.java | 2 +- .../community/post/dto/PostFindResponse.java | 1 - .../community/post/dto/PostLikeResponse.java | 1 + .../community/post/dto/PostListResponse.java | 1 - .../community/post/dto/PostUpdateRequest.java | 1 + .../post/dto/PostUpdateResponse.java | 1 + .../post/repository/PostImageRepository.java | 1 + .../post/repository/PostLikeRepository.java | 7 +- .../post/repository/PostRepository.java | 27 +- .../post/service/PostCommandService.java | 15 +- .../post/service/PostLikeService.java | 4 +- .../post/service/PostQueryService.java | 17 +- .../community/post/service/RedisService.java | 13 +- .../location/country/domain/Country.java | 2 +- .../country/domain/InterestedCountry.java | 4 +- .../country/repository/CountryRepository.java | 3 +- .../InterestedCountryRepository.java | 3 +- .../region/domain/InterestedRegion.java | 4 +- .../InterestedRegionRepository.java | 4 +- .../region/repository/RegionRepository.java | 3 +- .../mentor/controller/MentorController.java | 4 +- .../solidconnection/mentor/domain/Mentor.java | 5 +- .../mentor/domain/Mentoring.java | 9 +- .../mentor/dto/ChannelRequest.java | 1 + .../mentor/dto/MentorDetailResponse.java | 1 - .../mentor/dto/MentorMyPageResponse.java | 1 - .../mentor/dto/MentorMyPageUpdateRequest.java | 2 +- .../mentor/dto/MentorPreviewResponse.java | 1 - .../mentor/dto/MentorPreviewsResponse.java | 1 + .../mentor/dto/MentoringApplyRequest.java | 1 + .../mentor/dto/MentoringConfirmRequest.java | 1 + .../mentor/dto/MentoringConfirmResponse.java | 1 + .../mentor/dto/MentoringListResponse.java | 1 + .../mentor/dto/MentoringResponse.java | 2 +- .../mentor/repository/ChannelRepository.java | 1 + .../MentorBatchQueryRepository.java | 9 +- .../mentor/repository/MentorRepository.java | 3 +- .../repository/MentoringRepository.java | 9 +- .../mentor/service/MentorMyPageService.java | 11 +- .../mentor/service/MentorQueryService.java | 11 +- .../service/MentoringCommandService.java | 12 +- .../mentor/service/MentoringQueryService.java | 7 +- .../news/config/NewsProperties.java | 1 + .../news/dto/NewsCommandResponse.java | 1 + .../news/dto/NewsCreateRequest.java | 1 + .../news/dto/NewsListResponse.java | 1 + .../news/dto/NewsResponse.java | 2 +- .../news/dto/NewsUpdateRequest.java | 1 + .../news/repository/LikedNewsRepository.java | 3 +- .../news/repository/NewsRepository.java | 3 +- .../news/service/NewsCommandService.java | 11 +- .../news/service/NewsLikeService.java | 8 +- .../news/service/NewsQueryService.java | 5 +- .../s3/dto/UploadedFileUrlResponse.java | 1 + .../s3/dto/urlPrefixResponse.java | 1 + .../s3/service/FileUploadService.java | 11 +- .../solidconnection/s3/service/S3Service.java | 23 +- .../scheduler/UpdateViewCountScheduler.java | 7 +- .../scheduler/UserRemovalScheduler.java | 5 +- .../score/domain/GpaScore.java | 2 +- .../score/domain/LanguageTestScore.java | 2 +- .../score/dto/GpaResponse.java | 1 + .../score/dto/GpaScoreRequest.java | 1 + .../score/dto/GpaScoreStatusResponse.java | 1 + .../score/dto/GpaScoreStatusesResponse.java | 1 + .../score/dto/LanguageTestResponse.java | 1 + .../score/dto/LanguageTestScoreRequest.java | 1 + .../dto/LanguageTestScoreStatusResponse.java | 1 + .../LanguageTestScoreStatusesResponse.java | 1 + .../score/repository/GpaScoreRepository.java | 3 +- .../LanguageTestScoreRepository.java | 3 +- .../custom/GpaScoreFilterRepositoryImpl.java | 17 +- ...LanguageTestScoreFilterRepositoryImpl.java | 17 +- .../score/service/ScoreService.java | 7 +- .../annotation/RequireRoleAccess.java | 2 +- .../aspect/RoleAuthorizationAspect.java | 7 +- .../authentication/JwtAuthentication.java | 7 +- .../security/config/CorsProperties.java | 4 +- .../config/SecurityConfiguration.java | 4 +- .../filter/AuthorizationHeaderParser.java | 3 +- .../filter/ExceptionHandlerFilter.java | 7 +- .../filter/JwtAuthenticationFilter.java | 5 +- .../security/filter/SignOutCheckFilter.java | 9 +- .../userdetails/SecurityRoleMapper.java | 3 +- .../security/userdetails/SiteUserDetails.java | 3 +- .../userdetails/SiteUserDetailsService.java | 6 +- .../siteuser/domain/SiteUser.java | 5 +- .../siteuser/dto/NicknameExistsResponse.java | 1 + .../siteuser/dto/NicknameUpdateRequest.java | 1 + .../siteuser/dto/NicknameUpdateResponse.java | 1 + .../dto/PostFindSiteUserResponse.java | 1 + .../dto/ProfileImageUpdateResponse.java | 1 + .../repository/SiteUserRepository.java | 7 +- .../siteuser/service/MyPageService.java | 13 +- .../controller/UnivApplyInfoController.java | 3 +- .../university/domain/LikedUnivApplyInfo.java | 14 +- .../university/domain/UnivApplyInfo.java | 7 +- .../university/dto/IsLikeResponse.java | 1 + .../university/dto/LikeResultResponse.java | 1 + .../dto/UnivApplyInfoDetailResponse.java | 3 +- .../dto/UnivApplyInfoPreviewResponse.java | 1 - .../dto/UnivApplyInfoPreviewResponses.java | 1 + .../validation/ValidUnivApplyInfoChoice.java | 3 +- .../ValidUnivApplyInfoChoiceValidator.java | 9 +- .../LanguageRequirementRepository.java | 1 + .../LikedUnivApplyInfoRepository.java | 15 +- .../repository/UnivApplyInfoRepository.java | 77 ++- .../repository/UniversityRepository.java | 7 +- .../custom/UnivApplyInfoFilterRepository.java | 1 - .../UnivApplyInfoFilterRepositoryImpl.java | 9 +- .../GeneralUnivApplyInfoRecommendService.java | 7 +- .../service/LikedUnivApplyInfoService.java | 11 +- .../service/UnivApplyInfoQueryService.java | 13 +- .../UnivApplyInfoRecommendService.java | 15 +- .../solidconnection/util/RedisUtils.java | 15 +- .../RejectedReasonValidatorTest.java | 9 +- .../service/AdminGpaScoreServiceTest.java | 15 +- .../AdminLanguageTestScoreServiceTest.java | 17 +- .../fixture/ApplicationFixtureBuilder.java | 2 +- .../service/ApplicationQueryServiceTest.java | 34 +- .../ApplicationSubmissionServiceTest.java | 24 +- .../auth/service/AuthServiceTest.java | 13 +- .../auth/service/AuthTokenProviderTest.java | 6 +- .../auth/service/EmailSignInServiceTest.java | 6 +- .../auth/service/JwtTokenProviderTest.java | 17 +- .../auth/service/SignInServiceTest.java | 9 +- .../service/TokenBlackListServiceTest.java | 6 +- .../oauth/OAuthSignUpTokenProviderTest.java | 21 +- .../CustomAccessDeniedHandlerTest.java | 9 +- .../CustomAuthenticationEntryPointTest.java | 9 +- .../resolver/AuthorizedUserResolverTest.java | 12 +- ...ableHandlerMethodArgumentResolverTest.java | 9 +- .../community/board/fixture/BoardFixture.java | 8 +- .../repository/BoardRepositoryForTest.java | 3 +- .../comment/service/CommentServiceTest.java | 65 +- .../community/post/fixture/PostFixture.java | 30 +- .../post/service/PostCommandServiceTest.java | 85 ++- .../post/service/PostLikeServiceTest.java | 28 +- .../post/service/PostQueryServiceTest.java | 11 +- .../PostLikeCountConcurrencyTest.java | 13 +- .../PostViewCountConcurrencyTest.java | 15 +- .../concurrency/ThunderingHerdTest.java | 11 +- .../database/DatabaseConnectionTest.java | 15 +- .../database/RedisConnectionTest.java | 4 +- .../repository/CountryRepositoryForTest.java | 3 +- .../InterestedCountryRepositoryTest.java | 24 +- .../InterestedRegionRepositoryTest.java | 6 +- .../repository/RegionRepositoryForTest.java | 3 +- .../mentor/fixture/MentoringFixture.java | 9 +- .../fixture/MentoringFixtureBuilder.java | 3 +- .../repository/ChannelRepositoryForTest.java | 3 +- .../MentorBatchQueryRepositoryTest.java | 11 +- .../service/MentorMyPageServiceTest.java | 13 +- .../service/MentorQueryServiceTest.java | 15 +- .../service/MentoringCommandServiceTest.java | 18 +- .../service/MentoringQueryServiceTest.java | 6 +- .../news/service/NewsCommandServiceTest.java | 20 +- .../news/service/NewsLikeServiceTest.java | 18 +- .../news/service/NewsQueryServiceTest.java | 17 +- .../score/fixture/GpaScoreFixture.java | 2 +- .../fixture/LanguageTestScoreFixture.java | 6 +- .../score/service/ScoreServiceTest.java | 9 +- .../aspect/RoleAuthorizationAspectTest.java | 15 +- .../SiteUserAuthenticationTest.java | 4 +- .../filter/AuthorizationHeaderParserTest.java | 9 +- .../filter/ExceptionHandlerFilterTest.java | 17 +- .../filter/JwtAuthenticationFilterTest.java | 13 +- .../filter/SignOutCheckFilterTest.java | 19 +- .../SiteUserAuthenticationProviderTest.java | 19 +- .../SiteUserDetailsServiceTest.java | 15 +- .../userdetails/SiteUserDetailsTest.java | 7 +- .../repository/SiteUserRepositoryTest.java | 4 +- .../siteuser/service/MyPageServiceTest.java | 30 +- .../siteuser/service/SiteUserServiceTest.java | 4 +- .../support/DatabaseCleaner.java | 15 +- .../support/TestContainerDataJpaTest.java | 10 +- .../support/TestContainerSpringBootTest.java | 10 +- ...ValidUnivApplyInfoChoiceValidatorTest.java | 13 +- .../fixture/UnivApplyInfoFixtureBuilder.java | 11 +- .../LikedUnivApplyInfoRepositoryTest.java | 4 +- ...eralUnivApplyInfoRecommendServiceTest.java | 11 +- .../LikedUnivApplyInfoServiceTest.java | 15 +- .../UnivApplyInfoQueryServiceTest.java | 15 +- .../UnivApplyInfoRecommendServiceTest.java | 10 +- 275 files changed, 1626 insertions(+), 1098 deletions(-) create mode 100644 docs/code-style/solid-connection-intellij-scheme.xml diff --git a/docs/code-style/solid-connection-intellij-scheme.xml b/docs/code-style/solid-connection-intellij-scheme.xml new file mode 100644 index 000000000..3bcaefe90 --- /dev/null +++ b/docs/code-style/solid-connection-intellij-scheme.xml @@ -0,0 +1,595 @@ + + + + \ No newline at end of file diff --git a/src/main/java/com/example/solidconnection/admin/dto/GpaResponse.java b/src/main/java/com/example/solidconnection/admin/dto/GpaResponse.java index 564bc724b..36d66820d 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/GpaResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/GpaResponse.java @@ -5,4 +5,5 @@ public record GpaResponse( double gpaCriteria, String gpaReportUrl ) { + } diff --git a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreResponse.java b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreResponse.java index 7da28252a..524142feb 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreResponse.java @@ -10,6 +10,7 @@ public record GpaScoreResponse( VerifyStatus verifyStatus, String rejectedReason ) { + public static GpaScoreResponse from(GpaScore gpaScore) { return new GpaScoreResponse( gpaScore.getId(), diff --git a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreSearchResponse.java b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreSearchResponse.java index 2da39fb88..30bf26469 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreSearchResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreSearchResponse.java @@ -4,4 +4,5 @@ public record GpaScoreSearchResponse( GpaScoreStatusResponse gpaScoreStatusResponse, SiteUserResponse siteUserResponse ) { + } diff --git a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreStatusResponse.java b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreStatusResponse.java index 379d5567a..00937eb2d 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreStatusResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreStatusResponse.java @@ -1,7 +1,6 @@ package com.example.solidconnection.admin.dto; import com.example.solidconnection.common.VerifyStatus; - import java.time.ZonedDateTime; public record GpaScoreStatusResponse( @@ -12,4 +11,5 @@ public record GpaScoreStatusResponse( ZonedDateTime createdAt, ZonedDateTime updatedAt ) { + } diff --git a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreUpdateRequest.java b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreUpdateRequest.java index 9393c1e7f..85d0d2472 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/GpaScoreUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/admin/dto/GpaScoreUpdateRequest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.application.dto.validation.RejectedReasonRequired; +import com.example.solidconnection.common.VerifyStatus; import jakarta.validation.constraints.NotNull; @RejectedReasonRequired @@ -18,4 +18,5 @@ public record GpaScoreUpdateRequest( String rejectedReason ) implements ScoreUpdateRequest { + } diff --git a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestResponse.java b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestResponse.java index 257b8fd6f..2740a03f3 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestResponse.java @@ -7,4 +7,5 @@ public record LanguageTestResponse( String languageTestScore, String languageTestReportUrl ) { + } diff --git a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreResponse.java b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreResponse.java index f80f5e7cb..b8c2d2b73 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreResponse.java @@ -11,6 +11,7 @@ public record LanguageTestScoreResponse( VerifyStatus verifyStatus, String rejectedReason ) { + public static LanguageTestScoreResponse from(LanguageTestScore languageTestScore) { return new LanguageTestScoreResponse( languageTestScore.getId(), diff --git a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreSearchResponse.java b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreSearchResponse.java index 0e1830f66..ba4e27a7d 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreSearchResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreSearchResponse.java @@ -4,4 +4,5 @@ public record LanguageTestScoreSearchResponse( LanguageTestScoreStatusResponse languageTestScoreStatusResponse, SiteUserResponse siteUserResponse ) { + } diff --git a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreStatusResponse.java b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreStatusResponse.java index d060e0661..b2bd29fb9 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreStatusResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreStatusResponse.java @@ -1,7 +1,6 @@ package com.example.solidconnection.admin.dto; import com.example.solidconnection.common.VerifyStatus; - import java.time.ZonedDateTime; public record LanguageTestScoreStatusResponse( @@ -12,4 +11,5 @@ public record LanguageTestScoreStatusResponse( ZonedDateTime createdAt, ZonedDateTime updatedAt ) { + } diff --git a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreUpdateRequest.java b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreUpdateRequest.java index 51af55f79..150cd02b7 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/admin/dto/LanguageTestScoreUpdateRequest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.admin.dto; -import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.application.dto.validation.RejectedReasonRequired; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.university.domain.LanguageTestType; import jakarta.validation.constraints.NotNull; @@ -19,4 +19,5 @@ public record LanguageTestScoreUpdateRequest( String rejectedReason ) implements ScoreUpdateRequest { + } diff --git a/src/main/java/com/example/solidconnection/admin/dto/ScoreSearchCondition.java b/src/main/java/com/example/solidconnection/admin/dto/ScoreSearchCondition.java index d968af5b2..2efdb476b 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/ScoreSearchCondition.java +++ b/src/main/java/com/example/solidconnection/admin/dto/ScoreSearchCondition.java @@ -1,11 +1,11 @@ package com.example.solidconnection.admin.dto; import com.example.solidconnection.common.VerifyStatus; - import java.time.LocalDate; public record ScoreSearchCondition( VerifyStatus verifyStatus, String nickname, LocalDate createdAt) { + } diff --git a/src/main/java/com/example/solidconnection/admin/dto/ScoreUpdateRequest.java b/src/main/java/com/example/solidconnection/admin/dto/ScoreUpdateRequest.java index 7299d6433..8e04b06de 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/ScoreUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/admin/dto/ScoreUpdateRequest.java @@ -3,6 +3,8 @@ import com.example.solidconnection.common.VerifyStatus; public interface ScoreUpdateRequest { + VerifyStatus verifyStatus(); + String rejectedReason(); } diff --git a/src/main/java/com/example/solidconnection/admin/dto/SiteUserResponse.java b/src/main/java/com/example/solidconnection/admin/dto/SiteUserResponse.java index 1b62f262f..0b6d23816 100644 --- a/src/main/java/com/example/solidconnection/admin/dto/SiteUserResponse.java +++ b/src/main/java/com/example/solidconnection/admin/dto/SiteUserResponse.java @@ -5,4 +5,5 @@ public record SiteUserResponse( String nickname, String profileImageUrl ) { + } diff --git a/src/main/java/com/example/solidconnection/admin/service/AdminGpaScoreService.java b/src/main/java/com/example/solidconnection/admin/service/AdminGpaScoreService.java index 2f954d910..717f2a963 100644 --- a/src/main/java/com/example/solidconnection/admin/service/AdminGpaScoreService.java +++ b/src/main/java/com/example/solidconnection/admin/service/AdminGpaScoreService.java @@ -1,5 +1,7 @@ package com.example.solidconnection.admin.service; +import static com.example.solidconnection.common.exception.ErrorCode.GPA_SCORE_NOT_FOUND; + import com.example.solidconnection.admin.dto.GpaScoreResponse; import com.example.solidconnection.admin.dto.GpaScoreSearchResponse; import com.example.solidconnection.admin.dto.GpaScoreUpdateRequest; @@ -15,8 +17,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import static com.example.solidconnection.common.exception.ErrorCode.GPA_SCORE_NOT_FOUND; - @RequiredArgsConstructor @Service public class AdminGpaScoreService { diff --git a/src/main/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreService.java b/src/main/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreService.java index b8770caa5..0aa4a859d 100644 --- a/src/main/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreService.java +++ b/src/main/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreService.java @@ -1,5 +1,7 @@ package com.example.solidconnection.admin.service; +import static com.example.solidconnection.common.exception.ErrorCode.LANGUAGE_TEST_SCORE_NOT_FOUND; + import com.example.solidconnection.admin.dto.LanguageTestScoreResponse; import com.example.solidconnection.admin.dto.LanguageTestScoreSearchResponse; import com.example.solidconnection.admin.dto.LanguageTestScoreUpdateRequest; @@ -15,8 +17,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import static com.example.solidconnection.common.exception.ErrorCode.LANGUAGE_TEST_SCORE_NOT_FOUND; - @RequiredArgsConstructor @Service public class AdminLanguageTestScoreService { diff --git a/src/main/java/com/example/solidconnection/application/domain/Application.java b/src/main/java/com/example/solidconnection/application/domain/Application.java index 32c7a971c..db830ed0a 100644 --- a/src/main/java/com/example/solidconnection/application/domain/Application.java +++ b/src/main/java/com/example/solidconnection/application/domain/Application.java @@ -1,5 +1,7 @@ package com.example.solidconnection.application.domain; +import static com.example.solidconnection.common.VerifyStatus.PENDING; + import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.persistence.Column; @@ -18,8 +20,6 @@ import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; -import static com.example.solidconnection.common.VerifyStatus.PENDING; - @Getter @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) @DynamicUpdate @@ -48,23 +48,23 @@ public class Application { private LanguageTest languageTest; @Setter - @Column(columnDefinition = "varchar(50) not null default 'PENDING'", name="verify_status") + @Column(columnDefinition = "varchar(50) not null default 'PENDING'", name = "verify_status") @Enumerated(EnumType.STRING) private VerifyStatus verifyStatus; - @Column(length = 100, name="nickname_for_apply") + @Column(length = 100, name = "nickname_for_apply") private String nicknameForApply; - @Column(columnDefinition = "int not null default 1", name="update_count") + @Column(columnDefinition = "int not null default 1", name = "update_count") private Integer updateCount; - @Column(length = 50, nullable = false, name="term") + @Column(length = 50, nullable = false, name = "term") private String term; - @Column(name="is_delete") + @Column(name = "is_delete") private boolean isDelete = false; - @Column(nullable = false , name = "first_choice_university_info_for_apply_id") + @Column(nullable = false, name = "first_choice_university_info_for_apply_id") private long firstChoiceUnivApplyInfoId; @Column(name = "second_choice_university_info_for_apply_id") diff --git a/src/main/java/com/example/solidconnection/application/dto/ApplicantsResponse.java b/src/main/java/com/example/solidconnection/application/dto/ApplicantsResponse.java index b92f699a7..fdb9c357c 100644 --- a/src/main/java/com/example/solidconnection/application/dto/ApplicantsResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/ApplicantsResponse.java @@ -3,7 +3,6 @@ import com.example.solidconnection.application.domain.Application; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.university.domain.UnivApplyInfo; - import java.util.List; import java.util.Objects; @@ -13,6 +12,7 @@ public record ApplicantsResponse( String region, String country, List applicants) { + public static ApplicantsResponse of(UnivApplyInfo univApplyInfo, List applications, SiteUser siteUser) { return new ApplicantsResponse( univApplyInfo.getKoreanName(), @@ -20,8 +20,8 @@ public static ApplicantsResponse of(UnivApplyInfo univApplyInfo, List ApplicantResponse.of(application, isUsers(application, siteUser))) - .toList()); + .map(application -> ApplicantResponse.of(application, isUsers(application, siteUser))) + .toList()); } private static boolean isUsers(Application application, SiteUser siteUser) { diff --git a/src/main/java/com/example/solidconnection/application/dto/ApplicationSubmissionResponse.java b/src/main/java/com/example/solidconnection/application/dto/ApplicationSubmissionResponse.java index e90994c37..a2636c135 100644 --- a/src/main/java/com/example/solidconnection/application/dto/ApplicationSubmissionResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/ApplicationSubmissionResponse.java @@ -5,6 +5,7 @@ public record ApplicationSubmissionResponse( int applyCount ) { + public static ApplicationSubmissionResponse from(Application application) { return new ApplicationSubmissionResponse(application.getUpdateCount()); } diff --git a/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java b/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java index 13c641bb0..657c7c2c3 100644 --- a/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java @@ -6,4 +6,5 @@ public record ApplicationsResponse( List firstChoice, List secondChoice, List thirdChoice) { + } diff --git a/src/main/java/com/example/solidconnection/application/dto/ApplyRequest.java b/src/main/java/com/example/solidconnection/application/dto/ApplyRequest.java index cab079b21..c50252c00 100644 --- a/src/main/java/com/example/solidconnection/application/dto/ApplyRequest.java +++ b/src/main/java/com/example/solidconnection/application/dto/ApplyRequest.java @@ -16,4 +16,5 @@ public record ApplyRequest( @JsonProperty("universityChoiceRequest") UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest ) { + } diff --git a/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoChoiceRequest.java b/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoChoiceRequest.java index 9598a191b..449b1ca2c 100644 --- a/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoChoiceRequest.java +++ b/src/main/java/com/example/solidconnection/application/dto/UnivApplyInfoChoiceRequest.java @@ -14,4 +14,5 @@ public record UnivApplyInfoChoiceRequest( @JsonProperty("thirdChoiceUniversityId") Long thirdChoiceUnivApplyInfoId) { + } diff --git a/src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonRequired.java b/src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonRequired.java index 281be235b..7f00291ba 100644 --- a/src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonRequired.java +++ b/src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonRequired.java @@ -2,7 +2,6 @@ import jakarta.validation.Constraint; import jakarta.validation.Payload; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -14,6 +13,8 @@ public @interface RejectedReasonRequired { String message() default "거절 사유 입력값이 올바르지 않습니다."; + Class[] groups() default {}; + Class[] payload() default {}; } diff --git a/src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonValidator.java b/src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonValidator.java index 4b43673f3..85e6ecc78 100644 --- a/src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonValidator.java +++ b/src/main/java/com/example/solidconnection/application/dto/validation/RejectedReasonValidator.java @@ -1,13 +1,13 @@ package com.example.solidconnection.application.dto.validation; +import static com.example.solidconnection.common.exception.ErrorCode.REJECTED_REASON_REQUIRED; + import com.example.solidconnection.admin.dto.ScoreUpdateRequest; import com.example.solidconnection.common.VerifyStatus; import io.micrometer.common.util.StringUtils; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; -import static com.example.solidconnection.common.exception.ErrorCode.REJECTED_REASON_REQUIRED; - public class RejectedReasonValidator implements ConstraintValidator { private static final String REJECTED_REASON = "rejectedReason"; diff --git a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java index 54cb93ada..3916f37db 100644 --- a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java +++ b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java @@ -1,40 +1,39 @@ package com.example.solidconnection.application.repository; +import static com.example.solidconnection.common.exception.ErrorCode.APPLICATION_NOT_FOUND; + import com.example.solidconnection.application.domain.Application; import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; +import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; -import java.util.Optional; - -import static com.example.solidconnection.common.exception.ErrorCode.APPLICATION_NOT_FOUND; - public interface ApplicationRepository extends JpaRepository { boolean existsByNicknameForApply(String nicknameForApply); @Query(""" - SELECT a - FROM Application a - WHERE (a.firstChoiceUnivApplyInfoId IN :univApplyInfoIds - OR a.secondChoiceUnivApplyInfoId IN :univApplyInfoIds - OR a.thirdChoiceUnivApplyInfoId IN :univApplyInfoIds) - AND a.verifyStatus = :status - AND a.term = :term - AND a.isDelete = false - """) + SELECT a + FROM Application a + WHERE (a.firstChoiceUnivApplyInfoId IN :univApplyInfoIds + OR a.secondChoiceUnivApplyInfoId IN :univApplyInfoIds + OR a.thirdChoiceUnivApplyInfoId IN :univApplyInfoIds) + AND a.verifyStatus = :status + AND a.term = :term + AND a.isDelete = false + """) List findAllByUnivApplyInfoIds(@Param("univApplyInfoIds") List univApplyInfoIds, @Param("status") VerifyStatus status, @Param("term") String term); @Query(""" - SELECT a - FROM Application a - WHERE a.siteUserId = :siteUserId - AND a.term = :term - AND a.isDelete = false - """) + SELECT a + FROM Application a + WHERE a.siteUserId = :siteUserId + AND a.term = :term + AND a.isDelete = false + """) Optional findBySiteUserIdAndTerm(@Param("siteUserId") long siteUserId, @Param("term") String term); default Application getApplicationBySiteUserIdAndTerm(long siteUserId, String term) { diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java index f3dbc827c..9b34bdbc5 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java @@ -1,20 +1,17 @@ package com.example.solidconnection.application.service; +import static com.example.solidconnection.common.exception.ErrorCode.APPLICATION_NOT_APPROVED; + import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.common.VerifyStatus; -import com.example.solidconnection.application.dto.ApplicationsResponse; import com.example.solidconnection.application.dto.ApplicantsResponse; +import com.example.solidconnection.application.dto.ApplicationsResponse; import com.example.solidconnection.application.repository.ApplicationRepository; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; import com.example.solidconnection.university.repository.custom.UnivApplyInfoFilterRepositoryImpl; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -23,8 +20,10 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; - -import static com.example.solidconnection.common.exception.ErrorCode.APPLICATION_NOT_APPROVED; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor @Service diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java index e811a48a0..02c096529 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java @@ -1,30 +1,29 @@ package com.example.solidconnection.application.service; +import static com.example.solidconnection.common.exception.ErrorCode.APPLY_UPDATE_LIMIT_EXCEED; +import static com.example.solidconnection.common.exception.ErrorCode.GPA_SCORE_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_GPA_SCORE_STATUS; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE_STATUS; + import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.application.dto.ApplicationSubmissionResponse; import com.example.solidconnection.application.dto.ApplyRequest; import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; import com.example.solidconnection.application.repository.ApplicationRepository; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.repository.GpaScoreRepository; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Optional; - -import static com.example.solidconnection.common.exception.ErrorCode.APPLY_UPDATE_LIMIT_EXCEED; -import static com.example.solidconnection.common.exception.ErrorCode.GPA_SCORE_NOT_FOUND; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_GPA_SCORE_STATUS; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE_STATUS; - @RequiredArgsConstructor @Service public class ApplicationSubmissionService { diff --git a/src/main/java/com/example/solidconnection/application/service/NicknameCreator.java b/src/main/java/com/example/solidconnection/application/service/NicknameCreator.java index d9243ce39..c9699d4dc 100644 --- a/src/main/java/com/example/solidconnection/application/service/NicknameCreator.java +++ b/src/main/java/com/example/solidconnection/application/service/NicknameCreator.java @@ -1,10 +1,9 @@ package com.example.solidconnection.application.service; -import lombok.NoArgsConstructor; - import java.util.List; import java.util.Random; import java.util.Set; +import lombok.NoArgsConstructor; @NoArgsConstructor(access = lombok.AccessLevel.PRIVATE) class NicknameCreator { diff --git a/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClient.java b/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClient.java index 733eb5700..41c4eec4e 100644 --- a/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClient.java +++ b/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClient.java @@ -1,10 +1,15 @@ package com.example.solidconnection.auth.client; +import static com.example.solidconnection.common.exception.ErrorCode.APPLE_AUTHORIZATION_FAILED; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_APPLE_ID_TOKEN; + import com.example.solidconnection.auth.client.config.AppleOAuthClientProperties; import com.example.solidconnection.auth.dto.oauth.AppleTokenDto; import com.example.solidconnection.auth.dto.oauth.AppleUserInfoDto; import com.example.solidconnection.common.exception.CustomException; import io.jsonwebtoken.Jwts; +import java.security.PublicKey; +import java.util.Objects; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -16,12 +21,6 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; -import java.security.PublicKey; -import java.util.Objects; - -import static com.example.solidconnection.common.exception.ErrorCode.APPLE_AUTHORIZATION_FAILED; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_APPLE_ID_TOKEN; - /* * 애플 인증을 위한 OAuth2 클라이언트 * https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens diff --git a/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClientSecretProvider.java b/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClientSecretProvider.java index 6bdd6a5fb..a5a5cd315 100644 --- a/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClientSecretProvider.java +++ b/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClientSecretProvider.java @@ -1,22 +1,21 @@ package com.example.solidconnection.auth.client; +import static com.example.solidconnection.common.exception.ErrorCode.FAILED_TO_READ_APPLE_PRIVATE_KEY; + import com.example.solidconnection.auth.client.config.AppleOAuthClientProperties; import com.example.solidconnection.common.exception.CustomException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import jakarta.annotation.PostConstruct; -import lombok.RequiredArgsConstructor; -import org.apache.tomcat.util.codec.binary.Base64; -import org.springframework.stereotype.Component; - import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Date; - -import static com.example.solidconnection.common.exception.ErrorCode.FAILED_TO_READ_APPLE_PRIVATE_KEY; +import lombok.RequiredArgsConstructor; +import org.apache.tomcat.util.codec.binary.Base64; +import org.springframework.stereotype.Component; /* * 애플 OAuth 에 필요한 클라이언트 시크릿은 매번 동적으로 생성해야 한다. diff --git a/src/main/java/com/example/solidconnection/auth/client/ApplePublicKeyProvider.java b/src/main/java/com/example/solidconnection/auth/client/ApplePublicKeyProvider.java index 0fe6b9700..0b0aaa7d0 100644 --- a/src/main/java/com/example/solidconnection/auth/client/ApplePublicKeyProvider.java +++ b/src/main/java/com/example/solidconnection/auth/client/ApplePublicKeyProvider.java @@ -1,16 +1,16 @@ package com.example.solidconnection.auth.client; +import static com.example.solidconnection.common.exception.ErrorCode.APPLE_ID_TOKEN_EXPIRED; +import static com.example.solidconnection.common.exception.ErrorCode.APPLE_PUBLIC_KEY_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_APPLE_ID_TOKEN; +import static org.apache.tomcat.util.codec.binary.Base64.decodeBase64URLSafe; + import com.example.solidconnection.auth.client.config.AppleOAuthClientProperties; import com.example.solidconnection.common.exception.CustomException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.ExpiredJwtException; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; - import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; @@ -19,19 +19,18 @@ import java.util.Base64; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; - -import static com.example.solidconnection.common.exception.ErrorCode.APPLE_ID_TOKEN_EXPIRED; -import static com.example.solidconnection.common.exception.ErrorCode.APPLE_PUBLIC_KEY_NOT_FOUND; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_APPLE_ID_TOKEN; -import static org.apache.tomcat.util.codec.binary.Base64.decodeBase64URLSafe; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; /* -* idToken 검증을 위해서 애플의 공개키를 가져온다. -* - 애플 공개키는 주기적으로 바뀐다. 이를 효율적으로 관리하기 위해 캐싱한다. -* - idToken 의 헤더에 있는 kid 값에 해당하는 키가 캐싱되어있으면 그것을 반환한다. -* - 그렇지 않다면 공개키가 바뀌었다는 뜻이므로, JSON 형식의 공개키 목록을 받아오고 캐시를 갱신한다. -* https://developer.apple.com/documentation/signinwithapplerestapi/fetch_apple_s_public_key_for_verifying_token_signature -* */ + * idToken 검증을 위해서 애플의 공개키를 가져온다. + * - 애플 공개키는 주기적으로 바뀐다. 이를 효율적으로 관리하기 위해 캐싱한다. + * - idToken 의 헤더에 있는 kid 값에 해당하는 키가 캐싱되어있으면 그것을 반환한다. + * - 그렇지 않다면 공개키가 바뀌었다는 뜻이므로, JSON 형식의 공개키 목록을 받아오고 캐시를 갱신한다. + * https://developer.apple.com/documentation/signinwithapplerestapi/fetch_apple_s_public_key_for_verifying_token_signature + * */ @Component @RequiredArgsConstructor public class ApplePublicKeyProvider { @@ -62,9 +61,9 @@ public PublicKey getApplePublicKey(String idToken) { } /* - * idToken 은 JWS 이므로, 원칙적으로는 서명까지 검증되어야 parsing 이 가능하다 - * 하지만 이 시점에서는 서명(=공개키)을 알 수 없으므로, Jwt 를 직접 인코딩하여 헤더를 가져온다. - * */ + * idToken 은 JWS 이므로, 원칙적으로는 서명까지 검증되어야 parsing 이 가능하다 + * 하지만 이 시점에서는 서명(=공개키)을 알 수 없으므로, Jwt 를 직접 인코딩하여 헤더를 가져온다. + * */ private String getKeyIdFromTokenHeader(String idToken) throws JsonProcessingException { String[] jwtParts = idToken.split("\\."); if (jwtParts.length < 2) { diff --git a/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java b/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java index 9539116aa..eb30746b4 100644 --- a/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java +++ b/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java @@ -1,9 +1,14 @@ package com.example.solidconnection.auth.client; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_OR_EXPIRED_KAKAO_AUTH_CODE; +import static com.example.solidconnection.common.exception.ErrorCode.KAKAO_REDIRECT_URI_MISMATCH; +import static com.example.solidconnection.common.exception.ErrorCode.KAKAO_USER_INFO_FAIL; + import com.example.solidconnection.auth.client.config.KakaoOAuthClientProperties; import com.example.solidconnection.auth.dto.oauth.KakaoTokenDto; import com.example.solidconnection.auth.dto.oauth.KakaoUserInfoDto; import com.example.solidconnection.common.exception.CustomException; +import java.util.Objects; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -14,12 +19,6 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; -import java.util.Objects; - -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_OR_EXPIRED_KAKAO_AUTH_CODE; -import static com.example.solidconnection.common.exception.ErrorCode.KAKAO_REDIRECT_URI_MISMATCH; -import static com.example.solidconnection.common.exception.ErrorCode.KAKAO_USER_INFO_FAIL; - /* * 카카오 인증을 위한 OAuth2 클라이언트 * https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#request-code diff --git a/src/main/java/com/example/solidconnection/auth/client/config/AppleOAuthClientProperties.java b/src/main/java/com/example/solidconnection/auth/client/config/AppleOAuthClientProperties.java index ae62d3561..3ff927a03 100644 --- a/src/main/java/com/example/solidconnection/auth/client/config/AppleOAuthClientProperties.java +++ b/src/main/java/com/example/solidconnection/auth/client/config/AppleOAuthClientProperties.java @@ -13,4 +13,5 @@ public record AppleOAuthClientProperties( String keyId, String secretKey ) { + } diff --git a/src/main/java/com/example/solidconnection/auth/client/config/KakaoOAuthClientProperties.java b/src/main/java/com/example/solidconnection/auth/client/config/KakaoOAuthClientProperties.java index f9a476e6a..36dcdaec1 100644 --- a/src/main/java/com/example/solidconnection/auth/client/config/KakaoOAuthClientProperties.java +++ b/src/main/java/com/example/solidconnection/auth/client/config/KakaoOAuthClientProperties.java @@ -9,4 +9,5 @@ public record KakaoOAuthClientProperties( String redirectUrl, String clientId ) { + } diff --git a/src/main/java/com/example/solidconnection/auth/dto/EmailSignInRequest.java b/src/main/java/com/example/solidconnection/auth/dto/EmailSignInRequest.java index 306a8185a..925fb62cd 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/EmailSignInRequest.java +++ b/src/main/java/com/example/solidconnection/auth/dto/EmailSignInRequest.java @@ -10,4 +10,5 @@ public record EmailSignInRequest( @NotBlank(message = "비밀번호를 입력해주세요.") String password ) { + } diff --git a/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenRequest.java b/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenRequest.java index 92073b434..52e50e5b2 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenRequest.java +++ b/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenRequest.java @@ -11,4 +11,5 @@ public record EmailSignUpTokenRequest( @NotBlank(message = "비밀번호를 입력해주세요.") String password ) { + } diff --git a/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenResponse.java b/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenResponse.java index c8e983d0c..d056da89b 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenResponse.java +++ b/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenResponse.java @@ -3,4 +3,5 @@ public record EmailSignUpTokenResponse( String signUpToken ) { + } diff --git a/src/main/java/com/example/solidconnection/auth/dto/ReissueRequest.java b/src/main/java/com/example/solidconnection/auth/dto/ReissueRequest.java index 00443255d..417ed32b0 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/ReissueRequest.java +++ b/src/main/java/com/example/solidconnection/auth/dto/ReissueRequest.java @@ -5,4 +5,5 @@ public record ReissueRequest( @NotBlank(message = "리프레시 토큰과 함께 요청해주세요.") String refreshToken) { + } diff --git a/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java b/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java index 8f72d25da..bafb9b4c8 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java +++ b/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java @@ -6,7 +6,6 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.NotBlank; - import java.util.List; public record SignUpRequest( diff --git a/src/main/java/com/example/solidconnection/auth/dto/oauth/AppleTokenDto.java b/src/main/java/com/example/solidconnection/auth/dto/oauth/AppleTokenDto.java index 6772cb2c2..019066c81 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/oauth/AppleTokenDto.java +++ b/src/main/java/com/example/solidconnection/auth/dto/oauth/AppleTokenDto.java @@ -7,4 +7,5 @@ public record AppleTokenDto( @JsonProperty("id_token") String idToken ) { + } diff --git a/src/main/java/com/example/solidconnection/auth/dto/oauth/AppleUserInfoDto.java b/src/main/java/com/example/solidconnection/auth/dto/oauth/AppleUserInfoDto.java index 5c4363e51..5e95c2a5d 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/oauth/AppleUserInfoDto.java +++ b/src/main/java/com/example/solidconnection/auth/dto/oauth/AppleUserInfoDto.java @@ -1,10 +1,10 @@ package com.example.solidconnection.auth.dto.oauth; /* -* 애플로부터 사용자의 정보를 받아올 때 사용한다. -* 카카오와 달리 애플은 더 엄격하게 사용자 정보를 관리하여, 이름이나 프로필 이미지 url 을 제공하지 않는다. -* 따라서 닉네임, 프로필 정보는 null 을 반환한다. -* */ + * 애플로부터 사용자의 정보를 받아올 때 사용한다. + * 카카오와 달리 애플은 더 엄격하게 사용자 정보를 관리하여, 이름이나 프로필 이미지 url 을 제공하지 않는다. + * 따라서 닉네임, 프로필 정보는 null 을 반환한다. + * */ public record AppleUserInfoDto(String email) implements OAuthUserInfoDto { @Override diff --git a/src/main/java/com/example/solidconnection/auth/dto/oauth/KakaoTokenDto.java b/src/main/java/com/example/solidconnection/auth/dto/oauth/KakaoTokenDto.java index 6d4ccd10c..0663573b1 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/oauth/KakaoTokenDto.java +++ b/src/main/java/com/example/solidconnection/auth/dto/oauth/KakaoTokenDto.java @@ -7,4 +7,5 @@ public record KakaoTokenDto( @JsonProperty("access_token") String accessToken, @JsonProperty("refresh_token") String refreshToken) { + } diff --git a/src/main/java/com/example/solidconnection/auth/dto/oauth/OAuthCodeRequest.java b/src/main/java/com/example/solidconnection/auth/dto/oauth/OAuthCodeRequest.java index abbdb7802..5ecdf7cca 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/oauth/OAuthCodeRequest.java +++ b/src/main/java/com/example/solidconnection/auth/dto/oauth/OAuthCodeRequest.java @@ -6,4 +6,5 @@ public record OAuthCodeRequest( @NotBlank(message = "인증 코드를 입력해주세요.") String code) { + } diff --git a/src/main/java/com/example/solidconnection/auth/dto/oauth/OAuthResponse.java b/src/main/java/com/example/solidconnection/auth/dto/oauth/OAuthResponse.java index ddbe121f7..5fbb0fd0d 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/oauth/OAuthResponse.java +++ b/src/main/java/com/example/solidconnection/auth/dto/oauth/OAuthResponse.java @@ -1,4 +1,5 @@ package com.example.solidconnection.auth.dto.oauth; public interface OAuthResponse { + } diff --git a/src/main/java/com/example/solidconnection/auth/dto/oauth/OAuthSignInResponse.java b/src/main/java/com/example/solidconnection/auth/dto/oauth/OAuthSignInResponse.java index 8ad429876..6ac121c46 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/oauth/OAuthSignInResponse.java +++ b/src/main/java/com/example/solidconnection/auth/dto/oauth/OAuthSignInResponse.java @@ -4,4 +4,5 @@ public record OAuthSignInResponse( boolean isRegistered, String accessToken, String refreshToken) implements OAuthResponse { + } diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthService.java b/src/main/java/com/example/solidconnection/auth/service/AuthService.java index 6053e8716..d487d39e8 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -1,18 +1,17 @@ package com.example.solidconnection.auth.service; +import static com.example.solidconnection.common.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; + import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; import com.example.solidconnection.auth.token.TokenBlackListService; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; +import java.time.LocalDate; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDate; - -import static com.example.solidconnection.common.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; - @RequiredArgsConstructor @Service public class AuthService { diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java index 2ff3e5650..f15a3e7b4 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -2,12 +2,11 @@ import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.siteuser.domain.SiteUser; +import java.util.Objects; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; -import java.util.Objects; - @Component @RequiredArgsConstructor public class AuthTokenProvider { diff --git a/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java index d1ebe3b52..c6930315b 100644 --- a/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java @@ -1,13 +1,13 @@ package com.example.solidconnection.auth.service; +import static com.example.solidconnection.auth.service.EmailSignUpTokenProvider.AUTH_TYPE_CLAIM_KEY; +import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.AuthType; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import static com.example.solidconnection.auth.service.EmailSignUpTokenProvider.AUTH_TYPE_CLAIM_KEY; -import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; - @Component @RequiredArgsConstructor public class CommonSignUpTokenProvider { diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java index d80465791..d7ee365d8 100644 --- a/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java +++ b/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java @@ -1,19 +1,18 @@ package com.example.solidconnection.auth.service; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; + import com.example.solidconnection.auth.dto.EmailSignInRequest; import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import java.util.Optional; - -import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; - /* * 보안을 위해 이메일과 비밀번호 중 무엇이 틀렸는지 구체적으로 응답하지 않는다. * */ diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java index 4e796b81d..a3436cf5d 100644 --- a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java +++ b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java @@ -1,5 +1,7 @@ package com.example.solidconnection.auth.service; +import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_EXISTED; + import com.example.solidconnection.auth.dto.SignUpRequest; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.location.country.repository.CountryRepository; @@ -11,8 +13,6 @@ import com.example.solidconnection.siteuser.repository.SiteUserRepository; import org.springframework.stereotype.Service; -import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_EXISTED; - @Service public class EmailSignUpService extends SignUpService { diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java index fe9a176d3..238c7e517 100644 --- a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java @@ -1,5 +1,8 @@ package com.example.solidconnection.auth.service; +import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; +import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; + import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.EmailSignUpTokenRequest; import com.example.solidconnection.auth.token.config.JwtProperties; @@ -8,18 +11,14 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; -import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Component; - import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Objects; - -import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; -import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; @Component @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/auth/service/SignUpService.java b/src/main/java/com/example/solidconnection/auth/service/SignUpService.java index 8cce448f7..aeb67d037 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignUpService.java +++ b/src/main/java/com/example/solidconnection/auth/service/SignUpService.java @@ -1,5 +1,7 @@ package com.example.solidconnection.auth.service; +import static com.example.solidconnection.common.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; + import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.auth.dto.SignUpRequest; import com.example.solidconnection.common.exception.CustomException; @@ -11,11 +13,8 @@ import com.example.solidconnection.location.region.repository.RegionRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import org.springframework.transaction.annotation.Transactional; - import java.util.List; - -import static com.example.solidconnection.common.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; +import org.springframework.transaction.annotation.Transactional; /* * 우리 서버에서 인증되었음을 확인하기 위한 signUpToken 을 검증한다. diff --git a/src/main/java/com/example/solidconnection/auth/service/Subject.java b/src/main/java/com/example/solidconnection/auth/service/Subject.java index 2c03eb013..15e5c6c75 100644 --- a/src/main/java/com/example/solidconnection/auth/service/Subject.java +++ b/src/main/java/com/example/solidconnection/auth/service/Subject.java @@ -3,4 +3,5 @@ public record Subject( String value ) { + } diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java index 6e9bf7030..d02377341 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java @@ -11,9 +11,8 @@ import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import org.springframework.transaction.annotation.Transactional; - import java.util.Optional; +import org.springframework.transaction.annotation.Transactional; /* * OAuth 제공자로부터 이메일을 받아 기존 회원인지, 신규 회원인지 판별하고, 이에 따라 다르게 응답한다. @@ -57,5 +56,6 @@ protected final SignUpPrepareResponse getSignUpPrepareResponse(OAuthUserInfoDto } protected abstract OAuthUserInfoDto getOAuthUserInfo(String code); + protected abstract AuthType getAuthType(); } diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java index 96253f548..ca50442fc 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java @@ -1,5 +1,7 @@ package com.example.solidconnection.auth.service.oauth; +import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_EXISTED; + import com.example.solidconnection.auth.dto.SignUpRequest; import com.example.solidconnection.auth.service.SignInService; import com.example.solidconnection.auth.service.SignUpService; @@ -13,8 +15,6 @@ import com.example.solidconnection.siteuser.repository.SiteUserRepository; import org.springframework.stereotype.Service; -import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_EXISTED; - @Service public class OAuthSignUpService extends SignUpService { diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java index 1aae0338e..ae359c5b8 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java @@ -1,5 +1,8 @@ package com.example.solidconnection.auth.service.oauth; +import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; +import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; + import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.token.config.JwtProperties; @@ -8,17 +11,13 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; -import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Component; - import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Objects; - -import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; -import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; @Component @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java index 839e58362..f9f5b20ff 100644 --- a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java @@ -1,5 +1,7 @@ package com.example.solidconnection.auth.token; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; + import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.token.config.JwtProperties; @@ -7,15 +9,12 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import java.util.Date; +import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; -import java.util.Date; -import java.util.concurrent.TimeUnit; - -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; - @Component @RequiredArgsConstructor public class JwtTokenProvider implements TokenProvider { diff --git a/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java b/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java index 4175a577f..7f208710c 100644 --- a/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java +++ b/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java @@ -1,13 +1,13 @@ package com.example.solidconnection.auth.token; +import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST; + import com.example.solidconnection.auth.service.AccessToken; import com.example.solidconnection.security.filter.BlacklistChecker; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; -import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST; - @Component @RequiredArgsConstructor public class TokenBlackListService implements BlacklistChecker { diff --git a/src/main/java/com/example/solidconnection/auth/token/config/JwtProperties.java b/src/main/java/com/example/solidconnection/auth/token/config/JwtProperties.java index bf5180218..2d81601ee 100644 --- a/src/main/java/com/example/solidconnection/auth/token/config/JwtProperties.java +++ b/src/main/java/com/example/solidconnection/auth/token/config/JwtProperties.java @@ -4,4 +4,5 @@ @ConfigurationProperties(prefix = "jwt") public record JwtProperties(String secret) { + } diff --git a/src/main/java/com/example/solidconnection/cache/CacheUpdateListener.java b/src/main/java/com/example/solidconnection/cache/CacheUpdateListener.java index c785168b3..0d2ee9eaf 100644 --- a/src/main/java/com/example/solidconnection/cache/CacheUpdateListener.java +++ b/src/main/java/com/example/solidconnection/cache/CacheUpdateListener.java @@ -1,13 +1,12 @@ package com.example.solidconnection.cache; +import java.nio.charset.StandardCharsets; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.stereotype.Component; -import java.nio.charset.StandardCharsets; - @Component @RequiredArgsConstructor @Slf4j diff --git a/src/main/java/com/example/solidconnection/cache/CompletableFutureManager.java b/src/main/java/com/example/solidconnection/cache/CompletableFutureManager.java index 48c36b28c..f2c08edcc 100644 --- a/src/main/java/com/example/solidconnection/cache/CompletableFutureManager.java +++ b/src/main/java/com/example/solidconnection/cache/CompletableFutureManager.java @@ -1,10 +1,9 @@ package com.example.solidconnection.cache; -import org.springframework.stereotype.Component; - import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; +import org.springframework.stereotype.Component; @Component public class CompletableFutureManager { diff --git a/src/main/java/com/example/solidconnection/cache/ThunderingHerdCachingAspect.java b/src/main/java/com/example/solidconnection/cache/ThunderingHerdCachingAspect.java index 5527c2523..b6a9fe0b0 100644 --- a/src/main/java/com/example/solidconnection/cache/ThunderingHerdCachingAspect.java +++ b/src/main/java/com/example/solidconnection/cache/ThunderingHerdCachingAspect.java @@ -1,8 +1,20 @@ package com.example.solidconnection.cache; +import static com.example.solidconnection.community.post.service.RedisConstants.CREATE_CHANNEL; +import static com.example.solidconnection.community.post.service.RedisConstants.LOCK_TIMEOUT_MS; +import static com.example.solidconnection.community.post.service.RedisConstants.MAX_WAIT_TIME_MS; +import static com.example.solidconnection.community.post.service.RedisConstants.REFRESH_LIMIT_PERCENT; + import com.example.solidconnection.cache.annotation.ThunderingHerdCaching; import com.example.solidconnection.cache.manager.CacheManager; import com.example.solidconnection.util.RedisUtils; +import java.time.Duration; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; @@ -14,19 +26,6 @@ import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.stereotype.Component; -import java.time.Duration; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import static com.example.solidconnection.community.post.service.RedisConstants.CREATE_CHANNEL; -import static com.example.solidconnection.community.post.service.RedisConstants.LOCK_TIMEOUT_MS; -import static com.example.solidconnection.community.post.service.RedisConstants.MAX_WAIT_TIME_MS; -import static com.example.solidconnection.community.post.service.RedisConstants.REFRESH_LIMIT_PERCENT; - @Aspect @Component @Slf4j diff --git a/src/main/java/com/example/solidconnection/cache/annotation/ThunderingHerdCaching.java b/src/main/java/com/example/solidconnection/cache/annotation/ThunderingHerdCaching.java index c5a9e0e9b..5cf59b41d 100644 --- a/src/main/java/com/example/solidconnection/cache/annotation/ThunderingHerdCaching.java +++ b/src/main/java/com/example/solidconnection/cache/annotation/ThunderingHerdCaching.java @@ -8,6 +8,7 @@ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ThunderingHerdCaching { + String key(); String cacheManager(); diff --git a/src/main/java/com/example/solidconnection/cache/manager/CustomCacheManager.java b/src/main/java/com/example/solidconnection/cache/manager/CustomCacheManager.java index 2e489567c..581822f73 100644 --- a/src/main/java/com/example/solidconnection/cache/manager/CustomCacheManager.java +++ b/src/main/java/com/example/solidconnection/cache/manager/CustomCacheManager.java @@ -1,14 +1,13 @@ package com.example.solidconnection.cache.manager; +import java.time.Duration; +import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.stereotype.Component; -import java.time.Duration; -import java.util.Set; - @Component("customCacheManager") public class CustomCacheManager implements CacheManager { diff --git a/src/main/java/com/example/solidconnection/common/BaseEntity.java b/src/main/java/com/example/solidconnection/common/BaseEntity.java index febf5e77c..b3b597350 100644 --- a/src/main/java/com/example/solidconnection/common/BaseEntity.java +++ b/src/main/java/com/example/solidconnection/common/BaseEntity.java @@ -1,19 +1,18 @@ package com.example.solidconnection.common; +import static java.time.ZoneOffset.UTC; +import static java.time.temporal.ChronoUnit.MICROS; + import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; import jakarta.persistence.PrePersist; import jakarta.persistence.PreUpdate; +import java.time.ZonedDateTime; import lombok.Getter; import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import java.time.ZonedDateTime; - -import static java.time.ZoneOffset.UTC; -import static java.time.temporal.ChronoUnit.MICROS; - @MappedSuperclass @EntityListeners(AuditingEntityListener.class) @Getter diff --git a/src/main/java/com/example/solidconnection/common/config/client/RestTemplateConfig.java b/src/main/java/com/example/solidconnection/common/config/client/RestTemplateConfig.java index 88afde086..87b43eb28 100644 --- a/src/main/java/com/example/solidconnection/common/config/client/RestTemplateConfig.java +++ b/src/main/java/com/example/solidconnection/common/config/client/RestTemplateConfig.java @@ -1,12 +1,11 @@ package com.example.solidconnection.common.config.client; +import java.time.Duration; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; -import java.time.Duration; - @Configuration public class RestTemplateConfig { diff --git a/src/main/java/com/example/solidconnection/common/config/redis/RedisConfig.java b/src/main/java/com/example/solidconnection/common/config/redis/RedisConfig.java index 99c98581a..a59558993 100644 --- a/src/main/java/com/example/solidconnection/common/config/redis/RedisConfig.java +++ b/src/main/java/com/example/solidconnection/common/config/redis/RedisConfig.java @@ -1,5 +1,7 @@ package com.example.solidconnection.common.config.redis; +import static com.example.solidconnection.community.post.service.RedisConstants.CREATE_CHANNEL; + import com.example.solidconnection.cache.CacheUpdateListener; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; @@ -16,8 +18,6 @@ import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; -import static com.example.solidconnection.community.post.service.RedisConstants.CREATE_CHANNEL; - @Configuration @EnableRedisRepositories public class RedisConfig { diff --git a/src/main/java/com/example/solidconnection/common/config/web/WebMvcConfig.java b/src/main/java/com/example/solidconnection/common/config/web/WebMvcConfig.java index a6ba76f7b..56bb288e8 100644 --- a/src/main/java/com/example/solidconnection/common/config/web/WebMvcConfig.java +++ b/src/main/java/com/example/solidconnection/common/config/web/WebMvcConfig.java @@ -2,13 +2,12 @@ import com.example.solidconnection.common.resolver.AuthorizedUserResolver; import com.example.solidconnection.common.resolver.CustomPageableHandlerMethodArgumentResolver; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import java.util.List; - @Configuration @RequiredArgsConstructor public class WebMvcConfig implements WebMvcConfigurer { diff --git a/src/main/java/com/example/solidconnection/common/dto/SliceResponse.java b/src/main/java/com/example/solidconnection/common/dto/SliceResponse.java index 3f91dc7c4..8fe5c80b8 100644 --- a/src/main/java/com/example/solidconnection/common/dto/SliceResponse.java +++ b/src/main/java/com/example/solidconnection/common/dto/SliceResponse.java @@ -1,8 +1,7 @@ package com.example.solidconnection.common.dto; -import org.springframework.data.domain.Slice; - import java.util.List; +import org.springframework.data.domain.Slice; public record SliceResponse( List content, diff --git a/src/main/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandler.java b/src/main/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandler.java index 2c7e0fd19..a391ba66d 100644 --- a/src/main/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandler.java +++ b/src/main/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandler.java @@ -4,13 +4,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; import lombok.RequiredArgsConstructor; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.stereotype.Component; -import java.io.IOException; - @Component @RequiredArgsConstructor public class CustomAccessDeniedHandler implements AccessDeniedHandler { diff --git a/src/main/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPoint.java b/src/main/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPoint.java index d5eb87705..04b3d5aed 100644 --- a/src/main/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPoint.java +++ b/src/main/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPoint.java @@ -4,13 +4,12 @@ import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; import lombok.RequiredArgsConstructor; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; -import java.io.IOException; - @Component @RequiredArgsConstructor public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { diff --git a/src/main/java/com/example/solidconnection/common/exception/CustomExceptionHandler.java b/src/main/java/com/example/solidconnection/common/exception/CustomExceptionHandler.java index 57d6f4769..5700c3044 100644 --- a/src/main/java/com/example/solidconnection/common/exception/CustomExceptionHandler.java +++ b/src/main/java/com/example/solidconnection/common/exception/CustomExceptionHandler.java @@ -1,8 +1,16 @@ package com.example.solidconnection.common.exception; +import static com.example.solidconnection.common.exception.ErrorCode.DATA_INTEGRITY_VIOLATION; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_INPUT; +import static com.example.solidconnection.common.exception.ErrorCode.JSON_PARSING_FAILED; +import static com.example.solidconnection.common.exception.ErrorCode.JWT_EXCEPTION; +import static com.example.solidconnection.common.exception.ErrorCode.NOT_DEFINED_ERROR; + import com.example.solidconnection.common.response.ErrorResponse; import com.fasterxml.jackson.databind.exc.InvalidFormatException; import io.jsonwebtoken.JwtException; +import java.util.ArrayList; +import java.util.List; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; @@ -11,15 +19,6 @@ import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; -import java.util.ArrayList; -import java.util.List; - -import static com.example.solidconnection.common.exception.ErrorCode.DATA_INTEGRITY_VIOLATION; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_INPUT; -import static com.example.solidconnection.common.exception.ErrorCode.JSON_PARSING_FAILED; -import static com.example.solidconnection.common.exception.ErrorCode.JWT_EXCEPTION; -import static com.example.solidconnection.common.exception.ErrorCode.NOT_DEFINED_ERROR; - @Slf4j @ControllerAdvice public class CustomExceptionHandler { diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 75fbf5b0f..7b71469aa 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -1,12 +1,12 @@ package com.example.solidconnection.common.exception; +import static com.example.solidconnection.application.service.ApplicationSubmissionService.APPLICATION_UPDATE_COUNT_LIMIT; +import static com.example.solidconnection.siteuser.service.MyPageService.MIN_DAYS_BETWEEN_NICKNAME_CHANGES; + import lombok.AllArgsConstructor; import lombok.Getter; import org.springframework.http.HttpStatus; -import static com.example.solidconnection.application.service.ApplicationSubmissionService.APPLICATION_UPDATE_COUNT_LIMIT; -import static com.example.solidconnection.siteuser.service.MyPageService.MIN_DAYS_BETWEEN_NICKNAME_CHANGES; - @Getter @AllArgsConstructor public enum ErrorCode { diff --git a/src/main/java/com/example/solidconnection/common/resolver/AuthorizedUser.java b/src/main/java/com/example/solidconnection/common/resolver/AuthorizedUser.java index 0f4aa0954..ffe3a5431 100644 --- a/src/main/java/com/example/solidconnection/common/resolver/AuthorizedUser.java +++ b/src/main/java/com/example/solidconnection/common/resolver/AuthorizedUser.java @@ -8,5 +8,6 @@ @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface AuthorizedUser { + boolean required() default true; } diff --git a/src/main/java/com/example/solidconnection/common/resolver/AuthorizedUserResolver.java b/src/main/java/com/example/solidconnection/common/resolver/AuthorizedUserResolver.java index f729eb073..917784f8b 100644 --- a/src/main/java/com/example/solidconnection/common/resolver/AuthorizedUserResolver.java +++ b/src/main/java/com/example/solidconnection/common/resolver/AuthorizedUserResolver.java @@ -1,5 +1,7 @@ package com.example.solidconnection.common.resolver; +import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.security.userdetails.SiteUserDetails; import com.example.solidconnection.siteuser.domain.SiteUser; @@ -13,8 +15,6 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; -import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; - @Component @RequiredArgsConstructor public class AuthorizedUserResolver implements HandlerMethodArgumentResolver { diff --git a/src/main/java/com/example/solidconnection/common/response/PageResponse.java b/src/main/java/com/example/solidconnection/common/response/PageResponse.java index 90790d271..adf28cfc8 100644 --- a/src/main/java/com/example/solidconnection/common/response/PageResponse.java +++ b/src/main/java/com/example/solidconnection/common/response/PageResponse.java @@ -1,8 +1,7 @@ package com.example.solidconnection.common.response; -import org.springframework.data.domain.Page; - import java.util.List; +import org.springframework.data.domain.Page; public record PageResponse( List content, @@ -11,6 +10,7 @@ public record PageResponse( long totalElements, int totalPages ) { + /* * 페이지 번호는 1부터 시작하는 것이 사용자 입장에서 더 직관적이기 때문에 1을 더해줌 */ diff --git a/src/main/java/com/example/solidconnection/community/board/controller/BoardController.java b/src/main/java/com/example/solidconnection/community/board/controller/BoardController.java index b46c93256..20601eb65 100644 --- a/src/main/java/com/example/solidconnection/community/board/controller/BoardController.java +++ b/src/main/java/com/example/solidconnection/community/board/controller/BoardController.java @@ -5,6 +5,8 @@ import com.example.solidconnection.community.post.dto.PostListResponse; import com.example.solidconnection.community.post.service.PostQueryService; import com.example.solidconnection.siteuser.domain.SiteUser; +import java.util.ArrayList; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -13,9 +15,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.ArrayList; -import java.util.List; - @RestController @RequiredArgsConstructor @RequestMapping("/boards") diff --git a/src/main/java/com/example/solidconnection/community/board/dto/PostFindBoardResponse.java b/src/main/java/com/example/solidconnection/community/board/dto/PostFindBoardResponse.java index e4f66afdd..2ab10619e 100644 --- a/src/main/java/com/example/solidconnection/community/board/dto/PostFindBoardResponse.java +++ b/src/main/java/com/example/solidconnection/community/board/dto/PostFindBoardResponse.java @@ -6,6 +6,7 @@ public record PostFindBoardResponse( String code, String koreanName ) { + public static PostFindBoardResponse from(Board board) { return new PostFindBoardResponse( board.getCode(), diff --git a/src/main/java/com/example/solidconnection/community/board/repository/BoardRepository.java b/src/main/java/com/example/solidconnection/community/board/repository/BoardRepository.java index b81f9a15a..8e2c2b9d5 100644 --- a/src/main/java/com/example/solidconnection/community/board/repository/BoardRepository.java +++ b/src/main/java/com/example/solidconnection/community/board/repository/BoardRepository.java @@ -1,15 +1,14 @@ package com.example.solidconnection.community.board.repository; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_BOARD_CODE; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.community.board.domain.Board; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.query.Param; -import java.util.Optional; - -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_BOARD_CODE; - public interface BoardRepository extends JpaRepository { Optional findBoardByCode(@Param("code") String code); diff --git a/src/main/java/com/example/solidconnection/community/board/service/BoardService.java b/src/main/java/com/example/solidconnection/community/board/service/BoardService.java index c918f8126..8d97c8be0 100644 --- a/src/main/java/com/example/solidconnection/community/board/service/BoardService.java +++ b/src/main/java/com/example/solidconnection/community/board/service/BoardService.java @@ -6,4 +6,5 @@ @Service @RequiredArgsConstructor public class BoardService { + } diff --git a/src/main/java/com/example/solidconnection/community/comment/domain/Comment.java b/src/main/java/com/example/solidconnection/community/comment/domain/Comment.java index 5d47bd591..b60aa5077 100644 --- a/src/main/java/com/example/solidconnection/community/comment/domain/Comment.java +++ b/src/main/java/com/example/solidconnection/community/comment/domain/Comment.java @@ -13,13 +13,12 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Transient; +import java.util.ArrayList; +import java.util.List; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.ArrayList; -import java.util.List; - @Entity @Getter @NoArgsConstructor diff --git a/src/main/java/com/example/solidconnection/community/comment/dto/CommentCreateRequest.java b/src/main/java/com/example/solidconnection/community/comment/dto/CommentCreateRequest.java index dd25516eb..d055d2e2a 100644 --- a/src/main/java/com/example/solidconnection/community/comment/dto/CommentCreateRequest.java +++ b/src/main/java/com/example/solidconnection/community/comment/dto/CommentCreateRequest.java @@ -17,6 +17,7 @@ public record CommentCreateRequest( Long parentId ) { + public Comment toEntity(SiteUser siteUser, Post post, Comment parentComment) { Comment comment = new Comment( diff --git a/src/main/java/com/example/solidconnection/community/comment/dto/CommentDeleteResponse.java b/src/main/java/com/example/solidconnection/community/comment/dto/CommentDeleteResponse.java index 5283bb87f..7cfb0010e 100644 --- a/src/main/java/com/example/solidconnection/community/comment/dto/CommentDeleteResponse.java +++ b/src/main/java/com/example/solidconnection/community/comment/dto/CommentDeleteResponse.java @@ -3,4 +3,5 @@ public record CommentDeleteResponse( Long id ) { + } diff --git a/src/main/java/com/example/solidconnection/community/comment/dto/CommentUpdateRequest.java b/src/main/java/com/example/solidconnection/community/comment/dto/CommentUpdateRequest.java index 6e14dab45..a6983221f 100644 --- a/src/main/java/com/example/solidconnection/community/comment/dto/CommentUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/community/comment/dto/CommentUpdateRequest.java @@ -8,4 +8,5 @@ public record CommentUpdateRequest( @Size(min = 1, max = 255, message = "댓글 내용은 최소 1자 이상, 최대 255자 이하여야 합니다.") String content ) { + } diff --git a/src/main/java/com/example/solidconnection/community/comment/dto/PostFindCommentResponse.java b/src/main/java/com/example/solidconnection/community/comment/dto/PostFindCommentResponse.java index 0d9f5f295..16446f3ee 100644 --- a/src/main/java/com/example/solidconnection/community/comment/dto/PostFindCommentResponse.java +++ b/src/main/java/com/example/solidconnection/community/comment/dto/PostFindCommentResponse.java @@ -3,7 +3,6 @@ import com.example.solidconnection.community.comment.domain.Comment; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.PostFindSiteUserResponse; - import java.time.ZonedDateTime; public record PostFindCommentResponse( @@ -35,8 +34,7 @@ private static Long getParentCommentId(Comment comment) { return null; } - private static String getDisplayContent(Comment comment) - { + private static String getDisplayContent(Comment comment) { return comment.isDeleted() ? "" : comment.getContent(); } diff --git a/src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java b/src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java index a17565bee..c05cf9bd6 100644 --- a/src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java +++ b/src/main/java/com/example/solidconnection/community/comment/repository/CommentRepository.java @@ -1,36 +1,35 @@ package com.example.solidconnection.community.comment.repository; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_COMMENT_ID; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.comment.domain.Comment; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; - -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_COMMENT_ID; - public interface CommentRepository extends JpaRepository { @Query(value = """ - WITH RECURSIVE CommentTree AS ( - SELECT - id, parent_id, post_id, site_user_id, content, - created_at, updated_at, is_deleted, - 0 AS level, CAST(id AS CHAR(255)) AS path - FROM comment - WHERE post_id = :postId AND parent_id IS NULL - UNION ALL - SELECT - c.id, c.parent_id, c.post_id, c.site_user_id, c.content, - c.created_at, c.updated_at, c.is_deleted, - ct.level + 1, CONCAT(ct.path, '->', c.id) - FROM comment c - INNER JOIN CommentTree ct ON c.parent_id = ct.id - ) - SELECT * FROM CommentTree - ORDER BY path - """, nativeQuery = true) + WITH RECURSIVE CommentTree AS ( + SELECT + id, parent_id, post_id, site_user_id, content, + created_at, updated_at, is_deleted, + 0 AS level, CAST(id AS CHAR(255)) AS path + FROM comment + WHERE post_id = :postId AND parent_id IS NULL + UNION ALL + SELECT + c.id, c.parent_id, c.post_id, c.site_user_id, c.content, + c.created_at, c.updated_at, c.is_deleted, + ct.level + 1, CONCAT(ct.path, '->', c.id) + FROM comment c + INNER JOIN CommentTree ct ON c.parent_id = ct.id + ) + SELECT * FROM CommentTree + ORDER BY path + """, nativeQuery = true) List findCommentTreeByPostId(@Param("postId") Long postId); default Comment getById(Long id) { diff --git a/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java b/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java index 38a61b60d..7f31885ea 100644 --- a/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java +++ b/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java @@ -1,5 +1,9 @@ package com.example.solidconnection.community.comment.service; +import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPDATE_DEPRECATED_COMMENT; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_COMMENT_LEVEL; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ACCESS; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.comment.domain.Comment; import com.example.solidconnection.community.comment.dto.CommentCreateRequest; @@ -13,21 +17,15 @@ import com.example.solidconnection.community.post.repository.PostRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; - -import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPDATE_DEPRECATED_COMMENT; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_COMMENT_LEVEL; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ACCESS; -import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -73,8 +71,8 @@ private List filterCommentsByDeletionRules(List comments) { if (!allDeleted) { result.add(parent); result.addAll(children.stream() - .filter(child -> !child.isDeleted()) - .toList()); + .filter(child -> !child.isDeleted()) + .toList()); } } return result; diff --git a/src/main/java/com/example/solidconnection/community/post/controller/PostController.java b/src/main/java/com/example/solidconnection/community/post/controller/PostController.java index 23b34568e..c329425a8 100644 --- a/src/main/java/com/example/solidconnection/community/post/controller/PostController.java +++ b/src/main/java/com/example/solidconnection/community/post/controller/PostController.java @@ -14,6 +14,8 @@ import com.example.solidconnection.community.post.service.PostQueryService; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; +import java.util.Collections; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -27,9 +29,6 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import java.util.Collections; -import java.util.List; - @RestController @RequiredArgsConstructor @RequestMapping("/posts") diff --git a/src/main/java/com/example/solidconnection/community/post/domain/Post.java b/src/main/java/com/example/solidconnection/community/post/domain/Post.java index 7da358d43..190861131 100644 --- a/src/main/java/com/example/solidconnection/community/post/domain/Post.java +++ b/src/main/java/com/example/solidconnection/community/post/domain/Post.java @@ -12,14 +12,13 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.BatchSize; -import java.util.ArrayList; -import java.util.List; - @Entity @Getter @NoArgsConstructor diff --git a/src/main/java/com/example/solidconnection/community/post/dto/PostDeleteResponse.java b/src/main/java/com/example/solidconnection/community/post/dto/PostDeleteResponse.java index f98f5264f..e34241750 100644 --- a/src/main/java/com/example/solidconnection/community/post/dto/PostDeleteResponse.java +++ b/src/main/java/com/example/solidconnection/community/post/dto/PostDeleteResponse.java @@ -3,4 +3,5 @@ public record PostDeleteResponse( Long id ) { + } diff --git a/src/main/java/com/example/solidconnection/community/post/dto/PostDislikeResponse.java b/src/main/java/com/example/solidconnection/community/post/dto/PostDislikeResponse.java index 83ffc8305..3c77adebe 100644 --- a/src/main/java/com/example/solidconnection/community/post/dto/PostDislikeResponse.java +++ b/src/main/java/com/example/solidconnection/community/post/dto/PostDislikeResponse.java @@ -6,6 +6,7 @@ public record PostDislikeResponse( Long likeCount, Boolean isLiked ) { + public static PostDislikeResponse from(Post post) { return new PostDislikeResponse( post.getLikeCount(), diff --git a/src/main/java/com/example/solidconnection/community/post/dto/PostFindPostImageResponse.java b/src/main/java/com/example/solidconnection/community/post/dto/PostFindPostImageResponse.java index 648bdb72c..68bb6fd01 100644 --- a/src/main/java/com/example/solidconnection/community/post/dto/PostFindPostImageResponse.java +++ b/src/main/java/com/example/solidconnection/community/post/dto/PostFindPostImageResponse.java @@ -1,7 +1,6 @@ package com.example.solidconnection.community.post.dto; import com.example.solidconnection.community.post.domain.PostImage; - import java.util.List; import java.util.stream.Collectors; @@ -9,6 +8,7 @@ public record PostFindPostImageResponse( Long id, String url ) { + public static PostFindPostImageResponse from(PostImage postImage) { return new PostFindPostImageResponse( postImage.getId(), diff --git a/src/main/java/com/example/solidconnection/community/post/dto/PostFindResponse.java b/src/main/java/com/example/solidconnection/community/post/dto/PostFindResponse.java index 735defac1..e6bc6a0fa 100644 --- a/src/main/java/com/example/solidconnection/community/post/dto/PostFindResponse.java +++ b/src/main/java/com/example/solidconnection/community/post/dto/PostFindResponse.java @@ -4,7 +4,6 @@ import com.example.solidconnection.community.comment.dto.PostFindCommentResponse; import com.example.solidconnection.community.post.domain.Post; import com.example.solidconnection.siteuser.dto.PostFindSiteUserResponse; - import java.time.ZonedDateTime; import java.util.List; diff --git a/src/main/java/com/example/solidconnection/community/post/dto/PostLikeResponse.java b/src/main/java/com/example/solidconnection/community/post/dto/PostLikeResponse.java index 35b2840c0..b787dce7b 100644 --- a/src/main/java/com/example/solidconnection/community/post/dto/PostLikeResponse.java +++ b/src/main/java/com/example/solidconnection/community/post/dto/PostLikeResponse.java @@ -6,6 +6,7 @@ public record PostLikeResponse( Long likeCount, Boolean isLiked ) { + public static PostLikeResponse from(Post post) { return new PostLikeResponse( post.getLikeCount(), diff --git a/src/main/java/com/example/solidconnection/community/post/dto/PostListResponse.java b/src/main/java/com/example/solidconnection/community/post/dto/PostListResponse.java index 15f5ec7a5..89fb20aab 100644 --- a/src/main/java/com/example/solidconnection/community/post/dto/PostListResponse.java +++ b/src/main/java/com/example/solidconnection/community/post/dto/PostListResponse.java @@ -2,7 +2,6 @@ import com.example.solidconnection.community.post.domain.Post; import com.example.solidconnection.community.post.domain.PostImage; - import java.time.ZonedDateTime; import java.util.List; import java.util.stream.Collectors; diff --git a/src/main/java/com/example/solidconnection/community/post/dto/PostUpdateRequest.java b/src/main/java/com/example/solidconnection/community/post/dto/PostUpdateRequest.java index 339be3519..c82c98ce9 100644 --- a/src/main/java/com/example/solidconnection/community/post/dto/PostUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/community/post/dto/PostUpdateRequest.java @@ -16,4 +16,5 @@ public record PostUpdateRequest( @Size(min = 1, max = 1000, message = "댓글 내용은 최소 1자 이상, 최대 255자 이하여야 합니다.") String content ) { + } diff --git a/src/main/java/com/example/solidconnection/community/post/dto/PostUpdateResponse.java b/src/main/java/com/example/solidconnection/community/post/dto/PostUpdateResponse.java index 5c35f031d..2c09eefb3 100644 --- a/src/main/java/com/example/solidconnection/community/post/dto/PostUpdateResponse.java +++ b/src/main/java/com/example/solidconnection/community/post/dto/PostUpdateResponse.java @@ -5,6 +5,7 @@ public record PostUpdateResponse( Long id ) { + public static PostUpdateResponse from(Post post) { return new PostUpdateResponse( post.getId() diff --git a/src/main/java/com/example/solidconnection/community/post/repository/PostImageRepository.java b/src/main/java/com/example/solidconnection/community/post/repository/PostImageRepository.java index 096e47f20..81d6f0a32 100644 --- a/src/main/java/com/example/solidconnection/community/post/repository/PostImageRepository.java +++ b/src/main/java/com/example/solidconnection/community/post/repository/PostImageRepository.java @@ -4,4 +4,5 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface PostImageRepository extends JpaRepository { + } diff --git a/src/main/java/com/example/solidconnection/community/post/repository/PostLikeRepository.java b/src/main/java/com/example/solidconnection/community/post/repository/PostLikeRepository.java index a3b96e826..4fa3d3e72 100644 --- a/src/main/java/com/example/solidconnection/community/post/repository/PostLikeRepository.java +++ b/src/main/java/com/example/solidconnection/community/post/repository/PostLikeRepository.java @@ -1,13 +1,12 @@ package com.example.solidconnection.community.post.repository; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_LIKE; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.post.domain.Post; import com.example.solidconnection.community.post.domain.PostLike; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.Optional; - -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_LIKE; +import org.springframework.data.jpa.repository.JpaRepository; public interface PostLikeRepository extends JpaRepository { diff --git a/src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java b/src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java index 3a6d50d3d..de16d8ab1 100644 --- a/src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java +++ b/src/main/java/com/example/solidconnection/community/post/repository/PostRepository.java @@ -1,18 +1,17 @@ package com.example.solidconnection.community.post.repository; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ID; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.post.domain.Post; +import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; -import java.util.Optional; - -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ID; - public interface PostRepository extends JpaRepository { List findByBoardCode(String boardCode); @@ -22,23 +21,23 @@ public interface PostRepository extends JpaRepository { @Modifying(clearAutomatically = true, flushAutomatically = true) @Query(""" - UPDATE Post p SET p.likeCount = p.likeCount - 1 - WHERE p.id = :postId AND p.likeCount > 0 - """) + UPDATE Post p SET p.likeCount = p.likeCount - 1 + WHERE p.id = :postId AND p.likeCount > 0 + """) void decreaseLikeCount(@Param("postId") Long postId); @Modifying(clearAutomatically = true, flushAutomatically = true) @Query(""" - UPDATE Post p SET p.likeCount = p.likeCount + 1 - WHERE p.id = :postId - """) + UPDATE Post p SET p.likeCount = p.likeCount + 1 + WHERE p.id = :postId + """) void increaseLikeCount(@Param("postId") Long postId); @Modifying(clearAutomatically = true, flushAutomatically = true) @Query(""" - UPDATE Post p SET p.viewCount = p.viewCount + :count - WHERE p.id = :postId - """) + UPDATE Post p SET p.viewCount = p.viewCount + :count + WHERE p.id = :postId + """) void increaseViewCount(@Param("postId") Long postId, @Param("count") Long count); default Post getByIdUsingEntityGraph(Long id) { diff --git a/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java b/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java index a76543407..57721ee46 100644 --- a/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java +++ b/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java @@ -1,5 +1,10 @@ package com.example.solidconnection.community.post.service; +import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_DELETE_OR_UPDATE_QUESTION; +import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ACCESS; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_CATEGORY; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.board.domain.Board; import com.example.solidconnection.community.board.repository.BoardRepository; @@ -17,20 +22,14 @@ import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.util.RedisUtils; +import java.util.List; +import java.util.Objects; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.EnumUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import java.util.List; -import java.util.Objects; - -import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_DELETE_OR_UPDATE_QUESTION; -import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ACCESS; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_CATEGORY; - @Service @RequiredArgsConstructor public class PostCommandService { diff --git a/src/main/java/com/example/solidconnection/community/post/service/PostLikeService.java b/src/main/java/com/example/solidconnection/community/post/service/PostLikeService.java index 4ea99ad0f..1a7ba05ae 100644 --- a/src/main/java/com/example/solidconnection/community/post/service/PostLikeService.java +++ b/src/main/java/com/example/solidconnection/community/post/service/PostLikeService.java @@ -1,5 +1,7 @@ package com.example.solidconnection.community.post.service; +import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_POST_LIKE; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.post.domain.Post; import com.example.solidconnection.community.post.domain.PostLike; @@ -13,8 +15,6 @@ import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; -import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_POST_LIKE; - @Service @RequiredArgsConstructor public class PostLikeService { diff --git a/src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java b/src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java index 4ac06424b..a6f956fc0 100644 --- a/src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java +++ b/src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java @@ -1,5 +1,9 @@ package com.example.solidconnection.community.post.service; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_BOARD_CODE; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_CATEGORY; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.board.domain.Board; import com.example.solidconnection.community.board.domain.BoardCode; @@ -18,19 +22,14 @@ import com.example.solidconnection.siteuser.dto.PostFindSiteUserResponse; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.util.RedisUtils; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.EnumUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_BOARD_CODE; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_CATEGORY; -import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; - @Service @RequiredArgsConstructor public class PostQueryService { @@ -61,7 +60,7 @@ public PostFindResponse findPostById(SiteUser siteUser, Long postId) { Boolean isLiked = getIsLiked(post, siteUser); Board board = boardRepository.findBoardByCode(post.getBoardCode()) - .orElseThrow(()->new CustomException(INVALID_BOARD_CODE)); + .orElseThrow(() -> new CustomException(INVALID_BOARD_CODE)); SiteUser postAuthor = siteUserRepository.findById(post.getSiteUserId()) .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); PostFindBoardResponse boardPostFindResultDTO = PostFindBoardResponse.from(board); diff --git a/src/main/java/com/example/solidconnection/community/post/service/RedisService.java b/src/main/java/com/example/solidconnection/community/post/service/RedisService.java index 38d573d94..7b701fc2b 100644 --- a/src/main/java/com/example/solidconnection/community/post/service/RedisService.java +++ b/src/main/java/com/example/solidconnection/community/post/service/RedisService.java @@ -1,17 +1,16 @@ package com.example.solidconnection.community.post.service; +import static com.example.solidconnection.community.post.service.RedisConstants.VALIDATE_VIEW_COUNT_TTL; +import static com.example.solidconnection.community.post.service.RedisConstants.VIEW_COUNT_TTL; + +import java.util.Collections; +import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.script.RedisScript; import org.springframework.stereotype.Service; -import java.util.Collections; -import java.util.concurrent.TimeUnit; - -import static com.example.solidconnection.community.post.service.RedisConstants.VALIDATE_VIEW_COUNT_TTL; -import static com.example.solidconnection.community.post.service.RedisConstants.VIEW_COUNT_TTL; - @Service public class RedisService { // todo: 정말 필요한지 고민 필요 @@ -40,7 +39,7 @@ public Long getAndDelete(String key) { public boolean isPresent(String key) { return Boolean.TRUE.equals(redisTemplate.opsForValue() - .setIfAbsent(key, "1", Long.parseLong(VALIDATE_VIEW_COUNT_TTL.getValue()), TimeUnit.SECONDS)); + .setIfAbsent(key, "1", Long.parseLong(VALIDATE_VIEW_COUNT_TTL.getValue()), TimeUnit.SECONDS)); } public boolean isKeyExists(String key) { diff --git a/src/main/java/com/example/solidconnection/location/country/domain/Country.java b/src/main/java/com/example/solidconnection/location/country/domain/Country.java index 90ce8fe4f..f9a487eda 100644 --- a/src/main/java/com/example/solidconnection/location/country/domain/Country.java +++ b/src/main/java/com/example/solidconnection/location/country/domain/Country.java @@ -21,7 +21,7 @@ public class Country { @Column(nullable = false, length = 100) private String koreanName; - @Column(name="region_code") + @Column(name = "region_code") private String regionCode; public Country(String code, String koreanName, String regionCode) { diff --git a/src/main/java/com/example/solidconnection/location/country/domain/InterestedCountry.java b/src/main/java/com/example/solidconnection/location/country/domain/InterestedCountry.java index bfad77e10..b1b01c798 100644 --- a/src/main/java/com/example/solidconnection/location/country/domain/InterestedCountry.java +++ b/src/main/java/com/example/solidconnection/location/country/domain/InterestedCountry.java @@ -27,10 +27,10 @@ public class InterestedCountry { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name="site_user_id") + @Column(name = "site_user_id") private long siteUserId; - @Column(name="country_code") + @Column(name = "country_code") private String countryCode; public InterestedCountry(SiteUser siteUser, Country country) { diff --git a/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java b/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java index 4ac91c085..70477f1f4 100644 --- a/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java +++ b/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java @@ -1,12 +1,11 @@ package com.example.solidconnection.location.country.repository; import com.example.solidconnection.location.country.domain.Country; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; - public interface CountryRepository extends JpaRepository { @Query("SELECT c FROM Country c WHERE c.koreanName IN :names") diff --git a/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java b/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java index 9e80a84d2..0d555b7bb 100644 --- a/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java +++ b/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java @@ -1,9 +1,8 @@ package com.example.solidconnection.location.country.repository; import com.example.solidconnection.location.country.domain.InterestedCountry; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; public interface InterestedCountryRepository extends JpaRepository { diff --git a/src/main/java/com/example/solidconnection/location/region/domain/InterestedRegion.java b/src/main/java/com/example/solidconnection/location/region/domain/InterestedRegion.java index 29f6bcaa8..e08287fe9 100644 --- a/src/main/java/com/example/solidconnection/location/region/domain/InterestedRegion.java +++ b/src/main/java/com/example/solidconnection/location/region/domain/InterestedRegion.java @@ -27,10 +27,10 @@ public class InterestedRegion { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name="site_user_id") + @Column(name = "site_user_id") private long siteUserId; - @Column(name="region_code") + @Column(name = "region_code") private String regionCode; public InterestedRegion(SiteUser siteUser, Region region) { diff --git a/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java b/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java index 8e06e8411..335218c08 100644 --- a/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java +++ b/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java @@ -1,10 +1,8 @@ package com.example.solidconnection.location.region.repository; import com.example.solidconnection.location.region.domain.InterestedRegion; -import com.example.solidconnection.siteuser.domain.SiteUser; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; public interface InterestedRegionRepository extends JpaRepository { diff --git a/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java b/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java index 2cdb8c827..eca02bc93 100644 --- a/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java +++ b/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java @@ -1,12 +1,11 @@ package com.example.solidconnection.location.region.repository; import com.example.solidconnection.location.region.domain.Region; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; - public interface RegionRepository extends JpaRepository { @Query("SELECT r FROM Region r WHERE r.koreanName IN :names") diff --git a/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java b/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java index ea60b180c..9900e4496 100644 --- a/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java +++ b/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java @@ -1,5 +1,7 @@ package com.example.solidconnection.mentor.controller; +import static org.springframework.data.domain.Sort.Direction.DESC; + import com.example.solidconnection.common.dto.SliceResponse; import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.mentor.dto.MentorDetailResponse; @@ -19,8 +21,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import static org.springframework.data.domain.Sort.Direction.DESC; - @RequiredArgsConstructor @RequestMapping("/mentors") @RestController diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java index 253bf666d..1a5012015 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java @@ -8,15 +8,14 @@ import jakarta.persistence.Id; import jakarta.persistence.OneToMany; import jakarta.persistence.OrderBy; +import java.util.ArrayList; +import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.BatchSize; -import java.util.ArrayList; -import java.util.List; - @Entity @Getter @AllArgsConstructor diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java b/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java index dd2353df6..9f2409add 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java @@ -1,5 +1,8 @@ package com.example.solidconnection.mentor.domain; +import static java.time.ZoneOffset.UTC; +import static java.time.temporal.ChronoUnit.MICROS; + import com.example.solidconnection.common.VerifyStatus; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -10,6 +13,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.PrePersist; +import java.time.ZonedDateTime; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; @@ -17,11 +21,6 @@ import org.hibernate.annotations.DynamicInsert; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import java.time.ZonedDateTime; - -import static java.time.ZoneOffset.UTC; -import static java.time.temporal.ChronoUnit.MICROS; - @Entity @Getter @EntityListeners(AuditingEntityListener.class) diff --git a/src/main/java/com/example/solidconnection/mentor/dto/ChannelRequest.java b/src/main/java/com/example/solidconnection/mentor/dto/ChannelRequest.java index 9172262ae..11c401be8 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/ChannelRequest.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/ChannelRequest.java @@ -13,4 +13,5 @@ public record ChannelRequest( @URL String url ) { + } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java index cf0fbc98a..63255799e 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java @@ -3,7 +3,6 @@ import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.siteuser.domain.ExchangeStatus; import com.example.solidconnection.siteuser.domain.SiteUser; - import java.util.List; public record MentorDetailResponse( diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java index 91077051f..46012f068 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java @@ -3,7 +3,6 @@ import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.siteuser.domain.ExchangeStatus; import com.example.solidconnection.siteuser.domain.SiteUser; - import java.util.List; public record MentorMyPageResponse( diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageUpdateRequest.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageUpdateRequest.java index 5a6790aeb..646fc6e78 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageUpdateRequest.java @@ -2,7 +2,6 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; - import java.util.List; public record MentorMyPageUpdateRequest( @@ -15,4 +14,5 @@ public record MentorMyPageUpdateRequest( @Valid List channels ) { + } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java index bba3010f4..70695b462 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java @@ -3,7 +3,6 @@ import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.siteuser.domain.ExchangeStatus; import com.example.solidconnection.siteuser.domain.SiteUser; - import java.util.List; public record MentorPreviewResponse( diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java index cb49ba602..d322e22f9 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java @@ -6,4 +6,5 @@ public record MentorPreviewsResponse( List content, int nextPageNumber ) { + } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringApplyRequest.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringApplyRequest.java index 27d03afae..8ff6b6611 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentoringApplyRequest.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringApplyRequest.java @@ -6,4 +6,5 @@ public record MentoringApplyRequest( @NotNull(message = "멘토 id를 입력해주세요.") Long mentorId ) { + } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmRequest.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmRequest.java index 8436f263e..ab3f1a8a8 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmRequest.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmRequest.java @@ -9,4 +9,5 @@ public record MentoringConfirmRequest( String rejectedReason ) { + } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmResponse.java index 79ad48bf4..b4ed1e40f 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmResponse.java @@ -5,6 +5,7 @@ public record MentoringConfirmResponse( long mentoringId ) { + public static MentoringConfirmResponse from(Mentoring mentoring) { return new MentoringConfirmResponse(mentoring.getId()); } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringListResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringListResponse.java index d943db618..cca80e06d 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentoringListResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringListResponse.java @@ -5,6 +5,7 @@ public record MentoringListResponse( List requests ) { + public static MentoringListResponse from(List requests) { return new MentoringListResponse(requests); } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringResponse.java index 595f28b7b..6b96295ad 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentoringResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringResponse.java @@ -2,7 +2,6 @@ import com.example.solidconnection.mentor.domain.Mentoring; import com.example.solidconnection.siteuser.domain.SiteUser; - import java.time.ZonedDateTime; public record MentoringResponse( @@ -12,6 +11,7 @@ public record MentoringResponse( boolean isChecked, ZonedDateTime createdAt ) { + public static MentoringResponse from(Mentoring mentoring, SiteUser mentee) { return new MentoringResponse( mentoring.getId(), diff --git a/src/main/java/com/example/solidconnection/mentor/repository/ChannelRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/ChannelRepository.java index 571e4fb21..a8c607389 100644 --- a/src/main/java/com/example/solidconnection/mentor/repository/ChannelRepository.java +++ b/src/main/java/com/example/solidconnection/mentor/repository/ChannelRepository.java @@ -4,4 +4,5 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface ChannelRepository extends JpaRepository { + } diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepository.java index 026c7aef5..3e90f944f 100644 --- a/src/main/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepository.java +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepository.java @@ -1,20 +1,19 @@ package com.example.solidconnection.mentor.repository; +import static com.example.solidconnection.common.exception.ErrorCode.DATA_INTEGRITY_VIOLATION; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.mentor.domain.Mentoring; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; - -import static com.example.solidconnection.common.exception.ErrorCode.DATA_INTEGRITY_VIOLATION; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; @Repository @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java index aa8328ba7..b2332afad 100644 --- a/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java @@ -1,12 +1,11 @@ package com.example.solidconnection.mentor.repository; import com.example.solidconnection.mentor.domain.Mentor; +import java.util.Optional; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Optional; - public interface MentorRepository extends JpaRepository { boolean existsBySiteUserId(long siteUserId); diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java index 76a3758f2..8f7cfade3 100644 --- a/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java @@ -1,12 +1,11 @@ package com.example.solidconnection.mentor.repository; import com.example.solidconnection.mentor.domain.Mentoring; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; - public interface MentoringRepository extends JpaRepository { int countByMentorIdAndCheckedAtIsNull(long mentorId); @@ -16,8 +15,8 @@ public interface MentoringRepository extends JpaRepository { List findAllByMentorId(long mentorId); @Query(""" - SELECT m FROM Mentoring m - WHERE m.mentorId IN :mentorIds AND m.menteeId = :menteeId - """) + SELECT m FROM Mentoring m + WHERE m.mentorId IN :mentorIds AND m.menteeId = :menteeId + """) List findAllByMentorIdInAndMenteeId(@Param("mentorIds") List mentorIds, @Param("menteeId") long menteeId); } diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorMyPageService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorMyPageService.java index 6a2d47ded..e4a920397 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentorMyPageService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorMyPageService.java @@ -1,5 +1,8 @@ package com.example.solidconnection.mentor.service; +import static com.example.solidconnection.common.exception.ErrorCode.CHANNEL_REGISTRATION_LIMIT_EXCEEDED; +import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.mentor.domain.Channel; import com.example.solidconnection.mentor.domain.Mentor; @@ -8,16 +11,12 @@ import com.example.solidconnection.mentor.dto.MentorMyPageUpdateRequest; import com.example.solidconnection.mentor.repository.MentorRepository; import com.example.solidconnection.siteuser.domain.SiteUser; +import java.util.ArrayList; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; - -import static com.example.solidconnection.common.exception.ErrorCode.CHANNEL_REGISTRATION_LIMIT_EXCEEDED; -import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; - @RequiredArgsConstructor @Service public class MentorMyPageService { diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java index a7c46e285..e6d1ffd67 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java @@ -1,5 +1,7 @@ package com.example.solidconnection.mentor.service; +import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; + import com.example.solidconnection.common.dto.SliceResponse; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.mentor.domain.Mentor; @@ -10,18 +12,15 @@ import com.example.solidconnection.mentor.repository.MentoringRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; - @RequiredArgsConstructor @Service public class MentorQueryService { diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java b/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java index f7a9495ad..165011f55 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java @@ -1,5 +1,11 @@ package com.example.solidconnection.mentor.service; +import static com.example.solidconnection.common.exception.ErrorCode.MENTORING_ALREADY_CONFIRMED; +import static com.example.solidconnection.common.exception.ErrorCode.MENTORING_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.REJECTED_REASON_REQUIRED; +import static com.example.solidconnection.common.exception.ErrorCode.UNAUTHORIZED_MENTORING; + import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.mentor.domain.Mentor; @@ -15,12 +21,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import static com.example.solidconnection.common.exception.ErrorCode.MENTORING_ALREADY_CONFIRMED; -import static com.example.solidconnection.common.exception.ErrorCode.MENTORING_NOT_FOUND; -import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; -import static com.example.solidconnection.common.exception.ErrorCode.REJECTED_REASON_REQUIRED; -import static com.example.solidconnection.common.exception.ErrorCode.UNAUTHORIZED_MENTORING; - @Service @RequiredArgsConstructor public class MentoringCommandService { diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java index cf40b73a5..2ffa36602 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java @@ -1,5 +1,7 @@ package com.example.solidconnection.mentor.service; +import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.mentor.domain.Mentor; @@ -11,14 +13,11 @@ import com.example.solidconnection.mentor.repository.MentoringRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - -import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; - @Service @RequiredArgsConstructor public class MentoringQueryService { diff --git a/src/main/java/com/example/solidconnection/news/config/NewsProperties.java b/src/main/java/com/example/solidconnection/news/config/NewsProperties.java index ecd7c96b8..3289ab7de 100644 --- a/src/main/java/com/example/solidconnection/news/config/NewsProperties.java +++ b/src/main/java/com/example/solidconnection/news/config/NewsProperties.java @@ -6,4 +6,5 @@ public record NewsProperties( String defaultThumbnailUrl ) { + } diff --git a/src/main/java/com/example/solidconnection/news/dto/NewsCommandResponse.java b/src/main/java/com/example/solidconnection/news/dto/NewsCommandResponse.java index fc0b8daf3..bbc3059e1 100644 --- a/src/main/java/com/example/solidconnection/news/dto/NewsCommandResponse.java +++ b/src/main/java/com/example/solidconnection/news/dto/NewsCommandResponse.java @@ -5,6 +5,7 @@ public record NewsCommandResponse( long id ) { + public static NewsCommandResponse from(News news) { return new NewsCommandResponse( news.getId() diff --git a/src/main/java/com/example/solidconnection/news/dto/NewsCreateRequest.java b/src/main/java/com/example/solidconnection/news/dto/NewsCreateRequest.java index 660611537..c1359ae9d 100644 --- a/src/main/java/com/example/solidconnection/news/dto/NewsCreateRequest.java +++ b/src/main/java/com/example/solidconnection/news/dto/NewsCreateRequest.java @@ -19,6 +19,7 @@ public record NewsCreateRequest( @URL(message = "올바른 URL 형식이 아닙니다.") String url ) { + public News toEntity(String thumbnailUrl, long siteUserId) { return new News( title, diff --git a/src/main/java/com/example/solidconnection/news/dto/NewsListResponse.java b/src/main/java/com/example/solidconnection/news/dto/NewsListResponse.java index b501b3810..29d3a1544 100644 --- a/src/main/java/com/example/solidconnection/news/dto/NewsListResponse.java +++ b/src/main/java/com/example/solidconnection/news/dto/NewsListResponse.java @@ -5,6 +5,7 @@ public record NewsListResponse( List newsResponseList ) { + public static NewsListResponse from(List newsResponseList) { return new NewsListResponse(newsResponseList); } diff --git a/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java b/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java index b39daffce..77d8cd3a3 100644 --- a/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java +++ b/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java @@ -1,7 +1,6 @@ package com.example.solidconnection.news.dto; import com.example.solidconnection.news.domain.News; - import java.time.ZonedDateTime; public record NewsResponse( @@ -12,6 +11,7 @@ public record NewsResponse( String url, ZonedDateTime updatedAt ) { + public static NewsResponse from(News news) { return new NewsResponse( news.getId(), diff --git a/src/main/java/com/example/solidconnection/news/dto/NewsUpdateRequest.java b/src/main/java/com/example/solidconnection/news/dto/NewsUpdateRequest.java index 9d09001bd..df396d92b 100644 --- a/src/main/java/com/example/solidconnection/news/dto/NewsUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/news/dto/NewsUpdateRequest.java @@ -20,4 +20,5 @@ public record NewsUpdateRequest( Boolean resetToDefaultImage ) { + } diff --git a/src/main/java/com/example/solidconnection/news/repository/LikedNewsRepository.java b/src/main/java/com/example/solidconnection/news/repository/LikedNewsRepository.java index 6c9a51585..26e3d8e3c 100644 --- a/src/main/java/com/example/solidconnection/news/repository/LikedNewsRepository.java +++ b/src/main/java/com/example/solidconnection/news/repository/LikedNewsRepository.java @@ -1,9 +1,8 @@ package com.example.solidconnection.news.repository; import com.example.solidconnection.news.domain.LikedNews; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; public interface LikedNewsRepository extends JpaRepository { diff --git a/src/main/java/com/example/solidconnection/news/repository/NewsRepository.java b/src/main/java/com/example/solidconnection/news/repository/NewsRepository.java index 3171bfcf8..4ec1798df 100644 --- a/src/main/java/com/example/solidconnection/news/repository/NewsRepository.java +++ b/src/main/java/com/example/solidconnection/news/repository/NewsRepository.java @@ -1,9 +1,8 @@ package com.example.solidconnection.news.repository; import com.example.solidconnection.news.domain.News; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; public interface NewsRepository extends JpaRepository { diff --git a/src/main/java/com/example/solidconnection/news/service/NewsCommandService.java b/src/main/java/com/example/solidconnection/news/service/NewsCommandService.java index a9d8c74a4..2875742d5 100644 --- a/src/main/java/com/example/solidconnection/news/service/NewsCommandService.java +++ b/src/main/java/com/example/solidconnection/news/service/NewsCommandService.java @@ -1,5 +1,8 @@ package com.example.solidconnection.news.service; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_NEWS_ACCESS; +import static com.example.solidconnection.common.exception.ErrorCode.NEWS_NOT_FOUND; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.news.config.NewsProperties; import com.example.solidconnection.news.domain.News; @@ -17,9 +20,6 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_NEWS_ACCESS; -import static com.example.solidconnection.common.exception.ErrorCode.NEWS_NOT_FOUND; - @Service @RequiredArgsConstructor public class NewsCommandService { @@ -29,7 +29,7 @@ public class NewsCommandService { private final NewsRepository newsRepository; @Transactional - public NewsCommandResponse createNews(long siteUserId,NewsCreateRequest newsCreateRequest, MultipartFile imageFile) { + public NewsCommandResponse createNews(long siteUserId, NewsCreateRequest newsCreateRequest, MultipartFile imageFile) { String thumbnailUrl = getImageUrl(imageFile); News news = newsCreateRequest.toEntity(thumbnailUrl, siteUserId); News savedNews = newsRepository.save(news); @@ -69,8 +69,7 @@ private void updateThumbnail(News news, MultipartFile imageFile, Boolean resetTo if (Boolean.TRUE.equals(resetToDefaultImage)) { deleteCustomImage(news.getThumbnailUrl()); news.updateThumbnailUrl(newsProperties.defaultThumbnailUrl()); - } - else if (imageFile != null && !imageFile.isEmpty()) { + } else if (imageFile != null && !imageFile.isEmpty()) { UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(imageFile, ImgType.NEWS); deleteCustomImage(news.getThumbnailUrl()); news.updateThumbnailUrl(uploadedFile.fileUrl()); diff --git a/src/main/java/com/example/solidconnection/news/service/NewsLikeService.java b/src/main/java/com/example/solidconnection/news/service/NewsLikeService.java index 0ca88992f..e0e2e5114 100644 --- a/src/main/java/com/example/solidconnection/news/service/NewsLikeService.java +++ b/src/main/java/com/example/solidconnection/news/service/NewsLikeService.java @@ -1,5 +1,9 @@ package com.example.solidconnection.news.service; +import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_NEWS; +import static com.example.solidconnection.common.exception.ErrorCode.NEWS_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_NEWS; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.news.domain.LikedNews; import com.example.solidconnection.news.dto.LikedNewsResponse; @@ -9,10 +13,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_NEWS; -import static com.example.solidconnection.common.exception.ErrorCode.NEWS_NOT_FOUND; -import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_NEWS; - @RequiredArgsConstructor @Service public class NewsLikeService { diff --git a/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java b/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java index 3b04e3b86..cda55c1dd 100644 --- a/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java +++ b/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java @@ -1,15 +1,14 @@ package com.example.solidconnection.news.service; import com.example.solidconnection.news.domain.News; -import com.example.solidconnection.news.dto.NewsResponse; import com.example.solidconnection.news.dto.NewsListResponse; +import com.example.solidconnection.news.dto.NewsResponse; import com.example.solidconnection.news.repository.NewsRepository; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - @Service @RequiredArgsConstructor public class NewsQueryService { diff --git a/src/main/java/com/example/solidconnection/s3/dto/UploadedFileUrlResponse.java b/src/main/java/com/example/solidconnection/s3/dto/UploadedFileUrlResponse.java index ca6a08b46..0a4722807 100644 --- a/src/main/java/com/example/solidconnection/s3/dto/UploadedFileUrlResponse.java +++ b/src/main/java/com/example/solidconnection/s3/dto/UploadedFileUrlResponse.java @@ -2,4 +2,5 @@ public record UploadedFileUrlResponse( String fileUrl) { + } diff --git a/src/main/java/com/example/solidconnection/s3/dto/urlPrefixResponse.java b/src/main/java/com/example/solidconnection/s3/dto/urlPrefixResponse.java index f36838060..ab4d2f68b 100644 --- a/src/main/java/com/example/solidconnection/s3/dto/urlPrefixResponse.java +++ b/src/main/java/com/example/solidconnection/s3/dto/urlPrefixResponse.java @@ -6,4 +6,5 @@ public record urlPrefixResponse( String cloudFrontDefault, String cloudFrontUploaded ) { + } diff --git a/src/main/java/com/example/solidconnection/s3/service/FileUploadService.java b/src/main/java/com/example/solidconnection/s3/service/FileUploadService.java index 61bc6f89c..51ef4caa9 100644 --- a/src/main/java/com/example/solidconnection/s3/service/FileUploadService.java +++ b/src/main/java/com/example/solidconnection/s3/service/FileUploadService.java @@ -1,5 +1,8 @@ package com.example.solidconnection.s3.service; +import static com.example.solidconnection.common.exception.ErrorCode.S3_CLIENT_EXCEPTION; +import static com.example.solidconnection.common.exception.ErrorCode.S3_SERVICE_EXCEPTION; + import com.amazonaws.AmazonServiceException; import com.amazonaws.SdkClientException; import com.amazonaws.services.s3.AmazonS3Client; @@ -7,17 +10,13 @@ import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.example.solidconnection.common.exception.CustomException; +import java.io.IOException; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; - -import static com.example.solidconnection.common.exception.ErrorCode.S3_CLIENT_EXCEPTION; -import static com.example.solidconnection.common.exception.ErrorCode.S3_SERVICE_EXCEPTION; - @Component @EnableAsync @Slf4j @@ -39,7 +38,7 @@ public void uploadFile(String bucket, String fileName, MultipartFile multipartFi try { amazonS3.putObject(new PutObjectRequest(bucket, fileName, multipartFile.getInputStream(), metadata) - .withCannedAcl(CannedAccessControlList.PublicRead)); + .withCannedAcl(CannedAccessControlList.PublicRead)); log.info("이미지 업로드 정상적 완료 thread: {}", Thread.currentThread().getName()); } catch (AmazonServiceException e) { log.error("이미지 업로드 중 s3 서비스 예외 발생 : {}", e.getMessage()); diff --git a/src/main/java/com/example/solidconnection/s3/service/S3Service.java b/src/main/java/com/example/solidconnection/s3/service/S3Service.java index 1b7daf78f..1aa0ebbf1 100644 --- a/src/main/java/com/example/solidconnection/s3/service/S3Service.java +++ b/src/main/java/com/example/solidconnection/s3/service/S3Service.java @@ -1,5 +1,11 @@ package com.example.solidconnection.s3.service; +import static com.example.solidconnection.common.exception.ErrorCode.FILE_NOT_EXIST; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_FILE_EXTENSIONS; +import static com.example.solidconnection.common.exception.ErrorCode.NOT_ALLOWED_FILE_EXTENSIONS; +import static com.example.solidconnection.common.exception.ErrorCode.S3_CLIENT_EXCEPTION; +import static com.example.solidconnection.common.exception.ErrorCode.S3_SERVICE_EXCEPTION; + import com.amazonaws.AmazonServiceException; import com.amazonaws.SdkClientException; import com.amazonaws.services.s3.AmazonS3Client; @@ -9,6 +15,11 @@ import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,18 +28,6 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.UUID; - -import static com.example.solidconnection.common.exception.ErrorCode.FILE_NOT_EXIST; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_FILE_EXTENSIONS; -import static com.example.solidconnection.common.exception.ErrorCode.NOT_ALLOWED_FILE_EXTENSIONS; -import static com.example.solidconnection.common.exception.ErrorCode.S3_CLIENT_EXCEPTION; -import static com.example.solidconnection.common.exception.ErrorCode.S3_SERVICE_EXCEPTION; - @Service @RequiredArgsConstructor public class S3Service { diff --git a/src/main/java/com/example/solidconnection/scheduler/UpdateViewCountScheduler.java b/src/main/java/com/example/solidconnection/scheduler/UpdateViewCountScheduler.java index 97a546c05..746b50927 100644 --- a/src/main/java/com/example/solidconnection/scheduler/UpdateViewCountScheduler.java +++ b/src/main/java/com/example/solidconnection/scheduler/UpdateViewCountScheduler.java @@ -1,7 +1,10 @@ package com.example.solidconnection.scheduler; +import static com.example.solidconnection.community.post.service.RedisConstants.VIEW_COUNT_KEY_PATTERN; + import com.example.solidconnection.community.post.service.UpdateViewCountService; import com.example.solidconnection.util.RedisUtils; +import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; @@ -11,10 +14,6 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; -import java.util.List; - -import static com.example.solidconnection.community.post.service.RedisConstants.VIEW_COUNT_KEY_PATTERN; - @RequiredArgsConstructor @Component @EnableScheduling diff --git a/src/main/java/com/example/solidconnection/scheduler/UserRemovalScheduler.java b/src/main/java/com/example/solidconnection/scheduler/UserRemovalScheduler.java index 0af509833..da39cf2e9 100644 --- a/src/main/java/com/example/solidconnection/scheduler/UserRemovalScheduler.java +++ b/src/main/java/com/example/solidconnection/scheduler/UserRemovalScheduler.java @@ -2,13 +2,12 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import java.time.LocalDate; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; -import java.time.LocalDate; -import java.util.List; - @RequiredArgsConstructor @Component public class UserRemovalScheduler { diff --git a/src/main/java/com/example/solidconnection/score/domain/GpaScore.java b/src/main/java/com/example/solidconnection/score/domain/GpaScore.java index 8c2b42e02..284a815af 100644 --- a/src/main/java/com/example/solidconnection/score/domain/GpaScore.java +++ b/src/main/java/com/example/solidconnection/score/domain/GpaScore.java @@ -1,8 +1,8 @@ package com.example.solidconnection.score.domain; import com.example.solidconnection.application.domain.Gpa; -import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.BaseEntity; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.persistence.Column; import jakarta.persistence.Embedded; diff --git a/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java b/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java index 2bb87e707..04d95487b 100644 --- a/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java +++ b/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java @@ -1,8 +1,8 @@ package com.example.solidconnection.score.domain; import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.BaseEntity; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.persistence.Column; import jakarta.persistence.Embedded; diff --git a/src/main/java/com/example/solidconnection/score/dto/GpaResponse.java b/src/main/java/com/example/solidconnection/score/dto/GpaResponse.java index fc05667aa..74a471049 100644 --- a/src/main/java/com/example/solidconnection/score/dto/GpaResponse.java +++ b/src/main/java/com/example/solidconnection/score/dto/GpaResponse.java @@ -7,6 +7,7 @@ public record GpaResponse( double gpaCriteria, String gpaReportUrl ) { + public static GpaResponse from(Gpa gpa) { return new GpaResponse( gpa.getGpa(), diff --git a/src/main/java/com/example/solidconnection/score/dto/GpaScoreRequest.java b/src/main/java/com/example/solidconnection/score/dto/GpaScoreRequest.java index a95cced6c..cdbf87bd8 100644 --- a/src/main/java/com/example/solidconnection/score/dto/GpaScoreRequest.java +++ b/src/main/java/com/example/solidconnection/score/dto/GpaScoreRequest.java @@ -10,4 +10,5 @@ public record GpaScoreRequest( @NotNull(message = "학점 기준을 입력해주세요.") Double gpaCriteria ) { + } diff --git a/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusResponse.java b/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusResponse.java index b720e08f3..4d554269d 100644 --- a/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusResponse.java +++ b/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusResponse.java @@ -9,6 +9,7 @@ public record GpaScoreStatusResponse( VerifyStatus verifyStatus, String rejectedReason ) { + public static GpaScoreStatusResponse from(GpaScore gpaScore) { return new GpaScoreStatusResponse( gpaScore.getId(), diff --git a/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusesResponse.java b/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusesResponse.java index 24a12af18..4d31d4252 100644 --- a/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusesResponse.java +++ b/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusesResponse.java @@ -5,6 +5,7 @@ public record GpaScoreStatusesResponse( List gpaScoreStatusResponseList ) { + public static GpaScoreStatusesResponse from(List gpaScoreStatusResponseList) { return new GpaScoreStatusesResponse(gpaScoreStatusResponseList); } diff --git a/src/main/java/com/example/solidconnection/score/dto/LanguageTestResponse.java b/src/main/java/com/example/solidconnection/score/dto/LanguageTestResponse.java index 2368e18c7..d17f1632f 100644 --- a/src/main/java/com/example/solidconnection/score/dto/LanguageTestResponse.java +++ b/src/main/java/com/example/solidconnection/score/dto/LanguageTestResponse.java @@ -8,6 +8,7 @@ public record LanguageTestResponse( String languageTestScore, String languageTestReportUrl ) { + public static LanguageTestResponse from(LanguageTest languageTest) { return new LanguageTestResponse( languageTest.getLanguageTestType(), diff --git a/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreRequest.java b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreRequest.java index 6173e8f00..13bf31384 100644 --- a/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreRequest.java +++ b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreRequest.java @@ -12,4 +12,5 @@ public record LanguageTestScoreRequest( @NotBlank(message = "어학 점수를 입력해주세요.") String languageTestScore ) { + } diff --git a/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusResponse.java b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusResponse.java index 825ac63db..afd604906 100644 --- a/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusResponse.java +++ b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusResponse.java @@ -9,6 +9,7 @@ public record LanguageTestScoreStatusResponse( VerifyStatus verifyStatus, String rejectedReason ) { + public static LanguageTestScoreStatusResponse from(LanguageTestScore languageTestScore) { return new LanguageTestScoreStatusResponse( languageTestScore.getId(), diff --git a/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusesResponse.java b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusesResponse.java index 027794853..811928011 100644 --- a/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusesResponse.java +++ b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusesResponse.java @@ -5,6 +5,7 @@ public record LanguageTestScoreStatusesResponse( List languageTestScoreStatusResponseList ) { + public static LanguageTestScoreStatusesResponse from(List languageTestScoreStatusResponseList) { return new LanguageTestScoreStatusesResponse(languageTestScoreStatusResponseList); } diff --git a/src/main/java/com/example/solidconnection/score/repository/GpaScoreRepository.java b/src/main/java/com/example/solidconnection/score/repository/GpaScoreRepository.java index a37fbfd1c..207e36234 100644 --- a/src/main/java/com/example/solidconnection/score/repository/GpaScoreRepository.java +++ b/src/main/java/com/example/solidconnection/score/repository/GpaScoreRepository.java @@ -2,10 +2,9 @@ import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.repository.custom.GpaScoreFilterRepository; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.List; import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; public interface GpaScoreRepository extends JpaRepository, GpaScoreFilterRepository { diff --git a/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java b/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java index 2f4268c06..40fe50106 100644 --- a/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java +++ b/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java @@ -3,10 +3,9 @@ import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.score.repository.custom.LanguageTestScoreFilterRepository; import com.example.solidconnection.university.domain.LanguageTestType; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.List; import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; public interface LanguageTestScoreRepository extends JpaRepository, LanguageTestScoreFilterRepository { diff --git a/src/main/java/com/example/solidconnection/score/repository/custom/GpaScoreFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/score/repository/custom/GpaScoreFilterRepositoryImpl.java index dad2fa25e..839b4340e 100644 --- a/src/main/java/com/example/solidconnection/score/repository/custom/GpaScoreFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/score/repository/custom/GpaScoreFilterRepositoryImpl.java @@ -1,5 +1,9 @@ package com.example.solidconnection.score.repository.custom; +import static com.example.solidconnection.score.domain.QGpaScore.gpaScore; +import static com.example.solidconnection.siteuser.domain.QSiteUser.siteUser; +import static org.springframework.util.StringUtils.hasText; + import com.example.solidconnection.admin.dto.GpaResponse; import com.example.solidconnection.admin.dto.GpaScoreSearchResponse; import com.example.solidconnection.admin.dto.GpaScoreStatusResponse; @@ -11,21 +15,16 @@ import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.List; - -import static com.example.solidconnection.score.domain.QGpaScore.gpaScore; -import static com.example.solidconnection.siteuser.domain.QSiteUser.siteUser; -import static org.springframework.util.StringUtils.hasText; - @Repository public class GpaScoreFilterRepositoryImpl implements GpaScoreFilterRepository { diff --git a/src/main/java/com/example/solidconnection/score/repository/custom/LanguageTestScoreFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/score/repository/custom/LanguageTestScoreFilterRepositoryImpl.java index 8d958d12a..d8f42f6a5 100644 --- a/src/main/java/com/example/solidconnection/score/repository/custom/LanguageTestScoreFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/score/repository/custom/LanguageTestScoreFilterRepositoryImpl.java @@ -1,5 +1,9 @@ package com.example.solidconnection.score.repository.custom; +import static com.example.solidconnection.score.domain.QLanguageTestScore.languageTestScore; +import static com.example.solidconnection.siteuser.domain.QSiteUser.siteUser; +import static io.jsonwebtoken.lang.Strings.hasText; + import com.example.solidconnection.admin.dto.LanguageTestResponse; import com.example.solidconnection.admin.dto.LanguageTestScoreSearchResponse; import com.example.solidconnection.admin.dto.LanguageTestScoreStatusResponse; @@ -11,21 +15,16 @@ import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.List; - -import static com.example.solidconnection.score.domain.QLanguageTestScore.languageTestScore; -import static com.example.solidconnection.siteuser.domain.QSiteUser.siteUser; -import static io.jsonwebtoken.lang.Strings.hasText; - @Repository public class LanguageTestScoreFilterRepositoryImpl implements LanguageTestScoreFilterRepository { diff --git a/src/main/java/com/example/solidconnection/score/service/ScoreService.java b/src/main/java/com/example/solidconnection/score/service/ScoreService.java index 8b56461e9..daa054a83 100644 --- a/src/main/java/com/example/solidconnection/score/service/ScoreService.java +++ b/src/main/java/com/example/solidconnection/score/service/ScoreService.java @@ -16,14 +16,13 @@ import com.example.solidconnection.score.repository.GpaScoreRepository; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; +import java.util.List; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import java.util.List; -import java.util.stream.Collectors; - @Service @RequiredArgsConstructor public class ScoreService { @@ -45,7 +44,7 @@ public Long submitGpaScore(SiteUser siteUser, GpaScoreRequest gpaScoreRequest, M public Long submitLanguageTestScore(SiteUser siteUser, LanguageTestScoreRequest languageTestScoreRequest, MultipartFile file) { UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(file, ImgType.LANGUAGE_TEST); LanguageTest languageTest = new LanguageTest(languageTestScoreRequest.languageTestType(), - languageTestScoreRequest.languageTestScore(), uploadedFile.fileUrl()); + languageTestScoreRequest.languageTestScore(), uploadedFile.fileUrl()); LanguageTestScore newScore = new LanguageTestScore(languageTest, siteUser); LanguageTestScore savedNewScore = languageTestScoreRepository.save(newScore); return savedNewScore.getId(); diff --git a/src/main/java/com/example/solidconnection/security/annotation/RequireRoleAccess.java b/src/main/java/com/example/solidconnection/security/annotation/RequireRoleAccess.java index aecef342d..c97ad3d51 100644 --- a/src/main/java/com/example/solidconnection/security/annotation/RequireRoleAccess.java +++ b/src/main/java/com/example/solidconnection/security/annotation/RequireRoleAccess.java @@ -1,7 +1,6 @@ package com.example.solidconnection.security.annotation; import com.example.solidconnection.siteuser.domain.Role; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -10,5 +9,6 @@ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequireRoleAccess { + Role[] roles(); } diff --git a/src/main/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspect.java b/src/main/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspect.java index b1b1f4223..900251514 100644 --- a/src/main/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspect.java +++ b/src/main/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspect.java @@ -1,19 +1,18 @@ package com.example.solidconnection.security.aspect; +import static com.example.solidconnection.common.exception.ErrorCode.ACCESS_DENIED; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.security.annotation.RequireRoleAccess; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; +import java.util.Arrays; import lombok.RequiredArgsConstructor; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; -import java.util.Arrays; - -import static com.example.solidconnection.common.exception.ErrorCode.ACCESS_DENIED; - @Aspect @Component @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/security/authentication/JwtAuthentication.java b/src/main/java/com/example/solidconnection/security/authentication/JwtAuthentication.java index 64e89e891..d68458b18 100644 --- a/src/main/java/com/example/solidconnection/security/authentication/JwtAuthentication.java +++ b/src/main/java/com/example/solidconnection/security/authentication/JwtAuthentication.java @@ -1,10 +1,9 @@ package com.example.solidconnection.security.authentication; +import java.util.Collections; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; -import java.util.Collections; - public abstract class JwtAuthentication extends AbstractAuthenticationToken { private final String credentials; @@ -13,8 +12,8 @@ public abstract class JwtAuthentication extends AbstractAuthenticationToken { public JwtAuthentication(String token, Object principal) { super(principal instanceof UserDetails ? - ((UserDetails) principal).getAuthorities() : - Collections.emptyList()); + ((UserDetails) principal).getAuthorities() : + Collections.emptyList()); this.credentials = token; this.principal = principal; } diff --git a/src/main/java/com/example/solidconnection/security/config/CorsProperties.java b/src/main/java/com/example/solidconnection/security/config/CorsProperties.java index a179a7563..af16d782c 100644 --- a/src/main/java/com/example/solidconnection/security/config/CorsProperties.java +++ b/src/main/java/com/example/solidconnection/security/config/CorsProperties.java @@ -1,9 +1,9 @@ package com.example.solidconnection.security.config; -import org.springframework.boot.context.properties.ConfigurationProperties; - import java.util.List; +import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "cors") public record CorsProperties(List allowedOrigins) { + } diff --git a/src/main/java/com/example/solidconnection/security/config/SecurityConfiguration.java b/src/main/java/com/example/solidconnection/security/config/SecurityConfiguration.java index 46d1793df..b12bade83 100644 --- a/src/main/java/com/example/solidconnection/security/config/SecurityConfiguration.java +++ b/src/main/java/com/example/solidconnection/security/config/SecurityConfiguration.java @@ -1,5 +1,7 @@ package com.example.solidconnection.security.config; +import static com.example.solidconnection.siteuser.domain.Role.ADMIN; + import com.example.solidconnection.common.exception.CustomAccessDeniedHandler; import com.example.solidconnection.common.exception.CustomAuthenticationEntryPoint; import com.example.solidconnection.security.filter.ExceptionHandlerFilter; @@ -20,8 +22,6 @@ import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import static com.example.solidconnection.siteuser.domain.Role.ADMIN; - @Configuration @EnableWebSecurity @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/security/filter/AuthorizationHeaderParser.java b/src/main/java/com/example/solidconnection/security/filter/AuthorizationHeaderParser.java index 8bbbc30e4..9360b8c8a 100644 --- a/src/main/java/com/example/solidconnection/security/filter/AuthorizationHeaderParser.java +++ b/src/main/java/com/example/solidconnection/security/filter/AuthorizationHeaderParser.java @@ -1,9 +1,8 @@ package com.example.solidconnection.security.filter; import jakarta.servlet.http.HttpServletRequest; -import org.springframework.stereotype.Component; - import java.util.Optional; +import org.springframework.stereotype.Component; @Component public class AuthorizationHeaderParser { diff --git a/src/main/java/com/example/solidconnection/security/filter/ExceptionHandlerFilter.java b/src/main/java/com/example/solidconnection/security/filter/ExceptionHandlerFilter.java index 84602a5f8..fd6dfdbc0 100644 --- a/src/main/java/com/example/solidconnection/security/filter/ExceptionHandlerFilter.java +++ b/src/main/java/com/example/solidconnection/security/filter/ExceptionHandlerFilter.java @@ -1,5 +1,7 @@ package com.example.solidconnection.security.filter; +import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.common.response.ErrorResponse; @@ -8,16 +10,13 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; -import java.io.IOException; - -import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; - @Component @RequiredArgsConstructor public class ExceptionHandlerFilter extends OncePerRequestFilter { diff --git a/src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java index 8ee6a98e4..a36385085 100644 --- a/src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java @@ -6,6 +6,8 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Optional; import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.AuthenticationManager; @@ -14,9 +16,6 @@ import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; -import java.io.IOException; -import java.util.Optional; - @Component @RequiredArgsConstructor diff --git a/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java b/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java index c3926d67f..45f8d2106 100644 --- a/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java +++ b/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java @@ -1,20 +1,19 @@ package com.example.solidconnection.security.filter; +import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_SIGN_OUT; + import com.example.solidconnection.common.exception.CustomException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Optional; import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; -import java.io.IOException; -import java.util.Optional; - -import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_SIGN_OUT; - @Component @RequiredArgsConstructor public class SignOutCheckFilter extends OncePerRequestFilter { diff --git a/src/main/java/com/example/solidconnection/security/userdetails/SecurityRoleMapper.java b/src/main/java/com/example/solidconnection/security/userdetails/SecurityRoleMapper.java index 802229e46..5183d6411 100644 --- a/src/main/java/com/example/solidconnection/security/userdetails/SecurityRoleMapper.java +++ b/src/main/java/com/example/solidconnection/security/userdetails/SecurityRoleMapper.java @@ -1,9 +1,8 @@ package com.example.solidconnection.security.userdetails; import com.example.solidconnection.siteuser.domain.Role; -import org.springframework.security.core.authority.SimpleGrantedAuthority; - import java.util.List; +import org.springframework.security.core.authority.SimpleGrantedAuthority; public class SecurityRoleMapper { diff --git a/src/main/java/com/example/solidconnection/security/userdetails/SiteUserDetails.java b/src/main/java/com/example/solidconnection/security/userdetails/SiteUserDetails.java index d92d5cc32..25def3518 100644 --- a/src/main/java/com/example/solidconnection/security/userdetails/SiteUserDetails.java +++ b/src/main/java/com/example/solidconnection/security/userdetails/SiteUserDetails.java @@ -1,12 +1,11 @@ package com.example.solidconnection.security.userdetails; import com.example.solidconnection.siteuser.domain.SiteUser; +import java.util.Collection; import lombok.Getter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import java.util.Collection; - public class SiteUserDetails implements UserDetails { // userDetails 에서 userName 은 사용자 식별자를 의미함 diff --git a/src/main/java/com/example/solidconnection/security/userdetails/SiteUserDetailsService.java b/src/main/java/com/example/solidconnection/security/userdetails/SiteUserDetailsService.java index e13dc1089..aa9b5dbd2 100644 --- a/src/main/java/com/example/solidconnection/security/userdetails/SiteUserDetailsService.java +++ b/src/main/java/com/example/solidconnection/security/userdetails/SiteUserDetailsService.java @@ -1,5 +1,8 @@ package com.example.solidconnection.security.userdetails; +import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; @@ -9,9 +12,6 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; -import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; - @Service @RequiredArgsConstructor public class SiteUserDetailsService implements UserDetailsService { diff --git a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java index c7d30e7ae..0a15317c1 100644 --- a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java +++ b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java @@ -9,15 +9,14 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import jakarta.persistence.UniqueConstraint; +import java.time.LocalDate; +import java.time.LocalDateTime; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import java.time.LocalDate; -import java.time.LocalDateTime; - @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/NicknameExistsResponse.java b/src/main/java/com/example/solidconnection/siteuser/dto/NicknameExistsResponse.java index efb53df54..203b771e3 100644 --- a/src/main/java/com/example/solidconnection/siteuser/dto/NicknameExistsResponse.java +++ b/src/main/java/com/example/solidconnection/siteuser/dto/NicknameExistsResponse.java @@ -3,6 +3,7 @@ public record NicknameExistsResponse( boolean exists ) { + public static NicknameExistsResponse from(boolean exists) { return new NicknameExistsResponse(exists); } diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateRequest.java b/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateRequest.java index 9b83969b4..b2a8304f9 100644 --- a/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateRequest.java @@ -6,4 +6,5 @@ public record NicknameUpdateRequest( @NotBlank(message = "닉네임을 입력해주세요.") String nickname ) { + } diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateResponse.java b/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateResponse.java index a59e71824..a4335e670 100644 --- a/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateResponse.java +++ b/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateResponse.java @@ -5,6 +5,7 @@ public record NicknameUpdateResponse( String nickname ) { + public static NicknameUpdateResponse from(SiteUser siteUser) { return new NicknameUpdateResponse( siteUser.getNickname() diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/PostFindSiteUserResponse.java b/src/main/java/com/example/solidconnection/siteuser/dto/PostFindSiteUserResponse.java index 85d649631..048a76909 100644 --- a/src/main/java/com/example/solidconnection/siteuser/dto/PostFindSiteUserResponse.java +++ b/src/main/java/com/example/solidconnection/siteuser/dto/PostFindSiteUserResponse.java @@ -7,6 +7,7 @@ public record PostFindSiteUserResponse( String nickname, String profileImageUrl ) { + public static PostFindSiteUserResponse from(SiteUser siteUser) { return new PostFindSiteUserResponse( siteUser.getId(), diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/ProfileImageUpdateResponse.java b/src/main/java/com/example/solidconnection/siteuser/dto/ProfileImageUpdateResponse.java index d806fde20..3bbb33e08 100644 --- a/src/main/java/com/example/solidconnection/siteuser/dto/ProfileImageUpdateResponse.java +++ b/src/main/java/com/example/solidconnection/siteuser/dto/ProfileImageUpdateResponse.java @@ -5,6 +5,7 @@ public record ProfileImageUpdateResponse( String profileImageUrl ) { + public static ProfileImageUpdateResponse from(SiteUser siteUser) { return new ProfileImageUpdateResponse( siteUser.getProfileImageUrl() diff --git a/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java b/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java index c93ff0532..864e72ed3 100644 --- a/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java +++ b/src/main/java/com/example/solidconnection/siteuser/repository/SiteUserRepository.java @@ -2,13 +2,12 @@ import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - import java.time.LocalDate; import java.util.List; import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface SiteUserRepository extends JpaRepository { diff --git a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java index ccabb0565..b5602fbf5 100644 --- a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java +++ b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java @@ -1,5 +1,9 @@ package com.example.solidconnection.siteuser.service; +import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; +import static com.example.solidconnection.common.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.s3.domain.ImgType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; @@ -8,18 +12,13 @@ import com.example.solidconnection.siteuser.dto.MyPageResponse; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; - -import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; -import static com.example.solidconnection.common.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; -import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; - @RequiredArgsConstructor @Service public class MyPageService { diff --git a/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java b/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java index d93e1524b..5b1a759b8 100644 --- a/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java +++ b/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java @@ -10,6 +10,7 @@ import com.example.solidconnection.university.service.LikedUnivApplyInfoService; import com.example.solidconnection.university.service.UnivApplyInfoQueryService; import com.example.solidconnection.university.service.UnivApplyInfoRecommendService; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -20,8 +21,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import java.util.List; - @RequiredArgsConstructor @RequestMapping("/univ-apply-infos") @RestController diff --git a/src/main/java/com/example/solidconnection/university/domain/LikedUnivApplyInfo.java b/src/main/java/com/example/solidconnection/university/domain/LikedUnivApplyInfo.java index 0fc24ae3c..b1798f205 100644 --- a/src/main/java/com/example/solidconnection/university/domain/LikedUnivApplyInfo.java +++ b/src/main/java/com/example/solidconnection/university/domain/LikedUnivApplyInfo.java @@ -20,20 +20,20 @@ @Table( name = "liked_university_info_for_apply", uniqueConstraints = { - @UniqueConstraint( - name = "uk_liked_university_site_user_id_university_info_for_apply_id", - columnNames = {"site_user_id", "university_info_for_apply_id"} - ) -}) + @UniqueConstraint( + name = "uk_liked_university_site_user_id_university_info_for_apply_id", + columnNames = {"site_user_id", "university_info_for_apply_id"} + ) + }) public class LikedUnivApplyInfo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name="university_info_for_apply_id") + @Column(name = "university_info_for_apply_id") private long univApplyInfoId; - @Column(name="site_user_id") + @Column(name = "site_user_id") private long siteUserId; } diff --git a/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java b/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java index 9287f5bd4..09db466bd 100644 --- a/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java +++ b/src/main/java/com/example/solidconnection/university/domain/UnivApplyInfo.java @@ -1,5 +1,6 @@ package com.example.solidconnection.university.domain; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -11,16 +12,14 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; -import jakarta.persistence.CascadeType; +import java.util.HashSet; +import java.util.Set; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; -import java.util.HashSet; -import java.util.Set; - @Getter @EqualsAndHashCode(of = "id") @AllArgsConstructor diff --git a/src/main/java/com/example/solidconnection/university/dto/IsLikeResponse.java b/src/main/java/com/example/solidconnection/university/dto/IsLikeResponse.java index 7d4aebbf9..b0d340649 100644 --- a/src/main/java/com/example/solidconnection/university/dto/IsLikeResponse.java +++ b/src/main/java/com/example/solidconnection/university/dto/IsLikeResponse.java @@ -2,4 +2,5 @@ public record IsLikeResponse( boolean isLike) { + } diff --git a/src/main/java/com/example/solidconnection/university/dto/LikeResultResponse.java b/src/main/java/com/example/solidconnection/university/dto/LikeResultResponse.java index c67f2e408..11f01c180 100644 --- a/src/main/java/com/example/solidconnection/university/dto/LikeResultResponse.java +++ b/src/main/java/com/example/solidconnection/university/dto/LikeResultResponse.java @@ -2,4 +2,5 @@ public record LikeResultResponse( String result) { + } diff --git a/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoDetailResponse.java b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoDetailResponse.java index 673254d88..481629f24 100644 --- a/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoDetailResponse.java +++ b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoDetailResponse.java @@ -1,8 +1,7 @@ package com.example.solidconnection.university.dto; -import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.domain.UnivApplyInfo; - +import com.example.solidconnection.university.domain.University; import java.util.List; public record UnivApplyInfoDetailResponse( diff --git a/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoPreviewResponse.java b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoPreviewResponse.java index 4f7f7c3f1..0c6badbfd 100644 --- a/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoPreviewResponse.java +++ b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoPreviewResponse.java @@ -1,7 +1,6 @@ package com.example.solidconnection.university.dto; import com.example.solidconnection.university.domain.UnivApplyInfo; - import java.util.Collections; import java.util.List; diff --git a/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoPreviewResponses.java b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoPreviewResponses.java index 2afec8262..610404555 100644 --- a/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoPreviewResponses.java +++ b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoPreviewResponses.java @@ -6,4 +6,5 @@ public record UnivApplyInfoPreviewResponses( List univApplyInfoPreviews // todo: #345 응답 형식으로 바로 배열이 아니라, univApplyInfoPreviews로 감싸 응답한다고 전달 후, 코드 변경 필요 ) { + } diff --git a/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoice.java b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoice.java index 7d925287a..4cef2b5eb 100644 --- a/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoice.java +++ b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoice.java @@ -2,7 +2,6 @@ import jakarta.validation.Constraint; import jakarta.validation.Payload; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -14,6 +13,8 @@ public @interface ValidUnivApplyInfoChoice { String message() default "유효하지 않은 지망 대학 선택입니다."; + Class[] groups() default {}; + Class[] payload() default {}; } diff --git a/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java index a49d631d1..500695646 100644 --- a/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java +++ b/src/main/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidator.java @@ -1,18 +1,17 @@ package com.example.solidconnection.university.dto.validation; +import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_UNIV_APPLY_INFO_CHOICE; +import static com.example.solidconnection.common.exception.ErrorCode.FIRST_CHOICE_REQUIRED; +import static com.example.solidconnection.common.exception.ErrorCode.THIRD_CHOICE_REQUIRES_SECOND; + import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; - import java.util.HashSet; import java.util.Objects; import java.util.Set; import java.util.stream.Stream; -import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_UNIV_APPLY_INFO_CHOICE; -import static com.example.solidconnection.common.exception.ErrorCode.FIRST_CHOICE_REQUIRED; -import static com.example.solidconnection.common.exception.ErrorCode.THIRD_CHOICE_REQUIRES_SECOND; - public class ValidUnivApplyInfoChoiceValidator implements ConstraintValidator { @Override diff --git a/src/main/java/com/example/solidconnection/university/repository/LanguageRequirementRepository.java b/src/main/java/com/example/solidconnection/university/repository/LanguageRequirementRepository.java index 9a0e590e0..edaf4c3a8 100644 --- a/src/main/java/com/example/solidconnection/university/repository/LanguageRequirementRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/LanguageRequirementRepository.java @@ -4,4 +4,5 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface LanguageRequirementRepository extends JpaRepository { + } diff --git a/src/main/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepository.java b/src/main/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepository.java index fe4f93676..0dc8255a6 100644 --- a/src/main/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepository.java @@ -2,13 +2,12 @@ import com.example.solidconnection.university.domain.LikedUnivApplyInfo; import com.example.solidconnection.university.domain.UnivApplyInfo; +import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; -import java.util.Optional; - public interface LikedUnivApplyInfoRepository extends JpaRepository { List findAllBySiteUserId(long siteUserId); @@ -18,11 +17,11 @@ public interface LikedUnivApplyInfoRepository extends JpaRepository findBySiteUserIdAndUnivApplyInfoId(long siteUserId, long univApplyInfoId); @Query(""" - SELECT u - FROM UnivApplyInfo u - JOIN LikedUnivApplyInfo l ON u.id = l.univApplyInfoId - WHERE l.siteUserId = :siteUserId - """) + SELECT u + FROM UnivApplyInfo u + JOIN LikedUnivApplyInfo l ON u.id = l.univApplyInfoId + WHERE l.siteUserId = :siteUserId + """) List findUnivApplyInfosBySiteUserId(@Param("siteUserId") long siteUserId); boolean existsBySiteUserIdAndUnivApplyInfoId(long siteUserId, long univApplyInfoId); diff --git a/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java b/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java index c99ac01eb..b51ce5a89 100644 --- a/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/UnivApplyInfoRepository.java @@ -1,52 +1,51 @@ package com.example.solidconnection.university.repository; +import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.repository.custom.UnivApplyInfoFilterRepository; +import java.util.List; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.List; - -import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND; - @Repository public interface UnivApplyInfoRepository extends JpaRepository, UnivApplyInfoFilterRepository { @Query(""" - SELECT DISTINCT uai - FROM UnivApplyInfo uai - LEFT JOIN FETCH uai.languageRequirements lr - JOIN FETCH uai.university u - LEFT JOIN FETCH u.country c - LEFT JOIN FETCH u.region r - WHERE (c.code IN ( - SELECT ic.countryCode - FROM InterestedCountry ic - WHERE ic.siteUserId = :siteUserId - ) - OR r.code IN ( - SELECT ir.regionCode - FROM InterestedRegion ir - WHERE ir.siteUserId = :siteUserId - )) - AND uai.term = :term - """) + SELECT DISTINCT uai + FROM UnivApplyInfo uai + LEFT JOIN FETCH uai.languageRequirements lr + JOIN FETCH uai.university u + LEFT JOIN FETCH u.country c + LEFT JOIN FETCH u.region r + WHERE (c.code IN ( + SELECT ic.countryCode + FROM InterestedCountry ic + WHERE ic.siteUserId = :siteUserId + ) + OR r.code IN ( + SELECT ir.regionCode + FROM InterestedRegion ir + WHERE ir.siteUserId = :siteUserId + )) + AND uai.term = :term + """) List findAllBySiteUsersInterestedCountryOrRegionAndTerm(@Param("siteUserId") Long siteUserId, @Param("term") String term); @Query(""" - SELECT uai - FROM UnivApplyInfo uai - LEFT JOIN FETCH uai.languageRequirements lr - LEFT JOIN FETCH uai.university u - LEFT JOIN FETCH u.country c - LEFT JOIN FETCH u.region r - WHERE uai.term = :term - ORDER BY FUNCTION('RAND') - """) + SELECT uai + FROM UnivApplyInfo uai + LEFT JOIN FETCH uai.languageRequirements lr + LEFT JOIN FETCH uai.university u + LEFT JOIN FETCH u.country c + LEFT JOIN FETCH u.region r + WHERE uai.term = :term + ORDER BY FUNCTION('RAND') + """) List findRandomByTerm(@Param("term") String term, Pageable pageable); // JPA에서 LIMIT 사용이 불가하므로 Pageable을 통해 0page에서 정해진 개수 만큼 가져오는 방식으로 구현 default UnivApplyInfo getUnivApplyInfoById(Long id) { @@ -55,13 +54,13 @@ default UnivApplyInfo getUnivApplyInfoById(Long id) { } @Query(""" - SELECT DISTINCT uai - FROM UnivApplyInfo uai - LEFT JOIN FETCH uai.languageRequirements lr - LEFT JOIN FETCH uai.university u - LEFT JOIN FETCH u.country c - LEFT JOIN FETCH u.region r - WHERE uai.id IN :ids - """) + SELECT DISTINCT uai + FROM UnivApplyInfo uai + LEFT JOIN FETCH uai.languageRequirements lr + LEFT JOIN FETCH uai.university u + LEFT JOIN FETCH u.country c + LEFT JOIN FETCH u.region r + WHERE uai.id IN :ids + """) List findAllByIds(@Param("ids") List ids); } diff --git a/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java b/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java index a499d003c..5447a14ba 100644 --- a/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java @@ -1,15 +1,14 @@ package com.example.solidconnection.university.repository; +import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_NOT_FOUND; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.university.domain.University; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.List; - -import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_NOT_FOUND; - public interface UniversityRepository extends JpaRepository { @Query("SELECT u FROM University u WHERE u.country.code IN :countryCodes OR u.region.code IN :regionCodes") diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java index 588bfbf9f..5c95e5d1e 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java @@ -2,7 +2,6 @@ import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.UnivApplyInfo; - import java.util.List; public interface UnivApplyInfoFilterRepository { diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java index cf8f185d4..9e57274ee 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java @@ -3,20 +3,19 @@ import com.example.solidconnection.location.country.domain.QCountry; import com.example.solidconnection.location.region.domain.QRegion; import com.example.solidconnection.university.domain.LanguageTestType; +import com.example.solidconnection.university.domain.QLanguageRequirement; import com.example.solidconnection.university.domain.QUnivApplyInfo; import com.example.solidconnection.university.domain.QUniversity; import com.example.solidconnection.university.domain.UnivApplyInfo; -import com.example.solidconnection.university.domain.QLanguageRequirement; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.Expressions; import com.querydsl.core.types.dsl.StringPath; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; +import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; -import java.util.List; - @Repository public class UnivApplyInfoFilterRepositoryImpl implements UnivApplyInfoFilterRepository { @@ -84,8 +83,8 @@ public List findAllByRegionCodeAndKeywordsAndLanguageTestTypeAndT .join(university.country, country) .join(university.region, region) .where(regionCodeEq(country, regionCode) - .and(countryOrUniversityContainsKeyword(country, university, keywords)) - .and(univApplyInfo.term.eq(term))) + .and(countryOrUniversityContainsKeyword(country, university, keywords)) + .and(univApplyInfo.term.eq(term))) .fetch(); if (testScore == null || testScore.isEmpty()) { diff --git a/src/main/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendService.java b/src/main/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendService.java index 07c2ef653..627e579a3 100644 --- a/src/main/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendService.java +++ b/src/main/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendService.java @@ -1,7 +1,10 @@ package com.example.solidconnection.university.service; +import static com.example.solidconnection.university.service.UnivApplyInfoRecommendService.RECOMMEND_UNIV_APPLY_INFO_NUM; + import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; +import java.util.List; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -11,10 +14,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import java.util.List; - -import static com.example.solidconnection.university.service.UnivApplyInfoRecommendService.RECOMMEND_UNIV_APPLY_INFO_NUM; - @Service @RequiredArgsConstructor public class GeneralUnivApplyInfoRecommendService { diff --git a/src/main/java/com/example/solidconnection/university/service/LikedUnivApplyInfoService.java b/src/main/java/com/example/solidconnection/university/service/LikedUnivApplyInfoService.java index 2ac9ceb3d..7be082d9e 100644 --- a/src/main/java/com/example/solidconnection/university/service/LikedUnivApplyInfoService.java +++ b/src/main/java/com/example/solidconnection/university/service/LikedUnivApplyInfoService.java @@ -1,5 +1,8 @@ package com.example.solidconnection.university.service; +import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_UNIV_APPLY_INFO; +import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_UNIV_APPLY_INFO; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.university.domain.LikedUnivApplyInfo; @@ -8,17 +11,13 @@ import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; +import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.Optional; - -import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_UNIV_APPLY_INFO; -import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_UNIV_APPLY_INFO; - @RequiredArgsConstructor @Service public class LikedUnivApplyInfoService { diff --git a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java index 86717c382..ff0215c63 100644 --- a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java +++ b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java @@ -2,20 +2,19 @@ import com.example.solidconnection.cache.annotation.ThunderingHerdCaching; import com.example.solidconnection.university.domain.LanguageTestType; -import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.domain.UnivApplyInfo; +import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.dto.UnivApplyInfoDetailResponse; import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponses; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; import com.example.solidconnection.university.repository.custom.UnivApplyInfoFilterRepositoryImpl; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - @RequiredArgsConstructor @Service public class UnivApplyInfoQueryService { @@ -53,9 +52,9 @@ public UnivApplyInfoPreviewResponses searchUnivApplyInfo( String regionCode, List keywords, LanguageTestType testType, String testScore) { return new UnivApplyInfoPreviewResponses(universityFilterRepository - .findAllByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm(regionCode, keywords, testType, testScore, term) - .stream() - .map(UnivApplyInfoPreviewResponse::from) - .toList()); + .findAllByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm(regionCode, keywords, testType, testScore, term) + .stream() + .map(UnivApplyInfoPreviewResponse::from) + .toList()); } } diff --git a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendService.java b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendService.java index eaff6eec9..b3ef68baa 100644 --- a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendService.java +++ b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendService.java @@ -6,15 +6,14 @@ import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; import com.example.solidconnection.university.dto.UnivApplyInfoRecommendsResponse; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - @RequiredArgsConstructor @Service public class UnivApplyInfoRecommendService { @@ -48,8 +47,8 @@ public UnivApplyInfoRecommendsResponse getPersonalRecommends(SiteUser siteUser) } return new UnivApplyInfoRecommendsResponse(trimmedRecommends.stream() - .map(UnivApplyInfoPreviewResponse::from) - .toList()); + .map(UnivApplyInfoPreviewResponse::from) + .toList()); } private List getGeneralRecommendsExcludingSelected(List alreadyPicked) { @@ -68,7 +67,7 @@ private List getGeneralRecommendsExcludingSelected(List generalRecommends = new ArrayList<>(generalUnivApplyInfoRecommendService.getGeneralRecommends()); return new UnivApplyInfoRecommendsResponse(generalRecommends.stream() - .map(UnivApplyInfoPreviewResponse::from) - .toList()); + .map(UnivApplyInfoPreviewResponse::from) + .toList()); } } diff --git a/src/main/java/com/example/solidconnection/util/RedisUtils.java b/src/main/java/com/example/solidconnection/util/RedisUtils.java index 6f11eedfa..df4d7572d 100644 --- a/src/main/java/com/example/solidconnection/util/RedisUtils.java +++ b/src/main/java/com/example/solidconnection/util/RedisUtils.java @@ -1,8 +1,9 @@ package com.example.solidconnection.util; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Component; +import static com.example.solidconnection.community.post.service.RedisConstants.CREATE_LOCK_PREFIX; +import static com.example.solidconnection.community.post.service.RedisConstants.REFRESH_LOCK_PREFIX; +import static com.example.solidconnection.community.post.service.RedisConstants.VALIDATE_VIEW_COUNT_KEY_PREFIX; +import static com.example.solidconnection.community.post.service.RedisConstants.VIEW_COUNT_KEY_PREFIX; import java.util.Collections; import java.util.Comparator; @@ -10,11 +11,9 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; - -import static com.example.solidconnection.community.post.service.RedisConstants.CREATE_LOCK_PREFIX; -import static com.example.solidconnection.community.post.service.RedisConstants.REFRESH_LOCK_PREFIX; -import static com.example.solidconnection.community.post.service.RedisConstants.VALIDATE_VIEW_COUNT_KEY_PREFIX; -import static com.example.solidconnection.community.post.service.RedisConstants.VIEW_COUNT_KEY_PREFIX; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; @Component public class RedisUtils { diff --git a/src/test/java/com/example/solidconnection/admin/dto/validation/RejectedReasonValidatorTest.java b/src/test/java/com/example/solidconnection/admin/dto/validation/RejectedReasonValidatorTest.java index a09db4de7..ebeb2ccbe 100644 --- a/src/test/java/com/example/solidconnection/admin/dto/validation/RejectedReasonValidatorTest.java +++ b/src/test/java/com/example/solidconnection/admin/dto/validation/RejectedReasonValidatorTest.java @@ -1,5 +1,8 @@ package com.example.solidconnection.admin.dto.validation; +import static com.example.solidconnection.common.exception.ErrorCode.REJECTED_REASON_REQUIRED; +import static org.assertj.core.api.Assertions.assertThat; + import com.example.solidconnection.admin.dto.GpaScoreUpdateRequest; import com.example.solidconnection.admin.dto.LanguageTestScoreUpdateRequest; import com.example.solidconnection.common.VerifyStatus; @@ -8,16 +11,12 @@ import jakarta.validation.Validation; import jakarta.validation.Validator; import jakarta.validation.ValidatorFactory; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import java.util.Set; - -import static com.example.solidconnection.common.exception.ErrorCode.REJECTED_REASON_REQUIRED; -import static org.assertj.core.api.Assertions.assertThat; - @DisplayName("거절 사유 유효성 검사 테스트") class RejectedReasonValidatorTest { diff --git a/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java b/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java index 070ec9e6f..50c12c4b3 100644 --- a/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/service/AdminGpaScoreServiceTest.java @@ -1,5 +1,10 @@ package com.example.solidconnection.admin.service; +import static com.example.solidconnection.common.exception.ErrorCode.GPA_SCORE_NOT_FOUND; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.admin.dto.GpaScoreResponse; import com.example.solidconnection.admin.dto.GpaScoreSearchResponse; import com.example.solidconnection.admin.dto.GpaScoreUpdateRequest; @@ -11,6 +16,8 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.time.LocalDate; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -20,14 +27,6 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import java.time.LocalDate; -import java.util.List; - -import static com.example.solidconnection.common.exception.ErrorCode.GPA_SCORE_NOT_FOUND; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; - @TestContainerSpringBootTest @DisplayName("학점 검증 관리자 서비스 테스트") class AdminGpaScoreServiceTest { diff --git a/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java b/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java index c662f72d8..e0afc9610 100644 --- a/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/admin/service/AdminLanguageTestScoreServiceTest.java @@ -1,5 +1,11 @@ package com.example.solidconnection.admin.service; +import static com.example.solidconnection.common.exception.ErrorCode.LANGUAGE_TEST_SCORE_NOT_FOUND; +import static com.example.solidconnection.university.domain.LanguageTestType.TOEIC; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.admin.dto.LanguageTestScoreResponse; import com.example.solidconnection.admin.dto.LanguageTestScoreSearchResponse; import com.example.solidconnection.admin.dto.LanguageTestScoreUpdateRequest; @@ -11,6 +17,8 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.time.LocalDate; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -20,15 +28,6 @@ import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import java.time.LocalDate; -import java.util.List; - -import static com.example.solidconnection.common.exception.ErrorCode.LANGUAGE_TEST_SCORE_NOT_FOUND; -import static com.example.solidconnection.university.domain.LanguageTestType.TOEIC; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; - @TestContainerSpringBootTest @DisplayName("어학 검증 관리자 서비스 테스트") class AdminLanguageTestScoreServiceTest { diff --git a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java index efe76bd4a..818410938 100644 --- a/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/application/fixture/ApplicationFixtureBuilder.java @@ -3,8 +3,8 @@ import com.example.solidconnection.application.domain.Application; import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.application.repository.ApplicationRepository; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java index 3acb51d02..0fcec8d3b 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java @@ -1,12 +1,14 @@ package com.example.solidconnection.application.service; +import static org.assertj.core.api.Assertions.assertThat; + import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.application.dto.ApplicantResponse; -import com.example.solidconnection.application.dto.ApplicationsResponse; import com.example.solidconnection.application.dto.ApplicantsResponse; +import com.example.solidconnection.application.dto.ApplicationsResponse; import com.example.solidconnection.application.fixture.ApplicationFixture; import com.example.solidconnection.application.repository.ApplicationRepository; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.location.region.fixture.RegionFixture; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.domain.LanguageTestScore; @@ -17,6 +19,7 @@ import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -24,10 +27,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - @TestContainerSpringBootTest @DisplayName("지원서 조회 서비스 테스트") class ApplicationQueryServiceTest { @@ -141,11 +140,11 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsAll(List.of( ApplicantsResponse.of(괌대학_A_지원_정보, - List.of(application1), user1), + List.of(application1), user1), ApplicantsResponse.of(괌대학_B_지원_정보, - List.of(application2), user1), + List.of(application2), user1), ApplicantsResponse.of(서던덴마크대학교_지원_정보, - List.of(application3), user1) + List.of(application3), user1) )); } @@ -193,9 +192,9 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsExactlyInAnyOrder( ApplicantsResponse.of(괌대학_A_지원_정보, - List.of(application1), user1), + List.of(application1), user1), ApplicantsResponse.of(괌대학_B_지원_정보, - List.of(application2), user1) + List.of(application2), user1) ); } @@ -243,9 +242,9 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsExactlyInAnyOrder( ApplicantsResponse.of(괌대학_A_지원_정보, - List.of(application1), user1), + List.of(application1), user1), ApplicantsResponse.of(괌대학_B_지원_정보, - List.of(application2), user1) + List.of(application2), user1) ); } @@ -273,7 +272,7 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice()).doesNotContainAnyElementsOf(List.of( ApplicantsResponse.of(괌대학_A_지원_정보, - List.of(application), user1) + List.of(application), user1) )); } @@ -312,14 +311,15 @@ class 지원자_목록_조회_테스트 { // then assertThat(response.firstChoice().stream() - .flatMap(univ -> univ.applicants().stream()) - .filter(ApplicantResponse::isMine)) + .flatMap(univ -> univ.applicants().stream()) + .filter(ApplicantResponse::isMine)) .containsExactly(ApplicantResponse.of(secondApplication, true)); } } @Nested class 경쟁자_목록_조회_테스트 { + @Test void 이번_학기_지원한_대학의_경쟁자_목록을_조회한다() { // given @@ -359,7 +359,7 @@ class 경쟁자_목록_조회_테스트 { // then assertThat(response.firstChoice()).containsExactlyInAnyOrder( ApplicantsResponse.of(괌대학_A_지원_정보, - List.of(application1, application2), user1) + List.of(application1, application2), user1) ); } diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java index 891adc9a9..cbd36642e 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java @@ -1,11 +1,19 @@ package com.example.solidconnection.application.service; +import static com.example.solidconnection.application.service.ApplicationSubmissionService.APPLICATION_UPDATE_COUNT_LIMIT; +import static com.example.solidconnection.common.exception.ErrorCode.APPLY_UPDATE_LIMIT_EXCEED; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_GPA_SCORE_STATUS; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE_STATUS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.application.dto.ApplicationSubmissionResponse; import com.example.solidconnection.application.dto.ApplyRequest; import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; import com.example.solidconnection.application.repository.ApplicationRepository; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.score.domain.GpaScore; import com.example.solidconnection.score.domain.LanguageTestScore; @@ -22,14 +30,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import static com.example.solidconnection.application.service.ApplicationSubmissionService.APPLICATION_UPDATE_COUNT_LIMIT; -import static com.example.solidconnection.common.exception.ErrorCode.APPLY_UPDATE_LIMIT_EXCEED; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_GPA_SCORE_STATUS; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE_STATUS; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; - @TestContainerSpringBootTest @DisplayName("지원서 제출 서비스 테스트") class ApplicationSubmissionServiceTest { @@ -115,7 +115,7 @@ void setUp() { // when & then assertThatCode(() -> - applicationSubmissionService.apply(user, request) + applicationSubmissionService.apply(user, request) ) .isInstanceOf(CustomException.class) .hasMessage(INVALID_GPA_SCORE_STATUS.getMessage()); @@ -135,7 +135,7 @@ void setUp() { // when & then assertThatCode(() -> - applicationSubmissionService.apply(user, request) + applicationSubmissionService.apply(user, request) ) .isInstanceOf(CustomException.class) .hasMessage(INVALID_LANGUAGE_TEST_SCORE_STATUS.getMessage()); @@ -159,7 +159,7 @@ void setUp() { // when & then assertThatCode(() -> - applicationSubmissionService.apply(user, request) + applicationSubmissionService.apply(user, request) ) .isInstanceOf(CustomException.class) .hasMessage(APPLY_UPDATE_LIMIT_EXCEED.getMessage()); diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java index b4053fa93..0011f2db2 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -1,5 +1,10 @@ package com.example.solidconnection.auth.service; +import static com.example.solidconnection.common.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; @@ -8,19 +13,13 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.time.LocalDate; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; -import java.time.LocalDate; - -import static com.example.solidconnection.common.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; - @DisplayName("인증 서비스 테스트") @TestContainerSpringBootTest class AuthServiceTest { diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java index b6c111f24..b2e588a59 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java @@ -1,5 +1,8 @@ package com.example.solidconnection.auth.service; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.support.TestContainerSpringBootTest; import org.junit.jupiter.api.BeforeEach; @@ -9,9 +12,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; - @TestContainerSpringBootTest @DisplayName("인증 토큰 제공자 테스트") class AuthTokenProviderTest { diff --git a/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java index faa1c42b0..f9d481311 100644 --- a/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java @@ -1,5 +1,8 @@ package com.example.solidconnection.auth.service; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.auth.dto.EmailSignInRequest; import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.common.exception.CustomException; @@ -13,9 +16,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; - @DisplayName("이메일 로그인 서비스 테스트") @TestContainerSpringBootTest class EmailSignInServiceTest { diff --git a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java index ed8738a9a..813a827b3 100644 --- a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java @@ -1,28 +1,27 @@ package com.example.solidconnection.auth.service; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.token.JwtTokenProvider; +import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.exception.ErrorCode; -import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.support.TestContainerSpringBootTest; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; - @DisplayName("토큰 제공자 테스트") @TestContainerSpringBootTest class JwtTokenProviderTest { diff --git a/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java index 463bc4957..da06aa3e4 100644 --- a/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java @@ -1,21 +1,20 @@ package com.example.solidconnection.auth.service; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.time.LocalDate; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; -import java.time.LocalDate; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; - @DisplayName("로그인 서비스 테스트") @TestContainerSpringBootTest class SignInServiceTest { diff --git a/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java index e1974cc93..49328315a 100644 --- a/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java @@ -1,5 +1,8 @@ package com.example.solidconnection.auth.service; +import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST; +import static org.assertj.core.api.Assertions.assertThat; + import com.example.solidconnection.auth.token.TokenBlackListService; import com.example.solidconnection.support.TestContainerSpringBootTest; import org.junit.jupiter.api.Nested; @@ -7,9 +10,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; -import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST; -import static org.assertj.core.api.Assertions.assertThat; - @TestContainerSpringBootTest class TokenBlackListServiceTest { diff --git a/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java index c623285e4..bd8833bb0 100644 --- a/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java @@ -1,5 +1,12 @@ package com.example.solidconnection.auth.service.oauth; +import static com.example.solidconnection.auth.service.oauth.OAuthSignUpTokenProvider.AUTH_TYPE_CLAIM_KEY; +import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; +import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.token.config.JwtProperties; @@ -10,23 +17,15 @@ import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import static com.example.solidconnection.auth.service.oauth.OAuthSignUpTokenProvider.AUTH_TYPE_CLAIM_KEY; -import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; -import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; - @TestContainerSpringBootTest @DisplayName("OAuth 회원가입 토큰 제공자 테스트") class OAuthSignUpTokenProviderTest { diff --git a/src/test/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandlerTest.java b/src/test/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandlerTest.java index 6b993633a..f439621f7 100644 --- a/src/test/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandlerTest.java +++ b/src/test/java/com/example/solidconnection/common/exception/CustomAccessDeniedHandlerTest.java @@ -1,8 +1,12 @@ package com.example.solidconnection.common.exception; +import static com.example.solidconnection.common.exception.ErrorCode.ACCESS_DENIED; +import static org.assertj.core.api.Assertions.assertThat; + import com.example.solidconnection.common.response.ErrorResponse; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -11,11 +15,6 @@ import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.access.AccessDeniedException; -import java.io.IOException; - -import static com.example.solidconnection.common.exception.ErrorCode.ACCESS_DENIED; -import static org.assertj.core.api.Assertions.assertThat; - @TestContainerSpringBootTest @DisplayName("커스텀 인가 예외 처리 테스트") class CustomAccessDeniedHandlerTest { diff --git a/src/test/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPointTest.java b/src/test/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPointTest.java index 121cc3768..4636ccca2 100644 --- a/src/test/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPointTest.java +++ b/src/test/java/com/example/solidconnection/common/exception/CustomAuthenticationEntryPointTest.java @@ -1,8 +1,12 @@ package com.example.solidconnection.common.exception; +import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; +import static org.assertj.core.api.Assertions.assertThat; + import com.example.solidconnection.common.response.ErrorResponse; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -12,11 +16,6 @@ import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.AuthenticationException; -import java.io.IOException; - -import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; -import static org.assertj.core.api.Assertions.assertThat; - @TestContainerSpringBootTest @DisplayName("커스텀 인증 예외 처리 테스트") class CustomAuthenticationEntryPointTest { diff --git a/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java b/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java index 08cc483b6..541f1edc9 100644 --- a/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java +++ b/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java @@ -1,5 +1,11 @@ package com.example.solidconnection.common.resolver; +import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.security.authentication.SiteUserAuthentication; import com.example.solidconnection.security.userdetails.SiteUserDetails; @@ -15,12 +21,6 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - @TestContainerSpringBootTest @DisplayName("인증된 사용자 argument resolver 테스트") class AuthorizedUserResolverTest { diff --git a/src/test/java/com/example/solidconnection/common/resolver/CustomPageableHandlerMethodArgumentResolverTest.java b/src/test/java/com/example/solidconnection/common/resolver/CustomPageableHandlerMethodArgumentResolverTest.java index 5d1bc0faa..da4fcc852 100644 --- a/src/test/java/com/example/solidconnection/common/resolver/CustomPageableHandlerMethodArgumentResolverTest.java +++ b/src/test/java/com/example/solidconnection/common/resolver/CustomPageableHandlerMethodArgumentResolverTest.java @@ -1,6 +1,10 @@ package com.example.solidconnection.common.resolver; +import static org.assertj.core.api.Assertions.assertThat; + import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.lang.reflect.Method; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -14,11 +18,6 @@ import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; -import java.lang.reflect.Method; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; - @TestContainerSpringBootTest @DisplayName("커스텀 페이지 요청 argument resolver 테스트") class CustomPageableHandlerMethodArgumentResolverTest { diff --git a/src/test/java/com/example/solidconnection/community/board/fixture/BoardFixture.java b/src/test/java/com/example/solidconnection/community/board/fixture/BoardFixture.java index 16dc920d2..23815cf02 100644 --- a/src/test/java/com/example/solidconnection/community/board/fixture/BoardFixture.java +++ b/src/test/java/com/example/solidconnection/community/board/fixture/BoardFixture.java @@ -1,14 +1,14 @@ package com.example.solidconnection.community.board.fixture; -import com.example.solidconnection.community.board.domain.Board; -import lombok.RequiredArgsConstructor; -import org.springframework.boot.test.context.TestComponent; - import static com.example.solidconnection.community.board.domain.BoardCode.AMERICAS; import static com.example.solidconnection.community.board.domain.BoardCode.ASIA; import static com.example.solidconnection.community.board.domain.BoardCode.EUROPE; import static com.example.solidconnection.community.board.domain.BoardCode.FREE; +import com.example.solidconnection.community.board.domain.Board; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + @TestComponent @RequiredArgsConstructor public class BoardFixture { diff --git a/src/test/java/com/example/solidconnection/community/board/repository/BoardRepositoryForTest.java b/src/test/java/com/example/solidconnection/community/board/repository/BoardRepositoryForTest.java index bc6470e2c..c27d62452 100644 --- a/src/test/java/com/example/solidconnection/community/board/repository/BoardRepositoryForTest.java +++ b/src/test/java/com/example/solidconnection/community/board/repository/BoardRepositoryForTest.java @@ -1,12 +1,11 @@ package com.example.solidconnection.community.board.repository; import com.example.solidconnection.community.board.domain.Board; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import java.util.Optional; - public interface BoardRepositoryForTest extends JpaRepository { @Query("SELECT b FROM Board b WHERE b.code = :code") diff --git a/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java b/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java index da5372b82..489914266 100644 --- a/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java @@ -1,5 +1,13 @@ package com.example.solidconnection.community.comment.service; +import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPDATE_DEPRECATED_COMMENT; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_COMMENT_ID; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_COMMENT_LEVEL; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ACCESS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.board.fixture.BoardFixture; import com.example.solidconnection.community.comment.domain.Comment; @@ -19,22 +27,13 @@ import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; import jakarta.transaction.Transactional; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import java.util.List; - -import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPDATE_DEPRECATED_COMMENT; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_COMMENT_ID; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_COMMENT_LEVEL; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ACCESS; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; - @TestContainerSpringBootTest @DisplayName("댓글 서비스 테스트") class CommentServiceTest { @@ -196,7 +195,7 @@ class 댓글_생성_테스트 { @Test void 댓글을_성공적으로_생성한다() { // given - CommentCreateRequest request = new CommentCreateRequest(post.getId(),"댓글", null); + CommentCreateRequest request = new CommentCreateRequest(post.getId(), "댓글", null); // when CommentCreateResponse response = commentService.createComment(user1, request); @@ -241,10 +240,10 @@ class 댓글_생성_테스트 { // when & then assertThatThrownBy(() -> - commentService.createComment( - user1, - request - )) + commentService.createComment( + user1, + request + )) .isInstanceOf(CustomException.class) .hasMessage(INVALID_COMMENT_LEVEL.getMessage()); } @@ -257,10 +256,10 @@ class 댓글_생성_테스트 { // when & then assertThatThrownBy(() -> - commentService.createComment( - user1, - request - )) + commentService.createComment( + user1, + request + )) .isInstanceOf(CustomException.class) .hasMessage(INVALID_COMMENT_ID.getMessage()); } @@ -297,11 +296,11 @@ class 댓글_수정_테스트 { // when & then assertThatThrownBy(() -> - commentService.updateComment( - user2, - comment.getId(), - request - )) + commentService.updateComment( + user2, + comment.getId(), + request + )) .isInstanceOf(CustomException.class) .hasMessage(INVALID_POST_ACCESS.getMessage()); } @@ -314,11 +313,11 @@ class 댓글_수정_테스트 { // when & then assertThatThrownBy(() -> - commentService.updateComment( - user1, - comment.getId(), - request - )) + commentService.updateComment( + user1, + comment.getId(), + request + )) .isInstanceOf(CustomException.class) .hasMessage(CAN_NOT_UPDATE_DEPRECATED_COMMENT.getMessage()); } @@ -404,10 +403,10 @@ class 댓글_삭제_테스트 { // when & then assertThatThrownBy(() -> - commentService.deleteCommentById( - user2, - comment.getId() - )) + commentService.deleteCommentById( + user2, + comment.getId() + )) .isInstanceOf(CustomException.class) .hasMessage(INVALID_POST_ACCESS.getMessage()); } diff --git a/src/test/java/com/example/solidconnection/community/post/fixture/PostFixture.java b/src/test/java/com/example/solidconnection/community/post/fixture/PostFixture.java index 92fa64a37..5d6eaea55 100644 --- a/src/test/java/com/example/solidconnection/community/post/fixture/PostFixture.java +++ b/src/test/java/com/example/solidconnection/community/post/fixture/PostFixture.java @@ -14,22 +14,22 @@ public class PostFixture { private final PostFixtureBuilder postFixtureBuilder; public Post 게시글( - String title, - String content, - Boolean isQuestion, - PostCategory postCategory, - Board board, - SiteUser siteUser + String title, + String content, + Boolean isQuestion, + PostCategory postCategory, + Board board, + SiteUser siteUser ) { return postFixtureBuilder - .title(title) - .content(content) - .isQuestion(isQuestion) - .likeCount(0L) - .viewCount(0L) - .postCategory(postCategory) - .board(board) - .siteUser(siteUser) - .create(); + .title(title) + .content(content) + .isQuestion(isQuestion) + .likeCount(0L) + .viewCount(0L) + .postCategory(postCategory) + .board(board) + .siteUser(siteUser) + .create(); } } diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java index 405ac0d54..09e55b45e 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java @@ -1,5 +1,17 @@ package com.example.solidconnection.community.post.service; +import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_DELETE_OR_UPDATE_QUESTION; +import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ACCESS; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_CATEGORY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.BDDMockito.any; +import static org.mockito.BDDMockito.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.board.fixture.BoardFixture; import com.example.solidconnection.community.post.domain.Post; @@ -21,6 +33,7 @@ import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.util.RedisUtils; import jakarta.transaction.Transactional; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -30,20 +43,6 @@ import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; -import java.util.List; - -import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_DELETE_OR_UPDATE_QUESTION; -import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ACCESS; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_CATEGORY; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.BDDMockito.any; -import static org.mockito.BDDMockito.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.then; - @TestContainerSpringBootTest @DisplayName("게시글 생성/수정/삭제 서비스 테스트") class PostCommandServiceTest { @@ -135,7 +134,7 @@ class 게시글_생성_테스트 { // when & then assertThatThrownBy(() -> - postCommandService.createPost(user1, request, imageFiles)) + postCommandService.createPost(user1, request, imageFiles)) .isInstanceOf(CustomException.class) .hasMessage(INVALID_POST_CATEGORY.getMessage()); } @@ -148,7 +147,7 @@ class 게시글_생성_테스트 { // when & then assertThatThrownBy(() -> - postCommandService.createPost(user1, request, imageFiles)) + postCommandService.createPost(user1, request, imageFiles)) .isInstanceOf(CustomException.class) .hasMessage(INVALID_POST_CATEGORY.getMessage()); } @@ -161,7 +160,7 @@ class 게시글_생성_테스트 { // when & then assertThatThrownBy(() -> - postCommandService.createPost(user1, request, imageFiles)) + postCommandService.createPost(user1, request, imageFiles)) .isInstanceOf(CustomException.class) .hasMessage(CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES.getMessage()); } @@ -212,12 +211,12 @@ class 게시글_수정_테스트 { // when & then assertThatThrownBy(() -> - postCommandService.updatePost( - user2, - post.getId(), - request, - imageFiles - )) + postCommandService.updatePost( + user2, + post.getId(), + request, + imageFiles + )) .isInstanceOf(CustomException.class) .hasMessage(INVALID_POST_ACCESS.getMessage()); } @@ -230,12 +229,12 @@ class 게시글_수정_테스트 { // when & then assertThatThrownBy(() -> - postCommandService.updatePost( - user1, - questionPost.getId(), - request, - imageFiles - )) + postCommandService.updatePost( + user1, + questionPost.getId(), + request, + imageFiles + )) .isInstanceOf(CustomException.class) .hasMessage(CAN_NOT_DELETE_OR_UPDATE_QUESTION.getMessage()); } @@ -248,12 +247,12 @@ class 게시글_수정_테스트 { // when & then assertThatThrownBy(() -> - postCommandService.updatePost( - user1, - post.getId(), - request, - imageFiles - )) + postCommandService.updatePost( + user1, + post.getId(), + request, + imageFiles + )) .isInstanceOf(CustomException.class) .hasMessage(CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES.getMessage()); } @@ -289,10 +288,10 @@ class 게시글_삭제_테스트 { // when & then assertThatThrownBy(() -> - postCommandService.deletePostById( - user2, - post.getId() - )) + postCommandService.deletePostById( + user2, + post.getId() + )) .isInstanceOf(CustomException.class) .hasMessage(INVALID_POST_ACCESS.getMessage()); } @@ -301,10 +300,10 @@ class 게시글_삭제_테스트 { void 질문_게시글을_삭제하면_예외가_발생한다() { // when & then assertThatThrownBy(() -> - postCommandService.deletePostById( - user1, - questionPost.getId() - )) + postCommandService.deletePostById( + user1, + questionPost.getId() + )) .isInstanceOf(CustomException.class) .hasMessage(CAN_NOT_DELETE_OR_UPDATE_QUESTION.getMessage()); } diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java index d214d5a04..36de90c5f 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java @@ -1,5 +1,11 @@ package com.example.solidconnection.community.post.service; +import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_POST_LIKE; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_LIKE; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.board.fixture.BoardFixture; import com.example.solidconnection.community.post.domain.Post; @@ -18,12 +24,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_POST_LIKE; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_LIKE; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; - @TestContainerSpringBootTest @DisplayName("게시글 좋아요 서비스 테스트") class PostLikeServiceTest { @@ -90,10 +90,10 @@ class 게시글_좋아요_테스트 { // when & then assertThatThrownBy(() -> - postLikeService.likePost( - user, - post.getId() - )) + postLikeService.likePost( + user, + post.getId() + )) .isInstanceOf(CustomException.class) .hasMessage(DUPLICATE_POST_LIKE.getMessage()); } @@ -125,10 +125,10 @@ class 게시글_좋아요_취소_테스트 { void 좋아요하지_않은_게시글을_좋아요_취소하면_예외가_발생한다() { // when & then assertThatThrownBy(() -> - postLikeService.dislikePost( - user, - post.getId() - )) + postLikeService.dislikePost( + user, + post.getId() + )) .isInstanceOf(CustomException.class) .hasMessage(INVALID_POST_LIKE.getMessage()); } diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java index 3d198251a..d3c474ba0 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java @@ -1,5 +1,8 @@ package com.example.solidconnection.community.post.service; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.community.board.domain.BoardCode; import com.example.solidconnection.community.board.fixture.BoardFixture; import com.example.solidconnection.community.comment.domain.Comment; @@ -14,17 +17,13 @@ import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.util.RedisUtils; +import java.time.ZonedDateTime; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import java.time.ZonedDateTime; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; - @TestContainerSpringBootTest @DisplayName("게시글 조회 서비스 테스트") class PostQueryServiceTest { diff --git a/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java b/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java index 7c2e61bb0..8c36bfbab 100644 --- a/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java +++ b/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java @@ -1,5 +1,7 @@ package com.example.solidconnection.concurrency; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.example.solidconnection.community.board.domain.Board; import com.example.solidconnection.community.board.fixture.BoardFixture; import com.example.solidconnection.community.post.domain.Post; @@ -10,17 +12,14 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; @TestContainerSpringBootTest @DisplayName("게시글 좋아요 동시성 테스트") diff --git a/src/test/java/com/example/solidconnection/concurrency/PostViewCountConcurrencyTest.java b/src/test/java/com/example/solidconnection/concurrency/PostViewCountConcurrencyTest.java index 8f61f0e75..4396e697b 100644 --- a/src/test/java/com/example/solidconnection/concurrency/PostViewCountConcurrencyTest.java +++ b/src/test/java/com/example/solidconnection/concurrency/PostViewCountConcurrencyTest.java @@ -1,5 +1,8 @@ package com.example.solidconnection.concurrency; +import static com.example.solidconnection.community.post.service.RedisConstants.VALIDATE_VIEW_COUNT_TTL; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.example.solidconnection.community.board.domain.Board; import com.example.solidconnection.community.board.repository.BoardRepository; import com.example.solidconnection.community.post.domain.Post; @@ -10,20 +13,16 @@ import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.util.RedisUtils; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import static com.example.solidconnection.community.post.service.RedisConstants.VALIDATE_VIEW_COUNT_TTL; -import static org.junit.jupiter.api.Assertions.assertEquals; - @TestContainerSpringBootTest @DisplayName("게시글 조회수 동시성 테스트") class PostViewCountConcurrencyTest { diff --git a/src/test/java/com/example/solidconnection/concurrency/ThunderingHerdTest.java b/src/test/java/com/example/solidconnection/concurrency/ThunderingHerdTest.java index d2cadca65..566d2c8d0 100644 --- a/src/test/java/com/example/solidconnection/concurrency/ThunderingHerdTest.java +++ b/src/test/java/com/example/solidconnection/concurrency/ThunderingHerdTest.java @@ -4,12 +4,6 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.redis.core.RedisTemplate; - import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -17,6 +11,11 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; @TestContainerSpringBootTest @DisplayName("ThunderingHerd 테스트") diff --git a/src/test/java/com/example/solidconnection/database/DatabaseConnectionTest.java b/src/test/java/com/example/solidconnection/database/DatabaseConnectionTest.java index ca3c64c7a..aa7297083 100644 --- a/src/test/java/com/example/solidconnection/database/DatabaseConnectionTest.java +++ b/src/test/java/com/example/solidconnection/database/DatabaseConnectionTest.java @@ -1,5 +1,12 @@ package com.example.solidconnection.database; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.Objects; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -9,14 +16,6 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.jdbc.core.JdbcTemplate; -import java.sql.DatabaseMetaData; -import java.sql.SQLException; -import java.util.Objects; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; - @Disabled @AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2, replace = AutoConfigureTestDatabase.Replace.ANY) @DataJpaTest diff --git a/src/test/java/com/example/solidconnection/database/RedisConnectionTest.java b/src/test/java/com/example/solidconnection/database/RedisConnectionTest.java index 527ae7e07..487811392 100644 --- a/src/test/java/com/example/solidconnection/database/RedisConnectionTest.java +++ b/src/test/java/com/example/solidconnection/database/RedisConnectionTest.java @@ -1,5 +1,7 @@ package com.example.solidconnection.database; +import static org.assertj.core.api.Assertions.assertThat; + import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -7,8 +9,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; -import static org.assertj.core.api.Assertions.assertThat; - @Disabled @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class RedisConnectionTest { diff --git a/src/test/java/com/example/solidconnection/location/country/repository/CountryRepositoryForTest.java b/src/test/java/com/example/solidconnection/location/country/repository/CountryRepositoryForTest.java index c9c7400a9..6e9e1c52f 100644 --- a/src/test/java/com/example/solidconnection/location/country/repository/CountryRepositoryForTest.java +++ b/src/test/java/com/example/solidconnection/location/country/repository/CountryRepositoryForTest.java @@ -1,9 +1,8 @@ package com.example.solidconnection.location.country.repository; import com.example.solidconnection.location.country.domain.Country; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; public interface CountryRepositoryForTest extends JpaRepository { diff --git a/src/test/java/com/example/solidconnection/location/country/repository/InterestedCountryRepositoryTest.java b/src/test/java/com/example/solidconnection/location/country/repository/InterestedCountryRepositoryTest.java index 78f0a27e5..4d27b95f2 100644 --- a/src/test/java/com/example/solidconnection/location/country/repository/InterestedCountryRepositoryTest.java +++ b/src/test/java/com/example/solidconnection/location/country/repository/InterestedCountryRepositoryTest.java @@ -1,5 +1,8 @@ package com.example.solidconnection.location.country.repository; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + import com.example.solidconnection.location.country.domain.Country; import com.example.solidconnection.location.country.domain.InterestedCountry; import com.example.solidconnection.location.country.fixture.CountryFixture; @@ -11,9 +14,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; - @TestContainerSpringBootTest public class InterestedCountryRepositoryTest { @@ -34,12 +34,12 @@ class 사용자와_나라는_복합_유니크_제약_조건을_가진다 { // given SiteUser user = siteUserFixture.사용자(); Country country = countryFixture.미국(); - + InterestedCountry firstInterest = new InterestedCountry(user, country); interestedCountryRepository.save(firstInterest); - + InterestedCountry secondInterest = new InterestedCountry(user, country); - + // when & then assertThatCode(() -> interestedCountryRepository.save(secondInterest)) .isInstanceOf(DataIntegrityViolationException.class); @@ -51,12 +51,12 @@ class 사용자와_나라는_복합_유니크_제약_조건을_가진다 { SiteUser user1 = siteUserFixture.사용자(1, "user1"); SiteUser user2 = siteUserFixture.사용자(2, "user2"); Country country = countryFixture.미국(); - + InterestedCountry firstInterest = new InterestedCountry(user1, country); interestedCountryRepository.save(firstInterest); - + InterestedCountry secondInterest = new InterestedCountry(user2, country); - + // when & then assertThatCode(() -> { InterestedCountry saved = interestedCountryRepository.save(secondInterest); @@ -70,12 +70,12 @@ class 사용자와_나라는_복합_유니크_제약_조건을_가진다 { SiteUser user = siteUserFixture.사용자(); Country country1 = countryFixture.미국(); Country country2 = countryFixture.일본(); - + InterestedCountry firstInterest = new InterestedCountry(user, country1); interestedCountryRepository.save(firstInterest); - + InterestedCountry secondInterest = new InterestedCountry(user, country2); - + // when & then assertThatCode(() -> { InterestedCountry saved = interestedCountryRepository.save(secondInterest); diff --git a/src/test/java/com/example/solidconnection/location/region/repository/InterestedRegionRepositoryTest.java b/src/test/java/com/example/solidconnection/location/region/repository/InterestedRegionRepositoryTest.java index 0fb36cf5d..3fc993b93 100644 --- a/src/test/java/com/example/solidconnection/location/region/repository/InterestedRegionRepositoryTest.java +++ b/src/test/java/com/example/solidconnection/location/region/repository/InterestedRegionRepositoryTest.java @@ -1,5 +1,8 @@ package com.example.solidconnection.location.region.repository; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + import com.example.solidconnection.location.region.domain.InterestedRegion; import com.example.solidconnection.location.region.domain.Region; import com.example.solidconnection.location.region.fixture.RegionFixture; @@ -11,9 +14,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; - @TestContainerSpringBootTest public class InterestedRegionRepositoryTest { diff --git a/src/test/java/com/example/solidconnection/location/region/repository/RegionRepositoryForTest.java b/src/test/java/com/example/solidconnection/location/region/repository/RegionRepositoryForTest.java index a27c16b9b..05935f63f 100644 --- a/src/test/java/com/example/solidconnection/location/region/repository/RegionRepositoryForTest.java +++ b/src/test/java/com/example/solidconnection/location/region/repository/RegionRepositoryForTest.java @@ -1,9 +1,8 @@ package com.example.solidconnection.location.region.repository; import com.example.solidconnection.location.region.domain.Region; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; public interface RegionRepositoryForTest extends JpaRepository { diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java index 3d5896662..0315777ab 100644 --- a/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java +++ b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java @@ -1,15 +1,14 @@ package com.example.solidconnection.mentor.fixture; +import static java.time.ZoneOffset.UTC; +import static java.time.temporal.ChronoUnit.MICROS; + import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.mentor.domain.Mentoring; +import java.time.ZonedDateTime; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; -import java.time.ZonedDateTime; - -import static java.time.ZoneOffset.UTC; -import static java.time.temporal.ChronoUnit.MICROS; - @TestComponent @RequiredArgsConstructor public class MentoringFixture { diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java index 0c579de5b..9400a0a0f 100644 --- a/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java @@ -3,11 +3,10 @@ import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.mentor.domain.Mentoring; import com.example.solidconnection.mentor.repository.MentoringRepository; +import java.time.ZonedDateTime; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; -import java.time.ZonedDateTime; - @TestComponent @RequiredArgsConstructor public class MentoringFixtureBuilder { diff --git a/src/test/java/com/example/solidconnection/mentor/repository/ChannelRepositoryForTest.java b/src/test/java/com/example/solidconnection/mentor/repository/ChannelRepositoryForTest.java index 9e6fee1de..16000dddc 100644 --- a/src/test/java/com/example/solidconnection/mentor/repository/ChannelRepositoryForTest.java +++ b/src/test/java/com/example/solidconnection/mentor/repository/ChannelRepositoryForTest.java @@ -1,9 +1,8 @@ package com.example.solidconnection.mentor.repository; import com.example.solidconnection.mentor.domain.Channel; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; public interface ChannelRepositoryForTest extends JpaRepository { diff --git a/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java b/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java index c68deedb7..ffa55dd13 100644 --- a/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java +++ b/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java @@ -1,22 +1,21 @@ package com.example.solidconnection.mentor.repository; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.mentor.fixture.MentorFixture; import com.example.solidconnection.mentor.fixture.MentoringFixture; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.util.List; +import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; - @DisplayName("멘토 배치 조회 레포지토리 테스트") @TestContainerSpringBootTest class MentorBatchQueryRepositoryTest { diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java index b7c5724a7..e00eaadae 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java @@ -1,5 +1,10 @@ package com.example.solidconnection.mentor.service; +import static com.example.solidconnection.mentor.domain.ChannelType.BLOG; +import static com.example.solidconnection.mentor.domain.ChannelType.INSTAGRAM; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.mentor.domain.Channel; import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.mentor.dto.ChannelRequest; @@ -13,19 +18,13 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import java.util.List; - -import static com.example.solidconnection.mentor.domain.ChannelType.BLOG; -import static com.example.solidconnection.mentor.domain.ChannelType.INSTAGRAM; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; - @TestContainerSpringBootTest @DisplayName("멘토 마이페이지 서비스 테스트") class MentorMyPageServiceTest { diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java index cb1410aee..ba44c6db3 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java @@ -1,5 +1,9 @@ package com.example.solidconnection.mentor.service; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.common.dto.SliceResponse; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.exception.ErrorCode; @@ -14,6 +18,9 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -21,14 +28,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; - @DisplayName("멘토 조회 서비스 테스트") @TestContainerSpringBootTest class MentorQueryServiceTest { diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java index 5e0ef04c7..fb8fbd608 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java @@ -1,5 +1,13 @@ package com.example.solidconnection.mentor.service; +import static com.example.solidconnection.common.exception.ErrorCode.MENTORING_ALREADY_CONFIRMED; +import static com.example.solidconnection.common.exception.ErrorCode.MENTORING_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.REJECTED_REASON_REQUIRED; +import static com.example.solidconnection.common.exception.ErrorCode.UNAUTHORIZED_MENTORING; +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 com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.mentor.domain.Mentor; @@ -22,14 +30,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import static com.example.solidconnection.common.exception.ErrorCode.MENTORING_ALREADY_CONFIRMED; -import static com.example.solidconnection.common.exception.ErrorCode.MENTORING_NOT_FOUND; -import static com.example.solidconnection.common.exception.ErrorCode.REJECTED_REASON_REQUIRED; -import static com.example.solidconnection.common.exception.ErrorCode.UNAUTHORIZED_MENTORING; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; - @TestContainerSpringBootTest @DisplayName("멘토링 CUD 서비스 테스트") class MentoringCommandServiceTest { @@ -148,7 +148,7 @@ class 멘토링_승인_거절_테스트 { // when & then assertThatThrownBy(() -> - mentoringCommandService.confirmMentoring(mentorUser1.getId(), mentoring.getId(), request)) + mentoringCommandService.confirmMentoring(mentorUser1.getId(), mentoring.getId(), request)) .isInstanceOf(CustomException.class) .hasMessage(REJECTED_REASON_REQUIRED.getMessage()); } diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java index 869de8a3c..5951920ff 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java @@ -1,5 +1,8 @@ package com.example.solidconnection.mentor.service; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.mentor.domain.Mentoring; import com.example.solidconnection.mentor.dto.MentoringCountResponse; @@ -16,9 +19,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; - @TestContainerSpringBootTest @DisplayName("멘토링 조회 서비스 테스트") class MentoringQueryServiceTest { diff --git a/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java b/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java index f9ec5de6d..71c927efc 100644 --- a/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java @@ -1,5 +1,15 @@ package com.example.solidconnection.news.service; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_NEWS_ACCESS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.never; +import static org.mockito.BDDMockito.then; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.news.config.NewsProperties; import com.example.solidconnection.news.domain.News; @@ -23,16 +33,6 @@ import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_NEWS_ACCESS; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.never; -import static org.mockito.BDDMockito.then; - @TestContainerSpringBootTest @DisplayName("소식지 생성/수정/삭제 서비스 테스트") class NewsCommandServiceTest { diff --git a/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java b/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java index d8a51d7d9..e620fa206 100644 --- a/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java @@ -1,5 +1,10 @@ package com.example.solidconnection.news.service; +import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_NEWS; +import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_NEWS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.news.domain.News; import com.example.solidconnection.news.dto.LikedNewsResponse; @@ -14,11 +19,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_NEWS; -import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_NEWS; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; - @TestContainerSpringBootTest @DisplayName("소식지 좋아요 서비스 테스트") class NewsLikeServiceTest { @@ -88,8 +88,8 @@ class 소식지_좋아요를_등록한다 { // when & then assertThatCode(() -> newsLikeService.addNewsLike(user.getId(), news.getId())) - .isInstanceOf(CustomException.class) - .hasMessage(ALREADY_LIKED_NEWS.getMessage()); + .isInstanceOf(CustomException.class) + .hasMessage(ALREADY_LIKED_NEWS.getMessage()); } } @@ -112,8 +112,8 @@ class 소식지_좋아요를_취소한다 { void 좋아요하지_않았으면_예외가_발생한다() { // when & then assertThatCode(() -> newsLikeService.cancelNewsLike(user.getId(), news.getId())) - .isInstanceOf(CustomException.class) - .hasMessage(NOT_LIKED_NEWS.getMessage()); + .isInstanceOf(CustomException.class) + .hasMessage(NOT_LIKED_NEWS.getMessage()); } } } diff --git a/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java b/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java index 73926a6dc..12bfe2c59 100644 --- a/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java @@ -1,22 +1,21 @@ package com.example.solidconnection.news.service; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.news.domain.News; -import com.example.solidconnection.news.dto.NewsResponse; import com.example.solidconnection.news.dto.NewsListResponse; +import com.example.solidconnection.news.dto.NewsResponse; import com.example.solidconnection.news.fixture.NewsFixture; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.util.Comparator; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import java.util.Comparator; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; - @TestContainerSpringBootTest @DisplayName("소식지 조회 서비스 테스트") class NewsQueryServiceTest { @@ -47,8 +46,8 @@ class NewsQueryServiceTest { assertAll( () -> assertThat(response.newsResponseList()).hasSize(newsList.size()), () -> assertThat(response.newsResponseList()) - .extracting(NewsResponse::updatedAt) - .isSortedAccordingTo(Comparator.reverseOrder()) + .extracting(NewsResponse::updatedAt) + .isSortedAccordingTo(Comparator.reverseOrder()) ); } } diff --git a/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixture.java b/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixture.java index 49938a284..256e5c720 100644 --- a/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixture.java +++ b/src/test/java/com/example/solidconnection/score/fixture/GpaScoreFixture.java @@ -13,7 +13,7 @@ public class GpaScoreFixture { private final GpaScoreFixtureBuilder gpaScoreFixtureBuilder; - public GpaScore GPA_점수 (VerifyStatus verifyStatus, SiteUser siteUser) { + public GpaScore GPA_점수(VerifyStatus verifyStatus, SiteUser siteUser) { return gpaScoreFixtureBuilder.gpaScore() .gpa(new Gpa(4.0, 4.5, "/gpa-report.pdf")) .verifyStatus(verifyStatus) diff --git a/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixture.java b/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixture.java index 476783fe9..ce146a21c 100644 --- a/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixture.java +++ b/src/test/java/com/example/solidconnection/score/fixture/LanguageTestScoreFixture.java @@ -1,5 +1,7 @@ package com.example.solidconnection.score.fixture; +import static com.example.solidconnection.university.domain.LanguageTestType.TOEIC; + import com.example.solidconnection.application.domain.LanguageTest; import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.score.domain.LanguageTestScore; @@ -7,15 +9,13 @@ import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; -import static com.example.solidconnection.university.domain.LanguageTestType.TOEIC; - @TestComponent @RequiredArgsConstructor public class LanguageTestScoreFixture { private final LanguageTestScoreFixtureBuilder languageTestScoreFixtureBuilder; - public LanguageTestScore 어학_점수 (VerifyStatus verifyStatus, SiteUser siteUser) { + public LanguageTestScore 어학_점수(VerifyStatus verifyStatus, SiteUser siteUser) { return languageTestScoreFixtureBuilder.languageTestScore() .languageTest(new LanguageTest(TOEIC, "500", "/language-report.pdf")) .verifyStatus(verifyStatus) diff --git a/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java b/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java index 1f6dfad1e..e143579df 100644 --- a/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java @@ -1,5 +1,8 @@ package com.example.solidconnection.score.service; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; + import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.s3.domain.ImgType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; @@ -18,6 +21,7 @@ import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.university.domain.LanguageTestType; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -25,11 +29,6 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.mock.web.MockMultipartFile; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; - @TestContainerSpringBootTest @DisplayName("점수 서비스 테스트") class ScoreServiceTest { diff --git a/src/test/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspectTest.java b/src/test/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspectTest.java index a3a1333e8..62be411ee 100644 --- a/src/test/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspectTest.java +++ b/src/test/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspectTest.java @@ -1,5 +1,9 @@ package com.example.solidconnection.security.aspect; +import static com.example.solidconnection.common.exception.ErrorCode.ACCESS_DENIED; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.security.annotation.RequireRoleAccess; import com.example.solidconnection.siteuser.domain.Role; @@ -13,10 +17,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; -import static com.example.solidconnection.common.exception.ErrorCode.ACCESS_DENIED; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; - @TestContainerSpringBootTest @DisplayName("권한 검사 Aspect 테스트") class RoleAuthorizationAspectTest { @@ -30,7 +30,7 @@ class RoleAuthorizationAspectTest { @Test void 요구하는_역할을_가진_사용자는_메서드를_정상적으로_호출할_수_있다() { // given - SiteUser admin = siteUserFixture.관리자(); + SiteUser admin = siteUserFixture.관리자(); SiteUser mentor = siteUserFixture.멘토(1, "mentor"); // when & then @@ -56,9 +56,9 @@ class RoleAuthorizationAspectTest { @Test void 역할을_요구하지_않는_메서드는_누구나_호출할_수_있다() { // given - SiteUser admin = siteUserFixture.관리자(); + SiteUser admin = siteUserFixture.관리자(); SiteUser mentor = siteUserFixture.멘토(1, "mentor"); - SiteUser user = siteUserFixture.사용자(); + SiteUser user = siteUserFixture.사용자(); // when & then assertAll( @@ -73,6 +73,7 @@ class RoleAuthorizationAspectTest { @TestConfiguration static class TestConfig { + @Bean public TestService testService() { return new TestService(); diff --git a/src/test/java/com/example/solidconnection/security/authentication/SiteUserAuthenticationTest.java b/src/test/java/com/example/solidconnection/security/authentication/SiteUserAuthenticationTest.java index 29b47869d..e4586019e 100644 --- a/src/test/java/com/example/solidconnection/security/authentication/SiteUserAuthenticationTest.java +++ b/src/test/java/com/example/solidconnection/security/authentication/SiteUserAuthenticationTest.java @@ -1,13 +1,13 @@ package com.example.solidconnection.security.authentication; +import static org.assertj.core.api.Assertions.assertThat; + import com.example.solidconnection.security.userdetails.SiteUserDetails; import com.example.solidconnection.siteuser.domain.ExchangeStatus; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; import org.junit.jupiter.api.Test; -import static org.assertj.core.api.Assertions.assertThat; - class SiteUserAuthenticationTest { @Test diff --git a/src/test/java/com/example/solidconnection/security/filter/AuthorizationHeaderParserTest.java b/src/test/java/com/example/solidconnection/security/filter/AuthorizationHeaderParserTest.java index 1d4ba2533..809fb8c8a 100644 --- a/src/test/java/com/example/solidconnection/security/filter/AuthorizationHeaderParserTest.java +++ b/src/test/java/com/example/solidconnection/security/filter/AuthorizationHeaderParserTest.java @@ -1,16 +1,15 @@ package com.example.solidconnection.security.filter; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.util.Optional; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpServletRequest; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; - @TestContainerSpringBootTest class AuthorizationHeaderParserTest { diff --git a/src/test/java/com/example/solidconnection/security/filter/ExceptionHandlerFilterTest.java b/src/test/java/com/example/solidconnection/security/filter/ExceptionHandlerFilterTest.java index 086104a24..aae969160 100644 --- a/src/test/java/com/example/solidconnection/security/filter/ExceptionHandlerFilterTest.java +++ b/src/test/java/com/example/solidconnection/security/filter/ExceptionHandlerFilterTest.java @@ -1,11 +1,19 @@ package com.example.solidconnection.security.filter; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.then; +import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.BDDMockito.willThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.support.TestContainerSpringBootTest; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -19,15 +27,6 @@ import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContextHolder; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.then; -import static org.mockito.BDDMockito.willDoNothing; -import static org.mockito.BDDMockito.willThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; - @TestContainerSpringBootTest class ExceptionHandlerFilterTest { diff --git a/src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java b/src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java index 229fed27b..009275e06 100644 --- a/src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java +++ b/src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java @@ -1,7 +1,11 @@ package com.example.solidconnection.security.filter; -import com.example.solidconnection.security.authentication.SiteUserAuthentication; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.spy; + import com.example.solidconnection.auth.token.config.JwtProperties; +import com.example.solidconnection.security.authentication.SiteUserAuthentication; import com.example.solidconnection.security.userdetails.SiteUserDetailsService; import com.example.solidconnection.support.TestContainerSpringBootTest; import io.jsonwebtoken.Jwts; @@ -9,6 +13,7 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.util.Date; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -18,12 +23,6 @@ import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.core.context.SecurityContextHolder; -import java.util.Date; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.spy; - @TestContainerSpringBootTest @DisplayName("토큰 인증 필터 테스트") class JwtAuthenticationFilterTest { diff --git a/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java b/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java index 188e09f2c..3d78f1307 100644 --- a/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java +++ b/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java @@ -1,13 +1,21 @@ package com.example.solidconnection.security.filter; -import com.example.solidconnection.common.exception.CustomException; +import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST; +import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_SIGN_OUT; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.spy; + import com.example.solidconnection.auth.token.config.JwtProperties; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.support.TestContainerSpringBootTest; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.util.Date; +import java.util.Objects; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -16,15 +24,6 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import java.util.Date; -import java.util.Objects; - -import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST; -import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_SIGN_OUT; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.spy; - @TestContainerSpringBootTest @DisplayName("로그아웃 체크 필터 테스트") class SignOutCheckFilterTest { diff --git a/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java b/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java index 0c56d302a..178e9e50c 100644 --- a/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java +++ b/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java @@ -1,14 +1,22 @@ package com.example.solidconnection.security.provider; +import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.security.authentication.SiteUserAuthentication; -import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.security.userdetails.SiteUserDetails; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import java.net.PasswordAuthentication; +import java.util.Date; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -16,15 +24,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; -import java.net.PasswordAuthentication; -import java.util.Date; - -import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; - @TestContainerSpringBootTest @DisplayName("사용자 인증정보 provider 테스트") class SiteUserAuthenticationProviderTest { diff --git a/src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsServiceTest.java b/src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsServiceTest.java index babdd9196..d82e7406b 100644 --- a/src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsServiceTest.java +++ b/src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsServiceTest.java @@ -1,23 +1,22 @@ package com.example.solidconnection.security.userdetails; +import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.time.LocalDate; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import java.time.LocalDate; - -import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; - @DisplayName("사용자 인증 정보 서비스 테스트") @TestContainerSpringBootTest class SiteUserDetailsServiceTest { diff --git a/src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsTest.java b/src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsTest.java index 8f533e8b7..5d59e99f9 100644 --- a/src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsTest.java +++ b/src/test/java/com/example/solidconnection/security/userdetails/SiteUserDetailsTest.java @@ -1,17 +1,16 @@ package com.example.solidconnection.security.userdetails; +import static org.assertj.core.api.Assertions.assertThat; + import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.util.Collection; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; -import java.util.Collection; - -import static org.assertj.core.api.Assertions.assertThat; - @DisplayName("사용자 인증 정보 테스트") @TestContainerSpringBootTest class SiteUserDetailsTest { diff --git a/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java b/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java index b7bf16472..cca31fbbe 100644 --- a/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/repository/SiteUserRepositoryTest.java @@ -1,5 +1,7 @@ package com.example.solidconnection.siteuser.repository; +import static org.assertj.core.api.Assertions.assertThatCode; + import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.ExchangeStatus; import com.example.solidconnection.siteuser.domain.Role; @@ -10,8 +12,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; -import static org.assertj.core.api.Assertions.assertThatCode; - @TestContainerDataJpaTest class SiteUserRepositoryTest { diff --git a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java index e93ba3278..5fbebdeff 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java @@ -1,5 +1,17 @@ package com.example.solidconnection.siteuser.service; +import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; +import static com.example.solidconnection.siteuser.service.MyPageService.MIN_DAYS_BETWEEN_NICKNAME_CHANGES; +import static com.example.solidconnection.siteuser.service.MyPageService.NICKNAME_LAST_CHANGE_DATE_FORMAT; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.BDDMockito.any; +import static org.mockito.BDDMockito.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.never; +import static org.mockito.BDDMockito.then; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.s3.domain.ImgType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; @@ -15,6 +27,7 @@ import com.example.solidconnection.university.domain.LikedUnivApplyInfo; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; +import java.time.LocalDateTime; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -24,20 +37,6 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.mock.web.MockMultipartFile; -import java.time.LocalDateTime; - -import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; -import static com.example.solidconnection.siteuser.service.MyPageService.MIN_DAYS_BETWEEN_NICKNAME_CHANGES; -import static com.example.solidconnection.siteuser.service.MyPageService.NICKNAME_LAST_CHANGE_DATE_FORMAT; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.BDDMockito.any; -import static org.mockito.BDDMockito.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.never; -import static org.mockito.BDDMockito.then; - @TestContainerSpringBootTest @DisplayName("마이페이지 서비스 테스트") class MyPageServiceTest { @@ -78,7 +77,6 @@ void setUp() { // when MyPageResponse response = myPageService.getMyPageInfo(user); - // then Assertions.assertAll( () -> assertThat(response.nickname()).isEqualTo(user.getNickname()), @@ -137,7 +135,7 @@ class 프로필_이미지_수정_테스트 { // then then(s3Service).should().deleteExProfile(argThat(user -> - user.getId().equals(커스텀_프로필_사용자.getId()))); + user.getId().equals(커스텀_프로필_사용자.getId()))); } } diff --git a/src/test/java/com/example/solidconnection/siteuser/service/SiteUserServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/SiteUserServiceTest.java index 665fb11da..3a81d40e2 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/SiteUserServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/SiteUserServiceTest.java @@ -1,5 +1,7 @@ package com.example.solidconnection.siteuser.service; +import static org.assertj.core.api.Assertions.assertThat; + import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.NicknameExistsResponse; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; @@ -10,8 +12,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import static org.assertj.core.api.Assertions.assertThat; - @TestContainerSpringBootTest @DisplayName("유저 서비스 테스트") class SiteUserServiceTest { diff --git a/src/test/java/com/example/solidconnection/support/DatabaseCleaner.java b/src/test/java/com/example/solidconnection/support/DatabaseCleaner.java index aee6a2bc6..b8dd72670 100644 --- a/src/test/java/com/example/solidconnection/support/DatabaseCleaner.java +++ b/src/test/java/com/example/solidconnection/support/DatabaseCleaner.java @@ -2,14 +2,13 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; +import java.util.List; +import java.util.Objects; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.Objects; - @Component public class DatabaseCleaner { @@ -38,11 +37,11 @@ private void truncate() { @SuppressWarnings("unchecked") private List getTruncateQueries() { String sql = """ - SELECT CONCAT('TRUNCATE TABLE ', TABLE_NAME, ';') AS q - FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_SCHEMA = (SELECT DATABASE()) - AND TABLE_TYPE = 'BASE TABLE' - """; + SELECT CONCAT('TRUNCATE TABLE ', TABLE_NAME, ';') AS q + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = (SELECT DATABASE()) + AND TABLE_TYPE = 'BASE TABLE' + """; return em.createNativeQuery(sql).getResultList(); } diff --git a/src/test/java/com/example/solidconnection/support/TestContainerDataJpaTest.java b/src/test/java/com/example/solidconnection/support/TestContainerDataJpaTest.java index 415b21e78..31f5f6d2a 100644 --- a/src/test/java/com/example/solidconnection/support/TestContainerDataJpaTest.java +++ b/src/test/java/com/example/solidconnection/support/TestContainerDataJpaTest.java @@ -1,14 +1,13 @@ package com.example.solidconnection.support; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; -import org.springframework.test.context.ContextConfiguration; -import org.testcontainers.junit.jupiter.Testcontainers; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.ContextConfiguration; +import org.testcontainers.junit.jupiter.Testcontainers; @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @@ -17,4 +16,5 @@ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestContainerDataJpaTest { + } diff --git a/src/test/java/com/example/solidconnection/support/TestContainerSpringBootTest.java b/src/test/java/com/example/solidconnection/support/TestContainerSpringBootTest.java index 462400400..c99044a43 100644 --- a/src/test/java/com/example/solidconnection/support/TestContainerSpringBootTest.java +++ b/src/test/java/com/example/solidconnection/support/TestContainerSpringBootTest.java @@ -1,5 +1,9 @@ package com.example.solidconnection.support; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; @@ -7,11 +11,6 @@ import org.springframework.test.context.ContextConfiguration; import org.testcontainers.junit.jupiter.Testcontainers; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - @ComponentScan(basePackages = "com.example.solidconnection") @ExtendWith({DatabaseClearExtension.class}) @ContextConfiguration(initializers = {RedisTestContainer.class, MySQLTestContainer.class}) @@ -21,4 +20,5 @@ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestContainerSpringBootTest { + } diff --git a/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java b/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java index 8daacdd66..94b7eb7fc 100644 --- a/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java +++ b/src/test/java/com/example/solidconnection/university/dto/validation/ValidUnivApplyInfoChoiceValidatorTest.java @@ -1,21 +1,20 @@ package com.example.solidconnection.university.dto.validation; +import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_UNIV_APPLY_INFO_CHOICE; +import static com.example.solidconnection.common.exception.ErrorCode.FIRST_CHOICE_REQUIRED; +import static com.example.solidconnection.common.exception.ErrorCode.THIRD_CHOICE_REQUIRES_SECOND; +import static org.assertj.core.api.Assertions.assertThat; + import com.example.solidconnection.application.dto.UnivApplyInfoChoiceRequest; import jakarta.validation.ConstraintViolation; import jakarta.validation.Validation; import jakarta.validation.Validator; import jakarta.validation.ValidatorFactory; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import java.util.Set; - -import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_UNIV_APPLY_INFO_CHOICE; -import static com.example.solidconnection.common.exception.ErrorCode.FIRST_CHOICE_REQUIRED; -import static com.example.solidconnection.common.exception.ErrorCode.THIRD_CHOICE_REQUIRES_SECOND; -import static org.assertj.core.api.Assertions.assertThat; - @DisplayName("대학 선택 유효성 검사 테스트") class ValidUnivApplyInfoChoiceValidatorTest { diff --git a/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixtureBuilder.java b/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixtureBuilder.java index 0989beaca..55f81e9eb 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixtureBuilder.java @@ -1,16 +1,15 @@ package com.example.solidconnection.university.fixture; -import com.example.solidconnection.university.domain.University; +import static com.example.solidconnection.university.domain.SemesterAvailableForDispatch.ONE_SEMESTER; +import static com.example.solidconnection.university.domain.TuitionFeeType.HOME_UNIVERSITY_PAYMENT; + import com.example.solidconnection.university.domain.UnivApplyInfo; +import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; +import java.util.HashSet; import lombok.RequiredArgsConstructor; import org.springframework.boot.test.context.TestComponent; -import java.util.HashSet; - -import static com.example.solidconnection.university.domain.SemesterAvailableForDispatch.ONE_SEMESTER; -import static com.example.solidconnection.university.domain.TuitionFeeType.HOME_UNIVERSITY_PAYMENT; - @TestComponent @RequiredArgsConstructor public class UnivApplyInfoFixtureBuilder { diff --git a/src/test/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepositoryTest.java b/src/test/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepositoryTest.java index 72e3610a6..785b501b5 100644 --- a/src/test/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepositoryTest.java +++ b/src/test/java/com/example/solidconnection/university/repository/LikedUnivApplyInfoRepositoryTest.java @@ -1,5 +1,7 @@ package com.example.solidconnection.university.repository; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; + import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; @@ -12,8 +14,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; - @TestContainerSpringBootTest @DisplayName("대학교 좋아요 레파지토리 테스트") public class LikedUnivApplyInfoRepositoryTest { diff --git a/src/test/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendServiceTest.java b/src/test/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendServiceTest.java index e2dcdd255..4e22897ef 100644 --- a/src/test/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/GeneralUnivApplyInfoRecommendServiceTest.java @@ -1,20 +1,19 @@ package com.example.solidconnection.university.service; +import static com.example.solidconnection.university.service.UnivApplyInfoRecommendService.RECOMMEND_UNIV_APPLY_INFO_NUM; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import java.util.List; - -import static com.example.solidconnection.university.service.UnivApplyInfoRecommendService.RECOMMEND_UNIV_APPLY_INFO_NUM; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; - @TestContainerSpringBootTest @DisplayName("대학 지원 정보 공통 추천 서비스 테스트") class GeneralUnivApplyInfoRecommendServiceTest { diff --git a/src/test/java/com/example/solidconnection/university/service/LikedUnivApplyInfoServiceTest.java b/src/test/java/com/example/solidconnection/university/service/LikedUnivApplyInfoServiceTest.java index cc5220756..918838ce1 100644 --- a/src/test/java/com/example/solidconnection/university/service/LikedUnivApplyInfoServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/LikedUnivApplyInfoServiceTest.java @@ -1,5 +1,11 @@ package com.example.solidconnection.university.service; +import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_UNIV_APPLY_INFO; +import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_UNIV_APPLY_INFO; +import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; @@ -10,20 +16,13 @@ import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import java.util.List; - -import static com.example.solidconnection.common.exception.ErrorCode.ALREADY_LIKED_UNIV_APPLY_INFO; -import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_UNIV_APPLY_INFO; -import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; - @TestContainerSpringBootTest @DisplayName("대학 지원 정보 좋아요 서비스 테스트") class LikedUnivApplyInfoServiceTest { diff --git a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java index 949cbfc75..fd6b4b26d 100644 --- a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java @@ -1,5 +1,11 @@ package com.example.solidconnection.university.service; +import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.times; + import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.university.domain.LanguageTestType; @@ -11,19 +17,12 @@ import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; import com.example.solidconnection.university.repository.custom.UnivApplyInfoFilterRepository; +import java.util.List; 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 java.util.List; - -import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; -import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.times; - @TestContainerSpringBootTest @DisplayName("대학 지원 정보 조회 서비스 테스트") class UnivApplyInfoQueryServiceTest { diff --git a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendServiceTest.java index 96e8bf81d..94ad7b9ee 100644 --- a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendServiceTest.java @@ -1,5 +1,8 @@ package com.example.solidconnection.university.service; +import static com.example.solidconnection.university.service.UnivApplyInfoRecommendService.RECOMMEND_UNIV_APPLY_INFO_NUM; +import static org.assertj.core.api.Assertions.assertThat; + import com.example.solidconnection.location.country.domain.InterestedCountry; import com.example.solidconnection.location.country.fixture.CountryFixture; import com.example.solidconnection.location.country.repository.InterestedCountryRepository; @@ -13,16 +16,12 @@ import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; import com.example.solidconnection.university.dto.UnivApplyInfoRecommendsResponse; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import java.util.List; - -import static com.example.solidconnection.university.service.UnivApplyInfoRecommendService.RECOMMEND_UNIV_APPLY_INFO_NUM; -import static org.assertj.core.api.Assertions.assertThat; - @TestContainerSpringBootTest @DisplayName("대학 지원 정보 추천 서비스 테스트") class UnivApplyInfoRecommendServiceTest { @@ -146,6 +145,7 @@ void setUp() { .map(UnivApplyInfoPreviewResponse::from).toList() ); } + @Test void 일반_추천_대학_지원_정보를_조회한다() { // when From 69365db16b76eb5064bb57233b70fb00a20b1272 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Fri, 18 Jul 2025 23:08:15 +0900 Subject: [PATCH 46/90] =?UTF-8?q?chore:=20=EB=AC=B8=EC=84=9C,=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=20=ED=8C=8C=EC=9D=BC=EB=93=A4=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94=20=EB=B3=80=EA=B2=BD=20(#390)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: infra 관련 설정 파일 위치 이동 * chore: 스크립트 파일 위치 이동 * refactor: 위치 변경에 따른 상대 경로 수정 * chore: 경로와 관련된 변수 분리 --- docs/{ => infra-config}/config.alloy | 0 docs/{ => infra-config}/nginx.conf | 0 .../script/local_compose_down.sh | 4 ++- docs/script/local_compose_up.sh | 25 +++++++++++++++++++ local_compose_up.sh | 23 ----------------- 5 files changed, 28 insertions(+), 24 deletions(-) rename docs/{ => infra-config}/config.alloy (100%) rename docs/{ => infra-config}/nginx.conf (100%) rename local_compose_down.sh => docs/script/local_compose_down.sh (63%) create mode 100755 docs/script/local_compose_up.sh delete mode 100755 local_compose_up.sh diff --git a/docs/config.alloy b/docs/infra-config/config.alloy similarity index 100% rename from docs/config.alloy rename to docs/infra-config/config.alloy diff --git a/docs/nginx.conf b/docs/infra-config/nginx.conf similarity index 100% rename from docs/nginx.conf rename to docs/infra-config/nginx.conf diff --git a/local_compose_down.sh b/docs/script/local_compose_down.sh similarity index 63% rename from local_compose_down.sh rename to docs/script/local_compose_down.sh index 32792e490..ceb9df3e1 100755 --- a/local_compose_down.sh +++ b/docs/script/local_compose_down.sh @@ -2,8 +2,10 @@ set -e +BASE_DIR="$(cd "$(dirname "$0")/../.." && pwd)" + echo "Stopping all docker containers..." -docker compose -f docker-compose.local.yml down +docker compose -f "${BASE_DIR}/docker-compose.local.yml" down echo "Pruning unused Docker images..." docker image prune -f diff --git a/docs/script/local_compose_up.sh b/docs/script/local_compose_up.sh new file mode 100755 index 000000000..df36cb0ea --- /dev/null +++ b/docs/script/local_compose_up.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# 명령이 0이 아닌 종료값을 가질때 즉시 종료 +set -e + +BASE_DIR="$(cd "$(dirname "$0")/../.." && pwd)" + +if [ ! -d "${BASE_DIR}/mysql_data_local" ]; then + echo "mysql_data_local 디렉토리가 없습니다. ${BASE_DIR}/mysql_data_local 디렉토리를 생성합니다." + mkdir -p "${BASE_DIR}/mysql_data_local" +fi + +if [ ! -d "${BASE_DIR}/redis_data_local" ]; then + echo "redis_data_local 디렉토리가 없습니다. ${BASE_DIR}/redis_data_local 디렉토리를 생성합니다." + mkdir -p "${BASE_DIR}/redis_data_local" +fi + +echo "Starting all docker containers..." +docker compose -f "${BASE_DIR}/docker-compose.local.yml" up -d + +echo "Pruning unused Docker images..." +docker image prune -f + +echo "Containers are up and running." +docker compose ps -a diff --git a/local_compose_up.sh b/local_compose_up.sh deleted file mode 100755 index 400861e57..000000000 --- a/local_compose_up.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -# 명령이 0이 아닌 종료값을 가질때 즉시 종료 -set -e - -if [ ! -d "mysql_data_local" ]; then - echo "mysql_data_local 디렉토리가 없습니다. 디렉토리를 생성합니다." - mkdir -p mysql_data_local -fi - -if [ ! -d "redis_data_local" ]; then - echo "redis_data_local 디렉토리가 없습니다. 디렉토리를 생성합니다." - mkdir -p redis_data_local -fi - -echo "Starting all docker containers..." -docker compose -f docker-compose.local.yml up -d - -echo "Pruning unused Docker images..." -docker image prune -f - -echo "Containers are up and running." -docker compose ps -a From fcaba6166fdd07cfc3dd174235d99fc273d640f2 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Fri, 18 Jul 2025 23:42:05 +0900 Subject: [PATCH 47/90] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20Authentication=20=EC=83=81=EC=86=8D=20=EA=B4=80?= =?UTF-8?q?=EA=B3=84=20=ED=86=B5=ED=95=A9=20=EB=B0=8F=20=EC=9D=B4=EB=A6=84?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20(#388)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 의미상 중요도에 따라 나열 순서 변경 - '스프링 시큐리티의 용어'에 대해 주석으로 추가 설명을 한다. - 의미상 중요한 것을 위에 위치시킨다. * refactor: 토큰 인증 정보 클래스 이름 변경 - JwtAuthentication -> TokenAuthentication - Jwt라는 구체 기술을 드러내지 않도록 한다. * refactor: 토큰 인증 정보 필터 클래스 이름 변경 - JwtAuthenticationFilter -> TokenAuthenticationFilter - Jwt라는 구체 기술을 드러내지 않도록 한다. * refactor: SiteUserAuthentication을 TokenAuthentication으로 통합 - 불필요하게 상속 관계였던 클래스를 하나로 통합한다. - ExpiredTokenAuthentication와 SiteUserAuthentication을 다형성을 이용해 처리하기 위해 상속 관계를 만들었지만, ExpiredTokenAuthentication의 삭제(#308 PR)에 따라, 상속 관계가 필요하지 않게 되었으므로 통합한다. * test: TokenAuthentication 테스트 코드 작성 - SiteUserAuthentication 테스트 코드의 내용을 토대로 작성한다. * refactor: 토큰 인증 정보 provider 클래스 이름 변경 * refactor: 같은 관심사의 파일이 같은 패키지에 있도록 이동 - authentication 객체와 이를 처리하는 provider가 같은 패키지에 오도록 패키지 이동 * refactor: 구체 기술이 들어간 클래스를 다른 패키지로 분리 - "외부의 구체 기술"관련 코드를 모으는 패키지의 관례적인 이름인 'infrastructure'를 사용한다. * test: 테스트에 이름 추가 --- .../SiteUserAuthentication.java | 16 ---- ...tication.java => TokenAuthentication.java} | 26 +++--- .../TokenAuthenticationProvider.java} | 14 ++-- .../config/AuthenticationManagerConfig.java | 6 +- .../config/SecurityConfiguration.java | 8 +- .../security/filter/SignOutCheckFilter.java | 1 + ...er.java => TokenAuthenticationFilter.java} | 12 +-- .../AuthorizationHeaderParser.java | 2 +- .../resolver/AuthorizedUserResolverTest.java | 7 +- .../SiteUserAuthenticationTest.java | 70 ---------------- .../TokenAuthenticationProviderTest.java} | 30 +++---- .../TokenAuthenticationTest.java | 81 +++++++++++++++++++ ...ava => TokenAuthenticationFilterTest.java} | 12 +-- .../AuthorizationHeaderParserTest.java | 4 +- 14 files changed, 145 insertions(+), 144 deletions(-) delete mode 100644 src/main/java/com/example/solidconnection/security/authentication/SiteUserAuthentication.java rename src/main/java/com/example/solidconnection/security/authentication/{JwtAuthentication.java => TokenAuthentication.java} (61%) rename src/main/java/com/example/solidconnection/security/{provider/SiteUserAuthenticationProvider.java => authentication/TokenAuthenticationProvider.java} (65%) rename src/main/java/com/example/solidconnection/security/filter/{JwtAuthenticationFilter.java => TokenAuthenticationFilter.java} (77%) rename src/main/java/com/example/solidconnection/security/{filter => infrastructure}/AuthorizationHeaderParser.java (93%) delete mode 100644 src/test/java/com/example/solidconnection/security/authentication/SiteUserAuthenticationTest.java rename src/test/java/com/example/solidconnection/security/{provider/SiteUserAuthenticationProviderTest.java => authentication/TokenAuthenticationProviderTest.java} (77%) create mode 100644 src/test/java/com/example/solidconnection/security/authentication/TokenAuthenticationTest.java rename src/test/java/com/example/solidconnection/security/filter/{JwtAuthenticationFilterTest.java => TokenAuthenticationFilterTest.java} (88%) rename src/test/java/com/example/solidconnection/security/{filter => infrastructure}/AuthorizationHeaderParserTest.java (92%) diff --git a/src/main/java/com/example/solidconnection/security/authentication/SiteUserAuthentication.java b/src/main/java/com/example/solidconnection/security/authentication/SiteUserAuthentication.java deleted file mode 100644 index 60a71b032..000000000 --- a/src/main/java/com/example/solidconnection/security/authentication/SiteUserAuthentication.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.example.solidconnection.security.authentication; - -import com.example.solidconnection.security.userdetails.SiteUserDetails; - -public class SiteUserAuthentication extends JwtAuthentication { - - public SiteUserAuthentication(String token) { - super(token, null); - setAuthenticated(false); - } - - public SiteUserAuthentication(String token, SiteUserDetails principal) { - super(token, principal); - setAuthenticated(true); - } -} diff --git a/src/main/java/com/example/solidconnection/security/authentication/JwtAuthentication.java b/src/main/java/com/example/solidconnection/security/authentication/TokenAuthentication.java similarity index 61% rename from src/main/java/com/example/solidconnection/security/authentication/JwtAuthentication.java rename to src/main/java/com/example/solidconnection/security/authentication/TokenAuthentication.java index d68458b18..7fe51efee 100644 --- a/src/main/java/com/example/solidconnection/security/authentication/JwtAuthentication.java +++ b/src/main/java/com/example/solidconnection/security/authentication/TokenAuthentication.java @@ -4,28 +4,36 @@ import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; -public abstract class JwtAuthentication extends AbstractAuthenticationToken { +public class TokenAuthentication extends AbstractAuthenticationToken { - private final String credentials; + private final Object principal; // 인증 주체 - private final Object principal; + private final String credentials; // 증명 수단 - public JwtAuthentication(String token, Object principal) { + public TokenAuthentication(String token) { + super(Collections.emptyList()); + this.principal = null; + this.credentials = token; + setAuthenticated(false); + } + + public TokenAuthentication(String token, Object principal) { super(principal instanceof UserDetails ? ((UserDetails) principal).getAuthorities() : Collections.emptyList()); - this.credentials = token; this.principal = principal; + this.credentials = token; + setAuthenticated(true); } @Override - public Object getCredentials() { - return this.credentials; + public Object getPrincipal() { + return this.principal; } @Override - public Object getPrincipal() { - return this.principal; + public Object getCredentials() { + return this.credentials; } public final String getToken() { diff --git a/src/main/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProvider.java b/src/main/java/com/example/solidconnection/security/authentication/TokenAuthenticationProvider.java similarity index 65% rename from src/main/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProvider.java rename to src/main/java/com/example/solidconnection/security/authentication/TokenAuthenticationProvider.java index a00b77f9a..d0c105884 100644 --- a/src/main/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProvider.java +++ b/src/main/java/com/example/solidconnection/security/authentication/TokenAuthenticationProvider.java @@ -1,8 +1,6 @@ -package com.example.solidconnection.security.provider; +package com.example.solidconnection.security.authentication; import com.example.solidconnection.auth.service.TokenProvider; -import com.example.solidconnection.security.authentication.JwtAuthentication; -import com.example.solidconnection.security.authentication.SiteUserAuthentication; import com.example.solidconnection.security.userdetails.SiteUserDetails; import com.example.solidconnection.security.userdetails.SiteUserDetailsService; import lombok.RequiredArgsConstructor; @@ -13,23 +11,23 @@ @Component @RequiredArgsConstructor -public class SiteUserAuthenticationProvider implements AuthenticationProvider { +public class TokenAuthenticationProvider implements AuthenticationProvider { private final SiteUserDetailsService siteUserDetailsService; private final TokenProvider tokenProvider; @Override public Authentication authenticate(Authentication auth) throws AuthenticationException { - JwtAuthentication jwtAuth = (JwtAuthentication) auth; - String token = jwtAuth.getToken(); + TokenAuthentication tokenAuth = (TokenAuthentication) auth; + String token = tokenAuth.getToken(); String username = tokenProvider.parseSubject(token); SiteUserDetails userDetails = (SiteUserDetails) siteUserDetailsService.loadUserByUsername(username); - return new SiteUserAuthentication(token, userDetails); + return new TokenAuthentication(token, userDetails); } @Override public boolean supports(Class authentication) { - return SiteUserAuthentication.class.isAssignableFrom(authentication); + return TokenAuthentication.class.isAssignableFrom(authentication); } } diff --git a/src/main/java/com/example/solidconnection/security/config/AuthenticationManagerConfig.java b/src/main/java/com/example/solidconnection/security/config/AuthenticationManagerConfig.java index 72601237b..68ab7cdad 100644 --- a/src/main/java/com/example/solidconnection/security/config/AuthenticationManagerConfig.java +++ b/src/main/java/com/example/solidconnection/security/config/AuthenticationManagerConfig.java @@ -1,6 +1,6 @@ package com.example.solidconnection.security.config; -import com.example.solidconnection.security.provider.SiteUserAuthenticationProvider; +import com.example.solidconnection.security.authentication.TokenAuthenticationProvider; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -11,12 +11,12 @@ @Configuration public class AuthenticationManagerConfig { - private final SiteUserAuthenticationProvider siteUserAuthenticationProvider; + private final TokenAuthenticationProvider tokenAuthenticationProvider; @Bean public AuthenticationManager authenticationManager() { return new ProviderManager( - siteUserAuthenticationProvider + tokenAuthenticationProvider ); } } diff --git a/src/main/java/com/example/solidconnection/security/config/SecurityConfiguration.java b/src/main/java/com/example/solidconnection/security/config/SecurityConfiguration.java index b12bade83..3667e9d84 100644 --- a/src/main/java/com/example/solidconnection/security/config/SecurityConfiguration.java +++ b/src/main/java/com/example/solidconnection/security/config/SecurityConfiguration.java @@ -5,7 +5,7 @@ import com.example.solidconnection.common.exception.CustomAccessDeniedHandler; import com.example.solidconnection.common.exception.CustomAuthenticationEntryPoint; import com.example.solidconnection.security.filter.ExceptionHandlerFilter; -import com.example.solidconnection.security.filter.JwtAuthenticationFilter; +import com.example.solidconnection.security.filter.TokenAuthenticationFilter; import com.example.solidconnection.security.filter.SignOutCheckFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; @@ -30,7 +30,7 @@ public class SecurityConfiguration { private final CorsProperties corsProperties; private final ExceptionHandlerFilter exceptionHandlerFilter; private final SignOutCheckFilter signOutCheckFilter; - private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final TokenAuthenticationFilter tokenAuthenticationFilter; private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; private final CustomAccessDeniedHandler customAccessDeniedHandler; @@ -69,8 +69,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authenticationEntryPoint(customAuthenticationEntryPoint) .accessDeniedHandler(customAccessDeniedHandler) ) - .addFilterBefore(jwtAuthenticationFilter, BasicAuthenticationFilter.class) - .addFilterBefore(signOutCheckFilter, JwtAuthenticationFilter.class) + .addFilterBefore(tokenAuthenticationFilter, BasicAuthenticationFilter.class) + .addFilterBefore(signOutCheckFilter, TokenAuthenticationFilter.class) .addFilterBefore(exceptionHandlerFilter, SignOutCheckFilter.class) .build(); } diff --git a/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java b/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java index 45f8d2106..f35a234f6 100644 --- a/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java +++ b/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java @@ -3,6 +3,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_SIGN_OUT; import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.security.infrastructure.AuthorizationHeaderParser; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; diff --git a/src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/example/solidconnection/security/filter/TokenAuthenticationFilter.java similarity index 77% rename from src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java rename to src/main/java/com/example/solidconnection/security/filter/TokenAuthenticationFilter.java index a36385085..0a81b860e 100644 --- a/src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/solidconnection/security/filter/TokenAuthenticationFilter.java @@ -1,7 +1,7 @@ package com.example.solidconnection.security.filter; -import com.example.solidconnection.security.authentication.JwtAuthentication; -import com.example.solidconnection.security.authentication.SiteUserAuthentication; +import com.example.solidconnection.security.authentication.TokenAuthentication; +import com.example.solidconnection.security.infrastructure.AuthorizationHeaderParser; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -19,7 +19,7 @@ @Component @RequiredArgsConstructor -public class JwtAuthenticationFilter extends OncePerRequestFilter { +public class TokenAuthenticationFilter extends OncePerRequestFilter { private final AuthenticationManager authenticationManager; private final AuthorizationHeaderParser authorizationHeaderParser; @@ -34,14 +34,10 @@ public void doFilterInternal(@NonNull HttpServletRequest request, return; } - JwtAuthentication authToken = createAuthentication(token.get()); + TokenAuthentication authToken = new TokenAuthentication(token.get()); Authentication auth = authenticationManager.authenticate(authToken); SecurityContextHolder.getContext().setAuthentication(auth); filterChain.doFilter(request, response); } - - private JwtAuthentication createAuthentication(String token) { - return new SiteUserAuthentication(token); - } } diff --git a/src/main/java/com/example/solidconnection/security/filter/AuthorizationHeaderParser.java b/src/main/java/com/example/solidconnection/security/infrastructure/AuthorizationHeaderParser.java similarity index 93% rename from src/main/java/com/example/solidconnection/security/filter/AuthorizationHeaderParser.java rename to src/main/java/com/example/solidconnection/security/infrastructure/AuthorizationHeaderParser.java index 9360b8c8a..0c104d58d 100644 --- a/src/main/java/com/example/solidconnection/security/filter/AuthorizationHeaderParser.java +++ b/src/main/java/com/example/solidconnection/security/infrastructure/AuthorizationHeaderParser.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.security.filter; +package com.example.solidconnection.security.infrastructure; import jakarta.servlet.http.HttpServletRequest; import java.util.Optional; diff --git a/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java b/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java index 541f1edc9..c671c9a76 100644 --- a/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java +++ b/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java @@ -3,11 +3,12 @@ import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.security.authentication.SiteUserAuthentication; +import com.example.solidconnection.security.authentication.TokenAuthentication; import com.example.solidconnection.security.userdetails.SiteUserDetails; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; @@ -87,8 +88,8 @@ class security_context_에_저장된_사용자가_없는_경우 { } } - private SiteUserAuthentication createAuthenticationWithUser(SiteUser siteUser) { + private TokenAuthentication createAuthenticationWithUser(SiteUser siteUser) { SiteUserDetails userDetails = new SiteUserDetails(siteUser); - return new SiteUserAuthentication("token", userDetails); + return new TokenAuthentication("token", userDetails); } } diff --git a/src/test/java/com/example/solidconnection/security/authentication/SiteUserAuthenticationTest.java b/src/test/java/com/example/solidconnection/security/authentication/SiteUserAuthenticationTest.java deleted file mode 100644 index e4586019e..000000000 --- a/src/test/java/com/example/solidconnection/security/authentication/SiteUserAuthenticationTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.example.solidconnection.security.authentication; - -import static org.assertj.core.api.Assertions.assertThat; - -import com.example.solidconnection.security.userdetails.SiteUserDetails; -import com.example.solidconnection.siteuser.domain.ExchangeStatus; -import com.example.solidconnection.siteuser.domain.Role; -import com.example.solidconnection.siteuser.domain.SiteUser; -import org.junit.jupiter.api.Test; - -class SiteUserAuthenticationTest { - - @Test - void 인증_정보에_저장된_토큰을_반환한다() { - // given - String token = "token"; - SiteUserAuthentication authentication = new SiteUserAuthentication(token); - - // when - String result = authentication.getToken(); - - // then - assertThat(result).isEqualTo(token); - } - - @Test - void 인증_정보에_저장된_사용자를_반환한다() { - // given - SiteUserDetails userDetails = new SiteUserDetails(createSiteUser()); - SiteUserAuthentication authentication = new SiteUserAuthentication("token", userDetails); - - // when & then - SiteUserDetails actual = (SiteUserDetails) authentication.getPrincipal(); - - // then - assertThat(actual) - .extracting("siteUser") - .extracting("id") - .isEqualTo(userDetails.getSiteUser().getId()); - } - - @Test - void 인증_전에_생성되면_isAuthenticated_는_false_를_반환한다() { - // given - SiteUserAuthentication authentication = new SiteUserAuthentication("token"); - - // when & then - assertThat(authentication.isAuthenticated()).isFalse(); - } - - @Test - void 인증_후에_생성되면_isAuthenticated_는_true_를_반환한다() { - // given - SiteUserDetails userDetails = new SiteUserDetails(createSiteUser()); - SiteUserAuthentication authentication = new SiteUserAuthentication("token", userDetails); - - // when & then - assertThat(authentication.isAuthenticated()).isTrue(); - } - - private SiteUser createSiteUser() { - return new SiteUser( - "test@example.com", - "nickname", - "profileImageUrl", - ExchangeStatus.CONSIDERING, - Role.MENTEE - ); - } -} diff --git a/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java b/src/test/java/com/example/solidconnection/security/authentication/TokenAuthenticationProviderTest.java similarity index 77% rename from src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java rename to src/test/java/com/example/solidconnection/security/authentication/TokenAuthenticationProviderTest.java index 178e9e50c..c8715f81a 100644 --- a/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java +++ b/src/test/java/com/example/solidconnection/security/authentication/TokenAuthenticationProviderTest.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.security.provider; +package com.example.solidconnection.security.authentication; import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; @@ -8,7 +8,6 @@ import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.security.authentication.SiteUserAuthentication; import com.example.solidconnection.security.userdetails.SiteUserDetails; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; @@ -26,10 +25,10 @@ @TestContainerSpringBootTest @DisplayName("사용자 인증정보 provider 테스트") -class SiteUserAuthenticationProviderTest { +class TokenAuthenticationProviderTest { @Autowired - private SiteUserAuthenticationProvider siteUserAuthenticationProvider; + private TokenAuthenticationProvider tokenAuthenticationProvider; @Autowired private JwtProperties jwtProperties; @@ -47,13 +46,13 @@ void setUp() { @Test void 처리할_수_있는_타입인지를_반환한다() { // given - Class supportedType = SiteUserAuthentication.class; + Class supportedType = TokenAuthentication.class; Class notSupportedType = PasswordAuthentication.class; // when & then assertAll( - () -> assertThat(siteUserAuthenticationProvider.supports(supportedType)).isTrue(), - () -> assertThat(siteUserAuthenticationProvider.supports(notSupportedType)).isFalse() + () -> assertThat(tokenAuthenticationProvider.supports(supportedType)).isTrue(), + () -> assertThat(tokenAuthenticationProvider.supports(notSupportedType)).isFalse() ); } @@ -61,10 +60,10 @@ void setUp() { void 유효한_토큰이면_정상적으로_인증_정보를_반환한다() { // given String token = createValidToken(user.getId()); - SiteUserAuthentication auth = new SiteUserAuthentication(token); + TokenAuthentication auth = new TokenAuthentication(token); // when - Authentication result = siteUserAuthenticationProvider.authenticate(auth); + Authentication result = tokenAuthenticationProvider.authenticate(auth); // then assertThat(result).isNotNull(); @@ -80,10 +79,10 @@ class 예외가_발생한다 { @Test void 유효하지_않은_토큰이면_예외가_발생한다() { // given - SiteUserAuthentication expiredAuth = new SiteUserAuthentication(createExpiredToken()); + TokenAuthentication expiredAuth = new TokenAuthentication(createExpiredToken()); // when & then - assertThatCode(() -> siteUserAuthenticationProvider.authenticate(expiredAuth)) + assertThatCode(() -> tokenAuthenticationProvider.authenticate(expiredAuth)) .isInstanceOf(CustomException.class) .hasMessageContaining(INVALID_TOKEN.getMessage()); } @@ -91,10 +90,11 @@ class 예외가_발생한다 { @Test void 사용자_정보의_형식이_다르면_예외가_발생한다() { // given - SiteUserAuthentication wrongSubjectTypeAuth = new SiteUserAuthentication(createWrongSubjectTypeToken()); + TokenAuthentication wrongSubjectTypeAuth = new TokenAuthentication( + createWrongSubjectTypeToken()); // when & then - assertThatCode(() -> siteUserAuthenticationProvider.authenticate(wrongSubjectTypeAuth)) + assertThatCode(() -> tokenAuthenticationProvider.authenticate(wrongSubjectTypeAuth)) .isInstanceOf(CustomException.class) .hasMessageContaining(INVALID_TOKEN.getMessage()); } @@ -104,10 +104,10 @@ class 예외가_발생한다 { // given long notExistingUserId = user.getId() + 100; String token = createValidToken(notExistingUserId); - SiteUserAuthentication auth = new SiteUserAuthentication(token); + TokenAuthentication auth = new TokenAuthentication(token); // when & then - assertThatCode(() -> siteUserAuthenticationProvider.authenticate(auth)) + assertThatCode(() -> tokenAuthenticationProvider.authenticate(auth)) .isInstanceOf(CustomException.class) .hasMessageContaining(AUTHENTICATION_FAILED.getMessage()); } diff --git a/src/test/java/com/example/solidconnection/security/authentication/TokenAuthenticationTest.java b/src/test/java/com/example/solidconnection/security/authentication/TokenAuthenticationTest.java new file mode 100644 index 000000000..90e76abc2 --- /dev/null +++ b/src/test/java/com/example/solidconnection/security/authentication/TokenAuthenticationTest.java @@ -0,0 +1,81 @@ +package com.example.solidconnection.security.authentication; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.example.solidconnection.security.userdetails.SiteUserDetails; +import com.example.solidconnection.siteuser.domain.ExchangeStatus; +import com.example.solidconnection.siteuser.domain.Role; +import com.example.solidconnection.siteuser.domain.SiteUser; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayName("토큰 인증 정보 테스트") +class TokenAuthenticationTest { + + @Nested + class Authentication의_인증_정보를_반환한다 { + + @Test + void 토큰을_반환한다() { + // given + String token = "token"; + TokenAuthentication authentication = new TokenAuthentication(token); + + // when + String result = authentication.getToken(); + + // then + assertThat(result).isEqualTo(token); + } + + @Test + void 사용자_정보를_반환한다() { + // given + SiteUserDetails userDetails = new SiteUserDetails(createSiteUser()); + TokenAuthentication authentication = new TokenAuthentication("token", userDetails); + + // when & then + SiteUserDetails actual = (SiteUserDetails) authentication.getPrincipal(); + + // then + assertThat(actual) + .extracting("siteUser") + .extracting("id") + .isEqualTo(userDetails.getSiteUser().getId()); + } + } + + @Nested + class Authentication의_인증_상태를_반환한다 { + + @Test + void 증명_수단만_포함하여_생성하면_미인증_상태이다() { + // given + TokenAuthentication authentication = new TokenAuthentication("token"); + + // when & then + assertThat(authentication.isAuthenticated()).isFalse(); + } + + @Test + void 사용자_정보와_함께_생성하면_인증된_상태이다() { + // given + SiteUserDetails userDetails = new SiteUserDetails(createSiteUser()); + TokenAuthentication authentication = new TokenAuthentication("token", userDetails); + + // when & then + assertThat(authentication.isAuthenticated()).isTrue(); + } + } + + private SiteUser createSiteUser() { + return new SiteUser( + "test@example.com", + "nickname", + "profileImageUrl", + ExchangeStatus.CONSIDERING, + Role.MENTEE + ); + } +} diff --git a/src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java b/src/test/java/com/example/solidconnection/security/filter/TokenAuthenticationFilterTest.java similarity index 88% rename from src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java rename to src/test/java/com/example/solidconnection/security/filter/TokenAuthenticationFilterTest.java index 009275e06..36d8c3dd8 100644 --- a/src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java +++ b/src/test/java/com/example/solidconnection/security/filter/TokenAuthenticationFilterTest.java @@ -5,7 +5,7 @@ import static org.mockito.Mockito.spy; import com.example.solidconnection.auth.token.config.JwtProperties; -import com.example.solidconnection.security.authentication.SiteUserAuthentication; +import com.example.solidconnection.security.authentication.TokenAuthentication; import com.example.solidconnection.security.userdetails.SiteUserDetailsService; import com.example.solidconnection.support.TestContainerSpringBootTest; import io.jsonwebtoken.Jwts; @@ -25,10 +25,10 @@ @TestContainerSpringBootTest @DisplayName("토큰 인증 필터 테스트") -class JwtAuthenticationFilterTest { +class TokenAuthenticationFilterTest { @Autowired - private JwtAuthenticationFilter jwtAuthenticationFilter; + private TokenAuthenticationFilter tokenAuthenticationFilter; @Autowired private JwtProperties jwtProperties; @@ -53,7 +53,7 @@ void setUp() { request = new MockHttpServletRequest(); // when - jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); + tokenAuthenticationFilter.doFilterInternal(request, response, filterChain); // then assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); @@ -68,11 +68,11 @@ void setUp() { request = createRequestWithToken(token); // when - jwtAuthenticationFilter.doFilterInternal(request, response, filterChain); + tokenAuthenticationFilter.doFilterInternal(request, response, filterChain); // then assertThat(SecurityContextHolder.getContext().getAuthentication()) - .isExactlyInstanceOf(SiteUserAuthentication.class); + .isExactlyInstanceOf(TokenAuthentication.class); then(filterChain).should().doFilter(request, response); } diff --git a/src/test/java/com/example/solidconnection/security/filter/AuthorizationHeaderParserTest.java b/src/test/java/com/example/solidconnection/security/infrastructure/AuthorizationHeaderParserTest.java similarity index 92% rename from src/test/java/com/example/solidconnection/security/filter/AuthorizationHeaderParserTest.java rename to src/test/java/com/example/solidconnection/security/infrastructure/AuthorizationHeaderParserTest.java index 809fb8c8a..66ec3c0fb 100644 --- a/src/test/java/com/example/solidconnection/security/filter/AuthorizationHeaderParserTest.java +++ b/src/test/java/com/example/solidconnection/security/infrastructure/AuthorizationHeaderParserTest.java @@ -1,15 +1,17 @@ -package com.example.solidconnection.security.filter; +package com.example.solidconnection.security.infrastructure; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; import com.example.solidconnection.support.TestContainerSpringBootTest; import java.util.Optional; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpServletRequest; +@DisplayName("Authorization 해더 파서 테스트") @TestContainerSpringBootTest class AuthorizationHeaderParserTest { From 60ee7b961510a5b4fda8e47824a50dcf706a7448 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Sun, 20 Jul 2025 00:01:21 +0900 Subject: [PATCH 48/90] =?UTF-8?q?fix:=20cd=20=EC=8A=A4=ED=81=AC=EB=A6=BD?= =?UTF-8?q?=ED=8A=B8=EC=9D=98=20alloy=20=ED=8C=8C=EC=9D=BC=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95=20(#392)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/dev-cd.yml | 2 +- .github/workflows/prod-cd.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dev-cd.yml b/.github/workflows/dev-cd.yml index b86b541e1..4269f9d46 100644 --- a/.github/workflows/dev-cd.yml +++ b/.github/workflows/dev-cd.yml @@ -68,7 +68,7 @@ jobs: host: ${{ secrets.DEV_HOST }} username: ${{ secrets.DEV_USERNAME }} key: ${{ secrets.DEV_PRIVATE_KEY }} - source: "./docs/config.alloy" + source: "./docs/infra-config/config.alloy" target: "/home/${{ secrets.DEV_USERNAME }}/solid-connection-dev/" - name: Run docker compose diff --git a/.github/workflows/prod-cd.yml b/.github/workflows/prod-cd.yml index 354128b77..714aede30 100644 --- a/.github/workflows/prod-cd.yml +++ b/.github/workflows/prod-cd.yml @@ -68,7 +68,7 @@ jobs: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.PRIVATE_KEY }} - source: "./docs/config.alloy" + source: "./docs/infra-config/config.alloy" target: "/home/${{ secrets.USERNAME }}/solid-connect-server/" - name: Run docker compose From 0cfa442a5c81cd52aa6c5f28eee0941b7365d62b Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Fri, 25 Jul 2025 04:03:13 +0900 Subject: [PATCH 49/90] =?UTF-8?q?refactor:=20=EB=A9=98=ED=86=A0=EA=B0=80?= =?UTF-8?q?=20=EB=A9=98=ED=86=A0=EB=A7=81=20=EA=B1=B0=EC=A0=88=20=EC=8B=9C?= =?UTF-8?q?,=20=EA=B1=B0=EC=A0=88=20=EC=82=AC=EC=9C=A0=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20(#3?= =?UTF-8?q?98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 거절 사유 입력 기능 제거 * refactor: 거절 사유 컬럼 제거 * test: 거절사유 입력 기능제거 테스트 코드에 반영 * refactor: fixture 인자에서 rejectedReason 제거 --- .../mentor/domain/Mentoring.java | 6 +---- .../mentor/dto/MentoringConfirmRequest.java | 4 +-- .../service/MentoringCommandService.java | 8 +----- ...3__drop_mentoring_reject_reason_column.sql | 2 ++ .../mentor/fixture/MentoringFixture.java | 3 +-- .../fixture/MentoringFixtureBuilder.java | 7 ----- .../service/MentoringCommandServiceTest.java | 26 ++++--------------- .../service/MentoringQueryServiceTest.java | 2 +- 8 files changed, 12 insertions(+), 46 deletions(-) create mode 100644 src/main/resources/db/migration/V23__drop_mentoring_reject_reason_column.sql diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java b/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java index 9f2409add..47226076d 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java @@ -46,9 +46,6 @@ public class Mentoring { @Enumerated(EnumType.STRING) private VerifyStatus verifyStatus = VerifyStatus.PENDING; - @Column(length = 500) - private String rejectedReason; - @Column private long mentorId; @@ -66,9 +63,8 @@ public void onPrePersist() { this.createdAt = ZonedDateTime.now(UTC).truncatedTo(MICROS); // 나노초 6자리 까지만 저장 } - public void confirm(VerifyStatus status, String rejectedReason) { + public void confirm(VerifyStatus status) { this.verifyStatus = status; - this.rejectedReason = rejectedReason; this.confirmedAt = ZonedDateTime.now(UTC).truncatedTo(MICROS); if (this.checkedAt == null) { diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmRequest.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmRequest.java index ab3f1a8a8..cb284d4c6 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmRequest.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmRequest.java @@ -5,9 +5,7 @@ public record MentoringConfirmRequest( @NotNull(message = "승인 상태를 설정해주세요.") - VerifyStatus status, - - String rejectedReason + VerifyStatus status ) { } diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java b/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java index 165011f55..ca35f6c95 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java @@ -3,7 +3,6 @@ import static com.example.solidconnection.common.exception.ErrorCode.MENTORING_ALREADY_CONFIRMED; import static com.example.solidconnection.common.exception.ErrorCode.MENTORING_NOT_FOUND; import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; -import static com.example.solidconnection.common.exception.ErrorCode.REJECTED_REASON_REQUIRED; import static com.example.solidconnection.common.exception.ErrorCode.UNAUTHORIZED_MENTORING; import com.example.solidconnection.common.VerifyStatus; @@ -46,12 +45,7 @@ public MentoringConfirmResponse confirmMentoring(long siteUserId, long mentoring validateMentoringOwnership(mentor, mentoring); validateMentoringNotConfirmed(mentoring); - if (mentoringConfirmRequest.status() == VerifyStatus.REJECTED - && (mentoringConfirmRequest.rejectedReason() == null || mentoringConfirmRequest.rejectedReason().isBlank())) { - throw new CustomException(REJECTED_REASON_REQUIRED); - } - - mentoring.confirm(mentoringConfirmRequest.status(), mentoringConfirmRequest.rejectedReason()); + mentoring.confirm(mentoringConfirmRequest.status()); if (mentoringConfirmRequest.status() == VerifyStatus.APPROVED) { mentor.increaseMenteeCount(); diff --git a/src/main/resources/db/migration/V23__drop_mentoring_reject_reason_column.sql b/src/main/resources/db/migration/V23__drop_mentoring_reject_reason_column.sql new file mode 100644 index 000000000..dde39c460 --- /dev/null +++ b/src/main/resources/db/migration/V23__drop_mentoring_reject_reason_column.sql @@ -0,0 +1,2 @@ +ALTER TABLE mentoring + DROP COLUMN rejected_reason; diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java index 0315777ab..07925628e 100644 --- a/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java +++ b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java @@ -33,13 +33,12 @@ public class MentoringFixture { .create(); } - public Mentoring 거절된_멘토링(long mentorId, long menteeId, String rejectedReason) { + public Mentoring 거절된_멘토링(long mentorId, long menteeId) { ZonedDateTime now = getCurrentTime(); return mentoringFixtureBuilder.mentoring() .mentorId(mentorId) .menteeId(menteeId) .verifyStatus(VerifyStatus.REJECTED) - .rejectedReason(rejectedReason) .confirmedAt(now) .checkedAt(now) .create(); diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java index 9400a0a0f..857c18ea8 100644 --- a/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java @@ -17,7 +17,6 @@ public class MentoringFixtureBuilder { private ZonedDateTime confirmedAt; private ZonedDateTime checkedAt; private VerifyStatus verifyStatus = VerifyStatus.PENDING; - private String rejectedReason; private long mentorId; private long menteeId; @@ -45,11 +44,6 @@ public MentoringFixtureBuilder verifyStatus(VerifyStatus verifyStatus) { return this; } - public MentoringFixtureBuilder rejectedReason(String rejectedReason) { - this.rejectedReason = rejectedReason; - return this; - } - public MentoringFixtureBuilder mentorId(long mentorId) { this.mentorId = mentorId; return this; @@ -67,7 +61,6 @@ public Mentoring create() { confirmedAt, checkedAt, verifyStatus, - rejectedReason, mentorId, menteeId ); diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java index fb8fbd608..a7cea53bb 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java @@ -2,7 +2,6 @@ import static com.example.solidconnection.common.exception.ErrorCode.MENTORING_ALREADY_CONFIRMED; import static com.example.solidconnection.common.exception.ErrorCode.MENTORING_NOT_FOUND; -import static com.example.solidconnection.common.exception.ErrorCode.REJECTED_REASON_REQUIRED; import static com.example.solidconnection.common.exception.ErrorCode.UNAUTHORIZED_MENTORING; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -98,7 +97,7 @@ class 멘토링_승인_거절_테스트 { void 멘토링을_성공적으로_승인한다() { // given Mentoring mentoring = mentoringFixture.대기중_멘토링(mentor1.getId(), menteeUser.getId()); - MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.APPROVED, null); + MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.APPROVED); int beforeMenteeCount = mentor1.getMenteeCount(); // when @@ -120,8 +119,7 @@ class 멘토링_승인_거절_테스트 { void 멘토링을_성공적으로_거절한다() { // given Mentoring mentoring = mentoringFixture.대기중_멘토링(mentor1.getId(), menteeUser.getId()); - String rejectedReason = "멘토링 거절 사유"; - MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.REJECTED, rejectedReason); + MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.REJECTED); int beforeMenteeCount = mentor1.getMenteeCount(); // when @@ -133,31 +131,17 @@ class 멘토링_승인_거절_테스트 { assertAll( () -> assertThat(confirmedMentoring.getVerifyStatus()).isEqualTo(VerifyStatus.REJECTED), - () -> assertThat(confirmedMentoring.getRejectedReason()).isEqualTo(rejectedReason), () -> assertThat(confirmedMentoring.getConfirmedAt()).isNotNull(), () -> assertThat(confirmedMentoring.getCheckedAt()).isNotNull(), () -> assertThat(mentor.getMenteeCount()).isEqualTo(beforeMenteeCount) ); } - @Test - void 거절_시_사유가_없으면_예외가_발생한다() { - // given - Mentoring mentoring = mentoringFixture.대기중_멘토링(mentor1.getId(), menteeUser.getId()); - MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.REJECTED, null); - - // when & then - assertThatThrownBy(() -> - mentoringCommandService.confirmMentoring(mentorUser1.getId(), mentoring.getId(), request)) - .isInstanceOf(CustomException.class) - .hasMessage(REJECTED_REASON_REQUIRED.getMessage()); - } - @Test void 다른_멘토의_멘토링을_승인할_수_없다() { // given Mentoring mentoring = mentoringFixture.대기중_멘토링(mentor1.getId(), menteeUser.getId()); - MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.APPROVED, null); + MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.APPROVED); // when & then assertThatThrownBy(() -> mentoringCommandService.confirmMentoring(mentorUser2.getId(), mentoring.getId(), request)) @@ -169,7 +153,7 @@ class 멘토링_승인_거절_테스트 { void 이미_처리된_멘토링은_다시_승인할_수_없다() { // given Mentoring mentoring = mentoringFixture.승인된_멘토링(mentor1.getId(), menteeUser.getId()); - MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.APPROVED, null); + MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.APPROVED); // when & then assertThatThrownBy(() -> mentoringCommandService.confirmMentoring(mentorUser1.getId(), mentoring.getId(), request)) @@ -180,7 +164,7 @@ class 멘토링_승인_거절_테스트 { @Test void 존재하지_않는_멘토링_아이디로_요청시_예외가_발생한다() { // given - MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.APPROVED, null); + MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.APPROVED); long invalidMentoringId = 9999L; // when & then diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java index 5951920ff..6e61a4a7a 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java @@ -54,7 +54,7 @@ class 멘토링_목록_조회_테스트 { // given Mentoring mentoring1 = mentoringFixture.대기중_멘토링(mentor.getId(), menteeUser.getId()); Mentoring mentoring2 = mentoringFixture.승인된_멘토링(mentor.getId(), menteeUser.getId()); - Mentoring mentoring3 = mentoringFixture.거절된_멘토링(mentor.getId(), menteeUser.getId(), "거절 사유"); + Mentoring mentoring3 = mentoringFixture.거절된_멘토링(mentor.getId(), menteeUser.getId()); // when MentoringListResponse responses = mentoringQueryService.getMentorings(mentorUser.getId()); From a08689c84bf31ee443acf12eba95c821b52d40da Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Fri, 25 Jul 2025 04:03:49 +0900 Subject: [PATCH 50/90] =?UTF-8?q?refactor:=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20argumentResolver=EA=B0=80=20long=20siteUse?= =?UTF-8?q?rId=EB=A5=BC=20=EC=A3=BC=EC=9E=85=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?(#396)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: long 또는 Long을 추출하도록 * refactor: 파라미터가 원시타입이면 반드시 required로 해석하도록 * test: argumentResolver 변경사항 테스트 코드에 반영 - 추가로, 반복되던 mocking 으로 가독성을 해치던 것을 해결 * refactor: long id를 받도록 ApplicationController 수정 * refactor: long id를 받도록 AuthController 수정 * refactor: long id를 받도록 CommentController 수정 * refactor: long id를 받도록 PostController 수정 * refactor: long id를 받도록 MentorController 수정 * refactor: long id를 받도록 NewsController 수정 * refactor: long id를 받도록 S3Controller 수정 * refactor: long id를 받도록 ScoreController 수정 * refactor: long id를 받도록 MyPageController 수정 * refactor: long id를 받도록 UniversityController 수정 * refactor: 누락된 부분 포함하여 변경 * chore: todo 추가 --- .../controller/ApplicationController.java | 17 +++--- .../service/ApplicationQueryService.java | 15 ++++- .../service/ApplicationSubmissionService.java | 7 ++- .../auth/controller/AuthController.java | 7 +-- .../auth/service/AuthService.java | 7 ++- .../resolver/AuthorizedUserResolver.java | 23 ++++--- .../board/controller/BoardController.java | 3 +- .../comment/controller/CommentController.java | 13 ++-- .../comment/service/CommentService.java | 17 ++++-- .../post/controller/PostController.java | 25 ++++---- .../post/service/PostCommandService.java | 15 ++++- .../post/service/PostLikeService.java | 11 +++- .../post/service/PostQueryService.java | 6 +- .../mentor/controller/MentorController.java | 11 ++-- .../controller/MentorMyPageController.java | 9 ++- .../controller/MentoringController.java | 21 +++---- .../mentor/service/MentorMyPageService.java | 11 +++- .../mentor/service/MentorQueryService.java | 12 ++-- .../news/controller/NewsController.java | 25 ++++---- .../news/service/NewsCommandService.java | 7 ++- .../s3/controller/S3Controller.java | 5 +- .../solidconnection/s3/service/S3Service.java | 7 ++- .../score/controller/ScoreController.java | 17 +++--- .../score/service/ScoreService.java | 21 +++++-- .../siteuser/controller/MyPageController.java | 9 ++- .../siteuser/service/MyPageService.java | 10 +-- .../controller/UnivApplyInfoController.java | 23 ++++--- .../service/LikedUnivApplyInfoService.java | 19 +++--- .../UnivApplyInfoRecommendService.java | 5 +- .../service/ApplicationQueryServiceTest.java | 14 ++--- .../ApplicationSubmissionServiceTest.java | 10 +-- .../auth/service/AuthServiceTest.java | 9 ++- .../resolver/AuthorizedUserResolverTest.java | 61 ++++++++++++++----- .../comment/service/CommentServiceTest.java | 30 ++++----- .../post/service/PostCommandServiceTest.java | 22 +++---- .../post/service/PostLikeServiceTest.java | 12 ++-- .../post/service/PostQueryServiceTest.java | 2 +- .../PostLikeCountConcurrencyTest.java | 4 +- .../concurrency/ThunderingHerdTest.java | 11 ++-- .../service/MentorMyPageServiceTest.java | 6 +- .../service/MentorQueryServiceTest.java | 16 ++--- .../news/service/NewsCommandServiceTest.java | 2 +- .../score/service/ScoreServiceTest.java | 12 ++-- .../siteuser/service/MyPageServiceTest.java | 18 +++--- .../LikedUnivApplyInfoServiceTest.java | 18 +++--- .../UnivApplyInfoRecommendServiceTest.java | 8 +-- 46 files changed, 366 insertions(+), 267 deletions(-) diff --git a/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java b/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java index e79b4f8e3..07571d060 100644 --- a/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java +++ b/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java @@ -8,7 +8,6 @@ import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.security.annotation.RequireRoleAccess; import com.example.solidconnection.siteuser.domain.Role; -import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -31,10 +30,10 @@ public class ApplicationController { // 지원서 제출하기 api @PostMapping public ResponseEntity apply( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @Valid @RequestBody ApplyRequest applyRequest ) { - ApplicationSubmissionResponse applicationSubmissionResponse = applicationSubmissionService.apply(siteUser, applyRequest); + ApplicationSubmissionResponse applicationSubmissionResponse = applicationSubmissionService.apply(siteUserId, applyRequest); return ResponseEntity .status(HttpStatus.OK) .body(applicationSubmissionResponse); @@ -43,22 +42,22 @@ public ResponseEntity apply( @RequireRoleAccess(roles = {Role.ADMIN}) @GetMapping public ResponseEntity getApplicants( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @RequestParam(required = false, defaultValue = "") String region, @RequestParam(required = false, defaultValue = "") String keyword ) { - applicationQueryService.validateSiteUserCanViewApplicants(siteUser); - ApplicationsResponse result = applicationQueryService.getApplicants(siteUser, region, keyword); + applicationQueryService.validateSiteUserCanViewApplicants(siteUserId); + ApplicationsResponse result = applicationQueryService.getApplicants(siteUserId, region, keyword); return ResponseEntity .ok(result); } @GetMapping("/competitors") public ResponseEntity getApplicantsForUserCompetitors( - @AuthorizedUser SiteUser siteUser + @AuthorizedUser long siteUserId ) { - applicationQueryService.validateSiteUserCanViewApplicants(siteUser); - ApplicationsResponse result = applicationQueryService.getApplicantsByUserApplications(siteUser); + applicationQueryService.validateSiteUserCanViewApplicants(siteUserId); + ApplicationsResponse result = applicationQueryService.getApplicantsByUserApplications(siteUserId); return ResponseEntity .ok(result); } diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java index 9b34bdbc5..7eba19de3 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java @@ -1,6 +1,7 @@ package com.example.solidconnection.application.service; import static com.example.solidconnection.common.exception.ErrorCode.APPLICATION_NOT_APPROVED; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.application.domain.Application; import com.example.solidconnection.application.dto.ApplicantsResponse; @@ -9,6 +10,7 @@ import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; import com.example.solidconnection.university.repository.custom.UnivApplyInfoFilterRepositoryImpl; @@ -32,14 +34,17 @@ public class ApplicationQueryService { private final ApplicationRepository applicationRepository; private final UnivApplyInfoRepository univApplyInfoRepository; private final UnivApplyInfoFilterRepositoryImpl universityFilterRepository; + private final SiteUserRepository siteUserRepository; @Value("${university.term}") public String term; // todo: 캐싱 정책 변경 시 수정 필요 @Transactional(readOnly = true) - public ApplicationsResponse getApplicants(SiteUser siteUser, String regionCode, String keyword) { + public ApplicationsResponse getApplicants(long siteUserId, String regionCode, String keyword) { // 1. 대학 지원 정보 필터링 (regionCode, keyword) + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); List univApplyInfos = universityFilterRepository.findAllByRegionCodeAndKeywords(regionCode, List.of(keyword)); if (univApplyInfos.isEmpty()) { return new ApplicationsResponse(List.of(), List.of(), List.of()); @@ -54,7 +59,9 @@ public ApplicationsResponse getApplicants(SiteUser siteUser, String regionCode, } @Transactional(readOnly = true) - public ApplicationsResponse getApplicantsByUserApplications(SiteUser siteUser) { + public ApplicationsResponse getApplicantsByUserApplications(long siteUserId) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); Application userLatestApplication = applicationRepository.getApplicationBySiteUserIdAndTerm(siteUser.getId(), term); List univApplyInfoIds = Stream.of( @@ -118,7 +125,9 @@ private List createUniversityApplicantsResponses( } @Transactional(readOnly = true) - public void validateSiteUserCanViewApplicants(SiteUser siteUser) { + public void validateSiteUserCanViewApplicants(long siteUserId) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); VerifyStatus verifyStatus = applicationRepository.getApplicationBySiteUserIdAndTerm(siteUser.getId(), term).getVerifyStatus(); if (verifyStatus != VerifyStatus.APPROVED) { throw new CustomException(APPLICATION_NOT_APPROVED); diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java index 02c096529..4fd403d3a 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java @@ -5,6 +5,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.INVALID_GPA_SCORE_STATUS; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE_STATUS; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.application.domain.Application; import com.example.solidconnection.application.dto.ApplicationSubmissionResponse; @@ -18,6 +19,7 @@ import com.example.solidconnection.score.repository.GpaScoreRepository; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -33,6 +35,7 @@ public class ApplicationSubmissionService { private final ApplicationRepository applicationRepository; private final GpaScoreRepository gpaScoreRepository; private final LanguageTestScoreRepository languageTestScoreRepository; + private final SiteUserRepository siteUserRepository; @Value("${university.term}") private String term; @@ -40,7 +43,9 @@ public class ApplicationSubmissionService { // 학점 및 어학성적이 모두 유효한 경우에만 지원서 등록이 가능하다. // 기존에 있던 status field 우선 APRROVED로 입력시킨다. @Transactional - public ApplicationSubmissionResponse apply(SiteUser siteUser, ApplyRequest applyRequest) { + public ApplicationSubmissionResponse apply(long siteUserId, ApplyRequest applyRequest) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); UnivApplyInfoChoiceRequest univApplyInfoChoiceRequest = applyRequest.univApplyInfoChoiceRequest(); GpaScore gpaScore = getValidGpaScore(siteUser, applyRequest.gpaScoreId()); LanguageTestScore languageTestScore = getValidLanguageTestScore(siteUser, applyRequest.languageTestScoreId()); diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index da3ea12c1..57a42223d 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -21,7 +21,6 @@ import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.siteuser.domain.AuthType; -import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -104,11 +103,11 @@ public ResponseEntity signOut( @DeleteMapping("/quit") public ResponseEntity quit( - @AuthorizedUser SiteUser siteUser, - Authentication authentication // todo: #299를 작업하며 인자를 (Authentication authentication)만 받도록 수정해야 함 + Authentication authentication, + @AuthorizedUser long siteUserId ) { String accessToken = getAccessToken(authentication); - authService.quit(siteUser, accessToken); + authService.quit(siteUserId, accessToken); return ResponseEntity.ok().build(); } diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthService.java b/src/main/java/com/example/solidconnection/auth/service/AuthService.java index d487d39e8..2737360c2 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -1,12 +1,14 @@ package com.example.solidconnection.auth.service; import static com.example.solidconnection.common.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; import com.example.solidconnection.auth.token.TokenBlackListService; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; import java.time.LocalDate; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -18,6 +20,7 @@ public class AuthService { private final AuthTokenProvider authTokenProvider; private final TokenBlackListService tokenBlackListService; + private final SiteUserRepository siteUserRepository; /* * 로그아웃한다. @@ -37,7 +40,9 @@ public void signOut(String token) { * - 로그아웃한다. * */ @Transactional - public void quit(SiteUser siteUser, String token) { // todo: #299를 작업하며 인자를 (String token)만 받도록 수정해야 함 + public void quit(long siteUserId, String token) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); LocalDate tomorrow = LocalDate.now().plusDays(1); siteUser.setQuitedAt(tomorrow); signOut(token); diff --git a/src/main/java/com/example/solidconnection/common/resolver/AuthorizedUserResolver.java b/src/main/java/com/example/solidconnection/common/resolver/AuthorizedUserResolver.java index 917784f8b..7afb08d66 100644 --- a/src/main/java/com/example/solidconnection/common/resolver/AuthorizedUserResolver.java +++ b/src/main/java/com/example/solidconnection/common/resolver/AuthorizedUserResolver.java @@ -4,7 +4,6 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.security.userdetails.SiteUserDetails; -import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; import org.springframework.core.MethodParameter; import org.springframework.security.core.Authentication; @@ -22,7 +21,8 @@ public class AuthorizedUserResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(AuthorizedUser.class) - && parameter.getParameterType().equals(SiteUser.class); + && (parameter.getParameterType().equals(long.class) + || parameter.getParameterType().equals(Long.class)); } @Override @@ -30,21 +30,28 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { - SiteUser siteUser = extractSiteUserFromAuthentication(); - if (parameter.getParameterAnnotation(AuthorizedUser.class).required() && siteUser == null) { + Long siteUserId = extractIdFromAuthentication(); + if (isRequired(parameter) && siteUserId == null) { throw new CustomException(AUTHENTICATION_FAILED, "로그인 상태가 아닙니다."); } - - return siteUser; + return siteUserId; } - private SiteUser extractSiteUserFromAuthentication() { + private Long extractIdFromAuthentication() { try { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); SiteUserDetails principal = (SiteUserDetails) authentication.getPrincipal(); - return principal.getSiteUser(); + return principal.getSiteUser().getId(); } catch (Exception e) { return null; } } + + private boolean isRequired(MethodParameter parameter) { + if (parameter.getParameterType().isPrimitive()) { // NPE 방지를 위해 required로 간주 + return true; + } + AuthorizedUser authorizedUser = parameter.getParameterAnnotation(AuthorizedUser.class); + return authorizedUser != null && authorizedUser.required(); + } } diff --git a/src/main/java/com/example/solidconnection/community/board/controller/BoardController.java b/src/main/java/com/example/solidconnection/community/board/controller/BoardController.java index 20601eb65..196a48239 100644 --- a/src/main/java/com/example/solidconnection/community/board/controller/BoardController.java +++ b/src/main/java/com/example/solidconnection/community/board/controller/BoardController.java @@ -4,7 +4,6 @@ import com.example.solidconnection.community.board.domain.BoardCode; import com.example.solidconnection.community.post.dto.PostListResponse; import com.example.solidconnection.community.post.service.PostQueryService; -import com.example.solidconnection.siteuser.domain.SiteUser; import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; @@ -34,7 +33,7 @@ public ResponseEntity findAccessibleCodes() { @GetMapping("/{code}") public ResponseEntity findPostsByCodeAndCategory( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, // todo: '사용하지 않는 인자'로 인증된 유저만 접근하게 하기보다는, 다른 방식으로 접근하는것이 좋을 것 같다 @PathVariable(value = "code") String code, @RequestParam(value = "category", defaultValue = "전체") String category) { List postsByCodeAndPostCategory = postQueryService diff --git a/src/main/java/com/example/solidconnection/community/comment/controller/CommentController.java b/src/main/java/com/example/solidconnection/community/comment/controller/CommentController.java index f17792a75..9e64a25fd 100644 --- a/src/main/java/com/example/solidconnection/community/comment/controller/CommentController.java +++ b/src/main/java/com/example/solidconnection/community/comment/controller/CommentController.java @@ -7,7 +7,6 @@ import com.example.solidconnection.community.comment.dto.CommentUpdateRequest; import com.example.solidconnection.community.comment.dto.CommentUpdateResponse; import com.example.solidconnection.community.comment.service.CommentService; -import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -28,29 +27,29 @@ public class CommentController { @PostMapping public ResponseEntity createComment( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @Valid @RequestBody CommentCreateRequest commentCreateRequest ) { - CommentCreateResponse response = commentService.createComment(siteUser, commentCreateRequest); + CommentCreateResponse response = commentService.createComment(siteUserId, commentCreateRequest); return ResponseEntity.ok().body(response); } @PatchMapping("/{comment_id}") public ResponseEntity updateComment( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("comment_id") Long commentId, @Valid @RequestBody CommentUpdateRequest commentUpdateRequest ) { - CommentUpdateResponse response = commentService.updateComment(siteUser, commentId, commentUpdateRequest); + CommentUpdateResponse response = commentService.updateComment(siteUserId, commentId, commentUpdateRequest); return ResponseEntity.ok().body(response); } @DeleteMapping("/{comment_id}") public ResponseEntity deleteCommentById( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("comment_id") Long commentId ) { - CommentDeleteResponse response = commentService.deleteCommentById(siteUser, commentId); + CommentDeleteResponse response = commentService.deleteCommentById(siteUserId, commentId); return ResponseEntity.ok().body(response); } } diff --git a/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java b/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java index 7f31885ea..81b6bb49b 100644 --- a/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java +++ b/src/main/java/com/example/solidconnection/community/comment/service/CommentService.java @@ -3,6 +3,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPDATE_DEPRECATED_COMMENT; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_COMMENT_LEVEL; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ACCESS; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.comment.domain.Comment; @@ -36,7 +37,9 @@ public class CommentService { private final SiteUserRepository siteUserRepository; @Transactional(readOnly = true) - public List findCommentsByPostId(SiteUser siteUser, Long postId) { + public List findCommentsByPostId(long siteUserId, Long postId) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); List allComments = commentRepository.findCommentTreeByPostId(postId); List filteredComments = filterCommentsByDeletionRules(allComments); @@ -83,7 +86,9 @@ private Boolean isOwner(Comment comment, SiteUser siteUser) { } @Transactional - public CommentCreateResponse createComment(SiteUser siteUser, CommentCreateRequest commentCreateRequest) { + public CommentCreateResponse createComment(long siteUserId, CommentCreateRequest commentCreateRequest) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); Post post = postRepository.getById(commentCreateRequest.postId()); Comment parentComment = null; @@ -105,7 +110,9 @@ private void validateCommentDepth(Comment parentComment) { } @Transactional - public CommentUpdateResponse updateComment(SiteUser siteUser, Long commentId, CommentUpdateRequest commentUpdateRequest) { + public CommentUpdateResponse updateComment(long siteUserId, Long commentId, CommentUpdateRequest commentUpdateRequest) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); Comment comment = commentRepository.getById(commentId); validateDeprecated(comment); validateOwnership(comment, siteUser); @@ -122,7 +129,9 @@ private void validateDeprecated(Comment comment) { } @Transactional - public CommentDeleteResponse deleteCommentById(SiteUser siteUser, Long commentId) { + public CommentDeleteResponse deleteCommentById(long siteUserId, Long commentId) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); Comment comment = commentRepository.getById(commentId); validateOwnership(comment, siteUser); diff --git a/src/main/java/com/example/solidconnection/community/post/controller/PostController.java b/src/main/java/com/example/solidconnection/community/post/controller/PostController.java index c329425a8..88ea58695 100644 --- a/src/main/java/com/example/solidconnection/community/post/controller/PostController.java +++ b/src/main/java/com/example/solidconnection/community/post/controller/PostController.java @@ -12,7 +12,6 @@ import com.example.solidconnection.community.post.service.PostCommandService; import com.example.solidconnection.community.post.service.PostLikeService; import com.example.solidconnection.community.post.service.PostQueryService; -import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; import java.util.Collections; import java.util.List; @@ -40,20 +39,20 @@ public class PostController { @PostMapping public ResponseEntity createPost( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @Valid @RequestPart("postCreateRequest") PostCreateRequest postCreateRequest, @RequestParam(value = "file", required = false) List imageFile ) { if (imageFile == null) { imageFile = Collections.emptyList(); } - PostCreateResponse post = postCommandService.createPost(siteUser, postCreateRequest, imageFile); + PostCreateResponse post = postCommandService.createPost(siteUserId, postCreateRequest, imageFile); return ResponseEntity.ok().body(post); } @PatchMapping(value = "/{post_id}") public ResponseEntity updatePost( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("post_id") Long postId, @Valid @RequestPart("postUpdateRequest") PostUpdateRequest postUpdateRequest, @RequestParam(value = "file", required = false) List imageFile @@ -62,44 +61,44 @@ public ResponseEntity updatePost( imageFile = Collections.emptyList(); } PostUpdateResponse postUpdateResponse = postCommandService.updatePost( - siteUser, postId, postUpdateRequest, imageFile + siteUserId, postId, postUpdateRequest, imageFile ); return ResponseEntity.ok().body(postUpdateResponse); } @GetMapping("/{post_id}") public ResponseEntity findPostById( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("post_id") Long postId ) { - PostFindResponse postFindResponse = postQueryService.findPostById(siteUser, postId); + PostFindResponse postFindResponse = postQueryService.findPostById(siteUserId, postId); return ResponseEntity.ok().body(postFindResponse); } @DeleteMapping(value = "/{post_id}") public ResponseEntity deletePostById( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("post_id") Long postId ) { - PostDeleteResponse postDeleteResponse = postCommandService.deletePostById(siteUser, postId); + PostDeleteResponse postDeleteResponse = postCommandService.deletePostById(siteUserId, postId); return ResponseEntity.ok().body(postDeleteResponse); } @PostMapping(value = "/{post_id}/like") public ResponseEntity likePost( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("post_id") Long postId ) { - PostLikeResponse postLikeResponse = postLikeService.likePost(siteUser, postId); + PostLikeResponse postLikeResponse = postLikeService.likePost(siteUserId, postId); return ResponseEntity.ok().body(postLikeResponse); } @DeleteMapping(value = "/{post_id}/like") public ResponseEntity dislikePost( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("post_id") Long postId ) { - PostDislikeResponse postDislikeResponse = postLikeService.dislikePost(siteUser, postId); + PostDislikeResponse postDislikeResponse = postLikeService.dislikePost(siteUserId, postId); return ResponseEntity.ok().body(postDislikeResponse); } } diff --git a/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java b/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java index 57721ee46..4b3b8d15a 100644 --- a/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java +++ b/src/main/java/com/example/solidconnection/community/post/service/PostCommandService.java @@ -4,6 +4,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_ACCESS; import static com.example.solidconnection.common.exception.ErrorCode.INVALID_POST_CATEGORY; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.board.domain.Board; @@ -21,6 +22,7 @@ import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.util.RedisUtils; import java.util.List; import java.util.Objects; @@ -36,13 +38,16 @@ public class PostCommandService { private final PostRepository postRepository; private final BoardRepository boardRepository; + private final SiteUserRepository siteUserRepository; private final S3Service s3Service; private final RedisService redisService; private final RedisUtils redisUtils; @Transactional - public PostCreateResponse createPost(SiteUser siteUser, PostCreateRequest postCreateRequest, + public PostCreateResponse createPost(long siteUserId, PostCreateRequest postCreateRequest, List imageFile) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); // 유효성 검증 validatePostCategory(postCreateRequest.postCategory()); validateFileSize(imageFile); @@ -59,8 +64,10 @@ public PostCreateResponse createPost(SiteUser siteUser, PostCreateRequest postCr } @Transactional - public PostUpdateResponse updatePost(SiteUser siteUser, Long postId, PostUpdateRequest postUpdateRequest, + public PostUpdateResponse updatePost(long siteUserId, Long postId, PostUpdateRequest postUpdateRequest, List imageFile) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); // 유효성 검증 Post post = postRepository.getById(postId); validateOwnership(post, siteUser); @@ -89,7 +96,9 @@ private void savePostImages(List imageFile, Post post) { } @Transactional - public PostDeleteResponse deletePostById(SiteUser siteUser, Long postId) { + public PostDeleteResponse deletePostById(long siteUserId, Long postId) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); Post post = postRepository.getById(postId); validateOwnership(post, siteUser); validateQuestion(post); diff --git a/src/main/java/com/example/solidconnection/community/post/service/PostLikeService.java b/src/main/java/com/example/solidconnection/community/post/service/PostLikeService.java index 1a7ba05ae..8f9eeb318 100644 --- a/src/main/java/com/example/solidconnection/community/post/service/PostLikeService.java +++ b/src/main/java/com/example/solidconnection/community/post/service/PostLikeService.java @@ -1,6 +1,7 @@ package com.example.solidconnection.community.post.service; import static com.example.solidconnection.common.exception.ErrorCode.DUPLICATE_POST_LIKE; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.community.post.domain.Post; @@ -10,6 +11,7 @@ import com.example.solidconnection.community.post.repository.PostLikeRepository; import com.example.solidconnection.community.post.repository.PostRepository; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; @@ -21,9 +23,12 @@ public class PostLikeService { private final PostRepository postRepository; private final PostLikeRepository postLikeRepository; + private final SiteUserRepository siteUserRepository; @Transactional(isolation = Isolation.READ_COMMITTED) - public PostLikeResponse likePost(SiteUser siteUser, Long postId) { + public PostLikeResponse likePost(long siteUserId, Long postId) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); Post post = postRepository.getById(postId); validateDuplicatePostLike(post, siteUser); PostLike postLike = new PostLike(); @@ -35,7 +40,9 @@ public PostLikeResponse likePost(SiteUser siteUser, Long postId) { } @Transactional(isolation = Isolation.READ_COMMITTED) - public PostDislikeResponse dislikePost(SiteUser siteUser, Long postId) { + public PostDislikeResponse dislikePost(long siteUserId, Long postId) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); Post post = postRepository.getById(postId); PostLike postLike = postLikeRepository.getByPostAndSiteUserId(post, siteUser.getId()); diff --git a/src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java b/src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java index a6f956fc0..85657a8a7 100644 --- a/src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java +++ b/src/main/java/com/example/solidconnection/community/post/service/PostQueryService.java @@ -54,7 +54,9 @@ public List findPostsByCodeAndPostCategory(String code, String } @Transactional(readOnly = true) - public PostFindResponse findPostById(SiteUser siteUser, Long postId) { + public PostFindResponse findPostById(long siteUserId, Long postId) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); Post post = postRepository.getByIdUsingEntityGraph(postId); Boolean isOwner = getIsOwner(post, siteUser); Boolean isLiked = getIsLiked(post, siteUser); @@ -66,7 +68,7 @@ public PostFindResponse findPostById(SiteUser siteUser, Long postId) { PostFindBoardResponse boardPostFindResultDTO = PostFindBoardResponse.from(board); PostFindSiteUserResponse siteUserPostFindResultDTO = PostFindSiteUserResponse.from(postAuthor); List postImageFindResultDTOList = PostFindPostImageResponse.from(post.getPostImageList()); - List commentFindResultDTOList = commentService.findCommentsByPostId(siteUser, postId); + List commentFindResultDTOList = commentService.findCommentsByPostId(siteUser.getId(), postId); // caching && 어뷰징 방지 if (redisService.isPresent(redisUtils.getValidatePostViewCountRedisKey(siteUser.getId(), postId))) { diff --git a/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java b/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java index 9900e4496..5d9cde156 100644 --- a/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java +++ b/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java @@ -7,7 +7,6 @@ import com.example.solidconnection.mentor.dto.MentorDetailResponse; import com.example.solidconnection.mentor.dto.MentorPreviewResponse; import com.example.solidconnection.mentor.service.MentorQueryService; -import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -30,18 +29,18 @@ public class MentorController { @GetMapping("/{mentor-id}") public ResponseEntity getMentorDetails( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("mentor-id") Long mentorId ) { - MentorDetailResponse response = mentorQueryService.getMentorDetails(mentorId, siteUser); + MentorDetailResponse response = mentorQueryService.getMentorDetails(mentorId, siteUserId); return ResponseEntity.ok(response); } @GetMapping public ResponseEntity> getMentorPreviews( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @RequestParam("region") String region, - + @PageableDefault(size = 3, sort = "menteeCount", direction = DESC) @SortDefaults({ @SortDefault(sort = "menteeCount", direction = Sort.Direction.DESC), @@ -49,7 +48,7 @@ public ResponseEntity> getMentorPreviews( }) Pageable pageable ) { - SliceResponse response = mentorQueryService.getMentorPreviews(region, siteUser, pageable); + SliceResponse response = mentorQueryService.getMentorPreviews(region, siteUserId, pageable); return ResponseEntity.ok(response); } } diff --git a/src/main/java/com/example/solidconnection/mentor/controller/MentorMyPageController.java b/src/main/java/com/example/solidconnection/mentor/controller/MentorMyPageController.java index ba8bc0911..dd9289a3e 100644 --- a/src/main/java/com/example/solidconnection/mentor/controller/MentorMyPageController.java +++ b/src/main/java/com/example/solidconnection/mentor/controller/MentorMyPageController.java @@ -6,7 +6,6 @@ import com.example.solidconnection.mentor.service.MentorMyPageService; import com.example.solidconnection.security.annotation.RequireRoleAccess; import com.example.solidconnection.siteuser.domain.Role; -import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -26,19 +25,19 @@ public class MentorMyPageController { @RequireRoleAccess(roles = Role.MENTOR) @GetMapping public ResponseEntity getMentorMyPage( - @AuthorizedUser SiteUser siteUser + @AuthorizedUser long siteUserId ) { - MentorMyPageResponse mentorMyPageResponse = mentorMyPageService.getMentorMyPage(siteUser); + MentorMyPageResponse mentorMyPageResponse = mentorMyPageService.getMentorMyPage(siteUserId); return ResponseEntity.ok(mentorMyPageResponse); } @RequireRoleAccess(roles = Role.MENTOR) @PutMapping public ResponseEntity updateMentorMyPage( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @Valid @RequestBody MentorMyPageUpdateRequest mentorMyPageUpdateRequest ) { - mentorMyPageService.updateMentorMyPage(siteUser, mentorMyPageUpdateRequest); + mentorMyPageService.updateMentorMyPage(siteUserId, mentorMyPageUpdateRequest); return ResponseEntity.ok().build(); } } diff --git a/src/main/java/com/example/solidconnection/mentor/controller/MentoringController.java b/src/main/java/com/example/solidconnection/mentor/controller/MentoringController.java index 9df73e41c..5a347f3fb 100644 --- a/src/main/java/com/example/solidconnection/mentor/controller/MentoringController.java +++ b/src/main/java/com/example/solidconnection/mentor/controller/MentoringController.java @@ -12,7 +12,6 @@ import com.example.solidconnection.mentor.service.MentoringQueryService; import com.example.solidconnection.security.annotation.RequireRoleAccess; import com.example.solidconnection.siteuser.domain.Role; -import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -35,49 +34,49 @@ public class MentoringController { @RequireRoleAccess(roles = Role.MENTEE) @PostMapping("/apply") public ResponseEntity applyMentoring( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @Valid @RequestBody MentoringApplyRequest mentoringApplyRequest ) { - MentoringApplyResponse response = mentoringCommandService.applyMentoring(siteUser.getId(), mentoringApplyRequest); + MentoringApplyResponse response = mentoringCommandService.applyMentoring(siteUserId, mentoringApplyRequest); return ResponseEntity.ok(response); } @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) @GetMapping("/apply") public ResponseEntity getMentorings( - @AuthorizedUser SiteUser siteUser + @AuthorizedUser long siteUserId ) { - MentoringListResponse responses = mentoringQueryService.getMentorings(siteUser.getId()); + MentoringListResponse responses = mentoringQueryService.getMentorings(siteUserId); return ResponseEntity.ok(responses); } @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) @PatchMapping("/{mentoring-id}/apply") public ResponseEntity confirmMentoring( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("mentoring-id") Long mentoringId, @Valid @RequestBody MentoringConfirmRequest mentoringConfirmRequest ) { - MentoringConfirmResponse response = mentoringCommandService.confirmMentoring(siteUser.getId(), mentoringId, mentoringConfirmRequest); + MentoringConfirmResponse response = mentoringCommandService.confirmMentoring(siteUserId, mentoringId, mentoringConfirmRequest); return ResponseEntity.ok(response); } @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) @PatchMapping("/{mentoring-id}/check") public ResponseEntity checkMentoring( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("mentoring-id") Long mentoringId ) { - MentoringCheckResponse response = mentoringCommandService.checkMentoring(siteUser.getId(), mentoringId); + MentoringCheckResponse response = mentoringCommandService.checkMentoring(siteUserId, mentoringId); return ResponseEntity.ok(response); } @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) @GetMapping("/check") public ResponseEntity getUncheckedMentoringsCount( - @AuthorizedUser SiteUser siteUser + @AuthorizedUser long siteUserId ) { - MentoringCountResponse response = mentoringQueryService.getNewMentoringsCount(siteUser.getId()); + MentoringCountResponse response = mentoringQueryService.getNewMentoringsCount(siteUserId); return ResponseEntity.ok(response); } } diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorMyPageService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorMyPageService.java index e4a920397..365d10ad4 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentorMyPageService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorMyPageService.java @@ -2,6 +2,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.CHANNEL_REGISTRATION_LIMIT_EXCEEDED; import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.mentor.domain.Channel; @@ -11,6 +12,7 @@ import com.example.solidconnection.mentor.dto.MentorMyPageUpdateRequest; import com.example.solidconnection.mentor.repository.MentorRepository; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; @@ -25,18 +27,21 @@ public class MentorMyPageService { private static final int CHANNEL_SEQUENCE_START_NUMBER = 1; private final MentorRepository mentorRepository; + private final SiteUserRepository siteUserRepository; @Transactional(readOnly = true) - public MentorMyPageResponse getMentorMyPage(SiteUser siteUser) { + public MentorMyPageResponse getMentorMyPage(long siteUserId) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); Mentor mentor = mentorRepository.findBySiteUserId(siteUser.getId()) .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); return MentorMyPageResponse.of(mentor, siteUser); } @Transactional - public void updateMentorMyPage(SiteUser siteUser, MentorMyPageUpdateRequest request) { + public void updateMentorMyPage(long siteUserId, MentorMyPageUpdateRequest request) { validateChannelRegistrationLimit(request.channels()); - Mentor mentor = mentorRepository.findBySiteUserId(siteUser.getId()) + Mentor mentor = mentorRepository.findBySiteUserId(siteUserId) .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); mentor.updateIntroduction(request.introduction()); diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java index e6d1ffd67..7d878a1d9 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java @@ -31,28 +31,28 @@ public class MentorQueryService { private final MentorBatchQueryRepository mentorBatchQueryRepository; @Transactional(readOnly = true) - public MentorDetailResponse getMentorDetails(long mentorId, SiteUser currentUser) { + public MentorDetailResponse getMentorDetails(long mentorId, long currentUserId) { Mentor mentor = mentorRepository.findById(mentorId) .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); SiteUser mentorUser = siteUserRepository.findById(mentor.getSiteUserId()) .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); - boolean isApplied = mentoringRepository.existsByMentorIdAndMenteeId(mentorId, currentUser.getId()); + boolean isApplied = mentoringRepository.existsByMentorIdAndMenteeId(mentorId, currentUserId); return MentorDetailResponse.of(mentor, mentorUser, isApplied); } @Transactional(readOnly = true) - public SliceResponse getMentorPreviews(String region, SiteUser siteUser, Pageable pageable) { // todo: 멘토의 '인증' 작업 후 region 필터링 추가 + public SliceResponse getMentorPreviews(String region, long currentUserId, Pageable pageable) { // todo: 멘토의 '인증' 작업 후 region 필터링 추가 Slice mentorSlice = mentorRepository.findAllBy(pageable); List mentors = mentorSlice.toList(); - List content = getMentorPreviewResponses(mentors, siteUser); + List content = getMentorPreviewResponses(mentors, currentUserId); return SliceResponse.of(content, mentorSlice); } - private List getMentorPreviewResponses(List mentors, SiteUser siteUser) { + private List getMentorPreviewResponses(List mentors, long currentUserId) { Map mentorIdToSiteUser = mentorBatchQueryRepository.getMentorIdToSiteUserMap(mentors); - Map mentorIdToIsApplied = mentorBatchQueryRepository.getMentorIdToIsApplied(mentors, siteUser.getId()); + Map mentorIdToIsApplied = mentorBatchQueryRepository.getMentorIdToIsApplied(mentors, currentUserId); List mentorPreviews = new ArrayList<>(); for (Mentor mentor : mentors) { diff --git a/src/main/java/com/example/solidconnection/news/controller/NewsController.java b/src/main/java/com/example/solidconnection/news/controller/NewsController.java index dd522fb47..6ba7b2eda 100644 --- a/src/main/java/com/example/solidconnection/news/controller/NewsController.java +++ b/src/main/java/com/example/solidconnection/news/controller/NewsController.java @@ -11,7 +11,6 @@ import com.example.solidconnection.news.service.NewsQueryService; import com.example.solidconnection.security.annotation.RequireRoleAccess; import com.example.solidconnection.siteuser.domain.Role; -import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -47,24 +46,24 @@ public ResponseEntity findNewsBySiteUserId( @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) @PostMapping public ResponseEntity createNews( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @Valid @RequestPart("newsCreateRequest") NewsCreateRequest newsCreateRequest, @RequestParam(value = "file", required = false) MultipartFile imageFile ) { - NewsCommandResponse newsCommandResponse = newsCommandService.createNews(siteUser.getId(), newsCreateRequest, imageFile); + NewsCommandResponse newsCommandResponse = newsCommandService.createNews(siteUserId, newsCreateRequest, imageFile); return ResponseEntity.ok(newsCommandResponse); } @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) @PutMapping("/{news-id}") public ResponseEntity updateNews( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("news-id") Long newsId, @Valid @RequestPart(value = "newsUpdateRequest") NewsUpdateRequest newsUpdateRequest, @RequestParam(value = "file", required = false) MultipartFile imageFile ) { NewsCommandResponse newsCommandResponse = newsCommandService.updateNews( - siteUser.getId(), + siteUserId, newsId, newsUpdateRequest, imageFile); @@ -74,37 +73,37 @@ public ResponseEntity updateNews( @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) @DeleteMapping("/{news-id}") public ResponseEntity deleteNewsById( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("news-id") Long newsId ) { - NewsCommandResponse newsCommandResponse = newsCommandService.deleteNewsById(siteUser, newsId); + NewsCommandResponse newsCommandResponse = newsCommandService.deleteNewsById(siteUserId, newsId); return ResponseEntity.ok(newsCommandResponse); } @GetMapping("/{news-id}/like") public ResponseEntity isNewsLiked( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("news-id") Long newsId ) { - LikedNewsResponse likedNewsResponse = newsLikeService.isNewsLiked(siteUser.getId(), newsId); + LikedNewsResponse likedNewsResponse = newsLikeService.isNewsLiked(siteUserId, newsId); return ResponseEntity.ok(likedNewsResponse); } @PostMapping("/{news-id}/like") public ResponseEntity addNewsLike( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("news-id") Long newsId ) { - newsLikeService.addNewsLike(siteUser.getId(), newsId); + newsLikeService.addNewsLike(siteUserId, newsId); return ResponseEntity.ok().build(); } @DeleteMapping("/{news-id}/like") public ResponseEntity cancelNewsLike( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("news-id") Long newsId ) { - newsLikeService.cancelNewsLike(siteUser.getId(), newsId); + newsLikeService.cancelNewsLike(siteUserId, newsId); return ResponseEntity.ok().build(); } } diff --git a/src/main/java/com/example/solidconnection/news/service/NewsCommandService.java b/src/main/java/com/example/solidconnection/news/service/NewsCommandService.java index 2875742d5..ca1b262fe 100644 --- a/src/main/java/com/example/solidconnection/news/service/NewsCommandService.java +++ b/src/main/java/com/example/solidconnection/news/service/NewsCommandService.java @@ -2,6 +2,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.INVALID_NEWS_ACCESS; import static com.example.solidconnection.common.exception.ErrorCode.NEWS_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.news.config.NewsProperties; @@ -15,6 +16,7 @@ import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -27,6 +29,7 @@ public class NewsCommandService { private final S3Service s3Service; private final NewsProperties newsProperties; private final NewsRepository newsRepository; + private final SiteUserRepository siteUserRepository; @Transactional public NewsCommandResponse createNews(long siteUserId, NewsCreateRequest newsCreateRequest, MultipartFile imageFile) { @@ -77,7 +80,9 @@ private void updateThumbnail(News news, MultipartFile imageFile, Boolean resetTo } @Transactional - public NewsCommandResponse deleteNewsById(SiteUser siteUser, Long newsId) { + public NewsCommandResponse deleteNewsById(long siteUserId, Long newsId) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); News news = newsRepository.findById(newsId) .orElseThrow(() -> new CustomException(NEWS_NOT_FOUND)); validatePermission(siteUser, news); diff --git a/src/main/java/com/example/solidconnection/s3/controller/S3Controller.java b/src/main/java/com/example/solidconnection/s3/controller/S3Controller.java index 20013fa6a..1bd978627 100644 --- a/src/main/java/com/example/solidconnection/s3/controller/S3Controller.java +++ b/src/main/java/com/example/solidconnection/s3/controller/S3Controller.java @@ -5,7 +5,6 @@ import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.dto.urlPrefixResponse; import com.example.solidconnection.s3.service.S3Service; -import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; @@ -45,11 +44,11 @@ public ResponseEntity uploadPreProfileImage( @PostMapping("/profile/post") public ResponseEntity uploadPostProfileImage( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @RequestParam("file") MultipartFile imageFile ) { UploadedFileUrlResponse profileImageUrl = s3Service.uploadFile(imageFile, ImgType.PROFILE); - s3Service.deleteExProfile(siteUser); + s3Service.deleteExProfile(siteUserId); return ResponseEntity.ok(profileImageUrl); } diff --git a/src/main/java/com/example/solidconnection/s3/service/S3Service.java b/src/main/java/com/example/solidconnection/s3/service/S3Service.java index 1aa0ebbf1..11b66a499 100644 --- a/src/main/java/com/example/solidconnection/s3/service/S3Service.java +++ b/src/main/java/com/example/solidconnection/s3/service/S3Service.java @@ -5,6 +5,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.NOT_ALLOWED_FILE_EXTENSIONS; import static com.example.solidconnection.common.exception.ErrorCode.S3_CLIENT_EXCEPTION; import static com.example.solidconnection.common.exception.ErrorCode.S3_SERVICE_EXCEPTION; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.amazonaws.AmazonServiceException; import com.amazonaws.SdkClientException; @@ -15,6 +16,7 @@ import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import jakarta.transaction.Transactional; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -109,7 +111,10 @@ private String getFileExtension(String fileName) { * - 기존 파일의 key(S3파일명)를 찾는다. * - S3에서 파일을 삭제한다. * */ - public void deleteExProfile(SiteUser siteUser) { + @Transactional + public void deleteExProfile(long siteUserId) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); String key = siteUser.getProfileImageUrl(); deleteFile(key); } diff --git a/src/main/java/com/example/solidconnection/score/controller/ScoreController.java b/src/main/java/com/example/solidconnection/score/controller/ScoreController.java index 257c3ef4c..3689cf750 100644 --- a/src/main/java/com/example/solidconnection/score/controller/ScoreController.java +++ b/src/main/java/com/example/solidconnection/score/controller/ScoreController.java @@ -6,7 +6,6 @@ import com.example.solidconnection.score.dto.LanguageTestScoreRequest; import com.example.solidconnection.score.dto.LanguageTestScoreStatusesResponse; import com.example.solidconnection.score.service.ScoreService; -import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -28,40 +27,40 @@ public class ScoreController { // 학점을 등록하는 api @PostMapping("/gpas") public ResponseEntity submitGpaScore( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @Valid @RequestPart("gpaScoreRequest") GpaScoreRequest gpaScoreRequest, @RequestParam("file") MultipartFile file ) { - Long id = scoreService.submitGpaScore(siteUser, gpaScoreRequest, file); + Long id = scoreService.submitGpaScore(siteUserId, gpaScoreRequest, file); return ResponseEntity.ok(id); } // 어학성적을 등록하는 api @PostMapping("/language-tests") public ResponseEntity submitLanguageTestScore( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @Valid @RequestPart("languageTestScoreRequest") LanguageTestScoreRequest languageTestScoreRequest, @RequestParam("file") MultipartFile file ) { - Long id = scoreService.submitLanguageTestScore(siteUser, languageTestScoreRequest, file); + Long id = scoreService.submitLanguageTestScore(siteUserId, languageTestScoreRequest, file); return ResponseEntity.ok(id); } // 학점 상태를 확인하는 api @GetMapping("/gpas") public ResponseEntity getGpaScoreStatus( - @AuthorizedUser SiteUser siteUser + @AuthorizedUser long siteUserId ) { - GpaScoreStatusesResponse gpaScoreStatus = scoreService.getGpaScoreStatus(siteUser); + GpaScoreStatusesResponse gpaScoreStatus = scoreService.getGpaScoreStatus(siteUserId); return ResponseEntity.ok(gpaScoreStatus); } // 어학 성적 상태를 확인하는 api @GetMapping("/language-tests") public ResponseEntity getLanguageTestScoreStatus( - @AuthorizedUser SiteUser siteUser + @AuthorizedUser long siteUserId ) { - LanguageTestScoreStatusesResponse languageTestScoreStatus = scoreService.getLanguageTestScoreStatus(siteUser); + LanguageTestScoreStatusesResponse languageTestScoreStatus = scoreService.getLanguageTestScoreStatus(siteUserId); return ResponseEntity.ok(languageTestScoreStatus); } } diff --git a/src/main/java/com/example/solidconnection/score/service/ScoreService.java b/src/main/java/com/example/solidconnection/score/service/ScoreService.java index daa054a83..f16951d49 100644 --- a/src/main/java/com/example/solidconnection/score/service/ScoreService.java +++ b/src/main/java/com/example/solidconnection/score/service/ScoreService.java @@ -1,7 +1,10 @@ package com.example.solidconnection.score.service; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; + import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.s3.domain.ImgType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; @@ -16,6 +19,7 @@ import com.example.solidconnection.score.repository.GpaScoreRepository; import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -30,9 +34,12 @@ public class ScoreService { private final GpaScoreRepository gpaScoreRepository; private final S3Service s3Service; private final LanguageTestScoreRepository languageTestScoreRepository; + private final SiteUserRepository siteUserRepository; @Transactional - public Long submitGpaScore(SiteUser siteUser, GpaScoreRequest gpaScoreRequest, MultipartFile file) { + public Long submitGpaScore(long siteUserId, GpaScoreRequest gpaScoreRequest, MultipartFile file) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(file, ImgType.GPA); Gpa gpa = new Gpa(gpaScoreRequest.gpa(), gpaScoreRequest.gpaCriteria(), uploadedFile.fileUrl()); GpaScore newGpaScore = new GpaScore(gpa, siteUser); @@ -41,7 +48,9 @@ public Long submitGpaScore(SiteUser siteUser, GpaScoreRequest gpaScoreRequest, M } @Transactional - public Long submitLanguageTestScore(SiteUser siteUser, LanguageTestScoreRequest languageTestScoreRequest, MultipartFile file) { + public Long submitLanguageTestScore(long siteUserId, LanguageTestScoreRequest languageTestScoreRequest, MultipartFile file) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(file, ImgType.LANGUAGE_TEST); LanguageTest languageTest = new LanguageTest(languageTestScoreRequest.languageTestType(), languageTestScoreRequest.languageTestScore(), uploadedFile.fileUrl()); @@ -51,7 +60,9 @@ public Long submitLanguageTestScore(SiteUser siteUser, LanguageTestScoreRequest } @Transactional(readOnly = true) - public GpaScoreStatusesResponse getGpaScoreStatus(SiteUser siteUser) { + public GpaScoreStatusesResponse getGpaScoreStatus(long siteUserId) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); List gpaScoreStatusResponseList = gpaScoreRepository.findBySiteUserId(siteUser.getId()) .stream() @@ -62,7 +73,9 @@ public GpaScoreStatusesResponse getGpaScoreStatus(SiteUser siteUser) { } @Transactional(readOnly = true) - public LanguageTestScoreStatusesResponse getLanguageTestScoreStatus(SiteUser siteUser) { + public LanguageTestScoreStatusesResponse getLanguageTestScoreStatus(long siteUserId) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); List languageTestScores = languageTestScoreRepository.findBySiteUserId(siteUser.getId()); List languageTestScoreStatusResponseList = diff --git a/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java b/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java index 772da0d32..39dcda29a 100644 --- a/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java +++ b/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java @@ -2,7 +2,6 @@ import com.example.solidconnection.common.resolver.AuthorizedUser; -import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.MyPageResponse; import com.example.solidconnection.siteuser.service.MyPageService; import lombok.RequiredArgsConstructor; @@ -23,19 +22,19 @@ class MyPageController { @GetMapping public ResponseEntity getMyPageInfo( - @AuthorizedUser SiteUser siteUser + @AuthorizedUser long siteUserId ) { - MyPageResponse myPageResponse = myPageService.getMyPageInfo(siteUser); + MyPageResponse myPageResponse = myPageService.getMyPageInfo(siteUserId); return ResponseEntity.ok(myPageResponse); } @PatchMapping public ResponseEntity updateMyPageInfo( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @RequestParam(value = "file", required = false) MultipartFile imageFile, @RequestParam(value = "nickname", required = false) String nickname ) { - myPageService.updateMyPageInfo(siteUser, imageFile, nickname); + myPageService.updateMyPageInfo(siteUserId, imageFile, nickname); return ResponseEntity.ok().build(); } } diff --git a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java index b5602fbf5..7b85bd411 100644 --- a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java +++ b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java @@ -34,7 +34,9 @@ public class MyPageService { * 마이페이지 정보를 조회한다. * */ @Transactional(readOnly = true) - public MyPageResponse getMyPageInfo(SiteUser siteUser) { + public MyPageResponse getMyPageInfo(long siteUserId) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); int likedUnivApplyInfoCount = likedUnivApplyInfoRepository.countBySiteUserId(siteUser.getId()); return MyPageResponse.of(siteUser, likedUnivApplyInfoCount); } @@ -43,8 +45,8 @@ public MyPageResponse getMyPageInfo(SiteUser siteUser) { * 마이페이지 정보를 수정한다. * */ @Transactional - public void updateMyPageInfo(SiteUser siteUser, MultipartFile imageFile, String nickname) { - SiteUser user = siteUserRepository.findById(siteUser.getId()) + public void updateMyPageInfo(long siteUserId, MultipartFile imageFile, String nickname) { + SiteUser user = siteUserRepository.findById(siteUserId) .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); if (nickname != null) { @@ -57,7 +59,7 @@ public void updateMyPageInfo(SiteUser siteUser, MultipartFile imageFile, String if (imageFile != null && !imageFile.isEmpty()) { UploadedFileUrlResponse uploadedFile = s3Service.uploadFile(imageFile, ImgType.PROFILE); if (!isDefaultProfileImage(user.getProfileImageUrl())) { - s3Service.deleteExProfile(user); + s3Service.deleteExProfile(user.getId()); } String profileImageUrl = uploadedFile.fileUrl(); user.setProfileImageUrl(profileImageUrl); diff --git a/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java b/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java index 5b1a759b8..c3b986d40 100644 --- a/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java +++ b/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java @@ -1,7 +1,6 @@ package com.example.solidconnection.university.controller; import com.example.solidconnection.common.resolver.AuthorizedUser; -import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.dto.IsLikeResponse; import com.example.solidconnection.university.dto.UnivApplyInfoDetailResponse; @@ -32,48 +31,48 @@ public class UnivApplyInfoController { @GetMapping("/recommend") public ResponseEntity getUnivApplyInfoRecommends( - @AuthorizedUser(required = false) SiteUser siteUser + @AuthorizedUser(required = false) Long siteUserId ) { - if (siteUser == null) { + if (siteUserId == null) { return ResponseEntity.ok(univApplyInfoRecommendService.getGeneralRecommends()); } else { - return ResponseEntity.ok(univApplyInfoRecommendService.getPersonalRecommends(siteUser)); + return ResponseEntity.ok(univApplyInfoRecommendService.getPersonalRecommends(siteUserId)); } } // todo: return 타입 UnivApplyInfoPreviewResponses 같이 객체로 묶어서 반환하는 것으로 변경 필요 @GetMapping("/like") public ResponseEntity> getLikedUnivApplyInfos( - @AuthorizedUser SiteUser siteUser + @AuthorizedUser long siteUserId ) { - List likedUnivApplyInfos = likedUnivApplyInfoService.getLikedUnivApplyInfos(siteUser); + List likedUnivApplyInfos = likedUnivApplyInfoService.getLikedUnivApplyInfos(siteUserId); return ResponseEntity.ok(likedUnivApplyInfos); } @GetMapping("/{univ-apply-info-id}/like") public ResponseEntity isUnivApplyInfoLiked( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("univ-apply-info-id") Long univApplyInfoId ) { - IsLikeResponse isLiked = likedUnivApplyInfoService.isUnivApplyInfoLiked(siteUser, univApplyInfoId); + IsLikeResponse isLiked = likedUnivApplyInfoService.isUnivApplyInfoLiked(siteUserId, univApplyInfoId); return ResponseEntity.ok(isLiked); } @PostMapping("/{univ-apply-info-id}/like") public ResponseEntity addUnivApplyInfoLike( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("univ-apply-info-id") Long univApplyInfoId ) { - likedUnivApplyInfoService.addUnivApplyInfoLike(siteUser, univApplyInfoId); + likedUnivApplyInfoService.addUnivApplyInfoLike(siteUserId, univApplyInfoId); return ResponseEntity.ok().build(); } @DeleteMapping("/{univ-apply-info-id}/like") public ResponseEntity cancelUnivApplyInfoLike( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @PathVariable("univ-apply-info-id") Long univApplyInfoId ) { - likedUnivApplyInfoService.cancelUnivApplyInfoLike(siteUser, univApplyInfoId); + likedUnivApplyInfoService.cancelUnivApplyInfoLike(siteUserId, univApplyInfoId); return ResponseEntity.ok().build(); } diff --git a/src/main/java/com/example/solidconnection/university/service/LikedUnivApplyInfoService.java b/src/main/java/com/example/solidconnection/university/service/LikedUnivApplyInfoService.java index 7be082d9e..0c6e06d21 100644 --- a/src/main/java/com/example/solidconnection/university/service/LikedUnivApplyInfoService.java +++ b/src/main/java/com/example/solidconnection/university/service/LikedUnivApplyInfoService.java @@ -4,7 +4,6 @@ import static com.example.solidconnection.common.exception.ErrorCode.NOT_LIKED_UNIV_APPLY_INFO; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.university.domain.LikedUnivApplyInfo; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.dto.IsLikeResponse; @@ -32,8 +31,8 @@ public class LikedUnivApplyInfoService { * '좋아요'한 대학교 목록을 조회한다. * */ @Transactional(readOnly = true) - public List getLikedUnivApplyInfos(SiteUser siteUser) { - List univApplyInfos = likedUnivApplyInfoRepository.findUnivApplyInfosBySiteUserId(siteUser.getId()); + public List getLikedUnivApplyInfos(long siteUserId) { + List univApplyInfos = likedUnivApplyInfoRepository.findUnivApplyInfosBySiteUserId(siteUserId); return univApplyInfos.stream() .map(UnivApplyInfoPreviewResponse::from) .toList(); @@ -43,17 +42,17 @@ public List getLikedUnivApplyInfos(SiteUser siteUs * 대학교를 '좋아요' 한다. * */ @Transactional - public void addUnivApplyInfoLike(SiteUser siteUser, Long univApplyInfoId) { + public void addUnivApplyInfoLike(long siteUserId, Long univApplyInfoId) { UnivApplyInfo univApplyInfo = univApplyInfoRepository.getUnivApplyInfoById(univApplyInfoId); - Optional optionalLikedUnivApplyInfo = likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId(siteUser.getId(), univApplyInfo.getId()); + Optional optionalLikedUnivApplyInfo = likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId(siteUserId, univApplyInfo.getId()); if (optionalLikedUnivApplyInfo.isPresent()) { throw new CustomException(ALREADY_LIKED_UNIV_APPLY_INFO); } LikedUnivApplyInfo likedUnivApplyInfo = LikedUnivApplyInfo.builder() .univApplyInfoId(univApplyInfo.getId()) - .siteUserId(siteUser.getId()) + .siteUserId(siteUserId) .build(); likedUnivApplyInfoRepository.save(likedUnivApplyInfo); } @@ -62,10 +61,10 @@ public void addUnivApplyInfoLike(SiteUser siteUser, Long univApplyInfoId) { * 대학교 '좋아요'를 취소한다. * */ @Transactional - public void cancelUnivApplyInfoLike(SiteUser siteUser, long univApplyInfoId) { + public void cancelUnivApplyInfoLike(long siteUserId, long univApplyInfoId) { UnivApplyInfo univApplyInfo = univApplyInfoRepository.getUnivApplyInfoById(univApplyInfoId); - Optional optionalLikedUnivApplyInfo = likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId(siteUser.getId(), univApplyInfo.getId()); + Optional optionalLikedUnivApplyInfo = likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId(siteUserId, univApplyInfo.getId()); if (optionalLikedUnivApplyInfo.isEmpty()) { throw new CustomException(NOT_LIKED_UNIV_APPLY_INFO); } @@ -77,9 +76,9 @@ public void cancelUnivApplyInfoLike(SiteUser siteUser, long univApplyInfoId) { * '좋아요'한 대학교인지 확인한다. * */ @Transactional(readOnly = true) - public IsLikeResponse isUnivApplyInfoLiked(SiteUser siteUser, Long univApplyInfoId) { + public IsLikeResponse isUnivApplyInfoLiked(long siteUserId, Long univApplyInfoId) { UnivApplyInfo univApplyInfo = univApplyInfoRepository.getUnivApplyInfoById(univApplyInfoId); - boolean isLike = likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId(siteUser.getId(), univApplyInfo.getId()).isPresent(); + boolean isLike = likedUnivApplyInfoRepository.findBySiteUserIdAndUnivApplyInfoId(siteUserId, univApplyInfo.getId()).isPresent(); return new IsLikeResponse(isLike); } } diff --git a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendService.java b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendService.java index b3ef68baa..3baeff9f6 100644 --- a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendService.java +++ b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendService.java @@ -1,7 +1,6 @@ package com.example.solidconnection.university.service; import com.example.solidconnection.cache.annotation.ThunderingHerdCaching; -import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; import com.example.solidconnection.university.dto.UnivApplyInfoRecommendsResponse; @@ -33,10 +32,10 @@ public class UnivApplyInfoRecommendService { * - 맞춤 추천 대학교의 수가 6개보다 적다면, 공통 추천 대학교 후보에서 이번 term 에 열리는 학교들을 부족한 수 만큼 불러온다. * */ @Transactional(readOnly = true) - public UnivApplyInfoRecommendsResponse getPersonalRecommends(SiteUser siteUser) { + public UnivApplyInfoRecommendsResponse getPersonalRecommends(long siteUserId) { // 맞춤 추천 대학교를 불러온다. List personalRecommends = univApplyInfoRepository - .findAllBySiteUsersInterestedCountryOrRegionAndTerm(siteUser.getId(), term); + .findAllBySiteUsersInterestedCountryOrRegionAndTerm(siteUserId, term); List trimmedRecommends = personalRecommends.subList(0, Math.min(RECOMMEND_UNIV_APPLY_INFO_NUM, personalRecommends.size())); Collections.shuffle(trimmedRecommends); diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java index 0fcec8d3b..a53c6b6bf 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationQueryServiceTest.java @@ -132,7 +132,7 @@ class 지원자_목록_조회_테스트 { // when ApplicationsResponse response = applicationQueryService.getApplicants( - user1, + user1.getId(), "", "" ); @@ -184,7 +184,7 @@ class 지원자_목록_조회_테스트 { // when ApplicationsResponse response = applicationQueryService.getApplicants( - user1, + user1.getId(), regionFixture.영미권().getCode(), "" ); @@ -234,7 +234,7 @@ class 지원자_목록_조회_테스트 { // when ApplicationsResponse response = applicationQueryService.getApplicants( - user1, + user1.getId(), null, "괌" ); @@ -264,7 +264,7 @@ class 지원자_목록_조회_테스트 { // when ApplicationsResponse response = applicationQueryService.getApplicants( - user1, + user1.getId(), "", "" ); @@ -304,7 +304,7 @@ class 지원자_목록_조회_테스트 { // when ApplicationsResponse response = applicationQueryService.getApplicants( - user1, + user1.getId(), "", "" ); @@ -354,7 +354,7 @@ class 경쟁자_목록_조회_테스트 { null ); // when - ApplicationsResponse response = applicationQueryService.getApplicantsByUserApplications(user1); + ApplicationsResponse response = applicationQueryService.getApplicantsByUserApplications(user1.getId()); // then assertThat(response.firstChoice()).containsExactlyInAnyOrder( @@ -398,7 +398,7 @@ class 경쟁자_목록_조회_테스트 { ); // when - ApplicationsResponse response = applicationQueryService.getApplicantsByUserApplications(user1); + ApplicationsResponse response = applicationQueryService.getApplicantsByUserApplications(user1.getId()); // then assertThat(response.firstChoice()) diff --git a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java index cbd36642e..5281faa3e 100644 --- a/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java +++ b/src/test/java/com/example/solidconnection/application/service/ApplicationSubmissionServiceTest.java @@ -81,7 +81,7 @@ void setUp() { ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), univApplyInfoChoiceRequest); // when - ApplicationSubmissionResponse response = applicationSubmissionService.apply(user, request); + ApplicationSubmissionResponse response = applicationSubmissionService.apply(user.getId(), request); // then Application savedApplication = applicationRepository.findBySiteUserIdAndTerm(user.getId(), term).orElseThrow(); @@ -115,7 +115,7 @@ void setUp() { // when & then assertThatCode(() -> - applicationSubmissionService.apply(user, request) + applicationSubmissionService.apply(user.getId(), request) ) .isInstanceOf(CustomException.class) .hasMessage(INVALID_GPA_SCORE_STATUS.getMessage()); @@ -135,7 +135,7 @@ void setUp() { // when & then assertThatCode(() -> - applicationSubmissionService.apply(user, request) + applicationSubmissionService.apply(user.getId(), request) ) .isInstanceOf(CustomException.class) .hasMessage(INVALID_LANGUAGE_TEST_SCORE_STATUS.getMessage()); @@ -154,12 +154,12 @@ void setUp() { ApplyRequest request = new ApplyRequest(gpaScore.getId(), languageTestScore.getId(), univApplyInfoChoiceRequest); for (int i = 0; i < APPLICATION_UPDATE_COUNT_LIMIT; i++) { - applicationSubmissionService.apply(user, request); + applicationSubmissionService.apply(user.getId(), request); } // when & then assertThatCode(() -> - applicationSubmissionService.apply(user, request) + applicationSubmissionService.apply(user.getId(), request) ) .isInstanceOf(CustomException.class) .hasMessage(APPLY_UPDATE_LIMIT_EXCEED.getMessage()); diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java index 0011f2db2..88b835ee2 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -12,6 +12,7 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; import java.time.LocalDate; import org.junit.jupiter.api.DisplayName; @@ -39,6 +40,9 @@ class AuthServiceTest { @Autowired private SiteUserFixture siteUserFixture; + @Autowired + private SiteUserRepository siteUserRepository; + @Test void 로그아웃한다() { // given @@ -64,13 +68,14 @@ class AuthServiceTest { AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // when - authService.quit(user, accessToken.token()); + authService.quit(user.getId(), accessToken.token()); // then LocalDate tomorrow = LocalDate.now().plusDays(1); String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value()); + SiteUser actualSitUser = siteUserRepository.findById(user.getId()).orElseThrow(); assertAll( - () -> assertThat(user.getQuitedAt()).isEqualTo(tomorrow), + () -> assertThat(actualSitUser.getQuitedAt()).isEqualTo(tomorrow), () -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(), () -> assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isTrue() ); diff --git a/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java b/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java index c671c9a76..b53be9c79 100644 --- a/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java +++ b/src/test/java/com/example/solidconnection/common/resolver/AuthorizedUserResolverTest.java @@ -4,8 +4,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.security.authentication.TokenAuthentication; @@ -13,6 +11,7 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.lang.reflect.Method; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -44,28 +43,36 @@ void setUp() { Authentication authentication = createAuthenticationWithUser(user); SecurityContextHolder.getContext().setAuthentication(authentication); - MethodParameter parameter = mock(MethodParameter.class); - AuthorizedUser authorizedUser = mock(AuthorizedUser.class); - given(parameter.getParameterAnnotation(AuthorizedUser.class)).willReturn(authorizedUser); - given(authorizedUser.required()).willReturn(false); + MethodParameter parameter = getTestMethodParameter("method", Long.class); // when - SiteUser resolveSiteUser = (SiteUser) authorizedUserResolver.resolveArgument(parameter, null, null, null); + Long resolvedUserId = (Long) authorizedUserResolver.resolveArgument(parameter, null, null, null); // then - assertThat(resolveSiteUser).isEqualTo(user); + assertAll( + () -> assertThat(resolvedUserId).isNotNull(), + () -> assertThat(resolvedUserId).isEqualTo(user.getId()) + ); } @Nested class security_context_에_저장된_사용자가_없는_경우 { + @Test + void 파라미터가_원시값이면_예외가_발생한다() { + // given + MethodParameter primitiveTypeParameter = getTestMethodParameter("primitiveType", long.class); + + // when, then + assertThatCode(() -> authorizedUserResolver.resolveArgument(primitiveTypeParameter, null, null, null)) + .isInstanceOf(CustomException.class) + .hasMessageContaining(AUTHENTICATION_FAILED.getMessage()); + } + @Test void required_가_true_이면_예외가_발생한다() { // given - MethodParameter parameter = mock(MethodParameter.class); - AuthorizedUser authorizedUser = mock(AuthorizedUser.class); - given(parameter.getParameterAnnotation(AuthorizedUser.class)).willReturn(authorizedUser); - given(authorizedUser.required()).willReturn(true); + MethodParameter parameter = getTestMethodParameter("required", Long.class); // when, then assertThatCode(() -> authorizedUserResolver.resolveArgument(parameter, null, null, null)) @@ -76,10 +83,7 @@ class security_context_에_저장된_사용자가_없는_경우 { @Test void required_가_false_이면_null_을_반환한다() { // given - MethodParameter parameter = mock(MethodParameter.class); - AuthorizedUser authorizedUser = mock(AuthorizedUser.class); - given(parameter.getParameterAnnotation(AuthorizedUser.class)).willReturn(authorizedUser); - given(authorizedUser.required()).willReturn(false); + MethodParameter parameter = getTestMethodParameter("notRequired", Long.class); // when, then assertThat( @@ -92,4 +96,29 @@ private TokenAuthentication createAuthenticationWithUser(SiteUser siteUser) { SiteUserDetails userDetails = new SiteUserDetails(siteUser); return new TokenAuthentication("token", userDetails); } + + private MethodParameter getTestMethodParameter(String methodName, Class parameterType) { + // 테스트의 목적을 불분명히 만들 수 있는 throws 절을 제거하기 위해 uncheckedException 로 변환한다. + try { + Method method = TestController.class.getMethod(methodName, parameterType); + return new MethodParameter(method, 0); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Method not found: " + methodName, e); + } + } + + static class TestController { + + public void method(@AuthorizedUser Long userId) { + } + + public void primitiveType(@AuthorizedUser long userId) { + } + + public void required(@AuthorizedUser(required = true) Long userId) { + } + + public void notRequired(@AuthorizedUser(required = false) Long userId) { + } + } } diff --git a/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java b/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java index 489914266..c5219b036 100644 --- a/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/comment/service/CommentServiceTest.java @@ -85,7 +85,7 @@ class 댓글_조회_테스트 { List comments = List.of(parentComment, childComment); // when - List responses = commentService.findCommentsByPostId(user1, post.getId()); + List responses = commentService.findCommentsByPostId(user1.getId(), post.getId()); // then assertAll( @@ -122,7 +122,7 @@ class 댓글_조회_테스트 { commentRepository.saveAll(List.of(parentComment, childComment1, childComment2)); // when - List responses = commentService.findCommentsByPostId(user1, post.getId()); + List responses = commentService.findCommentsByPostId(user1.getId(), post.getId()); // then assertAll( @@ -141,7 +141,7 @@ class 댓글_조회_테스트 { commentRepository.saveAll(List.of(parentComment, childComment1, childComment2)); // when - List responses = commentService.findCommentsByPostId(user1, post.getId()); + List responses = commentService.findCommentsByPostId(user1.getId(), post.getId()); // then assertAll( @@ -171,7 +171,7 @@ class 댓글_조회_테스트 { commentRepository.saveAll(List.of(parentComment, childComment1, childComment2)); // when - List responses = commentService.findCommentsByPostId(user1, post.getId()); + List responses = commentService.findCommentsByPostId(user1.getId(), post.getId()); // then assertAll( @@ -198,7 +198,7 @@ class 댓글_생성_테스트 { CommentCreateRequest request = new CommentCreateRequest(post.getId(), "댓글", null); // when - CommentCreateResponse response = commentService.createComment(user1, request); + CommentCreateResponse response = commentService.createComment(user1.getId(), request); // then Comment savedComment = commentRepository.findById(response.id()).orElseThrow(); @@ -218,7 +218,7 @@ class 댓글_생성_테스트 { CommentCreateRequest request = new CommentCreateRequest(post.getId(), "자식 댓글", parentComment.getId()); // when - CommentCreateResponse response = commentService.createComment(user2, request); + CommentCreateResponse response = commentService.createComment(user2.getId(), request); // then Comment savedComment = commentRepository.findById(response.id()).orElseThrow(); @@ -241,7 +241,7 @@ class 댓글_생성_테스트 { // when & then assertThatThrownBy(() -> commentService.createComment( - user1, + user1.getId(), request )) .isInstanceOf(CustomException.class) @@ -257,7 +257,7 @@ class 댓글_생성_테스트 { // when & then assertThatThrownBy(() -> commentService.createComment( - user1, + user1.getId(), request )) .isInstanceOf(CustomException.class) @@ -275,7 +275,7 @@ class 댓글_수정_테스트 { CommentUpdateRequest request = new CommentUpdateRequest("수정된 댓글"); // when - CommentUpdateResponse response = commentService.updateComment(user1, comment.getId(), request); + CommentUpdateResponse response = commentService.updateComment(user1.getId(), comment.getId(), request); // then Comment updatedComment = commentRepository.findById(response.id()).orElseThrow(); @@ -297,7 +297,7 @@ class 댓글_수정_테스트 { // when & then assertThatThrownBy(() -> commentService.updateComment( - user2, + user2.getId(), comment.getId(), request )) @@ -314,7 +314,7 @@ class 댓글_수정_테스트 { // when & then assertThatThrownBy(() -> commentService.updateComment( - user1, + user1.getId(), comment.getId(), request )) @@ -335,7 +335,7 @@ class 댓글_삭제_테스트 { int expectedCommentsCount = comments.size() - 1; // when - CommentDeleteResponse response = commentService.deleteCommentById(user1, comment.getId()); + CommentDeleteResponse response = commentService.deleteCommentById(user1.getId(), comment.getId()); // then assertAll( @@ -355,7 +355,7 @@ class 댓글_삭제_테스트 { List childComments = parentComment.getCommentList(); // when - CommentDeleteResponse response = commentService.deleteCommentById(user1, parentComment.getId()); + CommentDeleteResponse response = commentService.deleteCommentById(user1.getId(), parentComment.getId()); // then Comment deletedComment = commentRepository.findById(response.id()).orElseThrow(); @@ -381,7 +381,7 @@ class 댓글_삭제_테스트 { int expectedChildCommentsCount = childComments.size() - 1; // when - CommentDeleteResponse response = commentService.deleteCommentById(user2, childComment1.getId()); + CommentDeleteResponse response = commentService.deleteCommentById(user2.getId(), childComment1.getId()); // then Comment remainingParentComment = commentRepository.findById(parentComment.getId()).orElseThrow(); @@ -404,7 +404,7 @@ class 댓글_삭제_테스트 { // when & then assertThatThrownBy(() -> commentService.deleteCommentById( - user2, + user2.getId(), comment.getId() )) .isInstanceOf(CustomException.class) diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java index 09e55b45e..36211c341 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostCommandServiceTest.java @@ -113,7 +113,7 @@ class 게시글_생성_테스트 { .willReturn(List.of(new UploadedFileUrlResponse(expectedImageUrl))); // when - PostCreateResponse response = postCommandService.createPost(user1, request, imageFiles); + PostCreateResponse response = postCommandService.createPost(user1.getId(), request, imageFiles); // then Post savedPost = postRepository.findById(response.id()).orElseThrow(); @@ -134,7 +134,7 @@ class 게시글_생성_테스트 { // when & then assertThatThrownBy(() -> - postCommandService.createPost(user1, request, imageFiles)) + postCommandService.createPost(user1.getId(), request, imageFiles)) .isInstanceOf(CustomException.class) .hasMessage(INVALID_POST_CATEGORY.getMessage()); } @@ -147,7 +147,7 @@ class 게시글_생성_테스트 { // when & then assertThatThrownBy(() -> - postCommandService.createPost(user1, request, imageFiles)) + postCommandService.createPost(user1.getId(), request, imageFiles)) .isInstanceOf(CustomException.class) .hasMessage(INVALID_POST_CATEGORY.getMessage()); } @@ -160,7 +160,7 @@ class 게시글_생성_테스트 { // when & then assertThatThrownBy(() -> - postCommandService.createPost(user1, request, imageFiles)) + postCommandService.createPost(user1.getId(), request, imageFiles)) .isInstanceOf(CustomException.class) .hasMessage(CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES.getMessage()); } @@ -184,7 +184,7 @@ class 게시글_수정_테스트 { // when PostUpdateResponse response = postCommandService.updatePost( - user1, + user1.getId(), post.getId(), request, imageFiles @@ -212,7 +212,7 @@ class 게시글_수정_테스트 { // when & then assertThatThrownBy(() -> postCommandService.updatePost( - user2, + user2.getId(), post.getId(), request, imageFiles @@ -230,7 +230,7 @@ class 게시글_수정_테스트 { // when & then assertThatThrownBy(() -> postCommandService.updatePost( - user1, + user1.getId(), questionPost.getId(), request, imageFiles @@ -248,7 +248,7 @@ class 게시글_수정_테스트 { // when & then assertThatThrownBy(() -> postCommandService.updatePost( - user1, + user1.getId(), post.getId(), request, imageFiles @@ -270,7 +270,7 @@ class 게시글_삭제_테스트 { redisService.increaseViewCount(viewCountKey); // when - PostDeleteResponse response = postCommandService.deletePostById(user1, post.getId()); + PostDeleteResponse response = postCommandService.deletePostById(user1.getId(), post.getId()); // then assertAll( @@ -289,7 +289,7 @@ class 게시글_삭제_테스트 { // when & then assertThatThrownBy(() -> postCommandService.deletePostById( - user2, + user2.getId(), post.getId() )) .isInstanceOf(CustomException.class) @@ -301,7 +301,7 @@ class 게시글_삭제_테스트 { // when & then assertThatThrownBy(() -> postCommandService.deletePostById( - user1, + user1.getId(), questionPost.getId() )) .isInstanceOf(CustomException.class) diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java index 36de90c5f..705ddd6f8 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostLikeServiceTest.java @@ -71,7 +71,7 @@ class 게시글_좋아요_테스트 { long beforeLikeCount = post.getLikeCount(); // when - PostLikeResponse response = postLikeService.likePost(user, post.getId()); + PostLikeResponse response = postLikeService.likePost(user.getId(), post.getId()); // then Post likedPost = postRepository.findById(post.getId()).orElseThrow(); @@ -86,12 +86,12 @@ class 게시글_좋아요_테스트 { @Test void 이미_좋아요한_게시글을_다시_좋아요하면_예외가_발생한다() { // given - postLikeService.likePost(user, post.getId()); + postLikeService.likePost(user.getId(), post.getId()); // when & then assertThatThrownBy(() -> postLikeService.likePost( - user, + user.getId(), post.getId() )) .isInstanceOf(CustomException.class) @@ -105,11 +105,11 @@ class 게시글_좋아요_취소_테스트 { @Test void 게시글_좋아요를_성공적으로_취소한다() { // given - PostLikeResponse beforeResponse = postLikeService.likePost(user, post.getId()); + PostLikeResponse beforeResponse = postLikeService.likePost(user.getId(), post.getId()); long beforeLikeCount = beforeResponse.likeCount(); // when - PostDislikeResponse response = postLikeService.dislikePost(user, post.getId()); + PostDislikeResponse response = postLikeService.dislikePost(user.getId(), post.getId()); // then Post unlikedPost = postRepository.findById(post.getId()).orElseThrow(); @@ -126,7 +126,7 @@ class 게시글_좋아요_취소_테스트 { // when & then assertThatThrownBy(() -> postLikeService.dislikePost( - user, + user.getId(), post.getId() )) .isInstanceOf(CustomException.class) diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java index d3c474ba0..2377847d6 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java @@ -153,7 +153,7 @@ void setUp() { String viewCountKey = redisUtils.getPostViewCountRedisKey(post.getId()); // when - PostFindResponse response = postQueryService.findPostById(user, post.getId()); + PostFindResponse response = postQueryService.findPostById(user.getId(), post.getId()); // then assertAll( diff --git a/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java b/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java index 8c36bfbab..7a9e67b99 100644 --- a/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java +++ b/src/test/java/com/example/solidconnection/concurrency/PostLikeCountConcurrencyTest.java @@ -74,8 +74,8 @@ void setUp() { SiteUser tmpSiteUser = siteUserFixture.사용자(i, nickname); executorService.submit(() -> { try { - postLikeService.likePost(tmpSiteUser, post.getId()); - postLikeService.dislikePost(tmpSiteUser, post.getId()); + postLikeService.likePost(tmpSiteUser.getId(), post.getId()); + postLikeService.dislikePost(tmpSiteUser.getId(), post.getId()); } finally { doneSignal.countDown(); } diff --git a/src/test/java/com/example/solidconnection/concurrency/ThunderingHerdTest.java b/src/test/java/com/example/solidconnection/concurrency/ThunderingHerdTest.java index 566d2c8d0..9e86965af 100644 --- a/src/test/java/com/example/solidconnection/concurrency/ThunderingHerdTest.java +++ b/src/test/java/com/example/solidconnection/concurrency/ThunderingHerdTest.java @@ -1,7 +1,6 @@ package com.example.solidconnection.concurrency; import com.example.solidconnection.application.service.ApplicationQueryService; -import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; import java.util.Arrays; @@ -33,11 +32,11 @@ class ThunderingHerdTest { private int THREAD_NUMS = 1000; private int THREAD_POOL_SIZE = 200; private int TIMEOUT_SECONDS = 10; - private SiteUser user; + private long siteUserId; @BeforeEach public void setUp() { - user = siteUserFixture.사용자(); + siteUserId = siteUserFixture.사용자().getId(); } @Test @@ -53,9 +52,9 @@ public void setUp() { executorService.submit(() -> { try { List tasks = Arrays.asList( - () -> applicationQueryService.getApplicants(user, "", ""), - () -> applicationQueryService.getApplicants(user, "ASIA", ""), - () -> applicationQueryService.getApplicants(user, "", "추오") + () -> applicationQueryService.getApplicants(siteUserId, "", ""), + () -> applicationQueryService.getApplicants(siteUserId, "ASIA", ""), + () -> applicationQueryService.getApplicants(siteUserId, "", "추오") ); Collections.shuffle(tasks); tasks.forEach(Runnable::run); diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java index e00eaadae..85667dffb 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java @@ -67,7 +67,7 @@ class 멘토의_마이_페이지를_조회한다 { Channel channel2 = channelFixture.채널(2, mentor); // when - MentorMyPageResponse response = mentorMyPageService.getMentorMyPage(mentorUser); + MentorMyPageResponse response = mentorMyPageService.getMentorMyPage(mentorUser.getId()); // then assertAll( @@ -90,7 +90,7 @@ class 멘토의_마이_페이지를_수정한다 { MentorMyPageUpdateRequest request = new MentorMyPageUpdateRequest(newIntroduction, newPassTip, List.of()); // when - mentorMyPageService.updateMentorMyPage(mentorUser, request); + mentorMyPageService.updateMentorMyPage(mentorUser.getId(), request); // then Mentor updatedMentor = mentorRepository.findById(mentor.getId()).get(); @@ -110,7 +110,7 @@ class 멘토의_마이_페이지를_수정한다 { MentorMyPageUpdateRequest request = new MentorMyPageUpdateRequest("introduction", "passTip", newChannels); // when - mentorMyPageService.updateMentorMyPage(mentorUser, request); + mentorMyPageService.updateMentorMyPage(mentorUser.getId(), request); // then List updatedChannels = channelRepositoryForTest.findAllByMentorId(mentor.getId()); diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java index ba44c6db3..a7634c7ba 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java @@ -63,7 +63,7 @@ class 멘토_단일_조회_성공 { Channel channel2 = channelFixture.채널(2, mentor); // when - MentorDetailResponse response = mentorQueryService.getMentorDetails(mentor.getId(), siteUser); + MentorDetailResponse response = mentorQueryService.getMentorDetails(mentor.getId(), siteUser.getId()); // then assertAll( @@ -85,8 +85,8 @@ class 멘토_단일_조회_성공 { mentoringFixture.대기중_멘토링(mentor.getId(), appliedUser.getId()); // when - MentorDetailResponse notAppliedResponse = mentorQueryService.getMentorDetails(mentor.getId(), notAppliedUser); - MentorDetailResponse appliedResponse = mentorQueryService.getMentorDetails(mentor.getId(), appliedUser); + MentorDetailResponse notAppliedResponse = mentorQueryService.getMentorDetails(mentor.getId(), notAppliedUser.getId()); + MentorDetailResponse appliedResponse = mentorQueryService.getMentorDetails(mentor.getId(), appliedUser.getId()); // then assertAll( @@ -105,7 +105,7 @@ class 멘토_단일_조회_실패 { long notExistingMentorId = 999L; // when & then - assertThatCode(() -> mentorQueryService.getMentorDetails(notExistingMentorId, siteUserFixture.사용자())) + assertThatCode(() -> mentorQueryService.getMentorDetails(notExistingMentorId, siteUserFixture.사용자().getId())) .isInstanceOf(CustomException.class) .hasMessageContaining(ErrorCode.MENTOR_NOT_FOUND.getMessage()); } @@ -135,7 +135,7 @@ void setUp() { Channel channel2 = channelFixture.채널(2, mentor2); // when - SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 10)); + SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser.getId(), PageRequest.of(0, 10)); // then Map mentorPreviewMap = response.content().stream() @@ -159,7 +159,7 @@ void setUp() { mentoringFixture.대기중_멘토링(mentor1.getId(), currentUser.getId()); // when - SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 10)); + SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser.getId(), PageRequest.of(0, 10)); // then Map mentorPreviewMap = response.content().stream() @@ -173,7 +173,7 @@ void setUp() { @Test void 다음_페이지_번호를_응답한다() { // given - SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 1)); + SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser.getId(), PageRequest.of(0, 1)); // then assertThat(response.nextPageNumber()).isEqualTo(2); @@ -182,7 +182,7 @@ void setUp() { @Test void 다음_페이지가_없으면_페이지_없음을_의미하는_값을_응답한다() { // given - SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser, PageRequest.of(0, 10)); + SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser.getId(), PageRequest.of(0, 10)); // then assertThat(response.nextPageNumber()).isEqualTo(NO_NEXT_PAGE_NUMBER); diff --git a/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java b/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java index 71c927efc..f82a3bd84 100644 --- a/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/news/service/NewsCommandServiceTest.java @@ -302,7 +302,7 @@ class 소식지_삭제_테스트 { String expectedImageUrl = originNews.getThumbnailUrl(); // when - NewsCommandResponse response = newsCommandService.deleteNewsById(user, originNews.getId()); + NewsCommandResponse response = newsCommandService.deleteNewsById(user.getId(), originNews.getId()); // then assertAll( diff --git a/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java b/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java index e143579df..8760a645b 100644 --- a/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java +++ b/src/test/java/com/example/solidconnection/score/service/ScoreServiceTest.java @@ -70,7 +70,7 @@ void setUp() { ); // when - GpaScoreStatusesResponse response = scoreService.getGpaScoreStatus(user); + GpaScoreStatusesResponse response = scoreService.getGpaScoreStatus(user.getId()); // then assertThat(response.gpaScoreStatusResponseList()).hasSize(scores.size()); @@ -79,7 +79,7 @@ void setUp() { @Test void GPA_점수가_없는_경우_빈_리스트를_반환한다() { // when - GpaScoreStatusesResponse response = scoreService.getGpaScoreStatus(user); + GpaScoreStatusesResponse response = scoreService.getGpaScoreStatus(user.getId()); // then assertThat(response.gpaScoreStatusResponseList()).isEmpty(); @@ -94,7 +94,7 @@ void setUp() { ); // when - LanguageTestScoreStatusesResponse response = scoreService.getLanguageTestScoreStatus(user); + LanguageTestScoreStatusesResponse response = scoreService.getLanguageTestScoreStatus(user.getId()); // then assertThat(response.languageTestScoreStatusResponseList()).hasSize(scores.size()); @@ -103,7 +103,7 @@ void setUp() { @Test void 어학_시험_점수가_없는_경우_빈_리스트를_반환한다() { // when - LanguageTestScoreStatusesResponse response = scoreService.getLanguageTestScoreStatus(user); + LanguageTestScoreStatusesResponse response = scoreService.getLanguageTestScoreStatus(user.getId()); // then assertThat(response.languageTestScoreStatusResponseList()).isEmpty(); @@ -118,7 +118,7 @@ void setUp() { given(s3Service.uploadFile(file, ImgType.GPA)).willReturn(new UploadedFileUrlResponse(fileUrl)); // when - long scoreId = scoreService.submitGpaScore(user, request, file); + long scoreId = scoreService.submitGpaScore(user.getId(), request, file); GpaScore savedScore = gpaScoreRepository.findById(scoreId).orElseThrow(); // then @@ -134,7 +134,7 @@ void setUp() { given(s3Service.uploadFile(file, ImgType.LANGUAGE_TEST)).willReturn(new UploadedFileUrlResponse(fileUrl)); // when - long scoreId = scoreService.submitLanguageTestScore(user, request, file); + long scoreId = scoreService.submitLanguageTestScore(user.getId(), request, file); LanguageTestScore savedScore = languageTestScoreRepository.findById(scoreId).orElseThrow(); // then diff --git a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java index 5fbebdeff..0358f8024 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java @@ -5,7 +5,6 @@ import static com.example.solidconnection.siteuser.service.MyPageService.NICKNAME_LAST_CHANGE_DATE_FORMAT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; -import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.BDDMockito.any; import static org.mockito.BDDMockito.eq; import static org.mockito.BDDMockito.given; @@ -75,7 +74,7 @@ void setUp() { int likedUnivApplyInfoCount = createLikedUnivApplyInfos(user); // when - MyPageResponse response = myPageService.getMyPageInfo(user); + MyPageResponse response = myPageService.getMyPageInfo(user.getId()); // then Assertions.assertAll( @@ -101,7 +100,7 @@ class 프로필_이미지_수정_테스트 { .willReturn(new UploadedFileUrlResponse(expectedUrl)); // when - myPageService.updateMyPageInfo(user, imageFile, "newNickname"); + myPageService.updateMyPageInfo(user.getId(), imageFile, "newNickname"); // then SiteUser updatedUser = siteUserRepository.findById(user.getId()).get(); @@ -116,10 +115,10 @@ class 프로필_이미지_수정_테스트 { .willReturn(new UploadedFileUrlResponse("newProfileImageUrl")); // when - myPageService.updateMyPageInfo(user, imageFile, "newNickname"); + myPageService.updateMyPageInfo(user.getId(), imageFile, "newNickname"); // then - then(s3Service).should(never()).deleteExProfile(any()); + then(s3Service).should(never()).deleteExProfile(user.getId()); } @Test @@ -131,11 +130,10 @@ class 프로필_이미지_수정_테스트 { .willReturn(new UploadedFileUrlResponse("newProfileImageUrl")); // when - myPageService.updateMyPageInfo(커스텀_프로필_사용자, imageFile, "newNickname"); + myPageService.updateMyPageInfo(커스텀_프로필_사용자.getId(), imageFile, "newNickname"); // then - then(s3Service).should().deleteExProfile(argThat(user -> - user.getId().equals(커스텀_프로필_사용자.getId()))); + then(s3Service).should().deleteExProfile(커스텀_프로필_사용자.getId()); } } @@ -155,7 +153,7 @@ void setUp() { String newNickname = "newNickname"; // when - myPageService.updateMyPageInfo(user, imageFile, newNickname); + myPageService.updateMyPageInfo(user.getId(), imageFile, newNickname); // then SiteUser updatedUser = siteUserRepository.findById(user.getId()).get(); @@ -172,7 +170,7 @@ void setUp() { siteUserRepository.save(user); // when & then - assertThatCode(() -> myPageService.updateMyPageInfo(user, imageFile, "nickname12")) + assertThatCode(() -> myPageService.updateMyPageInfo(user.getId(), imageFile, "nickname12")) .isInstanceOf(CustomException.class) .hasMessage(createExpectedErrorMessage(modifiedAt)); } diff --git a/src/test/java/com/example/solidconnection/university/service/LikedUnivApplyInfoServiceTest.java b/src/test/java/com/example/solidconnection/university/service/LikedUnivApplyInfoServiceTest.java index 918838ce1..2693d4501 100644 --- a/src/test/java/com/example/solidconnection/university/service/LikedUnivApplyInfoServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/LikedUnivApplyInfoServiceTest.java @@ -57,7 +57,7 @@ void setUp() { saveLikedUnivApplyInfo(user, 그라츠대학_지원_정보); // when - List response = likedUnivApplyInfoService.getLikedUnivApplyInfos(user); + List response = likedUnivApplyInfoService.getLikedUnivApplyInfos(user.getId()); // then assertThat(response).extracting(UnivApplyInfoPreviewResponse::id) @@ -70,7 +70,7 @@ class 대학_지원_정보_좋아요를_등록한다 { @Test void 성공적으로_좋아요를_등록한다() { // when - likedUnivApplyInfoService.addUnivApplyInfoLike(user, 괌대학_A_지원_정보.getId()); + likedUnivApplyInfoService.addUnivApplyInfoLike(user.getId(), 괌대학_A_지원_정보.getId()); // then assertThat( @@ -84,7 +84,7 @@ class 대학_지원_정보_좋아요를_등록한다 { saveLikedUnivApplyInfo(user, 괌대학_A_지원_정보); // when & then - assertThatCode(() -> likedUnivApplyInfoService.addUnivApplyInfoLike(user, 괌대학_A_지원_정보.getId())) + assertThatCode(() -> likedUnivApplyInfoService.addUnivApplyInfoLike(user.getId(), 괌대학_A_지원_정보.getId())) .isInstanceOf(CustomException.class) .hasMessage(ALREADY_LIKED_UNIV_APPLY_INFO.getMessage()); } @@ -99,7 +99,7 @@ class 대학_지원_정보_좋아요를_취소한다 { saveLikedUnivApplyInfo(user, 괌대학_A_지원_정보); // when - likedUnivApplyInfoService.cancelUnivApplyInfoLike(user, 괌대학_A_지원_정보.getId()); + likedUnivApplyInfoService.cancelUnivApplyInfoLike(user.getId(), 괌대학_A_지원_정보.getId()); // then assertThat( @@ -110,7 +110,7 @@ class 대학_지원_정보_좋아요를_취소한다 { @Test void 좋아요하지_않았으면_예외가_발생한다() { // when & then - assertThatCode(() -> likedUnivApplyInfoService.cancelUnivApplyInfoLike(user, 괌대학_A_지원_정보.getId())) + assertThatCode(() -> likedUnivApplyInfoService.cancelUnivApplyInfoLike(user.getId(), 괌대학_A_지원_정보.getId())) .isInstanceOf(CustomException.class) .hasMessage(NOT_LIKED_UNIV_APPLY_INFO.getMessage()); } @@ -122,7 +122,7 @@ class 대학_지원_정보_좋아요를_취소한다 { Long invalidUnivApplyInfoId = 9999L; // when & then - assertThatCode(() -> likedUnivApplyInfoService.addUnivApplyInfoLike(user, invalidUnivApplyInfoId)) + assertThatCode(() -> likedUnivApplyInfoService.addUnivApplyInfoLike(user.getId(), invalidUnivApplyInfoId)) .isInstanceOf(CustomException.class) .hasMessage(UNIV_APPLY_INFO_NOT_FOUND.getMessage()); } @@ -133,7 +133,7 @@ class 대학_지원_정보_좋아요를_취소한다 { saveLikedUnivApplyInfo(user, 괌대학_A_지원_정보); // when - IsLikeResponse response = likedUnivApplyInfoService.isUnivApplyInfoLiked(user, 괌대학_A_지원_정보.getId()); + IsLikeResponse response = likedUnivApplyInfoService.isUnivApplyInfoLiked(user.getId(), 괌대학_A_지원_정보.getId()); // then assertThat(response.isLike()).isTrue(); @@ -142,7 +142,7 @@ class 대학_지원_정보_좋아요를_취소한다 { @Test void 좋아요하지_않은_대학_지원_정보인지_확인한다() { // when - IsLikeResponse response = likedUnivApplyInfoService.isUnivApplyInfoLiked(user, 괌대학_A_지원_정보.getId()); + IsLikeResponse response = likedUnivApplyInfoService.isUnivApplyInfoLiked(user.getId(), 괌대학_A_지원_정보.getId()); // then assertThat(response.isLike()).isFalse(); @@ -154,7 +154,7 @@ class 대학_지원_정보_좋아요를_취소한다 { Long invalidUnivApplyInfoId = 9999L; // when & then - assertThatCode(() -> likedUnivApplyInfoService.isUnivApplyInfoLiked(user, invalidUnivApplyInfoId)) + assertThatCode(() -> likedUnivApplyInfoService.isUnivApplyInfoLiked(user.getId(), invalidUnivApplyInfoId)) .isInstanceOf(CustomException.class) .hasMessage(UNIV_APPLY_INFO_NOT_FOUND.getMessage()); } diff --git a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendServiceTest.java index 94ad7b9ee..2c8dd954e 100644 --- a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoRecommendServiceTest.java @@ -80,7 +80,7 @@ void setUp() { interestedRegionRepository.save(new InterestedRegion(user, regionFixture.영미권())); // when - UnivApplyInfoRecommendsResponse response = univApplyInfoRecommendService.getPersonalRecommends(user); + UnivApplyInfoRecommendsResponse response = univApplyInfoRecommendService.getPersonalRecommends(user.getId()); // then assertThat(response.recommendedUniversities()) @@ -99,7 +99,7 @@ void setUp() { interestedCountryRepository.save(new InterestedCountry(user, countryFixture.덴마크())); // when - UnivApplyInfoRecommendsResponse response = univApplyInfoRecommendService.getPersonalRecommends(user); + UnivApplyInfoRecommendsResponse response = univApplyInfoRecommendService.getPersonalRecommends(user.getId()); // then assertThat(response.recommendedUniversities()) @@ -117,7 +117,7 @@ void setUp() { interestedCountryRepository.save(new InterestedCountry(user, countryFixture.덴마크())); // when - UnivApplyInfoRecommendsResponse response = univApplyInfoRecommendService.getPersonalRecommends(user); + UnivApplyInfoRecommendsResponse response = univApplyInfoRecommendService.getPersonalRecommends(user.getId()); // then assertThat(response.recommendedUniversities()) @@ -135,7 +135,7 @@ void setUp() { @Test void 관심사_미설정_사용자는_일반_추천_대학_지원_정보를_조회한다() { // when - UnivApplyInfoRecommendsResponse response = univApplyInfoRecommendService.getPersonalRecommends(user); + UnivApplyInfoRecommendsResponse response = univApplyInfoRecommendService.getPersonalRecommends(user.getId()); // then assertThat(response.recommendedUniversities()) From d71fd0d452c213702f31f43b3409389e38c30ef1 Mon Sep 17 00:00:00 2001 From: seonghyeok cho <65901319+whqtker@users.noreply.github.com> Date: Fri, 25 Jul 2025 12:51:05 +0900 Subject: [PATCH 51/90] =?UTF-8?q?chore:=20build.gradle=EC=97=90=20flyway?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EA=B2=80=EC=A6=9D=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=B6=94=EA=B0=80=20(#393)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: build.gradle에 flyway 파일명 검증 관련 추가 * chore: flyway 플러그인 버전을 라이브러리 버전과 통일 * chore: flyway 관련 검증 로직이 연속되게 배치되도록 --- build.gradle | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/build.gradle b/build.gradle index 6f6a01f61..41ed7c856 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.1.5' id 'io.spring.dependency-management' version '1.1.4' + id 'org.flywaydb.flyway' version '9.16.3' } group = 'com.example' @@ -31,6 +32,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-mysql' + runtimeOnly 'com.h2database:h2' // QueryDSL implementation 'io.github.openfeign.querydsl:querydsl-jpa:6.11' @@ -74,3 +76,21 @@ tasks.named('test', Test) { sourceSets { main.java.srcDirs += ['build/generated/sources/annotationProcessor/java/main'] } + +// build 단계에서 flyway 검증 +flyway { + url = 'jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1' + user = 'sa' + password = '' + locations = ['filesystem:src/main/resources/db/migration'] + validateMigrationNaming = true + ignoreMigrationPatterns = ['*:pending'] +} + +tasks.named('build') { + dependsOn 'flywayValidate' +} + +tasks.named('bootJar') { + dependsOn 'flywayValidate' +} From 94580183b2d7c6934a8048d54866ab4479628632 Mon Sep 17 00:00:00 2001 From: seonghyeok cho <65901319+whqtker@users.noreply.github.com> Date: Fri, 25 Jul 2025 19:19:52 +0900 Subject: [PATCH 52/90] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EB=B0=8F=20DDL=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20(#395)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 채팅 관련 엔티티 생성 * feat: 채팅 관련 테이블의 DDL 작성 * fix: DDL 콤마 누락 문제 해결 * feat: chat_participant 테이블에서 chat_room, site_user 간 고유 키 생성 * chore: ChatReadStatus 엔티티에 UK 제약조건 추가 * fix: JPA가 UK 제약 조건 생성 시 컬럼을 찾지 못하는 문제 해결 - 명시적으로 컬럼 이름을 지정한다. * chore: UK 제약 조건 이름 변경 * chore: isGroup, hasAttachment 필드에 기본값 생성 - 데이터 타입을 primitive type으로 지정 - Column 어노테이션 제거 (App level과 DB level 간 일관성 보장) - DDL에 기본값 설정 * chore: hasAttachment 컬럼 제거 * chore: flyway 파일 버전 수정 --- .../chat/domain/ChatAttachment.java | 35 ++++++++++++ .../chat/domain/ChatMessage.java | 38 +++++++++++++ .../chat/domain/ChatParticipant.java | 37 +++++++++++++ .../chat/domain/ChatReadStatus.java | 35 ++++++++++++ .../solidconnection/chat/domain/ChatRoom.java | 32 +++++++++++ .../V24__add_chat_related_tables.sql | 55 +++++++++++++++++++ 6 files changed, 232 insertions(+) create mode 100644 src/main/java/com/example/solidconnection/chat/domain/ChatAttachment.java create mode 100644 src/main/java/com/example/solidconnection/chat/domain/ChatMessage.java create mode 100644 src/main/java/com/example/solidconnection/chat/domain/ChatParticipant.java create mode 100644 src/main/java/com/example/solidconnection/chat/domain/ChatReadStatus.java create mode 100644 src/main/java/com/example/solidconnection/chat/domain/ChatRoom.java create mode 100644 src/main/resources/db/migration/V24__add_chat_related_tables.sql diff --git a/src/main/java/com/example/solidconnection/chat/domain/ChatAttachment.java b/src/main/java/com/example/solidconnection/chat/domain/ChatAttachment.java new file mode 100644 index 000000000..5c0f5e651 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/domain/ChatAttachment.java @@ -0,0 +1,35 @@ +package com.example.solidconnection.chat.domain; + +import com.example.solidconnection.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ChatAttachment extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Boolean isImage; + + @Column(nullable = false, length = 500) + private String url; + + @Column(length = 500) + private String thumbnailUrl; + + @ManyToOne(fetch = FetchType.LAZY) + private ChatMessage chatMessage; +} diff --git a/src/main/java/com/example/solidconnection/chat/domain/ChatMessage.java b/src/main/java/com/example/solidconnection/chat/domain/ChatMessage.java new file mode 100644 index 000000000..8d513c5a7 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/domain/ChatMessage.java @@ -0,0 +1,38 @@ +package com.example.solidconnection.chat.domain; + +import com.example.solidconnection.common.BaseEntity; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ChatMessage extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 500) + private String content; + + private long senderId; + + @ManyToOne(fetch = FetchType.LAZY) + private ChatRoom chatRoom; + + @OneToMany(mappedBy = "chatMessage", cascade = CascadeType.ALL) + private List chatAttachments = new ArrayList<>(); +} diff --git a/src/main/java/com/example/solidconnection/chat/domain/ChatParticipant.java b/src/main/java/com/example/solidconnection/chat/domain/ChatParticipant.java new file mode 100644 index 000000000..169e1dd06 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/domain/ChatParticipant.java @@ -0,0 +1,37 @@ +package com.example.solidconnection.chat.domain; + +import com.example.solidconnection.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(uniqueConstraints = { + @UniqueConstraint( + name = "uk_chat_participant_chat_room_id_site_user_id", + columnNames = {"chat_room_id", "site_user_id"} + ) +}) +public class ChatParticipant extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "site_user_id") + private long siteUserId; + + @ManyToOne(fetch = FetchType.LAZY) + private ChatRoom chatRoom; +} diff --git a/src/main/java/com/example/solidconnection/chat/domain/ChatReadStatus.java b/src/main/java/com/example/solidconnection/chat/domain/ChatReadStatus.java new file mode 100644 index 000000000..13d4ac646 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/domain/ChatReadStatus.java @@ -0,0 +1,35 @@ +package com.example.solidconnection.chat.domain; + +import com.example.solidconnection.common.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(uniqueConstraints = { + @UniqueConstraint( + name = "uk_chat_read_status_chat_room_id_chat_participant_id", + columnNames = {"chat_room_id", "chat_participant_id"} + ) +}) +public class ChatReadStatus extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "chat_room_id") + private long chatRoomId; + + @Column(name = "chat_participant_id") + private long chatParticipantId; +} diff --git a/src/main/java/com/example/solidconnection/chat/domain/ChatRoom.java b/src/main/java/com/example/solidconnection/chat/domain/ChatRoom.java new file mode 100644 index 000000000..020befe5f --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/domain/ChatRoom.java @@ -0,0 +1,32 @@ +package com.example.solidconnection.chat.domain; + +import com.example.solidconnection.common.BaseEntity; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; +import java.util.List; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ChatRoom extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private boolean isGroup = false; + + @OneToMany(mappedBy = "chatRoom", cascade = CascadeType.ALL) + private List chatParticipants = new ArrayList<>(); + + @OneToMany(mappedBy = "chatRoom", cascade = CascadeType.ALL) + private List chatMessages = new ArrayList<>(); +} diff --git a/src/main/resources/db/migration/V24__add_chat_related_tables.sql b/src/main/resources/db/migration/V24__add_chat_related_tables.sql new file mode 100644 index 000000000..20898a147 --- /dev/null +++ b/src/main/resources/db/migration/V24__add_chat_related_tables.sql @@ -0,0 +1,55 @@ +CREATE TABLE chat_room +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + is_group BOOLEAN NOT NULL DEFAULT false, + created_at DATETIME(6) NOT NULL, + updated_at DATETIME(6) NOT NULL +); + +CREATE TABLE chat_participant +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + site_user_id BIGINT NOT NULL, + chat_room_id BIGINT NOT NULL, + created_at DATETIME(6) NOT NULL, + updated_at DATETIME(6) NOT NULL, + CONSTRAINT FK_CHAT_PARTICIPANT_CHAT_ROOM_ID FOREIGN KEY (chat_room_id) REFERENCES chat_room (id), + CONSTRAINT FK_CHAT_PARTICIPANT_SITE_USER_ID FOREIGN KEY (site_user_id) REFERENCES site_user (id), + CONSTRAINT UK_CHAT_PARTICIPANT_CHAT_ROOM_ID_SITE_USER_ID UNIQUE (chat_room_id, site_user_id) +); + +CREATE TABLE chat_message +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + content VARCHAR(500) NOT NULL, + sender_id BIGINT NOT NULL, + chat_room_id BIGINT NOT NULL, + created_at DATETIME(6) NOT NULL, + updated_at DATETIME(6) NOT NULL, + CONSTRAINT FK_CHAT_MESSAGE_CHAT_ROOM_ID FOREIGN KEY (chat_room_id) REFERENCES chat_room (id), + CONSTRAINT FK_CHAT_MESSAGE_SENDER_ID FOREIGN KEY (sender_id) REFERENCES chat_participant (id) +); + +CREATE TABLE chat_attachment +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + is_image BOOLEAN NOT NULL, + url VARCHAR(500) NOT NULL, + thumbnail_url VARCHAR(500), + chat_message_id BIGINT NOT NULL, + created_at DATETIME(6) NOT NULL, + updated_at DATETIME(6) NOT NULL, + CONSTRAINT FK_CHAT_ATTACHMENT_CHAT_MESSAGE_ID FOREIGN KEY (chat_message_id) REFERENCES chat_message (id) +); + +CREATE TABLE chat_read_status +( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + chat_room_id BIGINT NOT NULL, + chat_participant_id BIGINT NOT NULL, + created_at DATETIME(6) NOT NULL, + updated_at DATETIME(6) NOT NULL, + CONSTRAINT FK_CHAT_READ_STATUS_CHAT_ROOM_ID FOREIGN KEY (chat_room_id) REFERENCES chat_room (id), + CONSTRAINT FK_CHAT_READ_STATUS_CHAT_PARTICIPANT_ID FOREIGN KEY (chat_participant_id) REFERENCES chat_participant (id), + CONSTRAINT UK_CHAT_READ_STATUS_CHAT_ROOM_ID_CHAT_PARTICIPANT_ID UNIQUE (chat_room_id, chat_participant_id) +); From 80985c093f6422a4667f700ade5c01a8a657ac61 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Fri, 25 Jul 2025 20:51:26 +0900 Subject: [PATCH 53/90] =?UTF-8?q?feat:=20=EC=8B=A0=EA=B3=A0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#402)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 신고 도메인 생성 * feat: 신고 요청 dto 생성 * feat: 신고 레포지토리 생성 * feat: 신고 기능 서비스 구현 * test: 신고 테스트 작성 * feat: 신고 컨트롤러 생성 * refactor: '신고 유형' 컬럼 이름 변경 * refactor: findBy 보다 성능이 좋은 existsBy 로 존재 여부 검증 --- .../common/exception/ErrorCode.java | 6 +- .../report/controller/ReportController.java | 30 ++++++ .../solidconnection/report/domain/Report.java | 52 ++++++++++ .../report/domain/ReportType.java | 14 +++ .../report/domain/TargetType.java | 7 ++ .../report/dto/ReportRequest.java | 17 ++++ .../report/repository/ReportRepository.java | 10 ++ .../report/service/ReportService.java | 50 ++++++++++ .../db/migration/V24__create_report_table.sql | 11 +++ .../community/post/fixture/PostFixture.java | 15 +++ .../report/fixture/ReportFixture.java | 21 ++++ .../report/fixture/ReportFixtureBuilder.java | 54 ++++++++++ .../report/service/ReportServiceTest.java | 99 +++++++++++++++++++ 13 files changed, 385 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/example/solidconnection/report/controller/ReportController.java create mode 100644 src/main/java/com/example/solidconnection/report/domain/Report.java create mode 100644 src/main/java/com/example/solidconnection/report/domain/ReportType.java create mode 100644 src/main/java/com/example/solidconnection/report/domain/TargetType.java create mode 100644 src/main/java/com/example/solidconnection/report/dto/ReportRequest.java create mode 100644 src/main/java/com/example/solidconnection/report/repository/ReportRepository.java create mode 100644 src/main/java/com/example/solidconnection/report/service/ReportService.java create mode 100644 src/main/resources/db/migration/V24__create_report_table.sql create mode 100644 src/test/java/com/example/solidconnection/report/fixture/ReportFixture.java create mode 100644 src/test/java/com/example/solidconnection/report/fixture/ReportFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/report/service/ReportServiceTest.java diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 7b71469aa..824c531f6 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -44,6 +44,7 @@ public enum ErrorCode { LANGUAGE_TEST_SCORE_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 어학성적입니다."), NEWS_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 소식지입니다."), MENTOR_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 멘토입니다."), + REPORT_TARGET_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 신고 대상입니다."), // auth USER_ALREADY_SIGN_OUT(HttpStatus.UNAUTHORIZED.value(), "로그아웃 되었습니다."), @@ -78,7 +79,7 @@ public enum ErrorCode { // community INVALID_POST_CATEGORY(HttpStatus.BAD_REQUEST.value(), "잘못된 카테고리명입니다."), INVALID_BOARD_CODE(HttpStatus.BAD_REQUEST.value(), "잘못된 게시판 코드입니다."), - INVALID_POST_ID(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 게시글입니다."), + INVALID_POST_ID(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 게시글입니다."), // todo: NOT_FOUND로 통일 필요 INVALID_POST_ACCESS(HttpStatus.BAD_REQUEST.value(), "자신의 게시글만 제어할 수 있습니다."), CAN_NOT_DELETE_OR_UPDATE_QUESTION(HttpStatus.BAD_REQUEST.value(), "질문글은 수정이나 삭제할 수 없습니다."), CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES(HttpStatus.BAD_REQUEST.value(), "5개 이상의 파일을 업로드할 수 없습니다."), @@ -111,6 +112,9 @@ public enum ErrorCode { UNAUTHORIZED_MENTORING(HttpStatus.FORBIDDEN.value(), "멘토링 권한이 없습니다."), MENTORING_ALREADY_CONFIRMED(HttpStatus.BAD_REQUEST.value(), "이미 승인 또는 거절된 멘토링입니다."), + // report + ALREADY_REPORTED_BY_CURRENT_USER(HttpStatus.BAD_REQUEST.value(), "이미 신고한 상태입니다."), + // database DATA_INTEGRITY_VIOLATION(HttpStatus.CONFLICT.value(), "데이터베이스 무결성 제약조건 위반이 발생했습니다."), diff --git a/src/main/java/com/example/solidconnection/report/controller/ReportController.java b/src/main/java/com/example/solidconnection/report/controller/ReportController.java new file mode 100644 index 000000000..cab986f92 --- /dev/null +++ b/src/main/java/com/example/solidconnection/report/controller/ReportController.java @@ -0,0 +1,30 @@ +package com.example.solidconnection.report.controller; + +import com.example.solidconnection.common.resolver.AuthorizedUser; +import com.example.solidconnection.report.dto.ReportRequest; +import com.example.solidconnection.report.service.ReportService; +import com.example.solidconnection.siteuser.domain.SiteUser; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/reports") +public class ReportController { + + private final ReportService reportService; + + @PostMapping + public ResponseEntity createReport( + @AuthorizedUser SiteUser siteUser, + @Valid @RequestBody ReportRequest reportRequest + ) { + reportService.createReport(siteUser.getId(), reportRequest); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/example/solidconnection/report/domain/Report.java b/src/main/java/com/example/solidconnection/report/domain/Report.java new file mode 100644 index 000000000..e723db281 --- /dev/null +++ b/src/main/java/com/example/solidconnection/report/domain/Report.java @@ -0,0 +1,52 @@ +package com.example.solidconnection.report.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(uniqueConstraints = { + @UniqueConstraint( + name = "uk_report_reporter_id_target_type_target_id", + columnNames = {"reporter_id", "target_type", "target_id"} + ) +}) +public class Report { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "reporter_id") + private long reporterId; + + @Column(name = "report_type") + @Enumerated(value = EnumType.STRING) + private ReportType reportType; + + @Column(name = "target_type") + @Enumerated(value = EnumType.STRING) + private TargetType targetType; + + @Column(name = "target_id") + private long targetId; + + public Report(long reporterId, ReportType reportType, TargetType targetType, long targetId) { + this.reportType = reportType; + this.reporterId = reporterId; + this.targetType = targetType; + this.targetId = targetId; + } +} diff --git a/src/main/java/com/example/solidconnection/report/domain/ReportType.java b/src/main/java/com/example/solidconnection/report/domain/ReportType.java new file mode 100644 index 000000000..18a5e5d9b --- /dev/null +++ b/src/main/java/com/example/solidconnection/report/domain/ReportType.java @@ -0,0 +1,14 @@ +package com.example.solidconnection.report.domain; + +public enum ReportType { + + ADVERTISEMENT, // 광고 + SPAM, // 낚시/도배 + PERSONAL_INFO_EXPOSURE, // 개인정보 노출 + PORNOGRAPHY, // 선정성 + COPYRIGHT_INFRINGEMENT, // 저작권 침해 + ILLEGAL_ACTIVITY, // 불법 행위 + IMPERSONATION, // 사칭/도용 + INSULT, // 욕설/비하 + ; +} diff --git a/src/main/java/com/example/solidconnection/report/domain/TargetType.java b/src/main/java/com/example/solidconnection/report/domain/TargetType.java new file mode 100644 index 000000000..c48f50ac0 --- /dev/null +++ b/src/main/java/com/example/solidconnection/report/domain/TargetType.java @@ -0,0 +1,7 @@ +package com.example.solidconnection.report.domain; + +public enum TargetType { + + POST, + ; +} diff --git a/src/main/java/com/example/solidconnection/report/dto/ReportRequest.java b/src/main/java/com/example/solidconnection/report/dto/ReportRequest.java new file mode 100644 index 000000000..52f608015 --- /dev/null +++ b/src/main/java/com/example/solidconnection/report/dto/ReportRequest.java @@ -0,0 +1,17 @@ +package com.example.solidconnection.report.dto; + +import com.example.solidconnection.report.domain.ReportType; +import com.example.solidconnection.report.domain.TargetType; +import jakarta.validation.constraints.NotNull; + +public record ReportRequest( + @NotNull(message = "신고 유형을 선택해주세요.") + ReportType reportType, + + @NotNull(message = "신고 대상을 포함해주세요.") + TargetType targetType, + + long targetId +) { + +} diff --git a/src/main/java/com/example/solidconnection/report/repository/ReportRepository.java b/src/main/java/com/example/solidconnection/report/repository/ReportRepository.java new file mode 100644 index 000000000..c32d3cd5f --- /dev/null +++ b/src/main/java/com/example/solidconnection/report/repository/ReportRepository.java @@ -0,0 +1,10 @@ +package com.example.solidconnection.report.repository; + +import com.example.solidconnection.report.domain.Report; +import com.example.solidconnection.report.domain.TargetType; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ReportRepository extends JpaRepository { + + boolean existsByReporterIdAndTargetTypeAndTargetId(long reporterId, TargetType targetType, long targetId); +} diff --git a/src/main/java/com/example/solidconnection/report/service/ReportService.java b/src/main/java/com/example/solidconnection/report/service/ReportService.java new file mode 100644 index 000000000..3546861ea --- /dev/null +++ b/src/main/java/com/example/solidconnection/report/service/ReportService.java @@ -0,0 +1,50 @@ +package com.example.solidconnection.report.service; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; +import com.example.solidconnection.community.post.repository.PostRepository; +import com.example.solidconnection.report.domain.Report; +import com.example.solidconnection.report.domain.TargetType; +import com.example.solidconnection.report.dto.ReportRequest; +import com.example.solidconnection.report.repository.ReportRepository; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ReportService { + + private final ReportRepository reportRepository; + private final SiteUserRepository siteUserRepository; + private final PostRepository postRepository; + + @Transactional + public void createReport(long reporterId, ReportRequest request) { + validateReporterExists(reporterId); + validateTargetExists(request.targetType(), request.targetId()); + validateFirstReportByUser(reporterId, request.targetType(), request.targetId()); + + Report report = new Report(reporterId, request.reportType(), request.targetType(), request.targetId()); + reportRepository.save(report); + } + + private void validateReporterExists(long reporterId) { + if (!siteUserRepository.existsById(reporterId)) { + throw new CustomException(ErrorCode.USER_NOT_FOUND); + } + } + + private void validateTargetExists(TargetType targetType, long targetId) { + if (targetType == TargetType.POST && !postRepository.existsById(targetId)) { + throw new CustomException(ErrorCode.REPORT_TARGET_NOT_FOUND); + } + } + + private void validateFirstReportByUser(long reporterId, TargetType targetType, long targetId) { + if (reportRepository.existsByReporterIdAndTargetTypeAndTargetId(reporterId, targetType, targetId)) { + throw new CustomException(ErrorCode.ALREADY_REPORTED_BY_CURRENT_USER); + } + } +} diff --git a/src/main/resources/db/migration/V24__create_report_table.sql b/src/main/resources/db/migration/V24__create_report_table.sql new file mode 100644 index 000000000..f40b6f562 --- /dev/null +++ b/src/main/resources/db/migration/V24__create_report_table.sql @@ -0,0 +1,11 @@ +CREATE TABLE report +( + id BIGINT NOT NULL AUTO_INCREMENT, + reporter_id BIGINT NOT NULL, + target_type ENUM ('POST') NOT NULL, + target_id BIGINT NOT NULL, + report_type ENUM ('ADVERTISEMENT', 'SPAM', 'PERSONAL_INFO_EXPOSURE', 'PORNOGRAPHY', 'COPYRIGHT_INFRINGEMENT', 'ILLEGAL_ACTIVITY', 'IMPERSONATION', 'INSULT') NOT NULL, + primary key (id), + constraint fk_report_reporter_id foreign key (reporter_id) references site_user (id), + unique uk_report_reporter_id_target_type_target_id (reporter_id, target_type, target_id) +); diff --git a/src/test/java/com/example/solidconnection/community/post/fixture/PostFixture.java b/src/test/java/com/example/solidconnection/community/post/fixture/PostFixture.java index 5d6eaea55..5ddf13888 100644 --- a/src/test/java/com/example/solidconnection/community/post/fixture/PostFixture.java +++ b/src/test/java/com/example/solidconnection/community/post/fixture/PostFixture.java @@ -13,6 +13,21 @@ public class PostFixture { private final PostFixtureBuilder postFixtureBuilder; + public Post 게시글( + Board board, + SiteUser siteUser + ) { + return postFixtureBuilder + .title("제목") + .content("내용") + .isQuestion(false) + .likeCount(0L) + .postCategory(PostCategory.자유) + .board(board) + .siteUser(siteUser) + .create(); + } + public Post 게시글( String title, String content, diff --git a/src/test/java/com/example/solidconnection/report/fixture/ReportFixture.java b/src/test/java/com/example/solidconnection/report/fixture/ReportFixture.java new file mode 100644 index 000000000..91c837bf3 --- /dev/null +++ b/src/test/java/com/example/solidconnection/report/fixture/ReportFixture.java @@ -0,0 +1,21 @@ +package com.example.solidconnection.report.fixture; + +import com.example.solidconnection.report.domain.Report; +import com.example.solidconnection.report.domain.TargetType; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ReportFixture { + + private final ReportFixtureBuilder reportFixtureBuilder; + + public Report 신고(long reporterId, TargetType targetType, long targetId) { + return reportFixtureBuilder.report() + .reporterId(reporterId) + .targetType(targetType) + .targetId(targetId) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/report/fixture/ReportFixtureBuilder.java b/src/test/java/com/example/solidconnection/report/fixture/ReportFixtureBuilder.java new file mode 100644 index 000000000..08d0b276c --- /dev/null +++ b/src/test/java/com/example/solidconnection/report/fixture/ReportFixtureBuilder.java @@ -0,0 +1,54 @@ +package com.example.solidconnection.report.fixture; + +import com.example.solidconnection.report.domain.Report; +import com.example.solidconnection.report.domain.ReportType; +import com.example.solidconnection.report.domain.TargetType; +import com.example.solidconnection.report.repository.ReportRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ReportFixtureBuilder { + + private final ReportRepository reportRepository; + + private long reporterId; + private TargetType targetType; + private long targetId; + private ReportType reportType = ReportType.ADVERTISEMENT; + + public ReportFixtureBuilder report() { + return new ReportFixtureBuilder(reportRepository); + } + + public ReportFixtureBuilder reporterId(long reporterId) { + this.reporterId = reporterId; + return this; + } + + public ReportFixtureBuilder targetType(TargetType targetType) { + this.targetType = targetType; + return this; + } + + public ReportFixtureBuilder targetId(long targetId) { + this.targetId = targetId; + return this; + } + + public ReportFixtureBuilder reasonType(ReportType reportType) { + this.reportType = reportType; + return this; + } + + public Report create() { + Report report = new Report( + reporterId, + reportType, + targetType, + targetId + ); + return reportRepository.save(report); + } +} diff --git a/src/test/java/com/example/solidconnection/report/service/ReportServiceTest.java b/src/test/java/com/example/solidconnection/report/service/ReportServiceTest.java new file mode 100644 index 000000000..23523ae34 --- /dev/null +++ b/src/test/java/com/example/solidconnection/report/service/ReportServiceTest.java @@ -0,0 +1,99 @@ +package com.example.solidconnection.report.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; +import com.example.solidconnection.community.board.domain.Board; +import com.example.solidconnection.community.board.fixture.BoardFixture; +import com.example.solidconnection.community.post.domain.Post; +import com.example.solidconnection.community.post.fixture.PostFixture; +import com.example.solidconnection.report.domain.ReportType; +import com.example.solidconnection.report.domain.TargetType; +import com.example.solidconnection.report.dto.ReportRequest; +import com.example.solidconnection.report.fixture.ReportFixture; +import com.example.solidconnection.report.repository.ReportRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@DisplayName("신고 서비스 테스트") +@TestContainerSpringBootTest +class ReportServiceTest { + + @Autowired + private ReportService reportService; + + @Autowired + private ReportRepository reportRepository; + + @Autowired + private BoardFixture boardFixture; + + @Autowired + private PostFixture postFixture; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private ReportFixture reportFixture; + + private SiteUser siteUser; + private Post post; + + @BeforeEach + void setUp() { + siteUser = siteUserFixture.사용자(); + Board board = boardFixture.자유게시판(); + post = postFixture.게시글(board, siteUser); + } + + @Nested + class 신고_생성 { + + @Test + void 정상적으로_신고한다() { + // given + ReportRequest request = new ReportRequest(ReportType.INSULT, TargetType.POST, post.getId()); + + // when + reportService.createReport(siteUser.getId(), request); + + // then + boolean isSaved = reportRepository.existsByReporterIdAndTargetTypeAndTargetId( + siteUser.getId(), TargetType.POST, post.getId()); + assertThat(isSaved).isTrue(); + } + + @Test + void 신고_대상이_존재하지_않으면_예외가_발생한다() { + // given + long notExistingId = 999L; + ReportRequest request = new ReportRequest(ReportType.INSULT, TargetType.POST, notExistingId); + + // when & then + assertThatCode(() -> reportService.createReport(siteUser.getId(), request)) + .isInstanceOf(CustomException.class) + .hasMessageContaining(ErrorCode.REPORT_TARGET_NOT_FOUND.getMessage()); + } + + @Test + void 이미_신고한_경우_예외가_발생한다() { + // given + reportFixture.신고(siteUser.getId(), TargetType.POST, post.getId()); + ReportRequest request = new ReportRequest(ReportType.INSULT, TargetType.POST, post.getId()); + + // when & then + assertThatCode(() -> reportService.createReport(siteUser.getId(), request)) + .isInstanceOf(CustomException.class) + .hasMessageContaining(ErrorCode.ALREADY_REPORTED_BY_CURRENT_USER.getMessage()); + } + } +} From 11e589fab30bc7aed8237e1519cdddca9d9253fb Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Mon, 28 Jul 2025 05:31:03 +0900 Subject: [PATCH 54/90] =?UTF-8?q?fix:=20flyway=20=EB=B2=84=EC=A0=84=20?= =?UTF-8?q?=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0=20(#405)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...{V24__create_report_table.sql => V25__create_report_table.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/resources/db/migration/{V24__create_report_table.sql => V25__create_report_table.sql} (100%) diff --git a/src/main/resources/db/migration/V24__create_report_table.sql b/src/main/resources/db/migration/V25__create_report_table.sql similarity index 100% rename from src/main/resources/db/migration/V24__create_report_table.sql rename to src/main/resources/db/migration/V25__create_report_table.sql From 66aa67022a2c02ffef7ab53e6ae2e3ad3327f97b Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Tue, 29 Jul 2025 02:46:28 +0900 Subject: [PATCH 55/90] =?UTF-8?q?feat:=20access=20token=EC=9D=B4=20role?= =?UTF-8?q?=EC=9D=84=20cliam=20=EC=9C=BC=EB=A1=9C=20=EA=B0=96=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20(#407)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: claims가 있는 토큰 생성하는 함수 추가 * refactor: AccessToken 생성자 인자 변경 * test: AccessToken 생성과 관련없는 테스트에 변경 전파가 최소화되도록 리팩터링 * refactor: 재발급하는 accessToken도 role을 응답하도록 --- .../auth/controller/AuthController.java | 3 +- .../auth/service/AccessToken.java | 7 ++- .../auth/service/AuthService.java | 13 +++-- .../auth/service/AuthTokenProvider.java | 16 +++--- .../auth/service/SignInService.java | 2 +- .../auth/service/TokenProvider.java | 3 ++ .../auth/token/JwtTokenProvider.java | 17 ++++-- .../auth/service/AuthServiceTest.java | 31 ++++++----- .../auth/service/AuthTokenProviderTest.java | 16 ++++-- .../auth/service/JwtTokenProviderTest.java | 52 ++++++++++++++----- .../service/TokenBlackListServiceTest.java | 19 ++++--- 11 files changed, 122 insertions(+), 57 deletions(-) diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index 57a42223d..8940c108e 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -113,9 +113,10 @@ public ResponseEntity quit( @PostMapping("/reissue") public ResponseEntity reissueToken( + @AuthorizedUser long siteUserId, @Valid @RequestBody ReissueRequest reissueRequest ) { - ReissueResponse reissueResponse = authService.reissue(reissueRequest); + ReissueResponse reissueResponse = authService.reissue(siteUserId, reissueRequest); return ResponseEntity.ok(reissueResponse); } diff --git a/src/main/java/com/example/solidconnection/auth/service/AccessToken.java b/src/main/java/com/example/solidconnection/auth/service/AccessToken.java index c94e891aa..3456a2171 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AccessToken.java +++ b/src/main/java/com/example/solidconnection/auth/service/AccessToken.java @@ -1,11 +1,14 @@ package com.example.solidconnection.auth.service; +import com.example.solidconnection.siteuser.domain.Role; + public record AccessToken( Subject subject, + Role role, String token ) { - public AccessToken(String subject, String token) { - this(new Subject(subject), token); + public AccessToken(String subject, Role role, String token) { + this(new Subject(subject), role, token); } } diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthService.java b/src/main/java/com/example/solidconnection/auth/service/AuthService.java index 2737360c2..0d54c7672 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -28,7 +28,12 @@ public class AuthService { * - 리프레시 토큰을 삭제한다. * */ public void signOut(String token) { - AccessToken accessToken = authTokenProvider.toAccessToken(token); + Subject subject = authTokenProvider.parseSubject(token); + long siteUserId = Long.parseLong(subject.value()); + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); + + AccessToken accessToken = authTokenProvider.generateAccessToken(subject, siteUser.getRole()); authTokenProvider.deleteRefreshTokenByAccessToken(accessToken); tokenBlackListService.addToBlacklist(accessToken); } @@ -53,15 +58,17 @@ public void quit(long siteUserId, String token) { * - 유효한 리프레시토큰이면, 액세스 토큰을 재발급한다. * - 그렇지 않으면 예외를 발생시킨다. * */ - public ReissueResponse reissue(ReissueRequest reissueRequest) { + public ReissueResponse reissue(long siteUserId, ReissueRequest reissueRequest) { // 리프레시 토큰 확인 String requestedRefreshToken = reissueRequest.refreshToken(); if (!authTokenProvider.isValidRefreshToken(requestedRefreshToken)) { throw new CustomException(REFRESH_TOKEN_EXPIRED); } // 액세스 토큰 재발급 + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); Subject subject = authTokenProvider.parseSubject(requestedRefreshToken); - AccessToken newAccessToken = authTokenProvider.generateAccessToken(subject); + AccessToken newAccessToken = authTokenProvider.generateAccessToken(subject, siteUser.getRole()); return ReissueResponse.from(newAccessToken); } } diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java index f15a3e7b4..6f335256e 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -1,7 +1,9 @@ package com.example.solidconnection.auth.service; import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; +import java.util.Map; import java.util.Objects; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; @@ -11,12 +13,16 @@ @RequiredArgsConstructor public class AuthTokenProvider { + private static final String ROLE_CLAIM_KEY = "role"; + private final RedisTemplate redisTemplate; private final TokenProvider tokenProvider; - public AccessToken generateAccessToken(Subject subject) { - String token = tokenProvider.generateToken(subject.value(), TokenType.ACCESS); - return new AccessToken(subject, token); + public AccessToken generateAccessToken(Subject subject, Role role) { + String token = tokenProvider.generateToken( + subject.value(), Map.of(ROLE_CLAIM_KEY, role.name()), TokenType.ACCESS + ); + return new AccessToken(subject, role, token); } public RefreshToken generateAndSaveRefreshToken(Subject subject) { @@ -51,8 +57,4 @@ public Subject parseSubject(String token) { public Subject toSubject(SiteUser siteUser) { return new Subject(siteUser.getId().toString()); } - - public AccessToken toAccessToken(String token) { - return new AccessToken(parseSubject(token), token); - } } diff --git a/src/main/java/com/example/solidconnection/auth/service/SignInService.java b/src/main/java/com/example/solidconnection/auth/service/SignInService.java index c2b129214..2b6a57cbe 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignInService.java +++ b/src/main/java/com/example/solidconnection/auth/service/SignInService.java @@ -16,7 +16,7 @@ public class SignInService { public SignInResponse signIn(SiteUser siteUser) { resetQuitedAt(siteUser); Subject subject = authTokenProvider.toSubject(siteUser); - AccessToken accessToken = authTokenProvider.generateAccessToken(subject); + AccessToken accessToken = authTokenProvider.generateAccessToken(subject, siteUser.getRole()); RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(subject); return SignInResponse.of(accessToken, refreshToken); } diff --git a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java index 2cd93fb8e..22120b084 100644 --- a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java @@ -2,11 +2,14 @@ import com.example.solidconnection.auth.domain.TokenType; import io.jsonwebtoken.Claims; +import java.util.Map; public interface TokenProvider { String generateToken(String string, TokenType tokenType); + String generateToken(String string, Map claims, TokenType tokenType); + String saveToken(String token, TokenType tokenType); String parseSubject(String token); diff --git a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java index f9f5b20ff..d7c968ccf 100644 --- a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java @@ -10,6 +10,7 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; +import java.util.Map; import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; @@ -24,11 +25,21 @@ public class JwtTokenProvider implements TokenProvider { @Override public final String generateToken(String string, TokenType tokenType) { - Claims claims = Jwts.claims().setSubject(string); + return generateJwtTokenValue(string, Map.of(), tokenType.getExpireTime()); + } + + @Override + public String generateToken(String string, Map customClaims, TokenType tokenType) { + return generateJwtTokenValue(string, customClaims, tokenType.getExpireTime()); + } + + private String generateJwtTokenValue(String subject, Map claims, long expireTime) { + Claims jwtClaims = Jwts.claims().setSubject(subject); + jwtClaims.putAll(claims); Date now = new Date(); - Date expiredDate = new Date(now.getTime() + tokenType.getExpireTime()); + Date expiredDate = new Date(now.getTime() + expireTime); return Jwts.builder() - .setClaims(claims) + .setClaims(jwtClaims) .setIssuedAt(now) .setExpiration(expiredDate) .signWith(SignatureAlgorithm.HS512, jwtProperties.secret()) diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java index 88b835ee2..707c3dbbb 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -15,6 +15,7 @@ import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; import java.time.LocalDate; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -43,12 +44,19 @@ class AuthServiceTest { @Autowired private SiteUserRepository siteUserRepository; + private SiteUser siteUser; + private Subject subject; + private AccessToken accessToken; + + @BeforeEach + void setUp() { + siteUser = siteUserFixture.사용자(); + subject = authTokenProvider.toSubject(siteUser); + accessToken = authTokenProvider.generateAccessToken(subject, siteUser.getRole()); + } + @Test void 로그아웃한다() { - // given - Subject subject = new Subject("subject"); - AccessToken accessToken = authTokenProvider.generateAccessToken(subject); - // when authService.signOut(accessToken.token()); @@ -62,18 +70,13 @@ class AuthServiceTest { @Test void 탈퇴한다() { - // given - SiteUser user = siteUserFixture.사용자(); - Subject subject = authTokenProvider.toSubject(user); - AccessToken accessToken = authTokenProvider.generateAccessToken(subject); - // when - authService.quit(user.getId(), accessToken.token()); + authService.quit(siteUser.getId(), accessToken.token()); // then LocalDate tomorrow = LocalDate.now().plusDays(1); String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value()); - SiteUser actualSitUser = siteUserRepository.findById(user.getId()).orElseThrow(); + SiteUser actualSitUser = siteUserRepository.findById(siteUser.getId()).orElseThrow(); assertAll( () -> assertThat(actualSitUser.getQuitedAt()).isEqualTo(tomorrow), () -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(), @@ -91,7 +94,7 @@ class 토큰을_재발급한다 { ReissueRequest reissueRequest = new ReissueRequest(refreshToken.token()); // when - ReissueResponse reissuedAccessToken = authService.reissue(reissueRequest); + ReissueResponse reissuedAccessToken = authService.reissue(siteUser.getId(), reissueRequest); // then - 요청의 리프레시 토큰과 재발급한 액세스 토큰의 subject 가 동일해야 한다. Subject expectedSubject = authTokenProvider.parseSubject(refreshToken.token()); @@ -102,11 +105,11 @@ class 토큰을_재발급한다 { @Test void 요청의_리프레시_토큰이_저장되어있지_않다면_예외가_발생한다() { // given - String invalidRefreshToken = authTokenProvider.generateAccessToken(new Subject("subject")).token(); + String invalidRefreshToken = accessToken.token(); ReissueRequest reissueRequest = new ReissueRequest(invalidRefreshToken); // when, then - assertThatCode(() -> authService.reissue(reissueRequest)) + assertThatCode(() -> authService.reissue(siteUser.getId(), reissueRequest)) .isInstanceOf(CustomException.class) .hasMessage(REFRESH_TOKEN_EXPIRED.getMessage()); } diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java index b2e588a59..6a7cc40a3 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertAll; import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.support.TestContainerSpringBootTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -32,11 +33,16 @@ void setUp() { @Test void 액세스_토큰을_생성한다() { // when - AccessToken accessToken = authTokenProvider.generateAccessToken(subject); + Role expectedRole = Role.MENTEE; + AccessToken accessToken = authTokenProvider.generateAccessToken(subject, expectedRole); // then String actualSubject = authTokenProvider.parseSubject(accessToken.token()).value(); - assertThat(actualSubject).isEqualTo(subject.value()); + assertAll( + () -> assertThat(actualSubject).isEqualTo(subject.value()), + () -> assertThat(accessToken.role()).isEqualTo(expectedRole), + () -> assertThat(accessToken.token()).isNotNull() + ); } @Nested @@ -61,7 +67,7 @@ class 리프레시_토큰을_제공한다 { void 유효한_리프레시_토큰인지_확인한다() { // given RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(subject); - AccessToken fakeRefreshToken = authTokenProvider.generateAccessToken(subject); + AccessToken fakeRefreshToken = authTokenProvider.generateAccessToken(subject, Role.MENTEE); // when, then assertAll( @@ -74,7 +80,7 @@ class 리프레시_토큰을_제공한다 { void 액세스_토큰에_해당하는_리프레시_토큰을_삭제한다() { // given authTokenProvider.generateAndSaveRefreshToken(subject); - AccessToken accessToken = authTokenProvider.generateAccessToken(subject); + AccessToken accessToken = authTokenProvider.generateAccessToken(subject, Role.MENTEE); // when authTokenProvider.deleteRefreshTokenByAccessToken(accessToken); @@ -88,7 +94,7 @@ class 리프레시_토큰을_제공한다 { @Test void 토큰으로부터_Subject_를_추출한다() { // given - String accessToken = authTokenProvider.generateAccessToken(subject).token(); + String accessToken = authTokenProvider.generateAccessToken(subject, Role.MENTEE).token(); // when Subject actualSubject = authTokenProvider.parseSubject(accessToken); diff --git a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java index 813a827b3..62655df2a 100644 --- a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java @@ -35,22 +35,46 @@ class JwtTokenProviderTest { @Autowired private RedisTemplate redisTemplate; - @Test - void 토큰을_생성한다() { - // given - String actualSubject = "subject123"; - TokenType actualTokenType = TokenType.ACCESS; + @Nested + class 토큰을_생성한다 { - // when - String token = tokenProvider.generateToken(actualSubject, actualTokenType); + @Test + void subject_만_있는_토큰을_생성한다() { + // given + String actualSubject = "subject123"; + TokenType actualTokenType = TokenType.ACCESS; - // then - subject와 만료 시간이 일치하는지 검증 - Claims claims = tokenProvider.parseClaims(token); - long expectedExpireTime = claims.getExpiration().getTime() - claims.getIssuedAt().getTime(); - assertAll( - () -> assertThat(claims.getSubject()).isEqualTo(actualSubject), - () -> assertThat(expectedExpireTime).isEqualTo(actualTokenType.getExpireTime()) - ); + // when + String token = tokenProvider.generateToken(actualSubject, actualTokenType); + + // then - subject와 만료 시간이 일치하는지 검증 + Claims claims = tokenProvider.parseClaims(token); + long expectedExpireTime = claims.getExpiration().getTime() - claims.getIssuedAt().getTime(); + assertAll( + () -> assertThat(claims.getSubject()).isEqualTo(actualSubject), + () -> assertThat(expectedExpireTime).isEqualTo(actualTokenType.getExpireTime()) + ); + } + + @Test + void subject_와_claims_가_있는_토큰을_생성한다() { + // given + String actualSubject = "subject123"; + Map customClaims = Map.of("key1", "value1", "key2", "value2"); + TokenType actualTokenType = TokenType.ACCESS; + + // when + String token = tokenProvider.generateToken(actualSubject, customClaims, actualTokenType); + + // then - subject와 커스텀 클레임이 일치하는지 검증 + Claims claims = tokenProvider.parseClaims(token); + long expectedExpireTime = claims.getExpiration().getTime() - claims.getIssuedAt().getTime(); + assertAll( + () -> assertThat(claims.getSubject()).isEqualTo(actualSubject), + () -> assertThat(claims).containsAllEntriesOf(customClaims), + () -> assertThat(expectedExpireTime).isEqualTo(actualTokenType.getExpireTime()) + ); + } } @Test diff --git a/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java index 49328315a..5267f88f3 100644 --- a/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java @@ -4,12 +4,16 @@ import static org.assertj.core.api.Assertions.assertThat; import com.example.solidconnection.auth.token.TokenBlackListService; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; +@DisplayName("토큰 블랙리스트 서비스 테스트") @TestContainerSpringBootTest class TokenBlackListServiceTest { @@ -19,11 +23,16 @@ class TokenBlackListServiceTest { @Autowired private RedisTemplate redisTemplate; + private AccessToken accessToken; + + @BeforeEach + void setUp() { + accessToken = new AccessToken("subject", Role.MENTEE, "token"); + } + + @Test void 액세스_토큰을_블랙리스트에_추가한다() { - // given - AccessToken accessToken = new AccessToken("subject", "token"); - // when tokenBlackListService.addToBlacklist(accessToken); @@ -39,7 +48,6 @@ class 블랙리스트에_있는_토큰인지_확인한다 { @Test void 블랙리스트에_토큰이_있는_경우() { // given - AccessToken accessToken = new AccessToken("subject", "token"); tokenBlackListService.addToBlacklist(accessToken); // when, then @@ -48,9 +56,6 @@ class 블랙리스트에_있는_토큰인지_확인한다 { @Test void 블랙리스트에_토큰이_없는_경우() { - // given - AccessToken accessToken = new AccessToken("subject", "token"); - // when, then assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isFalse(); } From 0c91030fe61510f09df795264d5c314a0b8411e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Wed, 30 Jul 2025 08:40:58 +0900 Subject: [PATCH 56/90] =?UTF-8?q?fix:=20RequiredArgsConstructor=20?= =?UTF-8?q?=EB=8F=99=EC=9E=91=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=9E=84?= =?UTF-8?q?=EC=8B=9C=20=EC=88=98=EC=A0=95=20(#410)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aspect/RoleAuthorizationAspect.java | 52 +++++++++++++++---- .../aspect/RoleAuthorizationAspectTest.java | 19 +++---- 2 files changed, 51 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspect.java b/src/main/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspect.java index 900251514..55d640ed2 100644 --- a/src/main/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspect.java +++ b/src/main/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspect.java @@ -1,16 +1,21 @@ package com.example.solidconnection.security.aspect; import static com.example.solidconnection.common.exception.ErrorCode.ACCESS_DENIED; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.security.annotation.RequireRoleAccess; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import java.lang.reflect.Parameter; import java.util.Arrays; import lombok.RequiredArgsConstructor; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; @Aspect @@ -18,24 +23,49 @@ @RequiredArgsConstructor public class RoleAuthorizationAspect { - // todo: 추후 siteUserId로 파라미터 변경 시 수정 필요 + private final SiteUserRepository siteUserRepository; + + // todo: 추후 개선 필요 @Around("@annotation(requireRoleAccess)") public Object checkRoleAccess(ProceedingJoinPoint joinPoint, RequireRoleAccess requireRoleAccess) throws Throwable { - SiteUser siteUser = null; - for (Object arg : joinPoint.getArgs()) { - if (arg instanceof SiteUser) { - siteUser = (SiteUser) arg; - break; - } - } - if (siteUser == null) { + + Long siteUserId = extractAuthorizedUserId(joinPoint); + + if (siteUserId == null) { throw new CustomException(ACCESS_DENIED); } - Role[] allowedRoles = requireRoleAccess.roles(); + + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); + + validateUserRole(siteUser, requireRoleAccess.roles()); + + return joinPoint.proceed(); + } + + private Long extractAuthorizedUserId(ProceedingJoinPoint joinPoint) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Parameter[] parameters = signature.getMethod().getParameters(); + Object[] args = joinPoint.getArgs(); + + for (int i = 0; i < parameters.length; i++) { + if (parameters[i].isAnnotationPresent(AuthorizedUser.class)) { + Object arg = args[i]; + if (arg instanceof Long) { + return (Long) arg; + } else if (parameters[i].getType() == long.class) { + return (Long) arg; + } + } + } + return null; + } + + private void validateUserRole(SiteUser siteUser, Role[] allowedRoles) { boolean hasAccess = Arrays.asList(allowedRoles).contains(siteUser.getRole()); + if (!hasAccess) { throw new CustomException(ACCESS_DENIED); } - return joinPoint.proceed(); } } diff --git a/src/test/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspectTest.java b/src/test/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspectTest.java index 62be411ee..e68c61782 100644 --- a/src/test/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspectTest.java +++ b/src/test/java/com/example/solidconnection/security/aspect/RoleAuthorizationAspectTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertAll; import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.security.annotation.RequireRoleAccess; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; @@ -35,9 +36,9 @@ class RoleAuthorizationAspectTest { // when & then assertAll( - () -> assertThatCode(() -> testService.adminOnlyMethod(admin)) + () -> assertThatCode(() -> testService.adminOnlyMethod(admin.getId())) .doesNotThrowAnyException(), - () -> assertThatCode(() -> testService.mentorOrAdminMethod(mentor)) + () -> assertThatCode(() -> testService.mentorOrAdminMethod(mentor.getId())) .doesNotThrowAnyException() ); } @@ -48,7 +49,7 @@ class RoleAuthorizationAspectTest { SiteUser user = siteUserFixture.사용자(); // when & then - assertThatCode(() -> testService.mentorOrAdminMethod(user)) + assertThatCode(() -> testService.mentorOrAdminMethod(user.getId())) .isInstanceOf(CustomException.class) .hasMessage(ACCESS_DENIED.getMessage()); } @@ -62,11 +63,11 @@ class RoleAuthorizationAspectTest { // when & then assertAll( - () -> assertThatCode(() -> testService.publicMethod(admin)) + () -> assertThatCode(() -> testService.publicMethod(admin.getId())) .doesNotThrowAnyException(), - () -> assertThatCode(() -> testService.publicMethod(mentor)) + () -> assertThatCode(() -> testService.publicMethod(mentor.getId())) .doesNotThrowAnyException(), - () -> assertThatCode(() -> testService.publicMethod(user)) + () -> assertThatCode(() -> testService.publicMethod(user.getId())) .doesNotThrowAnyException() ); } @@ -84,16 +85,16 @@ public TestService testService() { static class TestService { @RequireRoleAccess(roles = {Role.ADMIN}) - public boolean adminOnlyMethod(SiteUser siteUser) { + public boolean adminOnlyMethod(@AuthorizedUser long siteUserId) { return true; } @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) - public boolean mentorOrAdminMethod(SiteUser siteUser) { + public boolean mentorOrAdminMethod(@AuthorizedUser long siteUserId) { return true; } - public boolean publicMethod(SiteUser siteUser) { + public boolean publicMethod(@AuthorizedUser long siteUserId) { return true; } } From d8710a0ee0db6eced04869b39e297d35f06b6979 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Wed, 30 Jul 2025 11:42:09 +0900 Subject: [PATCH 57/90] =?UTF-8?q?refactor:=20=EB=A9=98=ED=86=A0=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EC=8B=9C,=20=ED=8C=8C=EA=B2=AC=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EB=8C=80=EC=8B=A0=20'=ED=8C=8C=EA=B2=AC?= =?UTF-8?q?=20=ED=95=99=EA=B8=B0'=EB=A5=BC=20=EC=9D=91=EB=8B=B5=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20(#412)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 멘토 테이블에 '파견 학기' 컬럼을 추가 * refactor: 멘토 조회 시, 파견 상태 대신 '파견 학기'를 응답하도록 - 멘터 상세 페이지, 미리보기, 멘토 마이페이지에서 변경이 일어남 * refactor: 사용하지 않는 클래스 삭제 - sliceResponse 로 대체됨 --- .../example/solidconnection/mentor/domain/Mentor.java | 3 +++ .../mentor/dto/MentorDetailResponse.java | 5 ++--- .../mentor/dto/MentorMyPageResponse.java | 5 ++--- .../mentor/dto/MentorPreviewResponse.java | 5 ++--- .../mentor/dto/MentorPreviewsResponse.java | 10 ---------- .../db/migration/V26__add_term_column_to_mentor.sql | 2 ++ .../mentor/fixture/MentorFixtureBuilder.java | 7 +++++++ 7 files changed, 18 insertions(+), 19 deletions(-) delete mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java create mode 100644 src/main/resources/db/migration/V26__add_term_column_to_mentor.sql diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java index 1a5012015..d97f6a895 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java @@ -44,6 +44,9 @@ public class Mentor { @Column private long universityId; + @Column(length = 50, nullable = false) + private String term; + @BatchSize(size = 10) @OrderBy("sequence ASC") @OneToMany(mappedBy = "mentor", cascade = CascadeType.ALL, orphanRemoval = true) diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java index 63255799e..3fc042aa7 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java @@ -1,7 +1,6 @@ package com.example.solidconnection.mentor.dto; import com.example.solidconnection.mentor.domain.Mentor; -import com.example.solidconnection.siteuser.domain.ExchangeStatus; import com.example.solidconnection.siteuser.domain.SiteUser; import java.util.List; @@ -9,9 +8,9 @@ public record MentorDetailResponse( long id, String nickname, String profileImageUrl, - ExchangeStatus exchangeStatus, String country, String universityName, + String term, int menteeCount, boolean hasBadge, String introduction, @@ -25,9 +24,9 @@ public static MentorDetailResponse of(Mentor mentor, SiteUser mentorUser, boolea mentor.getId(), mentorUser.getNickname(), mentorUser.getProfileImageUrl(), - mentorUser.getExchangeStatus(), "국가", // todo: 교환학생 기록이 인증되면 추가 "대학 이름", // todo: 교환학생 기록이 인증되면 추가 + mentor.getTerm(), mentor.getMenteeCount(), mentor.isHasBadge(), mentor.getIntroduction(), diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java index 46012f068..95bb26506 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java @@ -1,7 +1,6 @@ package com.example.solidconnection.mentor.dto; import com.example.solidconnection.mentor.domain.Mentor; -import com.example.solidconnection.siteuser.domain.ExchangeStatus; import com.example.solidconnection.siteuser.domain.SiteUser; import java.util.List; @@ -9,9 +8,9 @@ public record MentorMyPageResponse( long id, String profileImageUrl, String nickname, - ExchangeStatus exchangeStatus, String country, String universityName, + String term, int menteeCount, boolean hasBadge, String introduction, @@ -23,9 +22,9 @@ public static MentorMyPageResponse of(Mentor mentor, SiteUser siteUser) { mentor.getId(), siteUser.getProfileImageUrl(), siteUser.getNickname(), - siteUser.getExchangeStatus(), "국가", // todo: 교환학생 기록이 인증되면 추가 "대학 이름", + mentor.getTerm(), mentor.getMenteeCount(), mentor.isHasBadge(), mentor.getIntroduction(), diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java index 70695b462..dd590c179 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java @@ -1,7 +1,6 @@ package com.example.solidconnection.mentor.dto; import com.example.solidconnection.mentor.domain.Mentor; -import com.example.solidconnection.siteuser.domain.ExchangeStatus; import com.example.solidconnection.siteuser.domain.SiteUser; import java.util.List; @@ -9,9 +8,9 @@ public record MentorPreviewResponse( long id, String nickname, String profileImageUrl, - ExchangeStatus exchangeStatus, String country, String universityName, + String term, int menteeCount, boolean hasBadge, String introduction, @@ -24,9 +23,9 @@ public static MentorPreviewResponse of(Mentor mentor, SiteUser mentorUser, boole mentor.getId(), mentorUser.getNickname(), mentorUser.getProfileImageUrl(), - mentorUser.getExchangeStatus(), "국가", // todo: 교환학생 기록이 인증되면 추가 "대학 이름", // todo: 교환학생 기록이 인증되면 추가 + mentor.getTerm(), mentor.getMenteeCount(), mentor.isHasBadge(), mentor.getIntroduction(), diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java deleted file mode 100644 index d322e22f9..000000000 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewsResponse.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.solidconnection.mentor.dto; - -import java.util.List; - -public record MentorPreviewsResponse( - List content, - int nextPageNumber -) { - -} diff --git a/src/main/resources/db/migration/V26__add_term_column_to_mentor.sql b/src/main/resources/db/migration/V26__add_term_column_to_mentor.sql new file mode 100644 index 000000000..dbf061090 --- /dev/null +++ b/src/main/resources/db/migration/V26__add_term_column_to_mentor.sql @@ -0,0 +1,2 @@ +ALTER TABLE mentor + ADD COLUMN term varchar(50) NOT NULL; diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixtureBuilder.java b/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixtureBuilder.java index d499ecc2a..7eaaefa94 100644 --- a/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/mentor/fixture/MentorFixtureBuilder.java @@ -17,6 +17,7 @@ public class MentorFixtureBuilder { private String passTip; private long siteUserId; private long universityId; + private String term = "2025-1"; public MentorFixtureBuilder mentor() { return new MentorFixtureBuilder(mentorRepository); @@ -52,6 +53,11 @@ public MentorFixtureBuilder universityId(Long universityId) { return this; } + public MentorFixtureBuilder term(String term) { + this.term = term; + return this; + } + public Mentor create() { Mentor mentor = new Mentor( null, @@ -61,6 +67,7 @@ public Mentor create() { passTip, siteUserId, universityId, + term, null ); return mentorRepository.save(mentor); From ae9babf7aa167dbeb7ec9d11892347d2ba87c024 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Thu, 31 Jul 2025 10:52:56 +0900 Subject: [PATCH 58/90] =?UTF-8?q?feat:=20=EB=A9=98=ED=86=A0=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=8B=9C=20=ED=8C=8C=EA=B2=AC=EB=8C=80=ED=95=99?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=ED=8F=AC=ED=95=A8=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5,=20=EA=B6=8C=EC=97=AD=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#415)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 파견대학 정보를 포함한 '멘토 상세 조회' 구현 * feat: 파견대학 정보를 포함한 '멘토 나의 페이지 조회' 구현 * feat: 파견대학 정보를 포함한 '멘토 목록 조회' 구현 * feat: 권역으로 멘토 필터링 구현 * refactor: 중복을 줄이기 위한 distinct 적용 - 여러 멘토가 동일한 대학을 나왔을 경우, 동일한 대학을 멘토 수만틈 가지고 있기보다는 distinct를 적용하여 하나씩만 가지고 있게 하는 것이 좋다. * refactor: batch 조회 후 조합하는 함수 이름 변경 --- .../region/repository/RegionRepository.java | 3 + .../mentor/dto/MentorDetailResponse.java | 8 +- .../mentor/dto/MentorMyPageResponse.java | 7 +- .../mentor/dto/MentorPreviewResponse.java | 8 +- .../MentorBatchQueryRepository.java | 21 ++++ .../mentor/repository/MentorRepository.java | 10 ++ .../mentor/service/MentorMyPageService.java | 8 +- .../mentor/service/MentorQueryService.java | 33 ++++- .../MentorBatchQueryRepositoryTest.java | 28 ++++- .../service/MentorMyPageServiceTest.java | 12 +- .../service/MentorQueryServiceTest.java | 116 +++++++++++++----- 11 files changed, 200 insertions(+), 54 deletions(-) diff --git a/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java b/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java index eca02bc93..656fc4377 100644 --- a/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java +++ b/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java @@ -2,6 +2,7 @@ import com.example.solidconnection.location.region.domain.Region; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -10,4 +11,6 @@ public interface RegionRepository extends JpaRepository { @Query("SELECT r FROM Region r WHERE r.koreanName IN :names") List findByKoreanNames(@Param(value = "names") List names); + + Optional findByKoreanName(String koreanName); } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java index 3fc042aa7..6b5f04b0c 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorDetailResponse.java @@ -2,6 +2,7 @@ import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.university.domain.University; import java.util.List; public record MentorDetailResponse( @@ -19,13 +20,14 @@ public record MentorDetailResponse( boolean isApplied ) { - public static MentorDetailResponse of(Mentor mentor, SiteUser mentorUser, boolean isApplied) { + public static MentorDetailResponse of(Mentor mentor, SiteUser mentorUser, + University university, boolean isApplied) { return new MentorDetailResponse( mentor.getId(), mentorUser.getNickname(), mentorUser.getProfileImageUrl(), - "국가", // todo: 교환학생 기록이 인증되면 추가 - "대학 이름", // todo: 교환학생 기록이 인증되면 추가 + university.getCountry().getKoreanName(), + university.getKoreanName(), mentor.getTerm(), mentor.getMenteeCount(), mentor.isHasBadge(), diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java index 95bb26506..50b7f6eab 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java @@ -2,6 +2,7 @@ import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.university.domain.University; import java.util.List; public record MentorMyPageResponse( @@ -17,13 +18,13 @@ public record MentorMyPageResponse( List channels ) { - public static MentorMyPageResponse of(Mentor mentor, SiteUser siteUser) { + public static MentorMyPageResponse of(Mentor mentor, SiteUser siteUser, University university) { return new MentorMyPageResponse( mentor.getId(), siteUser.getProfileImageUrl(), siteUser.getNickname(), - "국가", // todo: 교환학생 기록이 인증되면 추가 - "대학 이름", + university.getCountry().getKoreanName(), + university.getKoreanName(), mentor.getTerm(), mentor.getMenteeCount(), mentor.isHasBadge(), diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java index dd590c179..c6678fb53 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorPreviewResponse.java @@ -2,6 +2,7 @@ import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.university.domain.University; import java.util.List; public record MentorPreviewResponse( @@ -18,13 +19,14 @@ public record MentorPreviewResponse( boolean isApplied ) { - public static MentorPreviewResponse of(Mentor mentor, SiteUser mentorUser, boolean isApplied) { + public static MentorPreviewResponse of(Mentor mentor, SiteUser mentorUser, + University university, boolean isApplied) { return new MentorPreviewResponse( mentor.getId(), mentorUser.getNickname(), mentorUser.getProfileImageUrl(), - "국가", // todo: 교환학생 기록이 인증되면 추가 - "대학 이름", // todo: 교환학생 기록이 인증되면 추가 + university.getCountry().getKoreanName(), + university.getKoreanName(), mentor.getTerm(), mentor.getMenteeCount(), mentor.isHasBadge(), diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepository.java index 3e90f944f..0b01f3871 100644 --- a/src/main/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepository.java +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepository.java @@ -7,6 +7,8 @@ import com.example.solidconnection.mentor.domain.Mentoring; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.university.domain.University; +import com.example.solidconnection.university.repository.UniversityRepository; import java.util.List; import java.util.Map; import java.util.Set; @@ -21,6 +23,7 @@ public class MentorBatchQueryRepository { // 연관관계가 설정되지 않은 private final SiteUserRepository siteUserRepository; private final MentoringRepository mentoringRepository; + private final UniversityRepository universityRepository; public Map getMentorIdToSiteUserMap(List mentors) { List mentorUserIds = mentors.stream().map(Mentor::getSiteUserId).toList(); @@ -40,6 +43,24 @@ public Map getMentorIdToSiteUserMap(List mentors) { )); } + public Map getMentorIdToUniversityMap(List mentors) { + List universityIds = mentors.stream().map(Mentor::getUniversityId).distinct().toList(); + List universities = universityRepository.findAllById(universityIds); + Map universityIdToUniversityMap = universities.stream() + .collect(Collectors.toMap(University::getId, Function.identity())); + + return mentors.stream().collect(Collectors.toMap( + Mentor::getId, + mentor -> { + University university = universityIdToUniversityMap.get(mentor.getUniversityId()); + if (university == null) { // mentor.university_id에 해당하는 대학이 없으면 정합성 문제가 발생한 것 + throw new CustomException(DATA_INTEGRITY_VIOLATION, "mentor.university_id 에 해당하는 university 존재하지 않음"); + } + return university; + } + )); + } + public Map getMentorIdToIsApplied(List mentors, long currentUserId) { List mentorIds = mentors.stream().map(Mentor::getId).toList(); List appliedMentorings = mentoringRepository.findAllByMentorIdInAndMenteeId(mentorIds, currentUserId); diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java index b2332afad..430602f1e 100644 --- a/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentorRepository.java @@ -1,10 +1,13 @@ package com.example.solidconnection.mentor.repository; +import com.example.solidconnection.location.region.domain.Region; import com.example.solidconnection.mentor.domain.Mentor; import java.util.Optional; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface MentorRepository extends JpaRepository { @@ -13,4 +16,11 @@ public interface MentorRepository extends JpaRepository { Optional findBySiteUserId(long siteUserId); Slice findAllBy(Pageable pageable); + + @Query(""" + SELECT m FROM Mentor m + JOIN University u ON m.universityId = u.id + WHERE u.region = :region + """) + Slice findAllByRegion(@Param("region") Region region, Pageable pageable); } diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorMyPageService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorMyPageService.java index 365d10ad4..18e63a6fb 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentorMyPageService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorMyPageService.java @@ -2,6 +2,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.CHANNEL_REGISTRATION_LIMIT_EXCEEDED; import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_NOT_FOUND; import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.common.exception.CustomException; @@ -13,6 +14,8 @@ import com.example.solidconnection.mentor.repository.MentorRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.university.domain.University; +import com.example.solidconnection.university.repository.UniversityRepository; import java.util.ArrayList; import java.util.List; import lombok.RequiredArgsConstructor; @@ -28,6 +31,7 @@ public class MentorMyPageService { private final MentorRepository mentorRepository; private final SiteUserRepository siteUserRepository; + private final UniversityRepository universityRepository; @Transactional(readOnly = true) public MentorMyPageResponse getMentorMyPage(long siteUserId) { @@ -35,7 +39,9 @@ public MentorMyPageResponse getMentorMyPage(long siteUserId) { .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); Mentor mentor = mentorRepository.findBySiteUserId(siteUser.getId()) .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); - return MentorMyPageResponse.of(mentor, siteUser); + University university = universityRepository.findById(mentor.getUniversityId()) + .orElseThrow(() -> new CustomException(UNIVERSITY_NOT_FOUND)); + return MentorMyPageResponse.of(mentor, siteUser, university); } @Transactional diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java index 7d878a1d9..16b7172d1 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentorQueryService.java @@ -1,9 +1,13 @@ package com.example.solidconnection.mentor.service; import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_NOT_FOUND; import com.example.solidconnection.common.dto.SliceResponse; import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; +import com.example.solidconnection.location.region.domain.Region; +import com.example.solidconnection.location.region.repository.RegionRepository; import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.mentor.dto.MentorDetailResponse; import com.example.solidconnection.mentor.dto.MentorPreviewResponse; @@ -12,6 +16,8 @@ import com.example.solidconnection.mentor.repository.MentoringRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.university.domain.University; +import com.example.solidconnection.university.repository.UniversityRepository; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -29,36 +35,51 @@ public class MentorQueryService { private final MentoringRepository mentoringRepository; private final SiteUserRepository siteUserRepository; private final MentorBatchQueryRepository mentorBatchQueryRepository; + private final UniversityRepository universityRepository; + private final RegionRepository regionRepository; @Transactional(readOnly = true) public MentorDetailResponse getMentorDetails(long mentorId, long currentUserId) { Mentor mentor = mentorRepository.findById(mentorId) .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); + University university = universityRepository.findById(mentor.getUniversityId()) + .orElseThrow(() -> new CustomException(UNIVERSITY_NOT_FOUND)); SiteUser mentorUser = siteUserRepository.findById(mentor.getSiteUserId()) .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); boolean isApplied = mentoringRepository.existsByMentorIdAndMenteeId(mentorId, currentUserId); - return MentorDetailResponse.of(mentor, mentorUser, isApplied); + return MentorDetailResponse.of(mentor, mentorUser, university, isApplied); } @Transactional(readOnly = true) - public SliceResponse getMentorPreviews(String region, long currentUserId, Pageable pageable) { // todo: 멘토의 '인증' 작업 후 region 필터링 추가 - Slice mentorSlice = mentorRepository.findAllBy(pageable); + public SliceResponse getMentorPreviews(String regionKoreanName, long currentUserId, Pageable pageable) { + Slice mentorSlice = filterMentorsByRegion(regionKoreanName, pageable); List mentors = mentorSlice.toList(); - List content = getMentorPreviewResponses(mentors, currentUserId); + List content = buildMentorPreviewsWithBatchQuery(mentors, currentUserId); return SliceResponse.of(content, mentorSlice); } - private List getMentorPreviewResponses(List mentors, long currentUserId) { + private Slice filterMentorsByRegion(String regionKoreanName, Pageable pageable) { + if (regionKoreanName == null || regionKoreanName.isEmpty()) { + return mentorRepository.findAll(pageable); + } + Region region = regionRepository.findByKoreanName(regionKoreanName) + .orElseThrow(() -> new CustomException(ErrorCode.REGION_NOT_FOUND_BY_KOREAN_NAME)); + return mentorRepository.findAllByRegion(region, pageable); + } + + private List buildMentorPreviewsWithBatchQuery(List mentors, long currentUserId) { Map mentorIdToSiteUser = mentorBatchQueryRepository.getMentorIdToSiteUserMap(mentors); + Map mentorIdToUniversity = mentorBatchQueryRepository.getMentorIdToUniversityMap(mentors); Map mentorIdToIsApplied = mentorBatchQueryRepository.getMentorIdToIsApplied(mentors, currentUserId); List mentorPreviews = new ArrayList<>(); for (Mentor mentor : mentors) { SiteUser mentorUser = mentorIdToSiteUser.get(mentor.getId()); + University university = mentorIdToUniversity.get(mentor.getId()); boolean isApplied = mentorIdToIsApplied.get(mentor.getId()); - MentorPreviewResponse response = MentorPreviewResponse.of(mentor, mentorUser, isApplied); + MentorPreviewResponse response = MentorPreviewResponse.of(mentor, mentorUser, university, isApplied); mentorPreviews.add(response); } return mentorPreviews; diff --git a/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java b/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java index ffa55dd13..347bb684f 100644 --- a/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java +++ b/src/test/java/com/example/solidconnection/mentor/repository/MentorBatchQueryRepositoryTest.java @@ -9,6 +9,8 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import com.example.solidconnection.university.domain.University; +import com.example.solidconnection.university.fixture.UniversityFixture; import java.util.List; import java.util.Map; import org.junit.jupiter.api.BeforeEach; @@ -32,7 +34,10 @@ class MentorBatchQueryRepositoryTest { @Autowired private SiteUserFixture siteUserFixture; - private long universityId = 1L; // todo: 멘토 인증 기능 추가 변경 필요 + @Autowired + private UniversityFixture universityFixture; + + private University university1, university2; private Mentor mentor1, mentor2; private SiteUser mentorUser1, mentorUser2, currentUser; @@ -41,8 +46,10 @@ void setUp() { currentUser = siteUserFixture.사용자(1, "사용자"); mentorUser1 = siteUserFixture.사용자(2, "멘토1"); mentorUser2 = siteUserFixture.사용자(3, "멘토2"); - mentor1 = mentorFixture.멘토(mentorUser1.getId(), universityId); - mentor2 = mentorFixture.멘토(mentorUser2.getId(), universityId); + university1 = universityFixture.코펜하겐IT_대학(); + university2 = universityFixture.메모리얼_대학_세인트존스(); + mentor1 = mentorFixture.멘토(mentorUser1.getId(), university1.getId()); + mentor2 = mentorFixture.멘토(mentorUser2.getId(), university2.getId()); } @Test @@ -60,6 +67,21 @@ void setUp() { ); } + @Test + void 멘토_ID_와_멘토의_파견_대학교를_매핑한다() { + // given + List mentors = List.of(mentor1, mentor2); + + // when + Map mentorIdToUniversity = mentorBatchQueryRepository.getMentorIdToUniversityMap(mentors); + + // then + assertAll( + () -> assertThat(mentorIdToUniversity.get(mentor1.getId()).getId()).isEqualTo(university1.getId()), + () -> assertThat(mentorIdToUniversity.get(mentor2.getId()).getId()).isEqualTo(university2.getId()) + ); + } + @Test void 멘토_ID_와_현재_사용자의_지원_여부를_매핑한다() { // given diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java index 85667dffb..5d675e14b 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java @@ -18,6 +18,8 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import com.example.solidconnection.university.domain.University; +import com.example.solidconnection.university.fixture.UniversityFixture; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -44,17 +46,21 @@ class MentorMyPageServiceTest { @Autowired private MentorRepository mentorRepository; + @Autowired + private UniversityFixture universityFixture; + @Autowired private ChannelRepositoryForTest channelRepositoryForTest; private SiteUser mentorUser; private Mentor mentor; - private long universityId = 1L; + private University university; @BeforeEach void setUp() { + university = universityFixture.메이지_대학(); mentorUser = siteUserFixture.멘토(1, "멘토"); - mentor = mentorFixture.멘토(mentorUser.getId(), universityId); + mentor = mentorFixture.멘토(mentorUser.getId(), university.getId()); } @Nested @@ -73,6 +79,8 @@ class 멘토의_마이_페이지를_조회한다 { assertAll( () -> assertThat(response.id()).isEqualTo(mentor.getId()), () -> assertThat(response.nickname()).isEqualTo(mentorUser.getNickname()), + () -> assertThat(response.universityName()).isEqualTo(university.getKoreanName()), + () -> assertThat(response.country()).isEqualTo(university.getCountry().getKoreanName()), () -> assertThat(response.channels()).extracting(ChannelResponse::url) .containsExactly(channel1.getUrl(), channel2.getUrl()) ); diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java index a7634c7ba..d20bc28d7 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentorQueryServiceTest.java @@ -18,6 +18,8 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import com.example.solidconnection.university.domain.University; +import com.example.solidconnection.university.fixture.UniversityFixture; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; @@ -47,8 +49,15 @@ class MentorQueryServiceTest { @Autowired private ChannelFixture channelFixture; - private long universityId = 1L; // todo: 멘토 인증 기능 추가 변경 필요 - private String region = "아시아"; + @Autowired + private UniversityFixture universityFixture; + + private University university; + + @BeforeEach + void setUp() { + university = universityFixture.그라츠_대학(); + } @Nested class 멘토_단일_조회_성공 { @@ -58,7 +67,7 @@ class 멘토_단일_조회_성공 { // given SiteUser siteUser = siteUserFixture.사용자(); SiteUser mentorUser = siteUserFixture.사용자(1, "멘토"); - Mentor mentor = mentorFixture.멘토(mentorUser.getId(), universityId); + Mentor mentor = mentorFixture.멘토(mentorUser.getId(), university.getId()); Channel channel1 = channelFixture.채널(1, mentor); Channel channel2 = channelFixture.채널(2, mentor); @@ -69,6 +78,8 @@ class 멘토_단일_조회_성공 { assertAll( () -> assertThat(response.id()).isEqualTo(mentor.getId()), () -> assertThat(response.nickname()).isEqualTo(mentorUser.getNickname()), + () -> assertThat(response.universityName()).isEqualTo(university.getKoreanName()), + () -> assertThat(response.country()).isEqualTo(university.getCountry().getKoreanName()), () -> assertThat(response.channels()).extracting(ChannelResponse::url) .containsExactly(channel1.getUrl(), channel2.getUrl()) ); @@ -78,7 +89,7 @@ class 멘토_단일_조회_성공 { void 멘토에_대한_나의_멘토링_신청_여부를_조회한다() { // given SiteUser mentorUser = siteUserFixture.사용자(1, "멘토"); - Mentor mentor = mentorFixture.멘토(mentorUser.getId(), universityId); + Mentor mentor = mentorFixture.멘토(mentorUser.getId(), university.getId()); SiteUser notAppliedUser = siteUserFixture.사용자(2, "멘토링 지원 안한 사용자"); SiteUser appliedUser = siteUserFixture.사용자(3, "멘토링 지원한 사용자"); @@ -112,20 +123,23 @@ class 멘토_단일_조회_실패 { } @Nested - class 멘토_미리보기_목록_조회 { + class 멘토_미리보기_목록_정보_조회 { private static final int NO_NEXT_PAGE_NUMBER = -1; private Mentor mentor1, mentor2; private SiteUser mentorUser1, mentorUser2, currentUser; + private University university1, university2; @BeforeEach void setUp() { currentUser = siteUserFixture.사용자(1, "사용자1"); mentorUser1 = siteUserFixture.사용자(2, "멘토1"); mentorUser2 = siteUserFixture.사용자(3, "멘토2"); - mentor1 = mentorFixture.멘토(mentorUser1.getId(), universityId); - mentor2 = mentorFixture.멘토(mentorUser2.getId(), universityId); + university1 = universityFixture.괌_대학(); + university2 = universityFixture.린츠_카톨릭_대학(); + mentor1 = mentorFixture.멘토(mentorUser1.getId(), university1.getId()); + mentor2 = mentorFixture.멘토(mentorUser2.getId(), university2.getId()); } @Test @@ -135,57 +149,93 @@ void setUp() { Channel channel2 = channelFixture.채널(2, mentor2); // when - SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser.getId(), PageRequest.of(0, 10)); + SliceResponse response = mentorQueryService.getMentorPreviews("", currentUser.getId(), PageRequest.of(0, 10)); // then Map mentorPreviewMap = response.content().stream() .collect(Collectors.toMap(MentorPreviewResponse::id, Function.identity())); - + MentorPreviewResponse mentor1Response = mentorPreviewMap.get(mentor1.getId()); + MentorPreviewResponse mentor2Response = mentorPreviewMap.get(mentor2.getId()); assertAll( - () -> assertThat(mentorPreviewMap.get(mentor1.getId())).extracting(MentorPreviewResponse::nickname) - .isEqualTo(mentorUser1.getNickname()), - () -> assertThat(mentorPreviewMap.get(mentor1.getId()).channels()).extracting(ChannelResponse::url) + () -> assertThat(mentor1Response.nickname()).isEqualTo(mentorUser1.getNickname()), + () -> assertThat(mentor1Response.universityName()).isEqualTo(university1.getKoreanName()), + () -> assertThat(mentor1Response.country()).isEqualTo(university1.getCountry().getKoreanName()), + () -> assertThat(mentor1Response.channels()).extracting(ChannelResponse::url) .containsOnly(channel1.getUrl()), - () -> assertThat(mentorPreviewMap.get(mentor2.getId())).extracting(MentorPreviewResponse::nickname) - .isEqualTo(mentorUser2.getNickname()), - () -> assertThat(mentorPreviewMap.get(mentor2.getId()).channels()).extracting(ChannelResponse::url) + + () -> assertThat(mentor2Response.nickname()).isEqualTo(mentorUser2.getNickname()), + () -> assertThat(mentor2Response.universityName()).isEqualTo(university2.getKoreanName()), + () -> assertThat(mentor2Response.country()).isEqualTo(university2.getCountry().getKoreanName()), + () -> assertThat(mentor2Response.channels()).extracting(ChannelResponse::url) .containsOnly(channel2.getUrl()) ); } @Test - void 멘토들에_대한_나의_멘토링_지원_여부를_조회한다() { + void 다음_페이지_번호를_응답한다() { // given - mentoringFixture.대기중_멘토링(mentor1.getId(), currentUser.getId()); - - // when - SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser.getId(), PageRequest.of(0, 10)); + SliceResponse response = mentorQueryService.getMentorPreviews("", currentUser.getId(), PageRequest.of(0, 1)); // then - Map mentorPreviewMap = response.content().stream() - .collect(Collectors.toMap(MentorPreviewResponse::id, Function.identity())); - assertAll( - () -> assertThat(mentorPreviewMap.get(mentor1.getId()).isApplied()).isTrue(), - () -> assertThat(mentorPreviewMap.get(mentor2.getId()).isApplied()).isFalse() - ); + assertThat(response.nextPageNumber()).isEqualTo(2); } @Test - void 다음_페이지_번호를_응답한다() { + void 다음_페이지가_없으면_페이지_없음을_의미하는_값을_응답한다() { // given - SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser.getId(), PageRequest.of(0, 1)); + SliceResponse response = mentorQueryService.getMentorPreviews("", currentUser.getId(), PageRequest.of(0, 10)); // then - assertThat(response.nextPageNumber()).isEqualTo(2); + assertThat(response.nextPageNumber()).isEqualTo(NO_NEXT_PAGE_NUMBER); + } + } + + @Nested + class 멘토_미리보기_목록_필터링 { + + private Mentor asiaMentor, europeMentor; + private SiteUser currentUser; + private University asiaUniversity, europeUniversity; + + @BeforeEach + void setUp() { + currentUser = siteUserFixture.사용자(1, "사용자1"); + SiteUser mentorUser1 = siteUserFixture.사용자(2, "멘토1"); + SiteUser mentorUser2 = siteUserFixture.사용자(3, "멘토2"); + asiaUniversity = universityFixture.메이지_대학(); + europeUniversity = universityFixture.린츠_카톨릭_대학(); + asiaMentor = mentorFixture.멘토(mentorUser1.getId(), asiaUniversity.getId()); + europeMentor = mentorFixture.멘토(mentorUser2.getId(), europeUniversity.getId()); } @Test - void 다음_페이지가_없으면_페이지_없음을_의미하는_값을_응답한다() { - // given - SliceResponse response = mentorQueryService.getMentorPreviews(region, currentUser.getId(), PageRequest.of(0, 10)); + void 권역으로_멘토_목록을_필터링한다() { + // when + SliceResponse asiaFilteredResponse = mentorQueryService.getMentorPreviews( + asiaUniversity.getRegion().getKoreanName(), currentUser.getId(), PageRequest.of(0, 10)); + SliceResponse europeFilteredResponse = mentorQueryService.getMentorPreviews( + europeUniversity.getRegion().getKoreanName(), currentUser.getId(), PageRequest.of(0, 10)); // then - assertThat(response.nextPageNumber()).isEqualTo(NO_NEXT_PAGE_NUMBER); + assertAll( + () -> assertThat(asiaFilteredResponse.content()).hasSize(1) + .extracting(MentorPreviewResponse::id) + .containsExactly(asiaMentor.getId()), + () -> assertThat(europeFilteredResponse.content()).hasSize(1) + .extracting(MentorPreviewResponse::id) + .containsExactly(europeMentor.getId()) + ); + } + + @Test + void 권역을_지정하지_않으면_전체_멘토_목록을_조회한다() { + // when + SliceResponse response = mentorQueryService.getMentorPreviews("", currentUser.getId(), PageRequest.of(0, 10)); + + // then + assertThat(response.content()).hasSize(2) + .extracting(MentorPreviewResponse::id) + .containsExactlyInAnyOrder(asiaMentor.getId(), europeMentor.getId()); } } } From 69a0bc90c0f4d9847be953ebc48d0c2d84f0428d Mon Sep 17 00:00:00 2001 From: Yeon <84384499+lsy1307@users.noreply.github.com> Date: Thu, 31 Jul 2025 19:25:24 +0900 Subject: [PATCH 59/90] =?UTF-8?q?feat:=20STOMP=20Config=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#400)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Stomp Config 추가 - build.gradle 의존성 추가 - ErrorCode 추가 - config 작성 * feat: 수정사항 반영 - 불필요한 의존성 삭제 - 불필요한 파일 삭제 - 매직넘버 application-variable에서 관리 - 에러코드 추가 - 중복 코드 함수화 * fix: 테스트 코드 에러 해결을 위한 프로퍼티 기본값 추가 * refactor: 수정사항 반영 - 로그 삭제 - @ConfigurationProperties 사용을 위한 파일 추가 - application.yml에 websocket 값 추가 * refactor: 수정사항 반영 - application-variable.yml에 설정값 추가 - code formatting, 오타 수정 - 불필요한 @Sl4fj제거 - StompWebSocketConfig 변수 분리 * chore: secret 해시 수정 --- build.gradle | 1 + .../chat/config/StompEventListener.java | 27 ++++++++ .../chat/config/StompHandler.java | 64 +++++++++++++++++++ .../chat/config/StompProperties.java | 23 +++++++ .../chat/config/StompWebSocketConfig.java | 55 ++++++++++++++++ .../common/exception/ErrorCode.java | 4 ++ src/main/resources/secret | 2 +- src/test/resources/application.yml | 12 ++++ 8 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/example/solidconnection/chat/config/StompEventListener.java create mode 100644 src/main/java/com/example/solidconnection/chat/config/StompHandler.java create mode 100644 src/main/java/com/example/solidconnection/chat/config/StompProperties.java create mode 100644 src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java diff --git a/build.gradle b/build.gradle index 41ed7c856..2de93e064 100644 --- a/build.gradle +++ b/build.gradle @@ -66,6 +66,7 @@ dependencies { // Etc implementation 'org.hibernate.validator:hibernate-validator' implementation 'com.amazonaws:aws-java-sdk-s3:1.12.782' + implementation 'org.springframework.boot:spring-boot-starter-websocket' } tasks.named('test', Test) { diff --git a/src/main/java/com/example/solidconnection/chat/config/StompEventListener.java b/src/main/java/com/example/solidconnection/chat/config/StompEventListener.java new file mode 100644 index 000000000..1064eef3a --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/config/StompEventListener.java @@ -0,0 +1,27 @@ +package com.example.solidconnection.chat.config; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import org.springframework.context.event.EventListener; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.messaging.SessionConnectEvent; +import org.springframework.web.socket.messaging.SessionDisconnectEvent; + +@Component +public class StompEventListener { + + private final Set sessions = ConcurrentHashMap.newKeySet(); + + @EventListener + public void connectHandle(SessionConnectEvent event) { + StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage()); + sessions.add(accessor.getSessionId()); + } + + @EventListener + public void disconnectHandle(SessionDisconnectEvent event) { + StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage()); + sessions.remove(accessor.getSessionId()); + } +} diff --git a/src/main/java/com/example/solidconnection/chat/config/StompHandler.java b/src/main/java/com/example/solidconnection/chat/config/StompHandler.java new file mode 100644 index 000000000..660f01f28 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/config/StompHandler.java @@ -0,0 +1,64 @@ +package com.example.solidconnection.chat.config; + +import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; + +import com.example.solidconnection.auth.token.JwtTokenProvider; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; +import io.jsonwebtoken.Claims; +import lombok.RequiredArgsConstructor; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class StompHandler implements ChannelInterceptor { + + private final JwtTokenProvider jwtTokenProvider; + + @Override + public Message preSend(Message message, MessageChannel channel) { + final StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); + + if (StompCommand.CONNECT.equals(accessor.getCommand())) { + Claims claims = validateAndExtractClaims(accessor, AUTHENTICATION_FAILED); + } + + if (StompCommand.SUBSCRIBE.equals(accessor.getCommand())) { + Claims claims = validateAndExtractClaims(accessor, AUTHENTICATION_FAILED); + + String email = claims.getSubject(); + String destination = accessor.getDestination(); + + String roomId = extractRoomId(destination); + + // todo: roomId 기반 실제 구독 권한 검사 로직 추가 + } + + return message; + } + + private Claims validateAndExtractClaims(StompHeaderAccessor accessor, ErrorCode errorCode) { + String bearerToken = accessor.getFirstNativeHeader("Authorization"); + if (bearerToken == null || !bearerToken.startsWith("Bearer ")) { + throw new CustomException(errorCode); + } + String token = bearerToken.substring(7); + return jwtTokenProvider.parseClaims(token); + } + + private String extractRoomId(String destination) { + if (destination == null) { + throw new CustomException(ErrorCode.INVALID_ROOM_ID); + } + String[] parts = destination.split("/"); + if (parts.length < 3 || !parts[1].equals("topic")) { + throw new CustomException(ErrorCode.INVALID_ROOM_ID); + } + return parts[2]; + } +} diff --git a/src/main/java/com/example/solidconnection/chat/config/StompProperties.java b/src/main/java/com/example/solidconnection/chat/config/StompProperties.java new file mode 100644 index 000000000..ce9663c72 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/config/StompProperties.java @@ -0,0 +1,23 @@ +package com.example.solidconnection.chat.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "websocket") +public record StompProperties(ThreadPool threadPool, HeartbeatProperties heartbeat) { + + public record ThreadPool(InboundProperties inbound, OutboundProperties outbound) { + + } + + public record InboundProperties(int corePoolSize, int maxPoolSize, int queueCapacity) { + + } + + public record OutboundProperties(int corePoolSize, int maxPoolSize) { + + } + + public record HeartbeatProperties(long serverInterval, long clientInterval) { + + } +} \ No newline at end of file diff --git a/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java b/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java new file mode 100644 index 000000000..86b6eef5d --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java @@ -0,0 +1,55 @@ +package com.example.solidconnection.chat.config; + +import com.example.solidconnection.chat.config.StompProperties.HeartbeatProperties; +import com.example.solidconnection.chat.config.StompProperties.InboundProperties; +import com.example.solidconnection.chat.config.StompProperties.OutboundProperties; +import com.example.solidconnection.security.config.CorsProperties; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.ChannelRegistration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@EnableWebSocketMessageBroker +@RequiredArgsConstructor +public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer { + + private final StompHandler stompHandler; + private final StompProperties stompProperties; + private final CorsProperties corsProperties; + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + List strings = corsProperties.allowedOrigins(); + String[] allowedOrigins = strings.toArray(String[]::new); + registry.addEndpoint("/connect").setAllowedOrigins(allowedOrigins).withSockJS(); + } + + @Override + public void configureClientInboundChannel(ChannelRegistration registration) { + InboundProperties inboundProperties = stompProperties.threadPool().inbound(); + registration.interceptors(stompHandler).taskExecutor().corePoolSize(inboundProperties.corePoolSize()).maxPoolSize(inboundProperties.maxPoolSize()).queueCapacity(inboundProperties.queueCapacity()); + } + + @Override + public void configureClientOutboundChannel(ChannelRegistration registration) { + OutboundProperties outboundProperties = stompProperties.threadPool().outbound(); + registration.taskExecutor().corePoolSize(outboundProperties.corePoolSize()).maxPoolSize(outboundProperties.maxPoolSize()); + } + + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(1); + scheduler.setThreadNamePrefix("wss-heartbeat-"); + scheduler.initialize(); + HeartbeatProperties heartbeatProperties = stompProperties.heartbeat(); + registry.setApplicationDestinationPrefixes("/publish"); + registry.enableSimpleBroker("/topic").setHeartbeatValue(new long[]{heartbeatProperties.serverInterval(), heartbeatProperties.clientInterval()}).setTaskScheduler(scheduler); + } +} diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 824c531f6..4659e61bf 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -112,6 +112,10 @@ public enum ErrorCode { UNAUTHORIZED_MENTORING(HttpStatus.FORBIDDEN.value(), "멘토링 권한이 없습니다."), MENTORING_ALREADY_CONFIRMED(HttpStatus.BAD_REQUEST.value(), "이미 승인 또는 거절된 멘토링입니다."), + // socket + UNAUTHORIZED_SUBSCRIBE(HttpStatus.FORBIDDEN.value(), "구독 권한이 없습니다."), + INVALID_ROOM_ID(HttpStatus.BAD_REQUEST.value(), "경로의 roomId가 잘못되었습니다."), + // report ALREADY_REPORTED_BY_CURRENT_USER(HttpStatus.BAD_REQUEST.value(), "이미 신고한 상태입니다."), diff --git a/src/main/resources/secret b/src/main/resources/secret index be52e6ce9..e592f6d36 160000 --- a/src/main/resources/secret +++ b/src/main/resources/secret @@ -1 +1 @@ -Subproject commit be52e6ce9ca3d2c6eb51442108328b00a539510b +Subproject commit e592f6d36f57185c8d92a7838c0e3039603b2c57 diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 83fe6e8cf..7abc6949f 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -42,6 +42,18 @@ view: count: scheduling: delay: 3000 +websocket: + thread-pool: + inbound: + core-pool-size: 8 + max-pool-size: 16 + queue-capacity: 1000 + outbound: + core-pool-size: 8 + max-pool-size: 16 + heartbeat: + server-interval: 15000 + client-interval: 15000 oauth: apple: token-url: "https://appleid.apple.com/auth/token" From 88ef936887cceb133e649b2d0e62a2cad12ef0fb Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Sat, 2 Aug 2025 02:44:20 +0900 Subject: [PATCH 60/90] =?UTF-8?q?feat:=20=EB=A6=AC=ED=94=84=EB=A0=88?= =?UTF-8?q?=EC=8B=9C=20=ED=86=A0=ED=81=B0=EC=9D=84=20=EC=BF=A0=ED=82=A4?= =?UTF-8?q?=EC=97=90=20=EC=A0=80=EC=9E=A5=20(#417)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 리프레시 토큰을 쿠키로 설정하는 클래스 생성 * test: 리프레시 토큰 쿠키 메니저 테스트 코드 작성 * feat: 컨트롤러에 쿠키 설정/삭제 로직 추가 * refactor: milli와 micro 혼동하여 사용된 곳 수정 * refactor: 리프레시 토큰 쿠키 생성함수 분리 * refactor: set cookie 헤더에 상수 사용 * refactor: 의미가 더욱 전달되도록 함수명 수정 --- .../auth/controller/AuthController.java | 27 ++++++-- .../controller/RefreshTokenCookieManager.java | 42 ++++++++++++ .../RefreshTokenCookieManagerTest.java | 64 +++++++++++++++++++ 3 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java create mode 100644 src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index 8940c108e..2e581ab71 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -9,6 +9,7 @@ import com.example.solidconnection.auth.dto.SignUpRequest; import com.example.solidconnection.auth.dto.oauth.OAuthCodeRequest; import com.example.solidconnection.auth.dto.oauth.OAuthResponse; +import com.example.solidconnection.auth.dto.oauth.OAuthSignInResponse; import com.example.solidconnection.auth.service.AuthService; import com.example.solidconnection.auth.service.CommonSignUpTokenProvider; import com.example.solidconnection.auth.service.EmailSignInService; @@ -21,6 +22,7 @@ import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.siteuser.domain.AuthType; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -44,28 +46,39 @@ public class AuthController { private final EmailSignUpService emailSignUpService; private final EmailSignUpTokenProvider emailSignUpTokenProvider; private final CommonSignUpTokenProvider commonSignUpTokenProvider; + private final RefreshTokenCookieManager refreshTokenCookieManager; @PostMapping("/apple") public ResponseEntity processAppleOAuth( - @Valid @RequestBody OAuthCodeRequest oAuthCodeRequest + @Valid @RequestBody OAuthCodeRequest oAuthCodeRequest, + HttpServletResponse httpServletResponse ) { OAuthResponse oAuthResponse = appleOAuthService.processOAuth(oAuthCodeRequest); + if (oAuthResponse instanceof OAuthSignInResponse signInResponse) { + refreshTokenCookieManager.setCookie(httpServletResponse, signInResponse.refreshToken()); + } return ResponseEntity.ok(oAuthResponse); } @PostMapping("/kakao") public ResponseEntity processKakaoOAuth( - @Valid @RequestBody OAuthCodeRequest oAuthCodeRequest + @Valid @RequestBody OAuthCodeRequest oAuthCodeRequest, + HttpServletResponse httpServletResponse ) { OAuthResponse oAuthResponse = kakaoOAuthService.processOAuth(oAuthCodeRequest); + if (oAuthResponse instanceof OAuthSignInResponse signInResponse) { + refreshTokenCookieManager.setCookie(httpServletResponse, signInResponse.refreshToken()); + } return ResponseEntity.ok(oAuthResponse); } @PostMapping("/email/sign-in") public ResponseEntity signInWithEmail( - @Valid @RequestBody EmailSignInRequest signInRequest + @Valid @RequestBody EmailSignInRequest signInRequest, + HttpServletResponse httpServletResponse ) { SignInResponse signInResponse = emailSignInService.signIn(signInRequest); + refreshTokenCookieManager.setCookie(httpServletResponse, signInResponse.refreshToken()); return ResponseEntity.ok(signInResponse); } @@ -94,20 +107,24 @@ public ResponseEntity signUp( @PostMapping("/sign-out") public ResponseEntity signOut( - Authentication authentication + Authentication authentication, + HttpServletResponse httpServletResponse ) { String accessToken = getAccessToken(authentication); authService.signOut(accessToken); + refreshTokenCookieManager.deleteCookie(httpServletResponse); return ResponseEntity.ok().build(); } @DeleteMapping("/quit") public ResponseEntity quit( + @AuthorizedUser long siteUserId, Authentication authentication, - @AuthorizedUser long siteUserId + HttpServletResponse httpServletResponse ) { String accessToken = getAccessToken(authentication); authService.quit(siteUserId, accessToken); + refreshTokenCookieManager.deleteCookie(httpServletResponse); return ResponseEntity.ok().build(); } diff --git a/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java b/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java new file mode 100644 index 000000000..81bc45461 --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java @@ -0,0 +1,42 @@ +package com.example.solidconnection.auth.controller; + +import com.example.solidconnection.auth.domain.TokenType; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseCookie; +import org.springframework.stereotype.Component; + +@Component +public class RefreshTokenCookieManager { + + private static final String COOKIE_NAME = "refreshToken"; + private static final String PATH = "/"; + private static final String SAME_SITE = "Strict"; + + public void setCookie(HttpServletResponse response, String refreshToken) { + long maxAge = convertExpireTimeToCookieMaxAge(TokenType.REFRESH.getExpireTime()); + setRefreshTokenCookie(response, refreshToken, maxAge); + } + + private long convertExpireTimeToCookieMaxAge(long milliSeconds) { + // jwt의 expireTime: millisecond, cookie의 maxAge: second + return milliSeconds / 1000; + } + + public void deleteCookie(HttpServletResponse response) { + setRefreshTokenCookie(response, "", 0); // 쿠키 삭제를 위해 maxAge를 0으로 설정 + } + + private void setRefreshTokenCookie( + HttpServletResponse response, String refreshToken, long maxAge + ) { + ResponseCookie cookie = ResponseCookie.from(COOKIE_NAME, refreshToken) + .httpOnly(true) + .secure(true) + .path(PATH) + .maxAge(maxAge) + .sameSite(SAME_SITE) + .build(); + response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); + } +} diff --git a/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java b/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java new file mode 100644 index 000000000..944be37ab --- /dev/null +++ b/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java @@ -0,0 +1,64 @@ +package com.example.solidconnection.auth.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.example.solidconnection.auth.domain.TokenType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletResponse; + +@DisplayName("리프레시 토큰 쿠키 매니저 테스트") +class RefreshTokenCookieManagerTest { + + private RefreshTokenCookieManager cookieManager; + + @BeforeEach + void setUp() { + cookieManager = new RefreshTokenCookieManager(); + } + + @Test + void 리프레시_토큰을_쿠키로_설정한다() { + // given + MockHttpServletResponse response = new MockHttpServletResponse(); + String refreshToken = "test-refresh-token"; + + // when + cookieManager.setCookie(response, refreshToken); + + // then + String header = response.getHeader("Set-Cookie"); + assertAll( + () -> assertThat(header).isNotNull(), + () -> assertThat(header).contains("refreshToken=" + refreshToken), + () -> assertThat(header).contains("HttpOnly"), + () -> assertThat(header).contains("Secure"), + () -> assertThat(header).contains("Path=/"), + () -> assertThat(header).contains("Max-Age=" + TokenType.REFRESH.getExpireTime() / 1000), + () -> assertThat(header).contains("SameSite=Strict") + ); + } + + @Test + void 쿠키에서_리프레시_토큰을_삭제한다() { + // given + MockHttpServletResponse response = new MockHttpServletResponse(); + + // when + cookieManager.deleteCookie(response); + + // then + String header = response.getHeader("Set-Cookie"); + assertAll( + () -> assertThat(header).isNotNull(), + () -> assertThat(header).contains("refreshToken="), + () -> assertThat(header).contains("HttpOnly"), + () -> assertThat(header).contains("Secure"), + () -> assertThat(header).contains("Path=/"), + () -> assertThat(header).contains("Max-Age=0"), + () -> assertThat(header).contains("SameSite=Strict") + ); + } +} From fd60791fc087cb5a76ab19604c5a3e6bd0f179d8 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Sat, 2 Aug 2025 23:16:52 +0900 Subject: [PATCH 61/90] =?UTF-8?q?refactor:=20=EB=B3=80=EA=B2=BD=EB=90=9C?= =?UTF-8?q?=20=EB=A9=98=ED=86=A0/=EB=A9=98=ED=8B=B0=20=ED=83=AD=20?= =?UTF-8?q?=EB=AA=85=EC=84=B8=EC=97=90=20=EB=A7=9E=EA=B2=8C=20api=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#422)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 변경된 api 명세에 맞게 컨트롤러 수정 * feat: 멘토링 목록 조회 페이지네이션 적용 - 추가로 N+1 해결 * test: 페이지네이션으로 바뀐 것 테스트코드에 적용 - 추가로, 멘토링에 mentor_id와 mentee_id가 unique 해야 할 것을 염두에 두고, 테스트 코드 수정 - 테스트 단위가 작아지도록 수정 * refactor: 멘토링 체크 로직 서비스 분리 * refactor: 멘토링 테이블에 checked_at_by_mentee 컬럼 추가 * feat: 멘토,멘티의 멘토링 확인 기능 구현 * test: 멘토링 목록 조회 시, 확인여부 포함 검증 추가 * refactor: 멘토가 승인하면, 멘티의 멘토링 확인상태 초기화 * refactor: 멘티가 거절된 멘토링 조회하지 못하도록 --- .../MentoringForMenteeController.java | 76 +++++++ ...java => MentoringForMentorController.java} | 56 ++--- .../mentor/domain/Mentoring.java | 20 +- .../mentor/dto/CheckMentoringRequest.java | 9 + .../mentor/dto/CheckedMentoringsResponse.java | 15 ++ ...e.java => MentoringForMenteeResponse.java} | 12 +- .../dto/MentoringForMentorResponse.java | 24 +++ .../mentor/dto/MentoringListResponse.java | 12 -- .../repository/MentoringRepository.java | 13 +- .../mentor/service/MentoringCheckService.java | 74 +++++++ .../service/MentoringCommandService.java | 16 -- .../mentor/service/MentoringQueryService.java | 75 +++++-- .../V27__add_checked_at_by_mentee_column.sql | 5 + .../mentor/fixture/MentoringFixture.java | 6 +- .../fixture/MentoringFixtureBuilder.java | 15 +- .../service/MentoringCheckServiceTest.java | 171 +++++++++++++++ .../service/MentoringCommandServiceTest.java | 46 +--- .../service/MentoringQueryServiceTest.java | 198 ++++++++++++++---- 18 files changed, 669 insertions(+), 174 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/mentor/controller/MentoringForMenteeController.java rename src/main/java/com/example/solidconnection/mentor/controller/{MentoringController.java => MentoringForMentorController.java} (58%) create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/CheckMentoringRequest.java create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/CheckedMentoringsResponse.java rename src/main/java/com/example/solidconnection/mentor/dto/{MentoringResponse.java => MentoringForMenteeResponse.java} (58%) create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentoringForMentorResponse.java delete mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentoringListResponse.java create mode 100644 src/main/java/com/example/solidconnection/mentor/service/MentoringCheckService.java create mode 100644 src/main/resources/db/migration/V27__add_checked_at_by_mentee_column.sql create mode 100644 src/test/java/com/example/solidconnection/mentor/service/MentoringCheckServiceTest.java diff --git a/src/main/java/com/example/solidconnection/mentor/controller/MentoringForMenteeController.java b/src/main/java/com/example/solidconnection/mentor/controller/MentoringForMenteeController.java new file mode 100644 index 000000000..28683cd37 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/controller/MentoringForMenteeController.java @@ -0,0 +1,76 @@ +package com.example.solidconnection.mentor.controller; + +import com.example.solidconnection.common.VerifyStatus; +import com.example.solidconnection.common.dto.SliceResponse; +import com.example.solidconnection.common.resolver.AuthorizedUser; +import com.example.solidconnection.mentor.dto.CheckMentoringRequest; +import com.example.solidconnection.mentor.dto.CheckedMentoringsResponse; +import com.example.solidconnection.mentor.dto.MentoringApplyRequest; +import com.example.solidconnection.mentor.dto.MentoringApplyResponse; +import com.example.solidconnection.mentor.dto.MentoringForMenteeResponse; +import com.example.solidconnection.mentor.service.MentoringCheckService; +import com.example.solidconnection.mentor.service.MentoringCommandService; +import com.example.solidconnection.mentor.service.MentoringQueryService; +import com.example.solidconnection.security.annotation.RequireRoleAccess; +import com.example.solidconnection.siteuser.domain.Role; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.data.web.SortDefault; +import org.springframework.data.web.SortDefault.SortDefaults; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/mentee/mentorings") +public class MentoringForMenteeController { + + private final MentoringCommandService mentoringCommandService; + private final MentoringQueryService mentoringQueryService; + private final MentoringCheckService mentoringCheckService; + + @RequireRoleAccess(roles = Role.MENTEE) + @PostMapping + public ResponseEntity applyMentoring( + @AuthorizedUser long siteUserId, + @Valid @RequestBody MentoringApplyRequest mentoringApplyRequest + ) { + MentoringApplyResponse response = mentoringCommandService.applyMentoring(siteUserId, mentoringApplyRequest); + return ResponseEntity.ok(response); + } + + @RequireRoleAccess(roles = Role.MENTEE) + @GetMapping + public ResponseEntity> getMentorings( + @AuthorizedUser long siteUserId, + @RequestParam("verify-status") VerifyStatus verifyStatus, + @PageableDefault(size = 3) + @SortDefaults({ + @SortDefault(sort = "createdAt", direction = Sort.Direction.DESC), + @SortDefault(sort = "id", direction = Sort.Direction.DESC) + }) + Pageable pageable + ) { + SliceResponse response = mentoringQueryService.getMentoringsForMentee(siteUserId, verifyStatus, pageable); + return ResponseEntity.ok(response); + } + + @RequireRoleAccess(roles = {Role.MENTEE}) + @PatchMapping("/check") + public ResponseEntity checkMentorings( + @AuthorizedUser long siteUserId, + @Valid @RequestBody CheckMentoringRequest checkMentoringRequest + ) { + CheckedMentoringsResponse response = mentoringCheckService.checkMentoringsForMentee(siteUserId, checkMentoringRequest); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/controller/MentoringController.java b/src/main/java/com/example/solidconnection/mentor/controller/MentoringForMentorController.java similarity index 58% rename from src/main/java/com/example/solidconnection/mentor/controller/MentoringController.java rename to src/main/java/com/example/solidconnection/mentor/controller/MentoringForMentorController.java index 5a347f3fb..b9fb2f63e 100644 --- a/src/main/java/com/example/solidconnection/mentor/controller/MentoringController.java +++ b/src/main/java/com/example/solidconnection/mentor/controller/MentoringForMentorController.java @@ -1,57 +1,59 @@ package com.example.solidconnection.mentor.controller; +import com.example.solidconnection.common.dto.SliceResponse; import com.example.solidconnection.common.resolver.AuthorizedUser; -import com.example.solidconnection.mentor.dto.MentoringApplyRequest; -import com.example.solidconnection.mentor.dto.MentoringApplyResponse; -import com.example.solidconnection.mentor.dto.MentoringCheckResponse; +import com.example.solidconnection.mentor.dto.CheckMentoringRequest; +import com.example.solidconnection.mentor.dto.CheckedMentoringsResponse; import com.example.solidconnection.mentor.dto.MentoringConfirmRequest; import com.example.solidconnection.mentor.dto.MentoringConfirmResponse; import com.example.solidconnection.mentor.dto.MentoringCountResponse; -import com.example.solidconnection.mentor.dto.MentoringListResponse; +import com.example.solidconnection.mentor.dto.MentoringForMentorResponse; +import com.example.solidconnection.mentor.service.MentoringCheckService; import com.example.solidconnection.mentor.service.MentoringCommandService; import com.example.solidconnection.mentor.service.MentoringQueryService; import com.example.solidconnection.security.annotation.RequireRoleAccess; import com.example.solidconnection.siteuser.domain.Role; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.data.web.SortDefault; +import org.springframework.data.web.SortDefault.SortDefaults; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; 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.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor -@RequestMapping("/mentorings") -public class MentoringController { +@RequestMapping("/mentor/mentorings") +public class MentoringForMentorController { private final MentoringCommandService mentoringCommandService; private final MentoringQueryService mentoringQueryService; + private final MentoringCheckService mentoringCheckService; - @RequireRoleAccess(roles = Role.MENTEE) - @PostMapping("/apply") - public ResponseEntity applyMentoring( + @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) + @GetMapping + public ResponseEntity> getMentorings( @AuthorizedUser long siteUserId, - @Valid @RequestBody MentoringApplyRequest mentoringApplyRequest + @PageableDefault(size = 3) + @SortDefaults({ + @SortDefault(sort = "createdAt", direction = Sort.Direction.DESC), + @SortDefault(sort = "id", direction = Sort.Direction.DESC) + }) + Pageable pageable ) { - MentoringApplyResponse response = mentoringCommandService.applyMentoring(siteUserId, mentoringApplyRequest); + SliceResponse response = mentoringQueryService.getMentoringsForMentor(siteUserId, pageable); return ResponseEntity.ok(response); } @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) - @GetMapping("/apply") - public ResponseEntity getMentorings( - @AuthorizedUser long siteUserId - ) { - MentoringListResponse responses = mentoringQueryService.getMentorings(siteUserId); - return ResponseEntity.ok(responses); - } - - @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) - @PatchMapping("/{mentoring-id}/apply") + @PatchMapping("/{mentoring-id}") public ResponseEntity confirmMentoring( @AuthorizedUser long siteUserId, @PathVariable("mentoring-id") Long mentoringId, @@ -62,12 +64,12 @@ public ResponseEntity confirmMentoring( } @RequireRoleAccess(roles = {Role.ADMIN, Role.MENTOR}) - @PatchMapping("/{mentoring-id}/check") - public ResponseEntity checkMentoring( + @PatchMapping("/check") + public ResponseEntity checkMentoring( @AuthorizedUser long siteUserId, - @PathVariable("mentoring-id") Long mentoringId + @RequestBody CheckMentoringRequest mentoringCheckRequest ) { - MentoringCheckResponse response = mentoringCommandService.checkMentoring(siteUserId, mentoringId); + CheckedMentoringsResponse response = mentoringCheckService.checkMentoringsForMentor(siteUserId, mentoringCheckRequest); return ResponseEntity.ok(response); } @@ -76,7 +78,7 @@ public ResponseEntity checkMentoring( public ResponseEntity getUncheckedMentoringsCount( @AuthorizedUser long siteUserId ) { - MentoringCountResponse response = mentoringQueryService.getNewMentoringsCount(siteUserId); + MentoringCountResponse response = mentoringCheckService.getUncheckedMentoringCount(siteUserId); return ResponseEntity.ok(response); } } diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java b/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java index 47226076d..ead13132b 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Mentoring.java @@ -40,7 +40,10 @@ public class Mentoring { private ZonedDateTime confirmedAt; @Column - private ZonedDateTime checkedAt; + private ZonedDateTime checkedAtByMentor; + + @Column + private ZonedDateTime checkedAtByMentee; @Column(nullable = false) @Enumerated(EnumType.STRING) @@ -67,12 +70,19 @@ public void confirm(VerifyStatus status) { this.verifyStatus = status; this.confirmedAt = ZonedDateTime.now(UTC).truncatedTo(MICROS); - if (this.checkedAt == null) { - this.checkedAt = this.confirmedAt; + if (this.checkedAtByMentor == null) { + this.checkedAtByMentor = this.confirmedAt; + } + if (this.checkedAtByMentee != null) { + this.checkedAtByMentee = null; } } - public void check() { - this.checkedAt = ZonedDateTime.now(UTC).truncatedTo(MICROS); + public void checkByMentor() { + this.checkedAtByMentor = ZonedDateTime.now(UTC).truncatedTo(MICROS); + } + + public void checkByMentee() { + this.checkedAtByMentee = ZonedDateTime.now(UTC).truncatedTo(MICROS); } } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/CheckMentoringRequest.java b/src/main/java/com/example/solidconnection/mentor/dto/CheckMentoringRequest.java new file mode 100644 index 000000000..ebd3bacf1 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/CheckMentoringRequest.java @@ -0,0 +1,9 @@ +package com.example.solidconnection.mentor.dto; + +import java.util.List; + +public record CheckMentoringRequest( + List checkedMentoringIds +) { + +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/CheckedMentoringsResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/CheckedMentoringsResponse.java new file mode 100644 index 000000000..2b286543a --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/CheckedMentoringsResponse.java @@ -0,0 +1,15 @@ +package com.example.solidconnection.mentor.dto; + +import com.example.solidconnection.mentor.domain.Mentoring; +import java.util.List; + +public record CheckedMentoringsResponse( + List checkedMentoringIds +) { + + public static CheckedMentoringsResponse from(List mentorings) { + return new CheckedMentoringsResponse( + mentorings.stream().map(Mentoring::getId).toList() + ); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMenteeResponse.java similarity index 58% rename from src/main/java/com/example/solidconnection/mentor/dto/MentoringResponse.java rename to src/main/java/com/example/solidconnection/mentor/dto/MentoringForMenteeResponse.java index 6b96295ad..7f2591555 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentoringResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMenteeResponse.java @@ -4,7 +4,7 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import java.time.ZonedDateTime; -public record MentoringResponse( +public record MentoringForMenteeResponse( long mentoringId, String profileImageUrl, String nickname, @@ -12,12 +12,12 @@ public record MentoringResponse( ZonedDateTime createdAt ) { - public static MentoringResponse from(Mentoring mentoring, SiteUser mentee) { - return new MentoringResponse( + public static MentoringForMenteeResponse of(Mentoring mentoring, SiteUser partner) { + return new MentoringForMenteeResponse( mentoring.getId(), - mentee.getProfileImageUrl(), - mentee.getNickname(), - mentoring.getCheckedAt() != null, + partner.getProfileImageUrl(), + partner.getNickname(), + mentoring.getCheckedAtByMentee() != null, mentoring.getCreatedAt() ); } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMentorResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMentorResponse.java new file mode 100644 index 000000000..9ee1889f6 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMentorResponse.java @@ -0,0 +1,24 @@ +package com.example.solidconnection.mentor.dto; + +import com.example.solidconnection.mentor.domain.Mentoring; +import com.example.solidconnection.siteuser.domain.SiteUser; +import java.time.ZonedDateTime; + +public record MentoringForMentorResponse( + long mentoringId, + String profileImageUrl, + String nickname, + boolean isChecked, + ZonedDateTime createdAt +) { + + public static MentoringForMentorResponse of(Mentoring mentoring, SiteUser partner) { + return new MentoringForMentorResponse( + mentoring.getId(), + partner.getProfileImageUrl(), + partner.getNickname(), + mentoring.getCheckedAtByMentor() != null, + mentoring.getCreatedAt() + ); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringListResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringListResponse.java deleted file mode 100644 index cca80e06d..000000000 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentoringListResponse.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.solidconnection.mentor.dto; - -import java.util.List; - -public record MentoringListResponse( - List requests -) { - - public static MentoringListResponse from(List requests) { - return new MentoringListResponse(requests); - } -} diff --git a/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java b/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java index 8f7cfade3..16caf3318 100644 --- a/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java +++ b/src/main/java/com/example/solidconnection/mentor/repository/MentoringRepository.java @@ -1,18 +1,27 @@ package com.example.solidconnection.mentor.repository; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.mentor.domain.Mentoring; import java.util.List; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; public interface MentoringRepository extends JpaRepository { - int countByMentorIdAndCheckedAtIsNull(long mentorId); + int countByMentorIdAndCheckedAtByMentorIsNull(long mentorId); boolean existsByMentorIdAndMenteeId(long mentorId, long menteeId); - List findAllByMentorId(long mentorId); + Slice findAllByMentorId(long mentorId, Pageable pageable); + + @Query(""" + SELECT m FROM Mentoring m + WHERE m.menteeId = :menteeId AND m.verifyStatus = :verifyStatus + """) + Slice findAllByMenteeIdAndVerifyStatus(@Param("menteeId") long menteeId, @Param("verifyStatus") VerifyStatus verifyStatus, Pageable pageable); @Query(""" SELECT m FROM Mentoring m diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentoringCheckService.java b/src/main/java/com/example/solidconnection/mentor/service/MentoringCheckService.java new file mode 100644 index 000000000..5c73ad00b --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/service/MentoringCheckService.java @@ -0,0 +1,74 @@ +package com.example.solidconnection.mentor.service; + +import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.UNAUTHORIZED_MENTORING; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.domain.Mentoring; +import com.example.solidconnection.mentor.dto.CheckMentoringRequest; +import com.example.solidconnection.mentor.dto.CheckedMentoringsResponse; +import com.example.solidconnection.mentor.dto.MentoringCountResponse; +import com.example.solidconnection.mentor.repository.MentorRepository; +import com.example.solidconnection.mentor.repository.MentoringRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class MentoringCheckService { + + private final MentorRepository mentorRepository; + private final MentoringRepository mentoringRepository; + + @Transactional + public CheckedMentoringsResponse checkMentoringsForMentor(long mentorUserId, CheckMentoringRequest checkMentoringRequest) { + Mentor mentor = mentorRepository.findBySiteUserId(mentorUserId) + .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); + List mentorings = mentoringRepository.findAllById(checkMentoringRequest.checkedMentoringIds()); + List actualMentorIds = mentorings.stream() + .map(Mentoring::getMentorId) + .distinct() + .toList(); + + mentorings.forEach(Mentoring::checkByMentor); + validateMentoringsOwnership(actualMentorIds, mentor.getId()); + + return CheckedMentoringsResponse.from(mentorings); + } + + @Transactional + public CheckedMentoringsResponse checkMentoringsForMentee(long menteeUserId, CheckMentoringRequest checkMentoringRequest) { + List mentorings = mentoringRepository.findAllById(checkMentoringRequest.checkedMentoringIds()); + List actualMenteeIds = mentorings.stream() + .map(Mentoring::getMenteeId) + .distinct() + .toList(); + + validateMentoringsOwnership(actualMenteeIds, menteeUserId); + mentorings.forEach(Mentoring::checkByMentee); + + return CheckedMentoringsResponse.from(mentorings); + } + + private void validateMentoringsOwnership(List actualOwnerIds, long expectedOwnerId) { + actualOwnerIds.stream() + .filter(actualOwnerId -> actualOwnerId != expectedOwnerId) + .findFirst() + .ifPresent(ownerId -> { + throw new CustomException(UNAUTHORIZED_MENTORING); + }); + } + + @Transactional(readOnly = true) + public MentoringCountResponse getUncheckedMentoringCount(long siteUserId) { + Mentor mentor = mentorRepository.findBySiteUserId(siteUserId) + .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); + + int count = mentoringRepository.countByMentorIdAndCheckedAtByMentorIsNull(mentor.getId()); + + return MentoringCountResponse.from(count); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java b/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java index ca35f6c95..dd4e98936 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java @@ -11,7 +11,6 @@ import com.example.solidconnection.mentor.domain.Mentoring; import com.example.solidconnection.mentor.dto.MentoringApplyRequest; import com.example.solidconnection.mentor.dto.MentoringApplyResponse; -import com.example.solidconnection.mentor.dto.MentoringCheckResponse; import com.example.solidconnection.mentor.dto.MentoringConfirmRequest; import com.example.solidconnection.mentor.dto.MentoringConfirmResponse; import com.example.solidconnection.mentor.repository.MentorRepository; @@ -60,21 +59,6 @@ private void validateMentoringNotConfirmed(Mentoring mentoring) { } } - @Transactional - public MentoringCheckResponse checkMentoring(long siteUserId, long mentoringId) { - Mentoring mentoring = mentoringRepository.findById(mentoringId) - .orElseThrow(() -> new CustomException(MENTORING_NOT_FOUND)); - - Mentor mentor = mentorRepository.findBySiteUserId(siteUserId) - .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); - - validateMentoringOwnership(mentor, mentoring); - - mentoring.check(); - - return MentoringCheckResponse.from(mentoring.getId()); - } - // 멘토는 본인의 멘토링에 대해 confirm 및 check해야 한다. private void validateMentoringOwnership(Mentor mentor, Mentoring mentoring) { if (mentoring.getMentorId() != mentor.getId()) { diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java index 2ffa36602..bb9deafe0 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java @@ -2,19 +2,27 @@ import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; +import com.example.solidconnection.common.VerifyStatus; +import com.example.solidconnection.common.dto.SliceResponse; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.mentor.domain.Mentoring; -import com.example.solidconnection.mentor.dto.MentoringCountResponse; -import com.example.solidconnection.mentor.dto.MentoringListResponse; -import com.example.solidconnection.mentor.dto.MentoringResponse; +import com.example.solidconnection.mentor.dto.MentoringForMenteeResponse; +import com.example.solidconnection.mentor.dto.MentoringForMentorResponse; import com.example.solidconnection.mentor.repository.MentorRepository; import com.example.solidconnection.mentor.repository.MentoringRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -27,30 +35,61 @@ public class MentoringQueryService { private final SiteUserRepository siteUserRepository; @Transactional(readOnly = true) - public MentoringListResponse getMentorings(long siteUserId) { - Mentor mentor = mentorRepository.findBySiteUserId(siteUserId) - .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); + public SliceResponse getMentoringsForMentee( + long siteUserId, VerifyStatus verifyStatus, Pageable pageable + ) { + if (verifyStatus == VerifyStatus.REJECTED) { + throw new CustomException(ErrorCode.UNAUTHORIZED_MENTORING, "거절된 멘토링은 조회할 수 없습니다."); + } + Slice mentoringSlice = mentoringRepository.findAllByMenteeIdAndVerifyStatus(siteUserId, verifyStatus, pageable); + List mentorings = mentoringSlice.toList(); - List mentorings = mentoringRepository.findAllByMentorId(mentor.getId()); - List mentoringResponses = mentorings.stream() - .map(mentoring -> { - SiteUser mentee = siteUserRepository.findById(mentoring.getMenteeId()) - .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); + Map mentoringToPartnerUser = mapMentoringToPartnerUserWithBatchQuery( + mentorings, Mentoring::getMentorId + ); - return MentoringResponse.from(mentoring, mentee); - }) - .toList(); + List content = new ArrayList<>(); + for (Entry entry : mentoringToPartnerUser.entrySet()) { + content.add(MentoringForMenteeResponse.of(entry.getKey(), entry.getValue())); + } - return MentoringListResponse.from(mentoringResponses); + return SliceResponse.of(content, mentoringSlice); } @Transactional(readOnly = true) - public MentoringCountResponse getNewMentoringsCount(long siteUserId) { + public SliceResponse getMentoringsForMentor(long siteUserId, Pageable pageable) { Mentor mentor = mentorRepository.findBySiteUserId(siteUserId) .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); + Slice mentoringSlice = mentoringRepository.findAllByMentorId(mentor.getId(), pageable); + + Map mentoringToPartnerUser = mapMentoringToPartnerUserWithBatchQuery( + mentoringSlice.toList(), + Mentoring::getMenteeId + ); - int count = mentoringRepository.countByMentorIdAndCheckedAtIsNull(mentor.getId()); + List content = new ArrayList<>(); + for (Entry entry : mentoringToPartnerUser.entrySet()) { + content.add(MentoringForMentorResponse.of(entry.getKey(), entry.getValue())); + } + + return SliceResponse.of(content, mentoringSlice); + } + + // N+1 을 해결하면서 멘토링 상대방의 정보를 조회 + private Map mapMentoringToPartnerUserWithBatchQuery( + List mentorings, Function getPartnerId + ) { + List partnerUserId = mentorings.stream() + .map(getPartnerId) + .distinct() + .toList(); + List partnerUsers = siteUserRepository.findAllById(partnerUserId); + Map partnerIdToPartnerUsermap = partnerUsers.stream() + .collect(Collectors.toMap(SiteUser::getId, Function.identity())); - return MentoringCountResponse.from(count); + return mentorings.stream().collect(Collectors.toMap( + Function.identity(), + mentoring -> partnerIdToPartnerUsermap.get(getPartnerId.apply(mentoring)) + )); } } diff --git a/src/main/resources/db/migration/V27__add_checked_at_by_mentee_column.sql b/src/main/resources/db/migration/V27__add_checked_at_by_mentee_column.sql new file mode 100644 index 000000000..41e1ad637 --- /dev/null +++ b/src/main/resources/db/migration/V27__add_checked_at_by_mentee_column.sql @@ -0,0 +1,5 @@ +ALTER TABLE mentoring + RENAME COLUMN checked_at TO checked_at_by_mentor; + +ALTER TABLE mentoring + ADD COLUMN checked_at_by_mentee DATETIME(6) NULL; diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java index 07925628e..4779c643e 100644 --- a/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java +++ b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixture.java @@ -29,7 +29,7 @@ public class MentoringFixture { .menteeId(menteeId) .verifyStatus(VerifyStatus.APPROVED) .confirmedAt(now) - .checkedAt(now) + .checkedAtByMentor(now) .create(); } @@ -40,7 +40,7 @@ public class MentoringFixture { .menteeId(menteeId) .verifyStatus(VerifyStatus.REJECTED) .confirmedAt(now) - .checkedAt(now) + .checkedAtByMentor(now) .create(); } @@ -48,7 +48,7 @@ public class MentoringFixture { return mentoringFixtureBuilder.mentoring() .mentorId(mentorId) .menteeId(menteeId) - .checkedAt(null) + .checkedAtByMentor(null) .create(); } diff --git a/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java index 857c18ea8..f4e905b3a 100644 --- a/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/mentor/fixture/MentoringFixtureBuilder.java @@ -15,7 +15,8 @@ public class MentoringFixtureBuilder { private ZonedDateTime createdAt; private ZonedDateTime confirmedAt; - private ZonedDateTime checkedAt; + private ZonedDateTime checkedAtByMentor; + private ZonedDateTime checkedAtByMentee; private VerifyStatus verifyStatus = VerifyStatus.PENDING; private long mentorId; private long menteeId; @@ -34,8 +35,13 @@ public MentoringFixtureBuilder confirmedAt(ZonedDateTime confirmedAt) { return this; } - public MentoringFixtureBuilder checkedAt(ZonedDateTime checkedAt) { - this.checkedAt = checkedAt; + public MentoringFixtureBuilder checkedAtByMentor(ZonedDateTime checkedAtByMentor) { + this.checkedAtByMentor = checkedAtByMentor; + return this; + } + + public MentoringFixtureBuilder checkedAtByMentee(ZonedDateTime checkedAtByMentor) { + this.checkedAtByMentor = checkedAtByMentor; return this; } @@ -59,7 +65,8 @@ public Mentoring create() { null, createdAt, confirmedAt, - checkedAt, + checkedAtByMentor, + checkedAtByMentee, verifyStatus, mentorId, menteeId diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentoringCheckServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentoringCheckServiceTest.java new file mode 100644 index 000000000..c5f8a9463 --- /dev/null +++ b/src/test/java/com/example/solidconnection/mentor/service/MentoringCheckServiceTest.java @@ -0,0 +1,171 @@ +package com.example.solidconnection.mentor.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.domain.Mentoring; +import com.example.solidconnection.mentor.dto.CheckMentoringRequest; +import com.example.solidconnection.mentor.dto.CheckedMentoringsResponse; +import com.example.solidconnection.mentor.dto.MentoringCountResponse; +import com.example.solidconnection.mentor.fixture.MentorFixture; +import com.example.solidconnection.mentor.fixture.MentoringFixture; +import com.example.solidconnection.mentor.repository.MentoringRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@TestContainerSpringBootTest +@DisplayName("멘토링 확인 서비스 테스트") +class MentoringCheckServiceTest { + + @Autowired + private MentoringRepository mentoringRepository; + + @Autowired + private MentoringCheckService mentoringCheckService; + + @Autowired + private MentoringFixture mentoringFixture; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private MentorFixture mentorFixture; + + + private SiteUser mentorUser1, mentorUser2; + private SiteUser menteeUser1, menteeUser2, menteeUser3; + private Mentor mentor1, mentor2, mentor3; + + @BeforeEach + void setUp() { + mentorUser1 = siteUserFixture.멘토(1, "mentor1"); + mentorUser2 = siteUserFixture.멘토(2, "mentor2"); + SiteUser mentorUser3 = siteUserFixture.멘토(3, "mentor3"); + menteeUser1 = siteUserFixture.사용자(1, "mentee1"); + menteeUser2 = siteUserFixture.사용자(2, "mentee2"); + menteeUser3 = siteUserFixture.사용자(3, "mentee3"); + mentor1 = mentorFixture.멘토(mentorUser1.getId(), 1L); + mentor2 = mentorFixture.멘토(mentorUser2.getId(), 1L); + mentor3 = mentorFixture.멘토(mentorUser3.getId(), 1L); + } + + @Nested + class 멘토의_멘토링_확인_테스트 { + + @Test + void 멘토가_멘토링을_확인한다() { + // given + Mentoring mentoring1 = mentoringFixture.확인되지_않은_멘토링(mentor1.getId(), menteeUser1.getId()); + Mentoring mentoring2 = mentoringFixture.확인되지_않은_멘토링(mentor1.getId(), menteeUser2.getId()); + Mentoring mentoring3 = mentoringFixture.확인되지_않은_멘토링(mentor1.getId(), menteeUser3.getId()); + CheckMentoringRequest request = new CheckMentoringRequest(List.of(mentoring1.getId(), mentoring2.getId())); + + // when + CheckedMentoringsResponse response = mentoringCheckService.checkMentoringsForMentor(mentorUser1.getId(), request); + + // then + assertAll( + () -> assertThat(response.checkedMentoringIds()).containsExactlyInAnyOrder(mentoring1.getId(), mentoring2.getId()), + () -> assertThat(mentoringRepository.findById(mentoring1.getId())) + .hasValueSatisfying(mentoring -> assertThat(mentoring.getCheckedAtByMentor()).isNotNull()), + () -> assertThat(mentoringRepository.findById(mentoring2.getId())) + .hasValueSatisfying(mentoring -> assertThat(mentoring.getCheckedAtByMentor()).isNotNull()), + () -> assertThat(mentoringRepository.findById(mentoring3.getId())) + .hasValueSatisfying(mentoring -> assertThat(mentoring.getCheckedAtByMentor()).isNull()) + ); + } + + @Test + void 다른_멘토의_멘토링은_확인하면_예외가_발생한다() { + // given + Mentoring mentoring2 = mentoringFixture.확인되지_않은_멘토링(mentor2.getId(), menteeUser2.getId()); + CheckMentoringRequest request = new CheckMentoringRequest(List.of(mentoring2.getId())); + + // when, then + assertThatCode(() -> mentoringCheckService.checkMentoringsForMentor(mentorUser1.getId(), request)) + .isInstanceOf(CustomException.class) + .hasMessageContaining(ErrorCode.UNAUTHORIZED_MENTORING.getMessage()); + } + } + + @Nested + class 멘티의_멘토링_확인_테스트 { + + @Test + void 멘티가_멘토링을_확인한다() { + // given + Mentoring mentoring1 = mentoringFixture.확인되지_않은_멘토링(mentor1.getId(), menteeUser1.getId()); + Mentoring mentoring2 = mentoringFixture.확인되지_않은_멘토링(mentor2.getId(), menteeUser1.getId()); + Mentoring mentoring3 = mentoringFixture.확인되지_않은_멘토링(mentor3.getId(), menteeUser1.getId()); + CheckMentoringRequest request = new CheckMentoringRequest(List.of(mentoring1.getId(), mentoring2.getId())); + + // when + CheckedMentoringsResponse response = mentoringCheckService.checkMentoringsForMentee(menteeUser1.getId(), request); + + // then + assertAll( + () -> assertThat(response.checkedMentoringIds()).containsExactlyInAnyOrder(mentoring1.getId(), mentoring2.getId()), + () -> assertThat(mentoringRepository.findById(mentoring1.getId())) + .hasValueSatisfying(mentoring -> assertThat(mentoring.getCheckedAtByMentee()).isNotNull()), + () -> assertThat(mentoringRepository.findById(mentoring2.getId())) + .hasValueSatisfying(mentoring -> assertThat(mentoring.getCheckedAtByMentee()).isNotNull()), + () -> assertThat(mentoringRepository.findById(mentoring3.getId())) + .hasValueSatisfying(mentoring -> assertThat(mentoring.getCheckedAtByMentee()).isNull()) + ); + } + + @Test + void 다른_멘티의_멘토링을_확인하면_예외가_발생한다() { + // given + Mentoring mentoring2 = mentoringFixture.확인되지_않은_멘토링(mentor2.getId(), menteeUser2.getId()); + CheckMentoringRequest request = new CheckMentoringRequest(List.of(mentoring2.getId())); + + // when, then + assertThatCode(() -> mentoringCheckService.checkMentoringsForMentee(menteeUser1.getId(), request)) + .isInstanceOf(CustomException.class) + .hasMessageContaining(ErrorCode.UNAUTHORIZED_MENTORING.getMessage()); + } + + @Nested + class 멘토가_확인하지_않은_멘토링_개수_조회_테스트 { + + @Test + void 멘토가_확인하지_않은_멘토링_개수를_반환한다() { + // given + mentoringFixture.확인되지_않은_멘토링(mentor1.getId(), menteeUser1.getId()); + mentoringFixture.확인되지_않은_멘토링(mentor1.getId(), menteeUser2.getId()); + mentoringFixture.승인된_멘토링(mentor1.getId(), menteeUser3.getId()); + + // when + MentoringCountResponse response = mentoringCheckService.getUncheckedMentoringCount(mentorUser1.getId()); + + // then + assertThat(response.uncheckedCount()).isEqualTo(2); + } + + @Test + void 멘토가_확인하지_않은_멘토링이_없으면_0을_반환한다() { + // given + mentoringFixture.승인된_멘토링(mentor1.getId(), menteeUser1.getId()); + + // when + MentoringCountResponse response = mentoringCheckService.getUncheckedMentoringCount(mentorUser1.getId()); + + // then + assertThat(response.uncheckedCount()).isZero(); + } + } + } +} diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java index a7cea53bb..a92925653 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java @@ -13,7 +13,6 @@ import com.example.solidconnection.mentor.domain.Mentoring; import com.example.solidconnection.mentor.dto.MentoringApplyRequest; import com.example.solidconnection.mentor.dto.MentoringApplyResponse; -import com.example.solidconnection.mentor.dto.MentoringCheckResponse; import com.example.solidconnection.mentor.dto.MentoringConfirmRequest; import com.example.solidconnection.mentor.dto.MentoringConfirmResponse; import com.example.solidconnection.mentor.fixture.MentorFixture; @@ -110,7 +109,8 @@ class 멘토링_승인_거절_테스트 { assertAll( () -> assertThat(confirmedMentoring.getVerifyStatus()).isEqualTo(VerifyStatus.APPROVED), () -> assertThat(confirmedMentoring.getConfirmedAt()).isNotNull(), - () -> assertThat(confirmedMentoring.getCheckedAt()).isNotNull(), + () -> assertThat(confirmedMentoring.getCheckedAtByMentor()).isNotNull(), + () -> assertThat(confirmedMentoring.getCheckedAtByMentee()).isNull(), () -> assertThat(mentor.getMenteeCount()).isEqualTo(beforeMenteeCount + 1) ); } @@ -132,7 +132,7 @@ class 멘토링_승인_거절_테스트 { assertAll( () -> assertThat(confirmedMentoring.getVerifyStatus()).isEqualTo(VerifyStatus.REJECTED), () -> assertThat(confirmedMentoring.getConfirmedAt()).isNotNull(), - () -> assertThat(confirmedMentoring.getCheckedAt()).isNotNull(), + () -> assertThat(confirmedMentoring.getCheckedAtByMentor()).isNotNull(), () -> assertThat(mentor.getMenteeCount()).isEqualTo(beforeMenteeCount) ); } @@ -173,44 +173,4 @@ class 멘토링_승인_거절_테스트 { .hasMessage(MENTORING_NOT_FOUND.getMessage()); } } - - @Nested - class 멘토링_확인_테스트 { - - @Test - void 멘토링을_성공적으로_확인_처리한다() { - // given - Mentoring mentoring = mentoringFixture.확인되지_않은_멘토링(mentor1.getId(), menteeUser.getId()); - - // when - MentoringCheckResponse response = mentoringCommandService.checkMentoring(mentorUser1.getId(), mentoring.getId()); - - // then - Mentoring checked = mentoringRepository.findById(response.mentoringId()).orElseThrow(); - - assertThat(checked.getCheckedAt()).isNotNull(); - } - - @Test - void 다른_멘토의_멘토링은_확인할_수_없다() { - // given - Mentoring mentoring = mentoringFixture.확인되지_않은_멘토링(mentor1.getId(), menteeUser.getId()); - - // when & then - assertThatThrownBy(() -> mentoringCommandService.checkMentoring(mentorUser2.getId(), mentoring.getId())) - .isInstanceOf(CustomException.class) - .hasMessage(UNAUTHORIZED_MENTORING.getMessage()); - } - - @Test - void 존재하지_않는_멘토링_아이디로_요청시_예외가_발생한다() { - // given - long invalidMentoringId = 9999L; - - // when & then - assertThatThrownBy(() -> mentoringCommandService.checkMentoring(mentorUser1.getId(), invalidMentoringId)) - .isInstanceOf(CustomException.class) - .hasMessage(MENTORING_NOT_FOUND.getMessage()); - } - } } diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java index 6e61a4a7a..d8146dc51 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java @@ -1,15 +1,20 @@ package com.example.solidconnection.mentor.service; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.AssertionsForClassTypes.tuple; +import com.example.solidconnection.common.VerifyStatus; +import com.example.solidconnection.common.dto.SliceResponse; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.mentor.domain.Mentoring; -import com.example.solidconnection.mentor.dto.MentoringCountResponse; -import com.example.solidconnection.mentor.dto.MentoringListResponse; -import com.example.solidconnection.mentor.dto.MentoringResponse; +import com.example.solidconnection.mentor.dto.MentoringForMenteeResponse; +import com.example.solidconnection.mentor.dto.MentoringForMentorResponse; import com.example.solidconnection.mentor.fixture.MentorFixture; import com.example.solidconnection.mentor.fixture.MentoringFixture; +import com.example.solidconnection.mentor.repository.MentoringRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; @@ -18,6 +23,8 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; @TestContainerSpringBootTest @DisplayName("멘토링 조회 서비스 테스트") @@ -35,79 +42,194 @@ class MentoringQueryServiceTest { @Autowired private MentoringFixture mentoringFixture; - private SiteUser mentorUser; - private SiteUser menteeUser; - private Mentor mentor; + @Autowired + private MentoringRepository mentoringRepository; + + private SiteUser mentorUser1, mentorUser2; + private SiteUser menteeUser1, menteeUser2, menteeUser3; + private Mentor mentor1, mentor2, mentor3; + private Pageable pageable; @BeforeEach void setUp() { - mentorUser = siteUserFixture.멘토(1, "mentor1"); - menteeUser = siteUserFixture.사용자(2, "mentee1"); - mentor = mentorFixture.멘토(mentorUser.getId(), 1L); + mentorUser1 = siteUserFixture.멘토(1, "mentor1"); + mentorUser2 = siteUserFixture.멘토(2, "mentor2"); + SiteUser mentorUser3 = siteUserFixture.멘토(3, "mentor3"); + menteeUser1 = siteUserFixture.사용자(1, "mentee1"); + menteeUser2 = siteUserFixture.사용자(2, "mentee2"); + menteeUser3 = siteUserFixture.사용자(3, "mentee3"); + mentor1 = mentorFixture.멘토(mentorUser1.getId(), 1L); + mentor2 = mentorFixture.멘토(mentorUser2.getId(), 1L); + mentor3 = mentorFixture.멘토(mentorUser3.getId(), 1L); + pageable = PageRequest.of(0, 3); } @Nested - class 멘토링_목록_조회_테스트 { + class 멘토의_멘토링_목록_조회_테스트 { + + @Test + void 모든_상태의_멘토링_목록을_조회한다() { + // given + Mentoring mentoring1 = mentoringFixture.대기중_멘토링(mentor1.getId(), menteeUser1.getId()); + Mentoring mentoring2 = mentoringFixture.승인된_멘토링(mentor1.getId(), menteeUser2.getId()); + Mentoring mentoring3 = mentoringFixture.거절된_멘토링(mentor1.getId(), menteeUser3.getId()); + + // when + SliceResponse response = mentoringQueryService.getMentoringsForMentor(mentorUser1.getId(), pageable); + + // then + assertThat(response.content()).extracting(MentoringForMentorResponse::mentoringId) + .containsExactlyInAnyOrder( + mentoring1.getId(), + mentoring2.getId(), + mentoring3.getId() + ); + } @Test - void 멘토의_모든_멘토링을_조회한다() { + void 멘토링_상대의_정보를_포함한다() { // given - Mentoring mentoring1 = mentoringFixture.대기중_멘토링(mentor.getId(), menteeUser.getId()); - Mentoring mentoring2 = mentoringFixture.승인된_멘토링(mentor.getId(), menteeUser.getId()); - Mentoring mentoring3 = mentoringFixture.거절된_멘토링(mentor.getId(), menteeUser.getId()); + mentoringFixture.대기중_멘토링(mentor1.getId(), menteeUser1.getId()); + mentoringFixture.승인된_멘토링(mentor1.getId(), menteeUser2.getId()); // when - MentoringListResponse responses = mentoringQueryService.getMentorings(mentorUser.getId()); + SliceResponse response = mentoringQueryService.getMentoringsForMentor(mentorUser1.getId(), pageable); // then - assertAll( - () -> assertThat(responses.requests()).hasSize(3), - () -> assertThat(responses.requests()).extracting(MentoringResponse::mentoringId) - .containsExactlyInAnyOrder( - mentoring1.getId(), - mentoring2.getId(), - mentoring3.getId() - ) - ); + assertThat(response.content()).extracting(MentoringForMentorResponse::nickname) + .containsExactlyInAnyOrder( + menteeUser1.getNickname(), + menteeUser2.getNickname() + ); + } + + @Test + void 멘토링_확인_여부를_포함한다() { + // given + Mentoring mentoring1 = mentoringFixture.대기중_멘토링(mentor1.getId(), menteeUser1.getId()); + Mentoring mentoring2 = mentoringFixture.승인된_멘토링(mentor1.getId(), menteeUser2.getId()); + + // when + SliceResponse response = mentoringQueryService.getMentoringsForMentor(mentorUser1.getId(), pageable); + + // then + assertThat(response.content()) + .extracting(MentoringForMentorResponse::mentoringId, MentoringForMentorResponse::isChecked) + .containsExactlyInAnyOrder( + tuple(mentoring1.getId(), false), + tuple(mentoring2.getId(), true) + ); } @Test void 멘토링이_없는_경우_빈_리스트를_반환한다() { // when - MentoringListResponse responses = mentoringQueryService.getMentorings(mentorUser.getId()); + SliceResponse response = mentoringQueryService.getMentoringsForMentor(mentorUser1.getId(), pageable); // then - assertThat(responses.requests()).isEmpty(); + assertThat(response.content()).isEmpty(); } } @Nested - class 새_멘토링_개수_조회_테스트 { + class 멘티의_멘토링_목록_조회_테스트 { + + @Test + void 거절된_멘토링_목록을_조회하면_예외가_발생한다() { + // given + mentoringFixture.거절된_멘토링(mentor1.getId(), menteeUser1.getId()); + + // when & then + assertThatCode(() -> mentoringQueryService.getMentoringsForMentee(menteeUser1.getId(), VerifyStatus.REJECTED, pageable)) + .isInstanceOf(CustomException.class) + .hasMessageContaining(ErrorCode.UNAUTHORIZED_MENTORING.getMessage()); + } @Test - void 확인되지_않은_멘토링_개수를_반환한다() { + void 승인된_멘토링_목록을_조회한다() { // given - mentoringFixture.확인되지_않은_멘토링(mentor.getId(), menteeUser.getId()); - mentoringFixture.확인되지_않은_멘토링(mentor.getId(), menteeUser.getId()); - mentoringFixture.승인된_멘토링(mentor.getId(), menteeUser.getId()); + Mentoring mentoring1 = mentoringFixture.승인된_멘토링(mentor1.getId(), menteeUser1.getId()); + Mentoring mentoring2 = mentoringFixture.승인된_멘토링(mentor2.getId(), menteeUser1.getId()); + mentoringFixture.대기중_멘토링(mentor3.getId(), menteeUser1.getId()); // when - MentoringCountResponse response = mentoringQueryService.getNewMentoringsCount(mentorUser.getId()); + SliceResponse response = mentoringQueryService.getMentoringsForMentee( + menteeUser1.getId(), VerifyStatus.APPROVED, pageable); // then - assertThat(response.uncheckedCount()).isEqualTo(2); + assertThat(response.content()).extracting(MentoringForMenteeResponse::mentoringId) + .containsExactlyInAnyOrder( + mentoring1.getId(), + mentoring2.getId() + ); } @Test - void 확인되지_않은_멘토링이_없으면_0을_반환한다() { + void 대기중인_멘토링_목록을_조회한다() { // given - mentoringFixture.승인된_멘토링(mentor.getId(), menteeUser.getId()); + Mentoring mentoring1 = mentoringFixture.대기중_멘토링(mentor1.getId(), menteeUser1.getId()); + Mentoring mentoring2 = mentoringFixture.대기중_멘토링(mentor2.getId(), menteeUser1.getId()); + mentoringFixture.승인된_멘토링(mentor3.getId(), menteeUser1.getId()); // when - MentoringCountResponse response = mentoringQueryService.getNewMentoringsCount(mentorUser.getId()); + SliceResponse response = mentoringQueryService.getMentoringsForMentee( + menteeUser1.getId(), VerifyStatus.PENDING, pageable); + + // then + assertThat(response.content()).extracting(MentoringForMenteeResponse::mentoringId) + .containsExactlyInAnyOrder( + mentoring1.getId(), + mentoring2.getId() + ); + } + + @Test + void 멘토링_상대의_정보를_포함한다() { + // given + mentoringFixture.승인된_멘토링(mentor1.getId(), menteeUser1.getId()); + mentoringFixture.승인된_멘토링(mentor2.getId(), menteeUser1.getId()); + + // when + SliceResponse response = mentoringQueryService.getMentoringsForMentee( + menteeUser1.getId(), VerifyStatus.APPROVED, pageable); + + // then + assertThat(response.content()).extracting(MentoringForMenteeResponse::nickname) + .containsExactlyInAnyOrder( + mentorUser1.getNickname(), + mentorUser2.getNickname() + ); + } + + @Test + void 멘토링_확인_여부를_포함한다() { + // given + Mentoring mentoring1 = mentoringFixture.대기중_멘토링(mentor1.getId(), menteeUser1.getId()); + Mentoring mentoring2 = mentoringFixture.대기중_멘토링(mentor2.getId(), menteeUser1.getId()); + mentoring1.checkByMentee(); + mentoringRepository.save(mentoring1); + + // when + SliceResponse response = mentoringQueryService.getMentoringsForMentee( + menteeUser1.getId(), VerifyStatus.PENDING, pageable); + + // then + assertThat(response.content()) + .extracting(MentoringForMenteeResponse::mentoringId, MentoringForMenteeResponse::isChecked) + .containsExactlyInAnyOrder( + tuple(mentoring1.getId(), true), + tuple(mentoring2.getId(), false) + ); + } + + @Test + void 멘토링이_없는_경우_빈_리스트를_반환한다() { + // when + SliceResponse response = mentoringQueryService.getMentoringsForMentee( + mentorUser1.getId(), VerifyStatus.APPROVED, pageable); // then - assertThat(response.uncheckedCount()).isZero(); + assertThat(response.content()).isEmpty(); } } } From 51ae6894013466883eb376f1ce72a7cc68e4eb01 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Sun, 3 Aug 2025 01:34:47 +0900 Subject: [PATCH 62/90] =?UTF-8?q?refactor:=20=EB=A9=98=ED=86=A0=EC=9D=98?= =?UTF-8?q?=20=EB=A7=88=EC=9D=B4=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=EC=97=90=20'=ED=95=A9=EA=B2=A9=20=ED=8C=81'=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#420)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 중복되는 페이지네이션 설정 제거 * refactor: 멘토의 마이페이지 조회시, '합격 팁'을 포함하도록 - '멘토의 마이페이지 조회' api를 미리보기와 상세 보기에 모두 사용하게 한다. - 이를 위해, 마이페이지 응답이 전체 정보를 내려주도록 한다. --- .../solidconnection/mentor/controller/MentorController.java | 4 +--- .../solidconnection/mentor/dto/MentorMyPageResponse.java | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java b/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java index 5d9cde156..0ce04e1f7 100644 --- a/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java +++ b/src/main/java/com/example/solidconnection/mentor/controller/MentorController.java @@ -1,7 +1,5 @@ package com.example.solidconnection.mentor.controller; -import static org.springframework.data.domain.Sort.Direction.DESC; - import com.example.solidconnection.common.dto.SliceResponse; import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.mentor.dto.MentorDetailResponse; @@ -41,7 +39,7 @@ public ResponseEntity> getMentorPreviews( @AuthorizedUser long siteUserId, @RequestParam("region") String region, - @PageableDefault(size = 3, sort = "menteeCount", direction = DESC) + @PageableDefault(size = 3) @SortDefaults({ @SortDefault(sort = "menteeCount", direction = Sort.Direction.DESC), @SortDefault(sort = "id", direction = Sort.Direction.ASC) diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java index 50b7f6eab..2ae1acba7 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentorMyPageResponse.java @@ -15,6 +15,7 @@ public record MentorMyPageResponse( int menteeCount, boolean hasBadge, String introduction, + String passTip, List channels ) { @@ -29,6 +30,7 @@ public static MentorMyPageResponse of(Mentor mentor, SiteUser siteUser, Universi mentor.getMenteeCount(), mentor.isHasBadge(), mentor.getIntroduction(), + mentor.getPassTip(), mentor.getChannels().stream() .map(ChannelResponse::from) .toList() From 9c7249ca9f13f8a95aef66dc874f421b75531eb6 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Mon, 4 Aug 2025 03:26:06 +0900 Subject: [PATCH 63/90] =?UTF-8?q?fix:=20=EC=B1=84=EB=84=90=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=8B=9C=20=EC=9C=A0=EB=8B=88=ED=81=AC=20=EC=A0=9C?= =?UTF-8?q?=EC=95=BD=20=EC=9C=84=EB=B0=98=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20(#426)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mentor/domain/Channel.java | 6 ++++ .../solidconnection/mentor/domain/Mentor.java | 21 ++++++++---- .../service/MentorMyPageServiceTest.java | 34 +++++++++++++++---- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Channel.java b/src/main/java/com/example/solidconnection/mentor/domain/Channel.java index b3c12bff7..10ed3e940 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Channel.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Channel.java @@ -54,4 +54,10 @@ public Channel(int sequence, ChannelType type, String url) { public void updateMentor(Mentor mentor) { this.mentor = mentor; } + + public void update(Channel channel) { + this.sequence = channel.sequence; + this.type = channel.type; + this.url = channel.url; + } } diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java index d97f6a895..008917e89 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java @@ -65,13 +65,20 @@ public void updatePassTip(String passTip) { } public void updateChannels(List channels) { - this.channels.clear(); - if (channels == null || channels.isEmpty()) { - return; - } - for (Channel channel : channels) { - channel.updateMentor(this); - this.channels.add(channel); + int newChannelSize = Math.max(channels.size(), this.channels.size()); + int originalChannelSize = this.channels.size(); + for (int i = 0; i < newChannelSize; i++) { + if (i < channels.size() && i < this.channels.size()) { // 기존 채널 수정 + Channel existing = this.channels.get(i); + Channel newChannel = channels.get(i); + existing.update(newChannel); + } else if (i < channels.size()) { // 채널 갯수 늘어남 - 새로운 채널 추가 + Channel newChannel = channels.get(i); + newChannel.updateMentor(this); + this.channels.add(newChannel); + } else if (i < originalChannelSize) { // 채널 갯수 줄어듦 - 기존 채널 삭제 + this.channels.remove(this.channels.size() - 1); + } } } } diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java index 5d675e14b..f4bc6a0ea 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentorMyPageServiceTest.java @@ -3,6 +3,7 @@ import static com.example.solidconnection.mentor.domain.ChannelType.BLOG; import static com.example.solidconnection.mentor.domain.ChannelType.INSTAGRAM; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; import static org.junit.jupiter.api.Assertions.assertAll; import com.example.solidconnection.mentor.domain.Channel; @@ -109,8 +110,28 @@ class 멘토의_마이_페이지를_수정한다 { } @Test - void 채널_정보를_수정한다() { + void 기존보다_적게_채널_정보를_수정한다() { // given + channelFixture.채널(1, mentor); + channelFixture.채널(2, mentor); + channelFixture.채널(3, mentor); + channelFixture.채널(4, mentor); + List newChannels = List.of(new ChannelRequest(BLOG, "https://blog.com")); + MentorMyPageUpdateRequest request = new MentorMyPageUpdateRequest("introduction", "passTip", newChannels); + + // when + mentorMyPageService.updateMentorMyPage(mentorUser.getId(), request); + + // then + List updatedChannels = channelRepositoryForTest.findAllByMentorId(mentor.getId()); + assertThat(updatedChannels).extracting(Channel::getSequence, Channel::getType, Channel::getUrl) + .containsExactlyInAnyOrder(tuple(1, BLOG, "https://blog.com")); + } + + @Test + void 기존보다_많게_채널_정보를_수정한다() { + // given + channelFixture.채널(1, mentor); List newChannels = List.of( new ChannelRequest(BLOG, "https://blog.com"), new ChannelRequest(INSTAGRAM, "https://instagram.com") @@ -122,12 +143,11 @@ class 멘토의_마이_페이지를_수정한다 { // then List updatedChannels = channelRepositoryForTest.findAllByMentorId(mentor.getId()); - assertAll( - () -> assertThat(updatedChannels).extracting(Channel::getType) - .containsExactly(BLOG, INSTAGRAM), - () -> assertThat(updatedChannels).extracting(Channel::getUrl) - .containsExactly("https://blog.com", "https://instagram.com") - ); + assertThat(updatedChannels).extracting(Channel::getSequence, Channel::getType, Channel::getUrl) + .containsExactlyInAnyOrder( + tuple(1, BLOG, "https://blog.com"), + tuple(2, INSTAGRAM, "https://instagram.com") + ); } } } From 3f4a24aa3c1299ade57cf0de8fcbb8beafb9b0a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:51:56 +0900 Subject: [PATCH 64/90] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84=20(#408)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 채팅 도메인 관련 생성자 추가 * feat: 채팅방 목록 조회 응답 관련 dto 생성 * feat: 채팅방 관련 레포지토리 생성 * feat: 채팅방 목록 조회 관련 서비스 구현 * feat: 채팅방 컨트롤러 생성 * test: 채팅 관련 fixture 생성 * test: 채팅방 목록 조회 테스트 작성 * feat: 채팅 관련 레포지토리 추가 * feat: 채팅 응답 관련 dto 생성 * feat: 채팅 내역 조회 서비스 구현 * feat: 채팅 내역 조회 컨트롤러 생성 * test: 채팅 첨부파일 관련 fixture 생성 * test: 채팅 내역 조회 테스트 작성 * feat: 채팅 읽음 처리 관련 레포지토리 추가 * feat: 채팅 읽음 처리 서비스 구현 * feat: 채팅 읽음 처리 컨트롤러 생성 * test: 채팅 읽음 처리 테스트 작성 * style: reformat code 적용 * refactor: ChatMessageRepository로 함수 위치 변경 * feat: 멘토링 승인 시 채팅방 생성 서비스 구현 * test: 멘토링 승인 시 채팅방 생성 테스트 작성 * feat: 배치사이즈 추가 * chore: todo 주석 추가 - n + 1 해결 필요 * refactor: JPQL 대신 Spring Data JPA 표준에 맞게 변경 * refactor: DB와 영속성 컨텍스트 간 불일치 방지를 위한 옵션 추가 * refactor: findPartner 함수 최적화 - isGroup과 findFirst를 활용하여 중간 리스트 생성 제거 * refactor: 중복 검증 제거 * refactor: 에러 메시지 통일 * refactor: 컨벤션에 맞게 함수 위치 수정 * refactor: 채팅방 생성 관련 로직 제거 * style: 컨벤션에 맞게 개행 추가 * style: 컨벤션에 맞게 개행 추가 - 코드래빗 의견 반영 --- .../chat/controller/ChatController.java | 52 +++ .../chat/domain/ChatAttachment.java | 10 + .../chat/domain/ChatMessage.java | 11 +- .../chat/domain/ChatParticipant.java | 8 + .../chat/domain/ChatReadStatus.java | 5 + .../solidconnection/chat/domain/ChatRoom.java | 10 +- .../chat/dto/ChatAttachmentResponse.java | 17 + .../chat/dto/ChatMessageResponse.java | 18 + .../chat/dto/ChatParticipantResponse.java | 12 + .../chat/dto/ChatRoomListResponse.java | 12 + .../chat/dto/ChatRoomResponse.java | 22 ++ .../repository/ChatAttachmentRepository.java | 8 + .../repository/ChatMessageRepository.java | 22 ++ .../repository/ChatParticipantRepository.java | 12 + .../repository/ChatReadStatusRepository.java | 18 + .../chat/repository/ChatRoomRepository.java | 36 ++ .../chat/service/ChatService.java | 127 ++++++ .../common/exception/ErrorCode.java | 5 + .../chat/fixture/ChatAttachmentFixture.java | 30 ++ .../fixture/ChatAttachmentFixtureBuilder.java | 48 +++ .../chat/fixture/ChatMessageFixture.java | 21 + .../fixture/ChatMessageFixtureBuilder.java | 42 ++ .../chat/fixture/ChatParticipantFixture.java | 20 + .../ChatParticipantFixtureBuilder.java | 36 ++ .../chat/fixture/ChatReadStatusFixture.java | 19 + .../fixture/ChatReadStatusFixtureBuilder.java | 35 ++ .../chat/fixture/ChatRoomFixture.java | 18 + .../chat/fixture/ChatRoomFixtureBuilder.java | 29 ++ .../ChatReadStatusRepositoryForTest.java | 10 + .../chat/service/ChatServiceTest.java | 366 ++++++++++++++++++ 30 files changed, 1076 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/chat/controller/ChatController.java create mode 100644 src/main/java/com/example/solidconnection/chat/dto/ChatAttachmentResponse.java create mode 100644 src/main/java/com/example/solidconnection/chat/dto/ChatMessageResponse.java create mode 100644 src/main/java/com/example/solidconnection/chat/dto/ChatParticipantResponse.java create mode 100644 src/main/java/com/example/solidconnection/chat/dto/ChatRoomListResponse.java create mode 100644 src/main/java/com/example/solidconnection/chat/dto/ChatRoomResponse.java create mode 100644 src/main/java/com/example/solidconnection/chat/repository/ChatAttachmentRepository.java create mode 100644 src/main/java/com/example/solidconnection/chat/repository/ChatMessageRepository.java create mode 100644 src/main/java/com/example/solidconnection/chat/repository/ChatParticipantRepository.java create mode 100644 src/main/java/com/example/solidconnection/chat/repository/ChatReadStatusRepository.java create mode 100644 src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java create mode 100644 src/main/java/com/example/solidconnection/chat/service/ChatService.java create mode 100644 src/test/java/com/example/solidconnection/chat/fixture/ChatAttachmentFixture.java create mode 100644 src/test/java/com/example/solidconnection/chat/fixture/ChatAttachmentFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/chat/fixture/ChatMessageFixture.java create mode 100644 src/test/java/com/example/solidconnection/chat/fixture/ChatMessageFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/chat/fixture/ChatParticipantFixture.java create mode 100644 src/test/java/com/example/solidconnection/chat/fixture/ChatParticipantFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/chat/fixture/ChatReadStatusFixture.java create mode 100644 src/test/java/com/example/solidconnection/chat/fixture/ChatReadStatusFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/chat/fixture/ChatRoomFixture.java create mode 100644 src/test/java/com/example/solidconnection/chat/fixture/ChatRoomFixtureBuilder.java create mode 100644 src/test/java/com/example/solidconnection/chat/repository/ChatReadStatusRepositoryForTest.java create mode 100644 src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java diff --git a/src/main/java/com/example/solidconnection/chat/controller/ChatController.java b/src/main/java/com/example/solidconnection/chat/controller/ChatController.java new file mode 100644 index 000000000..1c3b1a5d1 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/controller/ChatController.java @@ -0,0 +1,52 @@ +package com.example.solidconnection.chat.controller; + +import com.example.solidconnection.chat.dto.ChatMessageResponse; +import com.example.solidconnection.chat.dto.ChatRoomListResponse; +import com.example.solidconnection.chat.service.ChatService; +import com.example.solidconnection.common.dto.SliceResponse; +import com.example.solidconnection.common.resolver.AuthorizedUser; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/chats") +public class ChatController { + + private final ChatService chatService; + + @GetMapping("/rooms") + public ResponseEntity getChatRooms( + @AuthorizedUser long siteUserId + ) { + ChatRoomListResponse chatRoomListResponse = chatService.getChatRooms(siteUserId); + return ResponseEntity.ok(chatRoomListResponse); + } + + @GetMapping("/rooms/{room-id}") + public ResponseEntity> getChatMessages( + @AuthorizedUser long siteUserId, + @PathVariable("room-id") Long roomId, + @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable + ) { + SliceResponse response = chatService.getChatMessages(siteUserId, roomId, pageable); + return ResponseEntity.ok(response); + } + + @PutMapping("/rooms/{room-id}/read") + public ResponseEntity markChatMessagesAsRead( + @AuthorizedUser long siteUserId, + @PathVariable("room-id") Long roomId + ) { + chatService.markChatMessagesAsRead(siteUserId, roomId); + return ResponseEntity.ok().build(); + } +} diff --git a/src/main/java/com/example/solidconnection/chat/domain/ChatAttachment.java b/src/main/java/com/example/solidconnection/chat/domain/ChatAttachment.java index 5c0f5e651..def9263c8 100644 --- a/src/main/java/com/example/solidconnection/chat/domain/ChatAttachment.java +++ b/src/main/java/com/example/solidconnection/chat/domain/ChatAttachment.java @@ -32,4 +32,14 @@ public class ChatAttachment extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) private ChatMessage chatMessage; + + public ChatAttachment(boolean isImage, String url, String thumbnailUrl, ChatMessage chatMessage) { + this.isImage = isImage; + this.url = url; + this.thumbnailUrl = thumbnailUrl; + this.chatMessage = chatMessage; + if (chatMessage != null) { + chatMessage.getChatAttachments().add(this); + } + } } diff --git a/src/main/java/com/example/solidconnection/chat/domain/ChatMessage.java b/src/main/java/com/example/solidconnection/chat/domain/ChatMessage.java index 8d513c5a7..07fc99131 100644 --- a/src/main/java/com/example/solidconnection/chat/domain/ChatMessage.java +++ b/src/main/java/com/example/solidconnection/chat/domain/ChatMessage.java @@ -34,5 +34,14 @@ public class ChatMessage extends BaseEntity { private ChatRoom chatRoom; @OneToMany(mappedBy = "chatMessage", cascade = CascadeType.ALL) - private List chatAttachments = new ArrayList<>(); + private final List chatAttachments = new ArrayList<>(); + + public ChatMessage(String content, long senderId, ChatRoom chatRoom) { + this.content = content; + this.senderId = senderId; + this.chatRoom = chatRoom; + if (chatRoom != null) { + chatRoom.getChatMessages().add(this); + } + } } diff --git a/src/main/java/com/example/solidconnection/chat/domain/ChatParticipant.java b/src/main/java/com/example/solidconnection/chat/domain/ChatParticipant.java index 169e1dd06..60fc6b795 100644 --- a/src/main/java/com/example/solidconnection/chat/domain/ChatParticipant.java +++ b/src/main/java/com/example/solidconnection/chat/domain/ChatParticipant.java @@ -34,4 +34,12 @@ public class ChatParticipant extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) private ChatRoom chatRoom; + + public ChatParticipant(long siteUserId, ChatRoom chatRoom) { + this.siteUserId = siteUserId; + this.chatRoom = chatRoom; + if (chatRoom != null) { + chatRoom.getChatParticipants().add(this); + } + } } diff --git a/src/main/java/com/example/solidconnection/chat/domain/ChatReadStatus.java b/src/main/java/com/example/solidconnection/chat/domain/ChatReadStatus.java index 13d4ac646..8c731738c 100644 --- a/src/main/java/com/example/solidconnection/chat/domain/ChatReadStatus.java +++ b/src/main/java/com/example/solidconnection/chat/domain/ChatReadStatus.java @@ -32,4 +32,9 @@ public class ChatReadStatus extends BaseEntity { @Column(name = "chat_participant_id") private long chatParticipantId; + + public ChatReadStatus(long chatRoomId, long chatParticipantId) { + this.chatRoomId = chatRoomId; + this.chatParticipantId = chatParticipantId; + } } diff --git a/src/main/java/com/example/solidconnection/chat/domain/ChatRoom.java b/src/main/java/com/example/solidconnection/chat/domain/ChatRoom.java index 020befe5f..e8e7a3ebb 100644 --- a/src/main/java/com/example/solidconnection/chat/domain/ChatRoom.java +++ b/src/main/java/com/example/solidconnection/chat/domain/ChatRoom.java @@ -12,6 +12,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.BatchSize; @Entity @Getter @@ -25,8 +26,13 @@ public class ChatRoom extends BaseEntity { private boolean isGroup = false; @OneToMany(mappedBy = "chatRoom", cascade = CascadeType.ALL) - private List chatParticipants = new ArrayList<>(); + @BatchSize(size = 10) + private final List chatParticipants = new ArrayList<>(); @OneToMany(mappedBy = "chatRoom", cascade = CascadeType.ALL) - private List chatMessages = new ArrayList<>(); + private final List chatMessages = new ArrayList<>(); + + public ChatRoom(boolean isGroup) { + this.isGroup = isGroup; + } } diff --git a/src/main/java/com/example/solidconnection/chat/dto/ChatAttachmentResponse.java b/src/main/java/com/example/solidconnection/chat/dto/ChatAttachmentResponse.java new file mode 100644 index 000000000..44c11246b --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/dto/ChatAttachmentResponse.java @@ -0,0 +1,17 @@ +package com.example.solidconnection.chat.dto; + +import java.time.ZonedDateTime; + +public record ChatAttachmentResponse( + long id, + boolean isImage, + String url, + String thumbnailUrl, + ZonedDateTime createdAt +) { + + public static ChatAttachmentResponse of(long id, boolean isImage, String url, + String thumbnailUrl, ZonedDateTime createdAt) { + return new ChatAttachmentResponse(id, isImage, url, thumbnailUrl, createdAt); + } +} diff --git a/src/main/java/com/example/solidconnection/chat/dto/ChatMessageResponse.java b/src/main/java/com/example/solidconnection/chat/dto/ChatMessageResponse.java new file mode 100644 index 000000000..a3728b7fd --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/dto/ChatMessageResponse.java @@ -0,0 +1,18 @@ +package com.example.solidconnection.chat.dto; + +import java.time.ZonedDateTime; +import java.util.List; + +public record ChatMessageResponse( + long id, + String content, + long senderId, + ZonedDateTime createdAt, + List attachments +) { + + public static ChatMessageResponse of(long id, String content, long senderId, + ZonedDateTime createdAt, List attachments) { + return new ChatMessageResponse(id, content, senderId, createdAt, attachments); + } +} diff --git a/src/main/java/com/example/solidconnection/chat/dto/ChatParticipantResponse.java b/src/main/java/com/example/solidconnection/chat/dto/ChatParticipantResponse.java new file mode 100644 index 000000000..ffa6b9b8c --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/dto/ChatParticipantResponse.java @@ -0,0 +1,12 @@ +package com.example.solidconnection.chat.dto; + +public record ChatParticipantResponse( + long partnerId, + String nickname, + String profileUrl +) { + + public static ChatParticipantResponse of(long partnerId, String nickname, String profileUrl) { + return new ChatParticipantResponse(partnerId, nickname, profileUrl); + } +} diff --git a/src/main/java/com/example/solidconnection/chat/dto/ChatRoomListResponse.java b/src/main/java/com/example/solidconnection/chat/dto/ChatRoomListResponse.java new file mode 100644 index 000000000..add17f1d1 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/dto/ChatRoomListResponse.java @@ -0,0 +1,12 @@ +package com.example.solidconnection.chat.dto; + +import java.util.List; + +public record ChatRoomListResponse( + List chatRooms +) { + + public static ChatRoomListResponse of(List chatRooms) { + return new ChatRoomListResponse(chatRooms); + } +} diff --git a/src/main/java/com/example/solidconnection/chat/dto/ChatRoomResponse.java b/src/main/java/com/example/solidconnection/chat/dto/ChatRoomResponse.java new file mode 100644 index 000000000..69ec047fb --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/dto/ChatRoomResponse.java @@ -0,0 +1,22 @@ +package com.example.solidconnection.chat.dto; + +import java.time.ZonedDateTime; + +public record ChatRoomResponse( + long id, + String lastChatMessage, + ZonedDateTime lastReceivedTime, + ChatParticipantResponse partner, + long unReadCount +) { + + public static ChatRoomResponse of( + long id, + String lastChatMessage, + ZonedDateTime lastReceivedTime, + ChatParticipantResponse partner, + long unReadCount + ) { + return new ChatRoomResponse(id, lastChatMessage, lastReceivedTime, partner, unReadCount); + } +} diff --git a/src/main/java/com/example/solidconnection/chat/repository/ChatAttachmentRepository.java b/src/main/java/com/example/solidconnection/chat/repository/ChatAttachmentRepository.java new file mode 100644 index 000000000..0d2dd3051 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/repository/ChatAttachmentRepository.java @@ -0,0 +1,8 @@ +package com.example.solidconnection.chat.repository; + +import com.example.solidconnection.chat.domain.ChatAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ChatAttachmentRepository extends JpaRepository { + +} diff --git a/src/main/java/com/example/solidconnection/chat/repository/ChatMessageRepository.java b/src/main/java/com/example/solidconnection/chat/repository/ChatMessageRepository.java new file mode 100644 index 000000000..ad0f15630 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/repository/ChatMessageRepository.java @@ -0,0 +1,22 @@ +package com.example.solidconnection.chat.repository; + +import com.example.solidconnection.chat.domain.ChatMessage; +import java.util.Optional; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface ChatMessageRepository extends JpaRepository { + + @Query(""" + SELECT cm FROM ChatMessage cm + LEFT JOIN FETCH cm.chatAttachments + WHERE cm.chatRoom.id = :roomId + ORDER BY cm.createdAt DESC + """) + Slice findByRoomIdWithPaging(@Param("roomId") long roomId, Pageable pageable); + + Optional findFirstByChatRoomIdOrderByCreatedAtDesc(long chatRoomId); +} diff --git a/src/main/java/com/example/solidconnection/chat/repository/ChatParticipantRepository.java b/src/main/java/com/example/solidconnection/chat/repository/ChatParticipantRepository.java new file mode 100644 index 000000000..4bce2d08c --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/repository/ChatParticipantRepository.java @@ -0,0 +1,12 @@ +package com.example.solidconnection.chat.repository; + +import com.example.solidconnection.chat.domain.ChatParticipant; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ChatParticipantRepository extends JpaRepository { + + boolean existsByChatRoomIdAndSiteUserId(long chatRoomId, long siteUserId); + + Optional findByChatRoomIdAndSiteUserId(long chatRoomId, long siteUserId); +} diff --git a/src/main/java/com/example/solidconnection/chat/repository/ChatReadStatusRepository.java b/src/main/java/com/example/solidconnection/chat/repository/ChatReadStatusRepository.java new file mode 100644 index 000000000..5ff82a75b --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/repository/ChatReadStatusRepository.java @@ -0,0 +1,18 @@ +package com.example.solidconnection.chat.repository; + +import com.example.solidconnection.chat.domain.ChatReadStatus; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface ChatReadStatusRepository extends JpaRepository { + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query(value = """ + INSERT INTO chat_read_status (chat_room_id, chat_participant_id, created_at, updated_at) + VALUES (:chatRoomId, :chatParticipantId, NOW(6), NOW(6)) + ON DUPLICATE KEY UPDATE updated_at = NOW(6) + """, nativeQuery = true) + void upsertReadStatus(@Param("chatRoomId") long chatRoomId, @Param("chatParticipantId") long chatParticipantId); +} diff --git a/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java b/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java new file mode 100644 index 000000000..dd5193abf --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java @@ -0,0 +1,36 @@ +package com.example.solidconnection.chat.repository; + +import com.example.solidconnection.chat.domain.ChatRoom; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface ChatRoomRepository extends JpaRepository { + + @Query(""" + SELECT cr FROM ChatRoom cr + JOIN cr.chatParticipants cp + WHERE cp.siteUserId = :userId AND cr.isGroup = false + ORDER BY ( + SELECT MAX(cm.createdAt) + FROM ChatMessage cm + WHERE cm.chatRoom = cr + ) DESC NULLS LAST + """) + List findOneOnOneChatRoomsByUserId(@Param("userId") long userId); + + @Query(""" + SELECT COUNT(cm) FROM ChatMessage cm + LEFT JOIN ChatReadStatus crs ON crs.chatRoomId = cm.chatRoom.id + AND crs.chatParticipantId = ( + SELECT cp.id FROM ChatParticipant cp + WHERE cp.chatRoom.id = :chatRoomId + AND cp.siteUserId = :userId + ) + WHERE cm.chatRoom.id = :chatRoomId + AND cm.senderId != :userId + AND (crs.updatedAt IS NULL OR cm.createdAt > crs.updatedAt) + """) + long countUnreadMessages(@Param("chatRoomId") long chatRoomId, @Param("userId") long userId); +} diff --git a/src/main/java/com/example/solidconnection/chat/service/ChatService.java b/src/main/java/com/example/solidconnection/chat/service/ChatService.java new file mode 100644 index 000000000..c378f6b50 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/service/ChatService.java @@ -0,0 +1,127 @@ +package com.example.solidconnection.chat.service; + +import static com.example.solidconnection.common.exception.ErrorCode.CHAT_PARTICIPANT_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.CHAT_PARTNER_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_CHAT_ROOM_STATE; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; + +import com.example.solidconnection.chat.domain.ChatMessage; +import com.example.solidconnection.chat.domain.ChatParticipant; +import com.example.solidconnection.chat.domain.ChatRoom; +import com.example.solidconnection.chat.dto.ChatAttachmentResponse; +import com.example.solidconnection.chat.dto.ChatMessageResponse; +import com.example.solidconnection.chat.dto.ChatParticipantResponse; +import com.example.solidconnection.chat.dto.ChatRoomListResponse; +import com.example.solidconnection.chat.dto.ChatRoomResponse; +import com.example.solidconnection.chat.repository.ChatMessageRepository; +import com.example.solidconnection.chat.repository.ChatParticipantRepository; +import com.example.solidconnection.chat.repository.ChatReadStatusRepository; +import com.example.solidconnection.chat.repository.ChatRoomRepository; +import com.example.solidconnection.common.dto.SliceResponse; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +public class ChatService { + + private final ChatRoomRepository chatRoomRepository; + private final ChatMessageRepository chatMessageRepository; + private final ChatParticipantRepository chatParticipantRepository; + private final ChatReadStatusRepository chatReadStatusRepository; + private final SiteUserRepository siteUserRepository; + + @Transactional(readOnly = true) + public ChatRoomListResponse getChatRooms(long siteUserId) { + // todo : n + 1 문제 해결 필요! + List chatRooms = chatRoomRepository.findOneOnOneChatRoomsByUserId(siteUserId); + List chatRoomInfos = chatRooms.stream() + .map(chatRoom -> toChatRoomResponse(chatRoom, siteUserId)) + .toList(); + return ChatRoomListResponse.of(chatRoomInfos); + } + + private ChatRoomResponse toChatRoomResponse(ChatRoom chatRoom, long siteUserId) { + Optional latestMessage = chatMessageRepository.findFirstByChatRoomIdOrderByCreatedAtDesc(chatRoom.getId()); + String lastChatMessage = latestMessage.map(ChatMessage::getContent).orElse(""); + ZonedDateTime lastReceivedTime = latestMessage.map(ChatMessage::getCreatedAt).orElse(null); + + ChatParticipant partnerParticipant = findPartner(chatRoom, siteUserId); + + SiteUser siteUser = siteUserRepository.findById(partnerParticipant.getSiteUserId()) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); + ChatParticipantResponse partner = ChatParticipantResponse.of(siteUser.getId(), siteUser.getNickname(), siteUser.getProfileImageUrl()); + + long unReadCount = chatRoomRepository.countUnreadMessages(chatRoom.getId(), siteUserId); + + return ChatRoomResponse.of(chatRoom.getId(), lastChatMessage, lastReceivedTime, partner, unReadCount); + } + + private ChatParticipant findPartner(ChatRoom chatRoom, long siteUserId) { + if (chatRoom.isGroup()) { + throw new CustomException(INVALID_CHAT_ROOM_STATE); + } + return chatRoom.getChatParticipants().stream() + .filter(participant -> participant.getSiteUserId() != siteUserId) + .findFirst() + .orElseThrow(() -> new CustomException(CHAT_PARTNER_NOT_FOUND)); + } + + @Transactional(readOnly = true) + public SliceResponse getChatMessages(long siteUserId, long roomId, Pageable pageable) { + validateChatRoomParticipant(siteUserId, roomId); + + Slice chatMessages = chatMessageRepository.findByRoomIdWithPaging(roomId, pageable); + + List content = chatMessages.getContent().stream() + .map(this::toChatMessageResponse) + .toList(); + + return SliceResponse.of(content, chatMessages); + } + + private ChatMessageResponse toChatMessageResponse(ChatMessage message) { + List attachments = message.getChatAttachments().stream() + .map(attachment -> ChatAttachmentResponse.of( + attachment.getId(), + attachment.getIsImage(), + attachment.getUrl(), + attachment.getThumbnailUrl(), + attachment.getCreatedAt() + )) + .toList(); + + return ChatMessageResponse.of( + message.getId(), + message.getContent(), + message.getSenderId(), + message.getCreatedAt(), + attachments + ); + } + + private void validateChatRoomParticipant(long siteUserId, long roomId) { + boolean isParticipant = chatParticipantRepository.existsByChatRoomIdAndSiteUserId(roomId, siteUserId); + if (!isParticipant) { + throw new CustomException(CHAT_PARTICIPANT_NOT_FOUND); + } + } + + @Transactional + public void markChatMessagesAsRead(long siteUserId, long roomId) { + ChatParticipant participant = chatParticipantRepository + .findByChatRoomIdAndSiteUserId(roomId, siteUserId) + .orElseThrow(() -> new CustomException(CHAT_PARTICIPANT_NOT_FOUND)); + + chatReadStatusRepository.upsertReadStatus(roomId, participant.getId()); + } +} diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 4659e61bf..b0b04829b 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -45,6 +45,8 @@ public enum ErrorCode { NEWS_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 소식지입니다."), MENTOR_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 멘토입니다."), REPORT_TARGET_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 신고 대상입니다."), + CHAT_PARTNER_NOT_FOUND(HttpStatus.BAD_REQUEST.value(), "채팅 상대를 찾을 수 없습니다."), + CHAT_PARTICIPANT_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "채팅 참여자를 찾을 수 없습니다."), // auth USER_ALREADY_SIGN_OUT(HttpStatus.UNAUTHORIZED.value(), "로그아웃 되었습니다."), @@ -119,6 +121,9 @@ public enum ErrorCode { // report ALREADY_REPORTED_BY_CURRENT_USER(HttpStatus.BAD_REQUEST.value(), "이미 신고한 상태입니다."), + // chat + INVALID_CHAT_ROOM_STATE(HttpStatus.BAD_REQUEST.value(), "잘못된 채팅방 상태입니다."), + // database DATA_INTEGRITY_VIOLATION(HttpStatus.CONFLICT.value(), "데이터베이스 무결성 제약조건 위반이 발생했습니다."), diff --git a/src/test/java/com/example/solidconnection/chat/fixture/ChatAttachmentFixture.java b/src/test/java/com/example/solidconnection/chat/fixture/ChatAttachmentFixture.java new file mode 100644 index 000000000..37f85c6e9 --- /dev/null +++ b/src/test/java/com/example/solidconnection/chat/fixture/ChatAttachmentFixture.java @@ -0,0 +1,30 @@ +package com.example.solidconnection.chat.fixture; + +import com.example.solidconnection.chat.domain.ChatAttachment; +import com.example.solidconnection.chat.domain.ChatMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ChatAttachmentFixture { + + private final ChatAttachmentFixtureBuilder chatAttachmentFixtureBuilder; + + public ChatAttachment 첨부파일(boolean isImage, String url, String thumbnailUrl, ChatMessage chatMessage) { + return chatAttachmentFixtureBuilder.chatAttachment() + .isImage(isImage) + .url(url) + .thumbnailUrl(thumbnailUrl) + .chatMessage(chatMessage) + .create(); + } + + public ChatAttachment 이미지(String url, String thumbnailUrl, ChatMessage chatMessage) { + return 첨부파일(true, url, thumbnailUrl, chatMessage); + } + + public ChatAttachment 파일(String url, ChatMessage chatMessage) { + return 첨부파일(false, url, null, chatMessage); + } +} diff --git a/src/test/java/com/example/solidconnection/chat/fixture/ChatAttachmentFixtureBuilder.java b/src/test/java/com/example/solidconnection/chat/fixture/ChatAttachmentFixtureBuilder.java new file mode 100644 index 000000000..7db17caf0 --- /dev/null +++ b/src/test/java/com/example/solidconnection/chat/fixture/ChatAttachmentFixtureBuilder.java @@ -0,0 +1,48 @@ +package com.example.solidconnection.chat.fixture; + +import com.example.solidconnection.chat.domain.ChatAttachment; +import com.example.solidconnection.chat.domain.ChatMessage; +import com.example.solidconnection.chat.repository.ChatAttachmentRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ChatAttachmentFixtureBuilder { + + private final ChatAttachmentRepository chatAttachmentRepository; + + private boolean isImage; + private String url; + private String thumbnailUrl; + private ChatMessage chatMessage; + + public ChatAttachmentFixtureBuilder chatAttachment() { + return new ChatAttachmentFixtureBuilder(chatAttachmentRepository); + } + + public ChatAttachmentFixtureBuilder isImage(boolean isImage) { + this.isImage = isImage; + return this; + } + + public ChatAttachmentFixtureBuilder url(String url) { + this.url = url; + return this; + } + + public ChatAttachmentFixtureBuilder thumbnailUrl(String thumbnailUrl) { + this.thumbnailUrl = thumbnailUrl; + return this; + } + + public ChatAttachmentFixtureBuilder chatMessage(ChatMessage chatMessage) { + this.chatMessage = chatMessage; + return this; + } + + public ChatAttachment create() { + ChatAttachment attachment = new ChatAttachment(isImage, url, thumbnailUrl, chatMessage); + return chatAttachmentRepository.save(attachment); + } +} diff --git a/src/test/java/com/example/solidconnection/chat/fixture/ChatMessageFixture.java b/src/test/java/com/example/solidconnection/chat/fixture/ChatMessageFixture.java new file mode 100644 index 000000000..f5a30cec8 --- /dev/null +++ b/src/test/java/com/example/solidconnection/chat/fixture/ChatMessageFixture.java @@ -0,0 +1,21 @@ +package com.example.solidconnection.chat.fixture; + +import com.example.solidconnection.chat.domain.ChatMessage; +import com.example.solidconnection.chat.domain.ChatRoom; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ChatMessageFixture { + + private final ChatMessageFixtureBuilder chatMessageFixtureBuilder; + + public ChatMessage 메시지(String content, long senderId, ChatRoom chatRoom) { + return chatMessageFixtureBuilder.chatMessage() + .content(content) + .senderId(senderId) + .chatRoom(chatRoom) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/chat/fixture/ChatMessageFixtureBuilder.java b/src/test/java/com/example/solidconnection/chat/fixture/ChatMessageFixtureBuilder.java new file mode 100644 index 000000000..8b30718cb --- /dev/null +++ b/src/test/java/com/example/solidconnection/chat/fixture/ChatMessageFixtureBuilder.java @@ -0,0 +1,42 @@ +package com.example.solidconnection.chat.fixture; + +import com.example.solidconnection.chat.domain.ChatMessage; +import com.example.solidconnection.chat.domain.ChatRoom; +import com.example.solidconnection.chat.repository.ChatMessageRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ChatMessageFixtureBuilder { + + private final ChatMessageRepository chatMessageRepository; + + private String content; + private long senderId; + private ChatRoom chatRoom; + + public ChatMessageFixtureBuilder chatMessage() { + return new ChatMessageFixtureBuilder(chatMessageRepository); + } + + public ChatMessageFixtureBuilder content(String content) { + this.content = content; + return this; + } + + public ChatMessageFixtureBuilder senderId(long senderId) { + this.senderId = senderId; + return this; + } + + public ChatMessageFixtureBuilder chatRoom(ChatRoom chatRoom) { + this.chatRoom = chatRoom; + return this; + } + + public ChatMessage create() { + ChatMessage chatMessage = new ChatMessage(content, senderId, chatRoom); + return chatMessageRepository.save(chatMessage); + } +} diff --git a/src/test/java/com/example/solidconnection/chat/fixture/ChatParticipantFixture.java b/src/test/java/com/example/solidconnection/chat/fixture/ChatParticipantFixture.java new file mode 100644 index 000000000..20825919d --- /dev/null +++ b/src/test/java/com/example/solidconnection/chat/fixture/ChatParticipantFixture.java @@ -0,0 +1,20 @@ +package com.example.solidconnection.chat.fixture; + +import com.example.solidconnection.chat.domain.ChatParticipant; +import com.example.solidconnection.chat.domain.ChatRoom; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ChatParticipantFixture { + + private final ChatParticipantFixtureBuilder chatParticipantFixtureBuilder; + + public ChatParticipant 참여자(long siteUserId, ChatRoom chatRoom) { + return chatParticipantFixtureBuilder.chatParticipant() + .siteUserId(siteUserId) + .chatRoom(chatRoom) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/chat/fixture/ChatParticipantFixtureBuilder.java b/src/test/java/com/example/solidconnection/chat/fixture/ChatParticipantFixtureBuilder.java new file mode 100644 index 000000000..8514ce77e --- /dev/null +++ b/src/test/java/com/example/solidconnection/chat/fixture/ChatParticipantFixtureBuilder.java @@ -0,0 +1,36 @@ +package com.example.solidconnection.chat.fixture; + +import com.example.solidconnection.chat.domain.ChatParticipant; +import com.example.solidconnection.chat.domain.ChatRoom; +import com.example.solidconnection.chat.repository.ChatParticipantRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ChatParticipantFixtureBuilder { + + private final ChatParticipantRepository chatParticipantRepository; + + private long siteUserId; + private ChatRoom chatRoom; + + public ChatParticipantFixtureBuilder chatParticipant() { + return new ChatParticipantFixtureBuilder(chatParticipantRepository); + } + + public ChatParticipantFixtureBuilder siteUserId(long siteUserId) { + this.siteUserId = siteUserId; + return this; + } + + public ChatParticipantFixtureBuilder chatRoom(ChatRoom chatRoom) { + this.chatRoom = chatRoom; + return this; + } + + public ChatParticipant create() { + ChatParticipant chatParticipant = new ChatParticipant(siteUserId, chatRoom); + return chatParticipantRepository.save(chatParticipant); + } +} diff --git a/src/test/java/com/example/solidconnection/chat/fixture/ChatReadStatusFixture.java b/src/test/java/com/example/solidconnection/chat/fixture/ChatReadStatusFixture.java new file mode 100644 index 000000000..f254faaf3 --- /dev/null +++ b/src/test/java/com/example/solidconnection/chat/fixture/ChatReadStatusFixture.java @@ -0,0 +1,19 @@ +package com.example.solidconnection.chat.fixture; + +import com.example.solidconnection.chat.domain.ChatReadStatus; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ChatReadStatusFixture { + + private final ChatReadStatusFixtureBuilder chatReadStatusFixtureBuilder; + + public ChatReadStatus 읽음상태(long chatRoomId, long chatParticipantId) { + return chatReadStatusFixtureBuilder.chatReadStatus() + .chatRoomId(chatRoomId) + .chatParticipantId(chatParticipantId) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/chat/fixture/ChatReadStatusFixtureBuilder.java b/src/test/java/com/example/solidconnection/chat/fixture/ChatReadStatusFixtureBuilder.java new file mode 100644 index 000000000..6f42c7d13 --- /dev/null +++ b/src/test/java/com/example/solidconnection/chat/fixture/ChatReadStatusFixtureBuilder.java @@ -0,0 +1,35 @@ +package com.example.solidconnection.chat.fixture; + +import com.example.solidconnection.chat.domain.ChatReadStatus; +import com.example.solidconnection.chat.repository.ChatReadStatusRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ChatReadStatusFixtureBuilder { + + private final ChatReadStatusRepository chatReadStatusRepository; + + private long chatRoomId; + private long chatParticipantId; + + public ChatReadStatusFixtureBuilder chatReadStatus() { + return new ChatReadStatusFixtureBuilder(chatReadStatusRepository); + } + + public ChatReadStatusFixtureBuilder chatRoomId(long chatRoomId) { + this.chatRoomId = chatRoomId; + return this; + } + + public ChatReadStatusFixtureBuilder chatParticipantId(long chatParticipantId) { + this.chatParticipantId = chatParticipantId; + return this; + } + + public ChatReadStatus create() { + ChatReadStatus chatReadStatus = new ChatReadStatus(chatRoomId, chatParticipantId); + return chatReadStatusRepository.save(chatReadStatus); + } +} diff --git a/src/test/java/com/example/solidconnection/chat/fixture/ChatRoomFixture.java b/src/test/java/com/example/solidconnection/chat/fixture/ChatRoomFixture.java new file mode 100644 index 000000000..cf80313bc --- /dev/null +++ b/src/test/java/com/example/solidconnection/chat/fixture/ChatRoomFixture.java @@ -0,0 +1,18 @@ +package com.example.solidconnection.chat.fixture; + +import com.example.solidconnection.chat.domain.ChatRoom; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ChatRoomFixture { + + private final ChatRoomFixtureBuilder chatRoomFixtureBuilder; + + public ChatRoom 채팅방(boolean isGroup) { + return chatRoomFixtureBuilder.chatRoom() + .isGroup(isGroup) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/chat/fixture/ChatRoomFixtureBuilder.java b/src/test/java/com/example/solidconnection/chat/fixture/ChatRoomFixtureBuilder.java new file mode 100644 index 000000000..bf7ed3387 --- /dev/null +++ b/src/test/java/com/example/solidconnection/chat/fixture/ChatRoomFixtureBuilder.java @@ -0,0 +1,29 @@ +package com.example.solidconnection.chat.fixture; + +import com.example.solidconnection.chat.domain.ChatRoom; +import com.example.solidconnection.chat.repository.ChatRoomRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class ChatRoomFixtureBuilder { + + private final ChatRoomRepository chatRoomRepository; + + private boolean isGroup; + + public ChatRoomFixtureBuilder chatRoom() { + return new ChatRoomFixtureBuilder(chatRoomRepository); + } + + public ChatRoomFixtureBuilder isGroup(boolean isGroup) { + this.isGroup = isGroup; + return this; + } + + public ChatRoom create() { + ChatRoom chatRoom = new ChatRoom(isGroup); + return chatRoomRepository.save(chatRoom); + } +} diff --git a/src/test/java/com/example/solidconnection/chat/repository/ChatReadStatusRepositoryForTest.java b/src/test/java/com/example/solidconnection/chat/repository/ChatReadStatusRepositoryForTest.java new file mode 100644 index 000000000..894276b78 --- /dev/null +++ b/src/test/java/com/example/solidconnection/chat/repository/ChatReadStatusRepositoryForTest.java @@ -0,0 +1,10 @@ +package com.example.solidconnection.chat.repository; + +import com.example.solidconnection.chat.domain.ChatReadStatus; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ChatReadStatusRepositoryForTest extends JpaRepository { + + Optional findByChatRoomIdAndChatParticipantId(long chatRoomId, long chatParticipantId); +} diff --git a/src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java b/src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java new file mode 100644 index 000000000..77120e884 --- /dev/null +++ b/src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java @@ -0,0 +1,366 @@ +package com.example.solidconnection.chat.service; + +import static com.example.solidconnection.common.exception.ErrorCode.CHAT_PARTICIPANT_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.CHAT_PARTNER_NOT_FOUND; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.example.solidconnection.chat.domain.ChatAttachment; +import com.example.solidconnection.chat.domain.ChatMessage; +import com.example.solidconnection.chat.domain.ChatParticipant; +import com.example.solidconnection.chat.domain.ChatReadStatus; +import com.example.solidconnection.chat.domain.ChatRoom; +import com.example.solidconnection.chat.dto.ChatMessageResponse; +import com.example.solidconnection.chat.dto.ChatRoomListResponse; +import com.example.solidconnection.chat.fixture.ChatAttachmentFixture; +import com.example.solidconnection.chat.fixture.ChatMessageFixture; +import com.example.solidconnection.chat.fixture.ChatParticipantFixture; +import com.example.solidconnection.chat.fixture.ChatReadStatusFixture; +import com.example.solidconnection.chat.fixture.ChatRoomFixture; +import com.example.solidconnection.chat.repository.ChatReadStatusRepositoryForTest; +import com.example.solidconnection.common.dto.SliceResponse; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.time.ZonedDateTime; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +@TestContainerSpringBootTest +@DisplayName("채팅 서비스 테스트") +class ChatServiceTest { + + @Autowired + private ChatService chatService; + + @Autowired + private ChatReadStatusRepositoryForTest chatReadStatusRepositoryForTest; + + @Autowired + private SiteUserFixture siteUserFixture; + + @Autowired + private ChatRoomFixture chatRoomFixture; + + @Autowired + private ChatParticipantFixture chatParticipantFixture; + + @Autowired + private ChatMessageFixture chatMessageFixture; + + @Autowired + private ChatReadStatusFixture chatReadStatusFixture; + + @Autowired + private ChatAttachmentFixture chatAttachmentFixture; + + private SiteUser user; + private SiteUser mentor1; + private SiteUser mentor2; + + @BeforeEach + void setUp() { + user = siteUserFixture.사용자(); + mentor1 = siteUserFixture.사용자(1, "mentor1"); + mentor2 = siteUserFixture.사용자(2, "mentor2"); + } + + @Nested + class 채팅방_목록을_조회한다 { + + @Test + void 채팅방이_없으면_빈_목록을_반환한다() { + // when + ChatRoomListResponse response = chatService.getChatRooms(user.getId()); + + // then + assertThat(response.chatRooms()).isEmpty(); + } + + @Test + void 최신_메시지_순으로_정렬되어_조회한다() { + // given + ChatRoom chatRoom1 = chatRoomFixture.채팅방(false); + chatParticipantFixture.참여자(user.getId(), chatRoom1); + chatParticipantFixture.참여자(mentor1.getId(), chatRoom1); + ChatMessage oldMessage = chatMessageFixture.메시지("오래된 메시지", mentor1.getId(), chatRoom1); + + ChatRoom chatRoom2 = chatRoomFixture.채팅방(false); + chatParticipantFixture.참여자(user.getId(), chatRoom2); + chatParticipantFixture.참여자(mentor2.getId(), chatRoom2); + ChatMessage newMessage = chatMessageFixture.메시지("최신 메시지", mentor2.getId(), chatRoom2); + + // when + ChatRoomListResponse response = chatService.getChatRooms(user.getId()); + + // then + assertAll( + () -> assertThat(response.chatRooms()).hasSize(2), + () -> assertThat(response.chatRooms().get(0).partner().partnerId()).isEqualTo(mentor2.getId()), + () -> assertThat(response.chatRooms().get(0).lastChatMessage()).isEqualTo(newMessage.getContent()), + () -> assertThat(response.chatRooms().get(1).partner().partnerId()).isEqualTo(mentor1.getId()), + () -> assertThat(response.chatRooms().get(1).lastChatMessage()).isEqualTo(oldMessage.getContent()) + ); + } + + @Test + void 그룹_채팅방은_제외하고_1대1_채팅방만_조회한다() { + // given + ChatRoom oneOnOneRoom = chatRoomFixture.채팅방(false); + chatParticipantFixture.참여자(user.getId(), oneOnOneRoom); + chatParticipantFixture.참여자(mentor1.getId(), oneOnOneRoom); + + ChatRoom groupRoom = chatRoomFixture.채팅방(true); + chatParticipantFixture.참여자(user.getId(), groupRoom); + chatParticipantFixture.참여자(mentor1.getId(), groupRoom); + chatParticipantFixture.참여자(mentor2.getId(), groupRoom); + + // when + ChatRoomListResponse response = chatService.getChatRooms(user.getId()); + + // then + assertAll( + () -> assertThat(response.chatRooms()).hasSize(1), + () -> assertThat(response.chatRooms().get(0).id()).isEqualTo(oneOnOneRoom.getId()) + ); + } + + @Test + void 채팅_상대방이_없으면_예외가_발생한다() { + // given + ChatRoom chatRoom = chatRoomFixture.채팅방(false); + chatParticipantFixture.참여자(user.getId(), chatRoom); + + // when & then + assertThatCode(() -> chatService.getChatRooms(user.getId())) + .isInstanceOf(CustomException.class) + .hasMessage(CHAT_PARTNER_NOT_FOUND.getMessage()); + } + } + + @Nested + class 읽지_않은_메시지_수를_조회한다 { + + private ChatRoom chatRoom; + private ChatParticipant participant; + + @BeforeEach + void setUp() { + chatRoom = chatRoomFixture.채팅방(false); + participant = chatParticipantFixture.참여자(user.getId(), chatRoom); + chatParticipantFixture.참여자(mentor1.getId(), chatRoom); + } + + @Test + void 읽음_상태가_없으면_모든_상대방_메시지를_카운팅한다() { + // given + chatMessageFixture.메시지("메시지1", mentor1.getId(), chatRoom); + chatMessageFixture.메시지("메시지2", mentor1.getId(), chatRoom); + + // when + ChatRoomListResponse response = chatService.getChatRooms(user.getId()); + + // then + assertThat(response.chatRooms().get(0).unReadCount()).isEqualTo(2); + } + + @Test + void 읽음_상태_이후_메시지만_읽지_않은_메시지로_카운팅한다() { + // given + chatMessageFixture.메시지("읽은 메시지", mentor1.getId(), chatRoom); + chatReadStatusFixture.읽음상태(chatRoom.getId(), participant.getId()); + + chatMessageFixture.메시지("읽지 않은 메시지1", mentor1.getId(), chatRoom); + chatMessageFixture.메시지("읽지 않은 메시지2", mentor1.getId(), chatRoom); + + // when + ChatRoomListResponse response = chatService.getChatRooms(user.getId()); + + // then + assertThat(response.chatRooms().get(0).unReadCount()).isEqualTo(2); + } + } + + @Nested + class 채팅_메시지를_조회한다 { + + private static final int NO_NEXT_PAGE_NUMBER = -1; + + private ChatRoom chatRoom; + private Pageable pageable; + + @BeforeEach + void setUp() { + chatRoom = chatRoomFixture.채팅방(false); + chatParticipantFixture.참여자(user.getId(), chatRoom); + chatParticipantFixture.참여자(mentor1.getId(), chatRoom); + + pageable = PageRequest.of(0, 20, Sort.by(Sort.Direction.DESC, "createdAt")); + } + + @Test + void 메시지가_없는_채팅방에서_빈_목록을_반환한다() { + // when + SliceResponse response = chatService.getChatMessages(user.getId(), chatRoom.getId(), pageable); + + // then + assertAll( + () -> assertThat(response.content()).isEmpty(), + () -> assertThat(response.nextPageNumber()).isEqualTo(NO_NEXT_PAGE_NUMBER) + ); + } + + @Test + void 첨부파일이_없는_메시지들을_정상_조회한다() { + // given + ChatMessage message1 = chatMessageFixture.메시지("메시지1", mentor1.getId(), chatRoom); + ChatMessage message2 = chatMessageFixture.메시지("메시지2", user.getId(), chatRoom); + + // when + SliceResponse response = chatService.getChatMessages(user.getId(), chatRoom.getId(), pageable); + + // then + assertAll( + () -> assertThat(response.content()).hasSize(2), + () -> assertThat(response.content().get(0).content()).isEqualTo(message2.getContent()), + () -> assertThat(response.content().get(0).senderId()).isEqualTo(user.getId()), + () -> assertThat(response.content().get(1).content()).isEqualTo(message1.getContent()), + () -> assertThat(response.content().get(1).senderId()).isEqualTo(mentor1.getId()) + ); + } + + @Test + void 첨부파일이_있는_메시지를_정상_조회한다() { + // given + ChatMessage messageWithImage = chatMessageFixture.메시지("이미지", mentor1.getId(), chatRoom); + ChatAttachment imageAttachment = chatAttachmentFixture.첨부파일( + true, + "https://example.com/image.png", + "https://example.com/thumb.png", + messageWithImage + ); + + // when + SliceResponse response = chatService.getChatMessages(user.getId(), chatRoom.getId(), pageable); + + // then + assertAll( + () -> assertThat(response.content()).hasSize(1), + () -> assertThat(response.content().get(0).content()).isEqualTo(messageWithImage.getContent()), + () -> assertThat(response.content().get(0).attachments()).hasSize(1), + () -> assertThat(response.content().get(0).attachments().get(0).id()).isEqualTo(imageAttachment.getId()) + ); + } + + @Test + void 페이징이_정상_작동한다() { + for (int i = 1; i <= 25; i++) { + chatMessageFixture.메시지("메시지" + i, (i % 2 == 0) ? user.getId() : mentor1.getId(), chatRoom); + } + + Pageable firstPage = PageRequest.of(0, 20, Sort.by(Sort.Direction.DESC, "createdAt")); + Pageable secondPage = PageRequest.of(1, 20, Sort.by(Sort.Direction.DESC, "createdAt")); + + // when + SliceResponse firstResponse = chatService.getChatMessages(user.getId(), chatRoom.getId(), firstPage); + SliceResponse secondResponse = chatService.getChatMessages(user.getId(), chatRoom.getId(), secondPage); + + // then + assertAll( + () -> assertThat(firstResponse.nextPageNumber()).isEqualTo(2), + () -> assertThat(secondResponse.nextPageNumber()).isEqualTo(NO_NEXT_PAGE_NUMBER) + ); + } + + @Test + void 채팅방_참여자가_아니면_예외가_발생한다() { + // when & then + assertThatCode(() -> chatService.getChatMessages(mentor2.getId(), chatRoom.getId(), pageable)) + .isInstanceOf(CustomException.class) + .hasMessage(CHAT_PARTICIPANT_NOT_FOUND.getMessage()); + } + + @Test + void 존재하지_않는_채팅방에_접근하면_예외가_발생한다() { + // given + long nonExistentRoomId = 999L; + + // when & then + assertThatCode(() -> chatService.getChatMessages(user.getId(), nonExistentRoomId, pageable)) + .isInstanceOf(CustomException.class) + .hasMessage(CHAT_PARTICIPANT_NOT_FOUND.getMessage()); + } + } + + @Nested + class 채팅_메시지_읽음을_처리한다 { + + private ChatRoom chatRoom; + private ChatParticipant participant; + + @BeforeEach + void setUp() { + chatRoom = chatRoomFixture.채팅방(false); + participant = chatParticipantFixture.참여자(user.getId(), chatRoom); + chatParticipantFixture.참여자(mentor1.getId(), chatRoom); + } + + @Test + void 처음_읽음_처리_시_새로운_읽음_상태를_생성한다() { + // given + chatMessageFixture.메시지("읽지 않은 메시지1", mentor1.getId(), chatRoom); + chatMessageFixture.메시지("읽지 않은 메시지2", mentor1.getId(), chatRoom); + + // when + chatService.markChatMessagesAsRead(user.getId(), chatRoom.getId()); + + // then + ChatReadStatus afterStatus = chatReadStatusRepositoryForTest + .findByChatRoomIdAndChatParticipantId(chatRoom.getId(), participant.getId()) + .orElseThrow(); + + assertThat(afterStatus.getChatRoomId()).isEqualTo(chatRoom.getId()); + } + + @Test + void 기존_읽음_상태가_있으면_updatedAt을_갱신한다() { + // given + ChatReadStatus chatReadStatus = chatReadStatusFixture.읽음상태(chatRoom.getId(), participant.getId()); + ZonedDateTime updatedAt = chatReadStatus.getUpdatedAt(); + chatMessageFixture.메시지("새로운 메시지", mentor1.getId(), chatRoom); + + // when + chatService.markChatMessagesAsRead(user.getId(), chatRoom.getId()); + + // then + ChatReadStatus updatedStatus = chatReadStatusRepositoryForTest + .findByChatRoomIdAndChatParticipantId(chatRoom.getId(), participant.getId()) + .orElseThrow(); + assertAll( + () -> assertThat(updatedStatus.getId()).isEqualTo(chatReadStatus.getId()), + () -> assertThat(updatedStatus.getUpdatedAt()).isAfter(updatedAt) + ); + } + + @Test + void 채팅방_참여자가_아니면_예외가_발생한다() { + // given + ChatRoom chatRoom = chatRoomFixture.채팅방(false); + chatParticipantFixture.참여자(user.getId(), chatRoom); + chatParticipantFixture.참여자(mentor1.getId(), chatRoom); + + // when & then + assertThatCode(() -> chatService.markChatMessagesAsRead(mentor2.getId(), chatRoom.getId())) + .isInstanceOf(CustomException.class) + .hasMessage(CHAT_PARTICIPANT_NOT_FOUND.getMessage()); + } + } +} From 5a2ad8894887a5b76ef8dfa525d6d82f1f952a77 Mon Sep 17 00:00:00 2001 From: seonghyeok cho <65901319+whqtker@users.noreply.github.com> Date: Sun, 10 Aug 2025 16:43:19 +0900 Subject: [PATCH 65/90] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EC=86=A1=EC=88=98=EC=8B=A0=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#423)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 토픽 주소 변경 - topic/{roomId} -> topic/chat/{roomId} - 의미적 명확성을 위해 * feat: 메시지 전송 DTO 작성 * feat: 메시지 전송 Service 작성 * feat: 메시지 전송 Controller 작성 * chore: 메시지 전송에 대한 컨트롤러 어노테이션을 RestController에서 Controller로 변경 * chore: WebSocket 초기 연결을 위한 HTTP 핸드셰이크에서 인증을 수행하도록 * fix: 핸드셰이크 후 Principal을 WebSocket 세션에 전달하도록 수정 - 이에 컨트롤러 인자로 siteUserId를 받도록 하고, DTO에 senderId를 삭제한다. * fix: 컨트롤러 파라미터 인자로 Principal를 받고, 이후 SiteUserDetails에서 siteUserId를 추출하도록 변경 * fix: DTO를 통해 순환참조 문제 해결 * chore: 실제 구독 권한 TODO 구현 - 검증 로직이 핸들러에서 사용됨에 따라 발생하는 순환 참조를 막기 위해 Lazy 어노테이션을 사용한 생성자를 직접 작성 * chore: 코드 리포매팅 * chore: 미사용 SiteUserPrincipal 제거 외 - 정규표현식을 사용하여 채팅방 ID 추출 - DTO 검증 추가 - 구체화 클래스가 아닌 인터페이스 사용하도록 (DIP) - senderId가 siteUserId가 아니라 chatParticipantId로 설정되도록 변경 * chore: withSockJS 추가 --- .../chat/config/CustomHandshakeHandler.java | 27 +++++++++ .../chat/config/StompHandler.java | 47 +++++++++------- .../chat/config/StompWebSocketConfig.java | 8 ++- .../config/WebSocketHandshakeInterceptor.java | 32 +++++++++++ .../controller/ChatMessageController.java | 32 +++++++++++ .../chat/dto/ChatMessageSendRequest.java | 12 ++++ .../chat/dto/ChatMessageSendResponse.java | 19 +++++++ .../chat/service/ChatService.java | 56 ++++++++++++++++--- .../config/SecurityConfiguration.java | 3 +- 9 files changed, 204 insertions(+), 32 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/chat/config/CustomHandshakeHandler.java create mode 100644 src/main/java/com/example/solidconnection/chat/config/WebSocketHandshakeInterceptor.java create mode 100644 src/main/java/com/example/solidconnection/chat/controller/ChatMessageController.java create mode 100644 src/main/java/com/example/solidconnection/chat/dto/ChatMessageSendRequest.java create mode 100644 src/main/java/com/example/solidconnection/chat/dto/ChatMessageSendResponse.java diff --git a/src/main/java/com/example/solidconnection/chat/config/CustomHandshakeHandler.java b/src/main/java/com/example/solidconnection/chat/config/CustomHandshakeHandler.java new file mode 100644 index 000000000..6c3054355 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/config/CustomHandshakeHandler.java @@ -0,0 +1,27 @@ +package com.example.solidconnection.chat.config; + +import java.security.Principal; +import java.util.Map; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.support.DefaultHandshakeHandler; + +// WebSocket 세션의 Principal을 결정한다. +@Component +public class CustomHandshakeHandler extends DefaultHandshakeHandler { + + @Override + protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, + Map attributes) { + + Object userAttribute = attributes.get("user"); + + if (userAttribute instanceof Principal) { + Principal principal = (Principal) userAttribute; + return principal; + } + + return super.determineUser(request, wsHandler, attributes); + } +} diff --git a/src/main/java/com/example/solidconnection/chat/config/StompHandler.java b/src/main/java/com/example/solidconnection/chat/config/StompHandler.java index 660f01f28..2e99bf9c4 100644 --- a/src/main/java/com/example/solidconnection/chat/config/StompHandler.java +++ b/src/main/java/com/example/solidconnection/chat/config/StompHandler.java @@ -2,10 +2,14 @@ import static com.example.solidconnection.common.exception.ErrorCode.AUTHENTICATION_FAILED; -import com.example.solidconnection.auth.token.JwtTokenProvider; +import com.example.solidconnection.chat.service.ChatService; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.exception.ErrorCode; -import io.jsonwebtoken.Claims; +import com.example.solidconnection.security.authentication.TokenAuthentication; +import com.example.solidconnection.security.userdetails.SiteUserDetails; +import java.security.Principal; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import lombok.RequiredArgsConstructor; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; @@ -18,47 +22,48 @@ @RequiredArgsConstructor public class StompHandler implements ChannelInterceptor { - private final JwtTokenProvider jwtTokenProvider; + private static final Pattern ROOM_ID_PATTERN = Pattern.compile("^/topic/chat/(\\d+)$"); + private final ChatService chatService; @Override public Message preSend(Message message, MessageChannel channel) { final StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); if (StompCommand.CONNECT.equals(accessor.getCommand())) { - Claims claims = validateAndExtractClaims(accessor, AUTHENTICATION_FAILED); + Principal user = accessor.getUser(); + if (user == null) { + throw new CustomException(AUTHENTICATION_FAILED); + } } if (StompCommand.SUBSCRIBE.equals(accessor.getCommand())) { - Claims claims = validateAndExtractClaims(accessor, AUTHENTICATION_FAILED); + Principal user = accessor.getUser(); + if (user == null) { + throw new CustomException(AUTHENTICATION_FAILED); + } - String email = claims.getSubject(); - String destination = accessor.getDestination(); + TokenAuthentication tokenAuthentication = (TokenAuthentication) user; + SiteUserDetails siteUserDetails = (SiteUserDetails) tokenAuthentication.getPrincipal(); - String roomId = extractRoomId(destination); + String destination = accessor.getDestination(); + long roomId = Long.parseLong(extractRoomId(destination)); - // todo: roomId 기반 실제 구독 권한 검사 로직 추가 + chatService.validateChatRoomParticipant(siteUserDetails.getSiteUser().getId(), roomId); } return message; } - private Claims validateAndExtractClaims(StompHeaderAccessor accessor, ErrorCode errorCode) { - String bearerToken = accessor.getFirstNativeHeader("Authorization"); - if (bearerToken == null || !bearerToken.startsWith("Bearer ")) { - throw new CustomException(errorCode); - } - String token = bearerToken.substring(7); - return jwtTokenProvider.parseClaims(token); - } - private String extractRoomId(String destination) { if (destination == null) { throw new CustomException(ErrorCode.INVALID_ROOM_ID); } - String[] parts = destination.split("/"); - if (parts.length < 3 || !parts[1].equals("topic")) { + + Matcher matcher = ROOM_ID_PATTERN.matcher(destination); + if (!matcher.matches()) { throw new CustomException(ErrorCode.INVALID_ROOM_ID); } - return parts[2]; + + return matcher.group(1); } } diff --git a/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java b/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java index 86b6eef5d..51259a0e1 100644 --- a/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java +++ b/src/main/java/com/example/solidconnection/chat/config/StompWebSocketConfig.java @@ -22,12 +22,18 @@ public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer { private final StompHandler stompHandler; private final StompProperties stompProperties; private final CorsProperties corsProperties; + private final WebSocketHandshakeInterceptor webSocketHandshakeInterceptor; + private final CustomHandshakeHandler customHandshakeHandler; @Override public void registerStompEndpoints(StompEndpointRegistry registry) { List strings = corsProperties.allowedOrigins(); String[] allowedOrigins = strings.toArray(String[]::new); - registry.addEndpoint("/connect").setAllowedOrigins(allowedOrigins).withSockJS(); + registry.addEndpoint("/connect") + .setAllowedOrigins(allowedOrigins) + .addInterceptors(webSocketHandshakeInterceptor) + .setHandshakeHandler(customHandshakeHandler) + .withSockJS(); } @Override diff --git a/src/main/java/com/example/solidconnection/chat/config/WebSocketHandshakeInterceptor.java b/src/main/java/com/example/solidconnection/chat/config/WebSocketHandshakeInterceptor.java new file mode 100644 index 000000000..9e8aafe2d --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/config/WebSocketHandshakeInterceptor.java @@ -0,0 +1,32 @@ +package com.example.solidconnection.chat.config; + +import java.security.Principal; +import java.util.Map; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; + +// Principal을 WebSocket 세션에 저장하는 것에만 집중한다. +@Component +public class WebSocketHandshakeInterceptor implements HandshakeInterceptor { + + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, + WebSocketHandler wsHandler, Map attributes) { + Principal principal = request.getPrincipal(); + + if (principal != null) { + attributes.put("user", principal); + return true; + } + + return false; + } + + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, + WebSocketHandler wsHandler, Exception exception) { + } +} diff --git a/src/main/java/com/example/solidconnection/chat/controller/ChatMessageController.java b/src/main/java/com/example/solidconnection/chat/controller/ChatMessageController.java new file mode 100644 index 000000000..a7e158224 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/controller/ChatMessageController.java @@ -0,0 +1,32 @@ +package com.example.solidconnection.chat.controller; + +import com.example.solidconnection.chat.dto.ChatMessageSendRequest; +import com.example.solidconnection.chat.service.ChatService; +import com.example.solidconnection.security.authentication.TokenAuthentication; +import com.example.solidconnection.security.userdetails.SiteUserDetails; +import jakarta.validation.Valid; +import java.security.Principal; +import lombok.RequiredArgsConstructor; +import org.springframework.messaging.handler.annotation.DestinationVariable; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.stereotype.Controller; + +@Controller +@RequiredArgsConstructor +public class ChatMessageController { + + private final ChatService chatService; + + @MessageMapping("/chat/{roomId}") + public void sendChatMessage( + @DestinationVariable Long roomId, + @Valid @Payload ChatMessageSendRequest chatMessageSendRequest, + Principal principal + ) { + TokenAuthentication tokenAuthentication = (TokenAuthentication) principal; + SiteUserDetails siteUserDetails = (SiteUserDetails) tokenAuthentication.getPrincipal(); + + chatService.sendChatMessage(chatMessageSendRequest, siteUserDetails.getSiteUser().getId(), roomId); + } +} diff --git a/src/main/java/com/example/solidconnection/chat/dto/ChatMessageSendRequest.java b/src/main/java/com/example/solidconnection/chat/dto/ChatMessageSendRequest.java new file mode 100644 index 000000000..22d652a35 --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/dto/ChatMessageSendRequest.java @@ -0,0 +1,12 @@ +package com.example.solidconnection.chat.dto; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +public record ChatMessageSendRequest( + @NotNull(message = "메시지를 입력해주세요.") + @Size(max = 500, message = "메시지는 500자를 초과할 수 없습니다") + String content +) { + +} diff --git a/src/main/java/com/example/solidconnection/chat/dto/ChatMessageSendResponse.java b/src/main/java/com/example/solidconnection/chat/dto/ChatMessageSendResponse.java new file mode 100644 index 000000000..065c7ba1c --- /dev/null +++ b/src/main/java/com/example/solidconnection/chat/dto/ChatMessageSendResponse.java @@ -0,0 +1,19 @@ +package com.example.solidconnection.chat.dto; + +import com.example.solidconnection.chat.domain.ChatMessage; + +public record ChatMessageSendResponse( + long messageId, + String content, + long senderId +) { + + public static ChatMessageSendResponse from(ChatMessage chatMessage) { + return new ChatMessageSendResponse( + chatMessage.getId(), + chatMessage.getContent(), + chatMessage.getSenderId() + ); + } + +} diff --git a/src/main/java/com/example/solidconnection/chat/service/ChatService.java b/src/main/java/com/example/solidconnection/chat/service/ChatService.java index c378f6b50..fadd284fe 100644 --- a/src/main/java/com/example/solidconnection/chat/service/ChatService.java +++ b/src/main/java/com/example/solidconnection/chat/service/ChatService.java @@ -10,6 +10,8 @@ import com.example.solidconnection.chat.domain.ChatRoom; import com.example.solidconnection.chat.dto.ChatAttachmentResponse; import com.example.solidconnection.chat.dto.ChatMessageResponse; +import com.example.solidconnection.chat.dto.ChatMessageSendRequest; +import com.example.solidconnection.chat.dto.ChatMessageSendResponse; import com.example.solidconnection.chat.dto.ChatParticipantResponse; import com.example.solidconnection.chat.dto.ChatRoomListResponse; import com.example.solidconnection.chat.dto.ChatRoomResponse; @@ -24,13 +26,13 @@ import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; -import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Lazy; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; +import org.springframework.messaging.simp.SimpMessageSendingOperations; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -@RequiredArgsConstructor @Service public class ChatService { @@ -40,6 +42,22 @@ public class ChatService { private final ChatReadStatusRepository chatReadStatusRepository; private final SiteUserRepository siteUserRepository; + private final SimpMessageSendingOperations simpMessageSendingOperations; + + public ChatService(ChatRoomRepository chatRoomRepository, + ChatMessageRepository chatMessageRepository, + ChatParticipantRepository chatParticipantRepository, + ChatReadStatusRepository chatReadStatusRepository, + SiteUserRepository siteUserRepository, + @Lazy SimpMessageSendingOperations simpMessageSendingOperations) { + this.chatRoomRepository = chatRoomRepository; + this.chatMessageRepository = chatMessageRepository; + this.chatParticipantRepository = chatParticipantRepository; + this.chatReadStatusRepository = chatReadStatusRepository; + this.siteUserRepository = siteUserRepository; + this.simpMessageSendingOperations = simpMessageSendingOperations; + } + @Transactional(readOnly = true) public ChatRoomListResponse getChatRooms(long siteUserId) { // todo : n + 1 문제 해결 필요! @@ -89,6 +107,13 @@ public SliceResponse getChatMessages(long siteUserId, long return SliceResponse.of(content, chatMessages); } + public void validateChatRoomParticipant(long siteUserId, long roomId) { + boolean isParticipant = chatParticipantRepository.existsByChatRoomIdAndSiteUserId(roomId, siteUserId); + if (!isParticipant) { + throw new CustomException(CHAT_PARTICIPANT_NOT_FOUND); + } + } + private ChatMessageResponse toChatMessageResponse(ChatMessage message) { List attachments = message.getChatAttachments().stream() .map(attachment -> ChatAttachmentResponse.of( @@ -109,13 +134,6 @@ private ChatMessageResponse toChatMessageResponse(ChatMessage message) { ); } - private void validateChatRoomParticipant(long siteUserId, long roomId) { - boolean isParticipant = chatParticipantRepository.existsByChatRoomIdAndSiteUserId(roomId, siteUserId); - if (!isParticipant) { - throw new CustomException(CHAT_PARTICIPANT_NOT_FOUND); - } - } - @Transactional public void markChatMessagesAsRead(long siteUserId, long roomId) { ChatParticipant participant = chatParticipantRepository @@ -124,4 +142,24 @@ public void markChatMessagesAsRead(long siteUserId, long roomId) { chatReadStatusRepository.upsertReadStatus(roomId, participant.getId()); } + + @Transactional + public void sendChatMessage(ChatMessageSendRequest chatMessageSendRequest, long siteUserId, long roomId) { + long senderId = chatParticipantRepository.findByChatRoomIdAndSiteUserId(roomId, siteUserId) + .orElseThrow(() -> new CustomException(CHAT_PARTICIPANT_NOT_FOUND)) + .getId(); + + ChatMessage chatMessage = new ChatMessage( + chatMessageSendRequest.content(), + senderId, + chatRoomRepository.findById(roomId) + .orElseThrow(() -> new CustomException(INVALID_CHAT_ROOM_STATE)) + ); + + chatMessageRepository.save(chatMessage); + + ChatMessageSendResponse chatMessageResponse = ChatMessageSendResponse.from(chatMessage); + + simpMessageSendingOperations.convertAndSend("/topic/chat/" + roomId, chatMessageResponse); + } } diff --git a/src/main/java/com/example/solidconnection/security/config/SecurityConfiguration.java b/src/main/java/com/example/solidconnection/security/config/SecurityConfiguration.java index 3667e9d84..706fedd52 100644 --- a/src/main/java/com/example/solidconnection/security/config/SecurityConfiguration.java +++ b/src/main/java/com/example/solidconnection/security/config/SecurityConfiguration.java @@ -5,8 +5,8 @@ import com.example.solidconnection.common.exception.CustomAccessDeniedHandler; import com.example.solidconnection.common.exception.CustomAuthenticationEntryPoint; import com.example.solidconnection.security.filter.ExceptionHandlerFilter; -import com.example.solidconnection.security.filter.TokenAuthenticationFilter; import com.example.solidconnection.security.filter.SignOutCheckFilter; +import com.example.solidconnection.security.filter.TokenAuthenticationFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -62,6 +62,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .cors(corsConfigurer -> corsConfigurer.configurationSource(corsConfigurationSource())) .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth + .requestMatchers("/connect/**").authenticated() .requestMatchers("/admin/**").hasRole(ADMIN.name()) .anyRequest().permitAll() ) From 1e230e44b4865cfe1e62b9f43289fd11e6e6d515 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Mon, 11 Aug 2025 14:25:03 +0900 Subject: [PATCH 66/90] =?UTF-8?q?refactor:=20OAuth=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EC=97=90=20=EC=A0=84=EB=9E=B5=20=ED=8C=A8=ED=84=B4=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20(#432)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 전략패턴 적용 * refactor: 컨트롤러에 변경 내용 적용 * chore: 사용되지 않는 코드 제거 * test: 테스트 코드 작성 --- .../auth/client/AppleOAuthClient.java | 15 +++- .../auth/client/KakaoOAuthClient.java | 13 ++- .../auth/controller/AuthController.java | 10 +-- .../auth/service/oauth/AppleOAuthService.java | 30 ------- .../auth/service/oauth/KakaoOAuthService.java | 30 ------- .../auth/service/oauth/OAuthClient.java | 11 +++ .../auth/service/oauth/OAuthClientMap.java | 32 +++++++ .../auth/service/oauth/OAuthService.java | 34 +++---- .../service/oauth/OAuthClientMapTest.java | 53 +++++++++++ .../auth/service/oauth/OAuthServiceTest.java | 88 +++++++++++++++++++ 10 files changed, 225 insertions(+), 91 deletions(-) delete mode 100644 src/main/java/com/example/solidconnection/auth/service/oauth/AppleOAuthService.java delete mode 100644 src/main/java/com/example/solidconnection/auth/service/oauth/KakaoOAuthService.java create mode 100644 src/main/java/com/example/solidconnection/auth/service/oauth/OAuthClient.java create mode 100644 src/main/java/com/example/solidconnection/auth/service/oauth/OAuthClientMap.java create mode 100644 src/test/java/com/example/solidconnection/auth/service/oauth/OAuthClientMapTest.java create mode 100644 src/test/java/com/example/solidconnection/auth/service/oauth/OAuthServiceTest.java diff --git a/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClient.java b/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClient.java index 41c4eec4e..48009cc82 100644 --- a/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClient.java +++ b/src/main/java/com/example/solidconnection/auth/client/AppleOAuthClient.java @@ -6,7 +6,10 @@ import com.example.solidconnection.auth.client.config.AppleOAuthClientProperties; import com.example.solidconnection.auth.dto.oauth.AppleTokenDto; import com.example.solidconnection.auth.dto.oauth.AppleUserInfoDto; +import com.example.solidconnection.auth.dto.oauth.OAuthUserInfoDto; +import com.example.solidconnection.auth.service.oauth.OAuthClient; import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.siteuser.domain.AuthType; import io.jsonwebtoken.Jwts; import java.security.PublicKey; import java.util.Objects; @@ -27,20 +30,26 @@ * */ @Component @RequiredArgsConstructor -public class AppleOAuthClient { +public class AppleOAuthClient implements OAuthClient { private final RestTemplate restTemplate; private final AppleOAuthClientProperties properties; private final AppleOAuthClientSecretProvider clientSecretProvider; private final ApplePublicKeyProvider publicKeyProvider; - public AppleUserInfoDto processOAuth(String code) { + @Override + public AuthType getAuthType() { + return AuthType.APPLE; + } + + @Override + public OAuthUserInfoDto getUserInfo(String code) { String idToken = requestIdToken(code); PublicKey applePublicKey = publicKeyProvider.getApplePublicKey(idToken); return new AppleUserInfoDto(parseEmailFromToken(applePublicKey, idToken)); } - public String requestIdToken(String code) { + private String requestIdToken(String code) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); MultiValueMap formData = buildFormData(code); diff --git a/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java b/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java index eb30746b4..a25743f7d 100644 --- a/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java +++ b/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java @@ -7,7 +7,10 @@ import com.example.solidconnection.auth.client.config.KakaoOAuthClientProperties; import com.example.solidconnection.auth.dto.oauth.KakaoTokenDto; import com.example.solidconnection.auth.dto.oauth.KakaoUserInfoDto; +import com.example.solidconnection.auth.dto.oauth.OAuthUserInfoDto; +import com.example.solidconnection.auth.service.oauth.OAuthClient; import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.siteuser.domain.AuthType; import java.util.Objects; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpEntity; @@ -27,12 +30,18 @@ * */ @Component @RequiredArgsConstructor -public class KakaoOAuthClient { +public class KakaoOAuthClient implements OAuthClient { private final RestTemplate restTemplate; private final KakaoOAuthClientProperties kakaoOAuthClientProperties; - public KakaoUserInfoDto getUserInfo(String code) { + @Override + public AuthType getAuthType() { + return AuthType.KAKAO; + } + + @Override + public OAuthUserInfoDto getUserInfo(String code) { String kakaoAccessToken = getKakaoAccessToken(code); return getKakaoUserInfo(kakaoAccessToken); } diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index 2e581ab71..085343dfd 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -15,8 +15,7 @@ import com.example.solidconnection.auth.service.EmailSignInService; import com.example.solidconnection.auth.service.EmailSignUpService; import com.example.solidconnection.auth.service.EmailSignUpTokenProvider; -import com.example.solidconnection.auth.service.oauth.AppleOAuthService; -import com.example.solidconnection.auth.service.oauth.KakaoOAuthService; +import com.example.solidconnection.auth.service.oauth.OAuthService; import com.example.solidconnection.auth.service.oauth.OAuthSignUpService; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.exception.ErrorCode; @@ -40,8 +39,7 @@ public class AuthController { private final AuthService authService; private final OAuthSignUpService oAuthSignUpService; - private final AppleOAuthService appleOAuthService; - private final KakaoOAuthService kakaoOAuthService; + private final OAuthService oAuthService; private final EmailSignInService emailSignInService; private final EmailSignUpService emailSignUpService; private final EmailSignUpTokenProvider emailSignUpTokenProvider; @@ -53,7 +51,7 @@ public ResponseEntity processAppleOAuth( @Valid @RequestBody OAuthCodeRequest oAuthCodeRequest, HttpServletResponse httpServletResponse ) { - OAuthResponse oAuthResponse = appleOAuthService.processOAuth(oAuthCodeRequest); + OAuthResponse oAuthResponse = oAuthService.processOAuth(AuthType.APPLE, oAuthCodeRequest); if (oAuthResponse instanceof OAuthSignInResponse signInResponse) { refreshTokenCookieManager.setCookie(httpServletResponse, signInResponse.refreshToken()); } @@ -65,7 +63,7 @@ public ResponseEntity processKakaoOAuth( @Valid @RequestBody OAuthCodeRequest oAuthCodeRequest, HttpServletResponse httpServletResponse ) { - OAuthResponse oAuthResponse = kakaoOAuthService.processOAuth(oAuthCodeRequest); + OAuthResponse oAuthResponse = oAuthService.processOAuth(AuthType.KAKAO, oAuthCodeRequest); if (oAuthResponse instanceof OAuthSignInResponse signInResponse) { refreshTokenCookieManager.setCookie(httpServletResponse, signInResponse.refreshToken()); } diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/AppleOAuthService.java b/src/main/java/com/example/solidconnection/auth/service/oauth/AppleOAuthService.java deleted file mode 100644 index 2605ad89f..000000000 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/AppleOAuthService.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.example.solidconnection.auth.service.oauth; - -import com.example.solidconnection.auth.client.AppleOAuthClient; -import com.example.solidconnection.auth.dto.oauth.OAuthUserInfoDto; -import com.example.solidconnection.auth.service.SignInService; -import com.example.solidconnection.siteuser.domain.AuthType; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import org.springframework.stereotype.Service; - -@Service -public class AppleOAuthService extends OAuthService { - - private final AppleOAuthClient appleOAuthClient; - - public AppleOAuthService(OAuthSignUpTokenProvider OAuthSignUpTokenProvider, SiteUserRepository siteUserRepository, - AppleOAuthClient appleOAuthClient, SignInService signInService) { - super(OAuthSignUpTokenProvider, siteUserRepository, signInService); - this.appleOAuthClient = appleOAuthClient; - } - - @Override - protected OAuthUserInfoDto getOAuthUserInfo(String code) { - return appleOAuthClient.processOAuth(code); - } - - @Override - protected AuthType getAuthType() { - return AuthType.APPLE; - } -} diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/KakaoOAuthService.java b/src/main/java/com/example/solidconnection/auth/service/oauth/KakaoOAuthService.java deleted file mode 100644 index c2202ab2a..000000000 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/KakaoOAuthService.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.example.solidconnection.auth.service.oauth; - -import com.example.solidconnection.auth.client.KakaoOAuthClient; -import com.example.solidconnection.auth.dto.oauth.OAuthUserInfoDto; -import com.example.solidconnection.auth.service.SignInService; -import com.example.solidconnection.siteuser.domain.AuthType; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import org.springframework.stereotype.Service; - -@Service -public class KakaoOAuthService extends OAuthService { - - private final KakaoOAuthClient kakaoOAuthClient; - - public KakaoOAuthService(OAuthSignUpTokenProvider OAuthSignUpTokenProvider, SiteUserRepository siteUserRepository, - KakaoOAuthClient kakaoOAuthClient, SignInService signInService) { - super(OAuthSignUpTokenProvider, siteUserRepository, signInService); - this.kakaoOAuthClient = kakaoOAuthClient; - } - - @Override - protected OAuthUserInfoDto getOAuthUserInfo(String code) { - return kakaoOAuthClient.getUserInfo(code); - } - - @Override - protected AuthType getAuthType() { - return AuthType.KAKAO; - } -} diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthClient.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthClient.java new file mode 100644 index 000000000..a62872f0c --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthClient.java @@ -0,0 +1,11 @@ +package com.example.solidconnection.auth.service.oauth; + +import com.example.solidconnection.auth.dto.oauth.OAuthUserInfoDto; +import com.example.solidconnection.siteuser.domain.AuthType; + +public interface OAuthClient { + + OAuthUserInfoDto getUserInfo(String code); + + AuthType getAuthType(); +} diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthClientMap.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthClientMap.java new file mode 100644 index 000000000..45e510136 --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthClientMap.java @@ -0,0 +1,32 @@ +package com.example.solidconnection.auth.service.oauth; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; +import com.example.solidconnection.siteuser.domain.AuthType; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.springframework.stereotype.Component; + +@Component +public class OAuthClientMap { + + private final Map oauthClientMap; + + public OAuthClientMap(List oAuthClientList) { + this.oauthClientMap = oAuthClientList.stream() + .collect(Collectors.toMap(OAuthClient::getAuthType, Function.identity())); + } + + public OAuthClient getOAuthClient(AuthType authType) { + OAuthClient oauthClient = oauthClientMap.get(authType); + if (oauthClient == null) { + throw new CustomException( + ErrorCode.NOT_DEFINED_ERROR, + "처리할 수 있는 OAuthClient가 없습니다. authType: " + authType.name() + ); + } + return oauthClient; + } +} diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java index d02377341..bb34a3739 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java @@ -1,6 +1,5 @@ package com.example.solidconnection.auth.service.oauth; - import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.auth.dto.oauth.OAuthCodeRequest; import com.example.solidconnection.auth.dto.oauth.OAuthResponse; @@ -12,6 +11,8 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /* @@ -19,43 +20,36 @@ * 기존 회원 : 로그인한다. * 신규 회원 : 회원가입할 때 필요한 정보를 제공한다. * */ -public abstract class OAuthService { +@Service +@RequiredArgsConstructor +public class OAuthService { private final OAuthSignUpTokenProvider OAuthSignUpTokenProvider; private final SignInService signInService; private final SiteUserRepository siteUserRepository; - - protected OAuthService(OAuthSignUpTokenProvider OAuthSignUpTokenProvider, SiteUserRepository siteUserRepository, SignInService signInService) { - this.OAuthSignUpTokenProvider = OAuthSignUpTokenProvider; - this.siteUserRepository = siteUserRepository; - this.signInService = signInService; - } + private final OAuthClientMap oauthClientMap; @Transactional - public OAuthResponse processOAuth(OAuthCodeRequest oauthCodeRequest) { - OAuthUserInfoDto userInfoDto = getOAuthUserInfo(oauthCodeRequest.code()); - String email = userInfoDto.getEmail(); - Optional optionalSiteUser = siteUserRepository.findByEmailAndAuthType(email, getAuthType()); + public OAuthResponse processOAuth(AuthType authType, OAuthCodeRequest codeRequest) { + OAuthClient oauthClient = oauthClientMap.getOAuthClient(authType); + OAuthUserInfoDto userInfo = oauthClient.getUserInfo(codeRequest.code()); + Optional optionalSiteUser = siteUserRepository.findByEmailAndAuthType(userInfo.getEmail(), authType); if (optionalSiteUser.isPresent()) { SiteUser siteUser = optionalSiteUser.get(); return getSignInResponse(siteUser); } - return getSignUpPrepareResponse(userInfoDto); + return getSignUpPrepareResponse(userInfo, authType); } - protected final OAuthSignInResponse getSignInResponse(SiteUser siteUser) { + private OAuthSignInResponse getSignInResponse(SiteUser siteUser) { SignInResponse signInResponse = signInService.signIn(siteUser); return new OAuthSignInResponse(true, signInResponse.accessToken(), signInResponse.refreshToken()); } - protected final SignUpPrepareResponse getSignUpPrepareResponse(OAuthUserInfoDto userInfoDto) { - String signUpToken = OAuthSignUpTokenProvider.generateAndSaveSignUpToken(userInfoDto.getEmail(), getAuthType()); + private SignUpPrepareResponse getSignUpPrepareResponse(OAuthUserInfoDto userInfoDto, AuthType authType) { + String signUpToken = OAuthSignUpTokenProvider.generateAndSaveSignUpToken(userInfoDto.getEmail(), authType); return SignUpPrepareResponse.of(userInfoDto, signUpToken); } - - protected abstract OAuthUserInfoDto getOAuthUserInfo(String code); - - protected abstract AuthType getAuthType(); } diff --git a/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthClientMapTest.java b/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthClientMapTest.java new file mode 100644 index 000000000..da4ce868b --- /dev/null +++ b/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthClientMapTest.java @@ -0,0 +1,53 @@ +package com.example.solidconnection.auth.service.oauth; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; +import com.example.solidconnection.siteuser.domain.AuthType; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("OAuthClientMap 테스트") +class OAuthClientMapTest { + + @Test + void AuthType에_해당하는_Client를_반환한다() { + // given + OAuthClient appleClient = mock(OAuthClient.class); + OAuthClient kakaoClient = mock(OAuthClient.class); + given(appleClient.getAuthType()).willReturn(AuthType.APPLE); + given(kakaoClient.getAuthType()).willReturn(AuthType.KAKAO); + + OAuthClientMap oAuthClientMap = new OAuthClientMap( + List.of(appleClient, kakaoClient) + ); + + // when & then + assertAll( + () -> assertThat(oAuthClientMap.getOAuthClient(AuthType.APPLE)).isEqualTo(appleClient), + () -> assertThat(oAuthClientMap.getOAuthClient(AuthType.KAKAO)).isEqualTo(kakaoClient) + ); + } + + @Test + void AuthType에_매칭되는_Client가_없으면_예외가_발생한다() { + // given + OAuthClient appleClient = mock(OAuthClient.class); + given(appleClient.getAuthType()).willReturn(AuthType.APPLE); + + OAuthClientMap oAuthClientMap = new OAuthClientMap( + List.of(appleClient) + ); + + // when & then + assertThatCode(() -> oAuthClientMap.getOAuthClient(AuthType.KAKAO)) + .isInstanceOf(CustomException.class) + .hasMessageContaining(ErrorCode.NOT_DEFINED_ERROR.getMessage()); + } +} diff --git a/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthServiceTest.java new file mode 100644 index 000000000..427701399 --- /dev/null +++ b/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthServiceTest.java @@ -0,0 +1,88 @@ +package com.example.solidconnection.auth.service.oauth; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +import com.example.solidconnection.auth.dto.oauth.OAuthCodeRequest; +import com.example.solidconnection.auth.dto.oauth.OAuthResponse; +import com.example.solidconnection.auth.dto.oauth.OAuthSignInResponse; +import com.example.solidconnection.auth.dto.oauth.OAuthUserInfoDto; +import com.example.solidconnection.auth.dto.oauth.SignUpPrepareResponse; +import com.example.solidconnection.siteuser.domain.AuthType; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.BeforeEach; +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.MockBean; + +@DisplayName("OAuth 서비스 테스트") +@TestContainerSpringBootTest +class OAuthServiceTest { + + @Autowired + private OAuthService oAuthService; + + @Autowired + private SiteUserFixture siteUserFixture; + + @MockBean + private OAuthClientMap oauthClientMap; + + private final AuthType authType = AuthType.KAKAO; + private final String oauthCode = "code"; + private final String email = "test@test.com"; + private final String profileImageUrl = "profile.jpg"; + private final String nickname = "testUser"; + + @BeforeEach + void setUp() { // 실제 client 호출하지 않도록 mocking + OAuthUserInfoDto oauthUserInfoDto = mock(OAuthUserInfoDto.class); + given(oauthUserInfoDto.getEmail()).willReturn(email); + given(oauthUserInfoDto.getProfileImageUrl()).willReturn(profileImageUrl); + given(oauthUserInfoDto.getNickname()).willReturn(nickname); + + OAuthClient oAuthClient = mock(OAuthClient.class); + given(oauthClientMap.getOAuthClient(authType)).willReturn(oAuthClient); + given(oAuthClient.getAuthType()).willReturn(authType); + given(oAuthClient.getUserInfo(oauthCode)).willReturn(oauthUserInfoDto); + } + + @Test + void 기존_회원이라면_로그인한다() { + // given + siteUserFixture.사용자(email, authType); + + // when + OAuthResponse response = oAuthService.processOAuth(authType, new OAuthCodeRequest(oauthCode)); + + // then + assertThat(response).isInstanceOf(OAuthSignInResponse.class); + OAuthSignInResponse signInResponse = (OAuthSignInResponse) response; + assertAll( + () -> assertThat(signInResponse.isRegistered()).isTrue(), + () -> assertThat(signInResponse.accessToken()).isNotBlank(), + () -> assertThat(signInResponse.refreshToken()).isNotBlank() + ); + } + + @Test + void 신규_회원이라면_회원가입에_필요한_정보를_응답한다() { + // when + OAuthResponse response = oAuthService.processOAuth(authType, new OAuthCodeRequest(oauthCode)); + + // then + assertThat(response).isInstanceOf(SignUpPrepareResponse.class); + SignUpPrepareResponse signUpPrepareResponse = (SignUpPrepareResponse) response; + assertAll( + () -> assertThat(signUpPrepareResponse.isRegistered()).isFalse(), + () -> assertThat(signUpPrepareResponse.signUpToken()).isNotBlank(), + () -> assertThat(signUpPrepareResponse.email()).isEqualTo(email), + () -> assertThat(signUpPrepareResponse.profileImageUrl()).isEqualTo(profileImageUrl), + () -> assertThat(signUpPrepareResponse.nickname()).isEqualTo(nickname) + ); + } +} From 51d1d067a50db7ce223db29aadcad02f0b8d020e Mon Sep 17 00:00:00 2001 From: seonghyeok cho <65901319+whqtker@users.noreply.github.com> Date: Mon, 11 Aug 2025 16:01:51 +0900 Subject: [PATCH 67/90] =?UTF-8?q?chore:=20=EC=A0=84=EC=B2=B4=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EC=B2=AB?= =?UTF-8?q?=20=EB=B2=88=EC=A7=B8=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20url?= =?UTF-8?q?=EC=97=90=20=ED=95=B4=EB=8B=B9=ED=95=98=EB=8A=94=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=EB=AA=85=20=EB=B3=80=EA=B2=BD=20(#433)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: url -> postThumbnailUrl 로 이름 변경 * test: 전체 게시글 조회 시 첫 번째 이미지를 썸네일로 설정하는지 확인하는 테스트 코드 작성 * test: 하나의 테스트가 두 개의 동작을 검증하지 않고, 하나의 동작만 검증하도록 --- .../community/post/dto/PostListResponse.java | 2 +- .../post/service/PostQueryServiceTest.java | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/community/post/dto/PostListResponse.java b/src/main/java/com/example/solidconnection/community/post/dto/PostListResponse.java index 89fb20aab..7c4871419 100644 --- a/src/main/java/com/example/solidconnection/community/post/dto/PostListResponse.java +++ b/src/main/java/com/example/solidconnection/community/post/dto/PostListResponse.java @@ -15,7 +15,7 @@ public record PostListResponse( ZonedDateTime createdAt, ZonedDateTime updatedAt, String postCategory, - String url + String postThumbnailUrl ) { public static PostListResponse from(Post post) { diff --git a/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java b/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java index 2377847d6..f5e1bb45b 100644 --- a/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/community/post/service/PostQueryServiceTest.java @@ -166,4 +166,44 @@ void setUp() { () -> assertThat(redisService.isKeyExists(validateKey)).isTrue() ); } + + @Test + void 게시글_목록_조회시_첫번째_이미지를_썸네일로_반환한다() { + // given + String firstImageUrl = "first-thumbnail-url"; + String secondImageUrl = "second-thumbnail-url"; + postImageFixture.게시글_이미지(firstImageUrl, post1); + postImageFixture.게시글_이미지(secondImageUrl, post1); + + // when + List actualResponses = postQueryService.findPostsByCodeAndPostCategory( + BoardCode.FREE.name(), + PostCategory.전체.name() + ); + + // then + PostListResponse postResponse = actualResponses.stream() + .filter(p -> p.id().equals(post1.getId())) + .findFirst() + .orElseThrow(); + + assertThat(postResponse.postThumbnailUrl()).isEqualTo(firstImageUrl); + } + + @Test + void 게시글에_이미지가_없다면_썸네일로_null을_반환한다() { + // when + List actualResponses = postQueryService.findPostsByCodeAndPostCategory( + BoardCode.FREE.name(), + PostCategory.전체.name() + ); + + // then + PostListResponse postResponse = actualResponses.stream() + .filter(p -> p.id().equals(post3.getId())) + .findFirst() + .orElseThrow(); + + assertThat(postResponse.postThumbnailUrl()).isNull(); + } } From d5a9f4963714a80013e36cc44458debfc7b44ae0 Mon Sep 17 00:00:00 2001 From: seonghyeok cho <65901319+whqtker@users.noreply.github.com> Date: Tue, 12 Aug 2025 10:24:37 +0900 Subject: [PATCH 68/90] =?UTF-8?q?test:=20WebSocket=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20(#440)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: WebSocket - STOMP 통합 테스트 작성 * test: 메시지 송신 테스트 * chore: 코드 리포매팅 * chore: Mockito -> BDDMockito로 변경 --- .../chat/service/ChatServiceTest.java | 58 +++++++++ .../WebSocketStompIntegrationTest.java | 114 ++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java diff --git a/src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java b/src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java index 77120e884..667a02d5e 100644 --- a/src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java +++ b/src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java @@ -12,6 +12,8 @@ import com.example.solidconnection.chat.domain.ChatReadStatus; import com.example.solidconnection.chat.domain.ChatRoom; import com.example.solidconnection.chat.dto.ChatMessageResponse; +import com.example.solidconnection.chat.dto.ChatMessageSendRequest; +import com.example.solidconnection.chat.dto.ChatMessageSendResponse; import com.example.solidconnection.chat.dto.ChatRoomListResponse; import com.example.solidconnection.chat.fixture.ChatAttachmentFixture; import com.example.solidconnection.chat.fixture.ChatMessageFixture; @@ -29,10 +31,14 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.BDDMockito; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.messaging.simp.SimpMessagingTemplate; @TestContainerSpringBootTest @DisplayName("채팅 서비스 테스트") @@ -62,6 +68,9 @@ class ChatServiceTest { @Autowired private ChatAttachmentFixture chatAttachmentFixture; + @MockBean + private SimpMessagingTemplate simpMessagingTemplate; + private SiteUser user; private SiteUser mentor1; private SiteUser mentor2; @@ -363,4 +372,53 @@ void setUp() { .hasMessage(CHAT_PARTICIPANT_NOT_FOUND.getMessage()); } } + + @Nested + class 채팅_메시지를_전송한다 { + + private SiteUser sender; + private ChatParticipant senderParticipant; + private ChatRoom chatRoom; + + @BeforeEach + void setUp() { + sender = siteUserFixture.사용자(111, "sender"); + chatRoom = chatRoomFixture.채팅방(false); + senderParticipant = chatParticipantFixture.참여자(sender.getId(), chatRoom); + } + + @Test + void 채팅방_참여자는_메시지를_전송할_수_있다() { + // given + final String content = "안녕하세요"; + ChatMessageSendRequest request = new ChatMessageSendRequest(content); + + // when + chatService.sendChatMessage(request, sender.getId(), chatRoom.getId()); + + // then + ArgumentCaptor destinationCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor payloadCaptor = ArgumentCaptor.forClass(ChatMessageSendResponse.class); + + BDDMockito.verify(simpMessagingTemplate).convertAndSend(destinationCaptor.capture(), payloadCaptor.capture()); + + assertAll( + () -> assertThat(destinationCaptor.getValue()).isEqualTo("/topic/chat/" + chatRoom.getId()), + () -> assertThat(payloadCaptor.getValue().content()).isEqualTo(content), + () -> assertThat(payloadCaptor.getValue().senderId()).isEqualTo(senderParticipant.getId()) + ); + } + + @Test + void 채팅_참여자가_아니면_메시지를_전송할_수_없다() { + // given + SiteUser nonParticipant = siteUserFixture.사용자(333, "nonParticipant"); + ChatMessageSendRequest request = new ChatMessageSendRequest("안녕하세요"); + + // when & then + assertThatCode(() -> chatService.sendChatMessage(request, nonParticipant.getId(), chatRoom.getId())) + .isInstanceOf(CustomException.class) + .hasMessage(CHAT_PARTICIPANT_NOT_FOUND.getMessage()); + } + } } diff --git a/src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java b/src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java new file mode 100644 index 000000000..c74d84534 --- /dev/null +++ b/src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java @@ -0,0 +1,114 @@ +package com.example.solidconnection.websocket; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.ThrowableAssert.catchThrowable; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.example.solidconnection.auth.service.AccessToken; +import com.example.solidconnection.auth.service.AuthTokenProvider; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutionException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.messaging.converter.MappingJackson2MessageConverter; +import org.springframework.messaging.simp.stomp.StompHeaders; +import org.springframework.messaging.simp.stomp.StompSession; +import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.socket.WebSocketHttpHeaders; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; +import org.springframework.web.socket.messaging.WebSocketStompClient; +import org.springframework.web.socket.sockjs.client.SockJsClient; +import org.springframework.web.socket.sockjs.client.Transport; +import org.springframework.web.socket.sockjs.client.WebSocketTransport; + +@TestContainerSpringBootTest +@DisplayName("WebSocket/STOMP 통합 테스트") +class WebSocketStompIntegrationTest { + + @LocalServerPort + private int port; + private String url; + private WebSocketStompClient stompClient; + private StompSession stompSession; + + @Autowired + private AuthTokenProvider authTokenProvider; + + @Autowired + private SiteUserFixture siteUserFixture; + + @BeforeEach + void setUp() { + this.url = String.format("ws://localhost:%d/connect", port); + List transports = List.of(new WebSocketTransport(new StandardWebSocketClient())); + this.stompClient = new WebSocketStompClient(new SockJsClient(transports)); + this.stompClient.setMessageConverter(new MappingJackson2MessageConverter()); + } + + @AfterEach + void tearDown() { + if (this.stompSession != null && this.stompSession.isConnected()) { + this.stompSession.disconnect(); + } + } + + @Nested + class WebSocket_핸드셰이크_및_STOMP_세션_수립_테스트 { + + private final BlockingQueue transportErrorQueue = new ArrayBlockingQueue<>(1); + + private final StompSessionHandlerAdapter sessionHandler = new StompSessionHandlerAdapter() { + @Override + public void handleTransportError(StompSession session, Throwable exception) { + transportErrorQueue.add(exception); + } + }; + + @Test + void 인증된_사용자는_핸드셰이크를_성공한다() throws Exception { + // given + SiteUser user = siteUserFixture.사용자(); + AccessToken accessToken = authTokenProvider.generateAccessToken(authTokenProvider.toSubject(user), user.getRole()); + + WebSocketHttpHeaders handshakeHeaders = new WebSocketHttpHeaders(); + handshakeHeaders.add("Authorization", "Bearer " + accessToken.token()); + + // when + stompSession = stompClient.connectAsync(url, handshakeHeaders, new StompHeaders(), sessionHandler).get(5, SECONDS); + + // then + assertAll( + () -> assertThat(stompSession).isNotNull(), + () -> assertThat(transportErrorQueue).isEmpty() + ); + } + + @Test + void 인증되지_않은_사용자는_핸드셰이크를_실패한다() { + // when + Throwable thrown = catchThrowable(() -> { + stompSession = stompClient.connectAsync(url, new WebSocketHttpHeaders(), new StompHeaders(), sessionHandler).get(5, SECONDS); + }); + + // then + assertAll( + () -> assertThat(thrown) + .isInstanceOf(ExecutionException.class) + .hasCauseInstanceOf(HttpClientErrorException.Unauthorized.class), + () -> assertThat(transportErrorQueue).hasSize(1) + ); + } + } +} From 289cd890db968fe7207d4dbce01f33a3ea80af3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Tue, 12 Aug 2025 15:11:49 +0900 Subject: [PATCH 69/90] =?UTF-8?q?feat:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=A0=95=EC=B1=85=20=EC=B6=94=EA=B0=80=20(#435)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 비밀번호 검증 추가 - 비밀번호는 영문, 숫자, 특수문자를 포함한 8자리 이상 * test: 비밀번호 검증 테스트 추가 * refactor: 이메일 검증 조건 추가 - @NotBlank와 @Email을 함께 사용하도록 * style: 개행 제거 --- .../auth/dto/EmailSignInRequest.java | 4 ++ .../auth/dto/EmailSignUpTokenRequest.java | 5 ++- .../auth/dto/validation/Password.java | 20 +++++++++ .../dto/validation/PasswordValidator.java | 16 +++++++ .../dto/validation/PasswordValidatorTest.java | 42 +++++++++++++++++++ 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/example/solidconnection/auth/dto/validation/Password.java create mode 100644 src/main/java/com/example/solidconnection/auth/dto/validation/PasswordValidator.java create mode 100644 src/test/java/com/example/solidconnection/auth/dto/validation/PasswordValidatorTest.java diff --git a/src/main/java/com/example/solidconnection/auth/dto/EmailSignInRequest.java b/src/main/java/com/example/solidconnection/auth/dto/EmailSignInRequest.java index 925fb62cd..3987bc508 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/EmailSignInRequest.java +++ b/src/main/java/com/example/solidconnection/auth/dto/EmailSignInRequest.java @@ -1,12 +1,16 @@ package com.example.solidconnection.auth.dto; +import com.example.solidconnection.auth.dto.validation.Password; +import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; public record EmailSignInRequest( @NotBlank(message = "이메일을 입력해주세요.") + @Email(message = "유효한 이메일 주소를 입력해주세요.") String email, + @Password @NotBlank(message = "비밀번호를 입력해주세요.") String password ) { diff --git a/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenRequest.java b/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenRequest.java index 52e50e5b2..ffafa222d 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenRequest.java +++ b/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenRequest.java @@ -1,13 +1,16 @@ package com.example.solidconnection.auth.dto; +import com.example.solidconnection.auth.dto.validation.Password; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; public record EmailSignUpTokenRequest( - @Email(message = "이메일을 입력해주세요.") + @NotBlank(message = "이메일을 입력해주세요.") + @Email(message = "유효한 이메일 주소를 입력해주세요.") String email, + @Password @NotBlank(message = "비밀번호를 입력해주세요.") String password ) { diff --git a/src/main/java/com/example/solidconnection/auth/dto/validation/Password.java b/src/main/java/com/example/solidconnection/auth/dto/validation/Password.java new file mode 100644 index 000000000..a896b5724 --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/dto/validation/Password.java @@ -0,0 +1,20 @@ +package com.example.solidconnection.auth.dto.validation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD, ElementType.RECORD_COMPONENT}) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = PasswordValidator.class) +public @interface Password { + + String message() default "비밀번호는 영문, 숫자, 특수문자를 포함한 8자리 이상이어야 합니다."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/main/java/com/example/solidconnection/auth/dto/validation/PasswordValidator.java b/src/main/java/com/example/solidconnection/auth/dto/validation/PasswordValidator.java new file mode 100644 index 000000000..77f8da213 --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/dto/validation/PasswordValidator.java @@ -0,0 +1,16 @@ +package com.example.solidconnection.auth.dto.validation; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class PasswordValidator implements ConstraintValidator { + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + if (value == null || value.isBlank()) { + return true; + } + + return value.matches("^(?=.*[A-Za-z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-={}\\[\\]|:;\"'<>,.?/`~])\\S{8,}$"); + } +} diff --git a/src/test/java/com/example/solidconnection/auth/dto/validation/PasswordValidatorTest.java b/src/test/java/com/example/solidconnection/auth/dto/validation/PasswordValidatorTest.java new file mode 100644 index 000000000..8beb4c99e --- /dev/null +++ b/src/test/java/com/example/solidconnection/auth/dto/validation/PasswordValidatorTest.java @@ -0,0 +1,42 @@ +package com.example.solidconnection.auth.dto.validation; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("비밀번호 유효성 검사 테스트") +class PasswordValidatorTest { + + private final PasswordValidator validator = new PasswordValidator(); + + @Test + void 정상_패턴이면_true를_반환한다() { + assertThat(validator.isValid("abcd123!", null)).isTrue(); + } + + @Test + void 숫자가_없으면_false를_반환한다() { + assertThat(validator.isValid("abcdefg!", null)).isFalse(); + } + + @Test + void 영문자가_없으면_false를_반환한다() { + assertThat(validator.isValid("1234567!", null)).isFalse(); + } + + @Test + void 특수문자가_없으면_false를_반환한다() { + assertThat(validator.isValid("abcd1234", null)).isFalse(); + } + + @Test + void 공백을_포함하면_false를_반환한다() { + assertThat(validator.isValid("abcd123! ", null)).isFalse(); + } + + @Test + void 길이가_8자_미만이면_false를_반환한다() { + assertThat(validator.isValid("ab1!ab", null)).isFalse(); + } +} From 655e0bdf83f7774bb8cdce7cdba57c29498606ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Tue, 12 Aug 2025 20:14:57 +0900 Subject: [PATCH 70/90] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?(#439)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: mentoring_id 컬럼 추가 및 유니크 제약조건 추가 - 멘토링의 경우에만 mentoringId가 존재하므로 long이 아닌 Long으로 설정 * feat: 멘토링 채팅방 생성 서비스 로직 추가 * feat: 멘토링 승인 시 이벤트 기반 호출 기능 추가 * test: 채팅방 개설 관련 테스트 코드 작성 * refactor: @TransactionalEventListener로 변경 * fix: 잘못된 어노테이션 import 변경 * refactor: DISTINCT를 추가하여 데이터 중복 해결 * refactor: @Column에 유니크키 설정하도록 변경 * refactor: fk 추가 * refactor: find -> exists로 변경하여 성능 개선 * test: 비동기 이벤트 처리 완료까지 대기 및 재시도하도록 수정 * test: awaitility:4.2.0 도입 --- build.gradle | 1 + .../solidconnection/chat/domain/ChatRoom.java | 9 +++ .../chat/repository/ChatRoomRepository.java | 2 + .../chat/service/ChatService.java | 13 ++++ .../mentor/dto/MentoringApprovedEvent.java | 12 ++++ .../service/MentoringCommandService.java | 4 ++ .../mentor/service/MentoringEventHandler.java | 24 +++++++ .../V28__add_mentoring_id_to_chat_room.sql | 5 ++ .../repository/ChatRoomRepositoryForTest.java | 29 ++++++++ .../service/MentoringCommandServiceTest.java | 66 ++++++++++++++++++- 10 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentoringApprovedEvent.java create mode 100644 src/main/java/com/example/solidconnection/mentor/service/MentoringEventHandler.java create mode 100644 src/main/resources/db/migration/V28__add_mentoring_id_to_chat_room.sql create mode 100644 src/test/java/com/example/solidconnection/chat/repository/ChatRoomRepositoryForTest.java diff --git a/build.gradle b/build.gradle index 2de93e064..91cc2e77d 100644 --- a/build.gradle +++ b/build.gradle @@ -62,6 +62,7 @@ dependencies { testImplementation 'org.testcontainers:mysql' testImplementation 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.awaitility:awaitility:4.2.0' // Etc implementation 'org.hibernate.validator:hibernate-validator' diff --git a/src/main/java/com/example/solidconnection/chat/domain/ChatRoom.java b/src/main/java/com/example/solidconnection/chat/domain/ChatRoom.java index e8e7a3ebb..fc159c2cd 100644 --- a/src/main/java/com/example/solidconnection/chat/domain/ChatRoom.java +++ b/src/main/java/com/example/solidconnection/chat/domain/ChatRoom.java @@ -2,6 +2,7 @@ import com.example.solidconnection.common.BaseEntity; import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -25,6 +26,9 @@ public class ChatRoom extends BaseEntity { private boolean isGroup = false; + @Column(name = "mentoring_id", unique = true) + private Long mentoringId; + @OneToMany(mappedBy = "chatRoom", cascade = CascadeType.ALL) @BatchSize(size = 10) private final List chatParticipants = new ArrayList<>(); @@ -35,4 +39,9 @@ public class ChatRoom extends BaseEntity { public ChatRoom(boolean isGroup) { this.isGroup = isGroup; } + + public ChatRoom(Long mentoringId, boolean isGroup) { + this.mentoringId = mentoringId; + this.isGroup = isGroup; + } } diff --git a/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java b/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java index dd5193abf..ad815dbe1 100644 --- a/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java +++ b/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java @@ -33,4 +33,6 @@ SELECT COUNT(cm) FROM ChatMessage cm AND (crs.updatedAt IS NULL OR cm.createdAt > crs.updatedAt) """) long countUnreadMessages(@Param("chatRoomId") long chatRoomId, @Param("userId") long userId); + + boolean existsByMentoringId(long mentoringId); } diff --git a/src/main/java/com/example/solidconnection/chat/service/ChatService.java b/src/main/java/com/example/solidconnection/chat/service/ChatService.java index fadd284fe..4a9c02eed 100644 --- a/src/main/java/com/example/solidconnection/chat/service/ChatService.java +++ b/src/main/java/com/example/solidconnection/chat/service/ChatService.java @@ -162,4 +162,17 @@ public void sendChatMessage(ChatMessageSendRequest chatMessageSendRequest, long simpMessageSendingOperations.convertAndSend("/topic/chat/" + roomId, chatMessageResponse); } + + @Transactional + public void createMentoringChatRoom(Long mentoringId, Long mentorId, Long menteeId) { + if (chatRoomRepository.existsByMentoringId(mentoringId)) { + return; + } + + ChatRoom chatRoom = new ChatRoom(mentoringId, false); + chatRoom = chatRoomRepository.save(chatRoom); + ChatParticipant mentorParticipant = new ChatParticipant(mentorId, chatRoom); + ChatParticipant menteeParticipant = new ChatParticipant(menteeId, chatRoom); + chatParticipantRepository.saveAll(List.of(mentorParticipant, menteeParticipant)); + } } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringApprovedEvent.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringApprovedEvent.java new file mode 100644 index 000000000..4909a5c85 --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringApprovedEvent.java @@ -0,0 +1,12 @@ +package com.example.solidconnection.mentor.dto; + +public record MentoringApprovedEvent( + long mentoringId, + long mentorId, + long menteeId +) { + + public static MentoringApprovedEvent of(long mentoringId, long mentorId, long menteeId) { + return new MentoringApprovedEvent(mentoringId, mentorId, menteeId); + } +} diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java b/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java index dd4e98936..15a1e2507 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java @@ -11,11 +11,13 @@ import com.example.solidconnection.mentor.domain.Mentoring; import com.example.solidconnection.mentor.dto.MentoringApplyRequest; import com.example.solidconnection.mentor.dto.MentoringApplyResponse; +import com.example.solidconnection.mentor.dto.MentoringApprovedEvent; import com.example.solidconnection.mentor.dto.MentoringConfirmRequest; import com.example.solidconnection.mentor.dto.MentoringConfirmResponse; import com.example.solidconnection.mentor.repository.MentorRepository; import com.example.solidconnection.mentor.repository.MentoringRepository; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -25,6 +27,7 @@ public class MentoringCommandService { private final MentoringRepository mentoringRepository; private final MentorRepository mentorRepository; + private final ApplicationEventPublisher eventPublisher; @Transactional public MentoringApplyResponse applyMentoring(long siteUserId, MentoringApplyRequest mentoringApplyRequest) { @@ -48,6 +51,7 @@ public MentoringConfirmResponse confirmMentoring(long siteUserId, long mentoring if (mentoringConfirmRequest.status() == VerifyStatus.APPROVED) { mentor.increaseMenteeCount(); + eventPublisher.publishEvent(MentoringApprovedEvent.of(mentoringId, mentor.getSiteUserId(), mentoring.getMenteeId())); } return MentoringConfirmResponse.from(mentoring); diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentoringEventHandler.java b/src/main/java/com/example/solidconnection/mentor/service/MentoringEventHandler.java new file mode 100644 index 000000000..920ff007f --- /dev/null +++ b/src/main/java/com/example/solidconnection/mentor/service/MentoringEventHandler.java @@ -0,0 +1,24 @@ +package com.example.solidconnection.mentor.service; + +import com.example.solidconnection.chat.service.ChatService; +import com.example.solidconnection.mentor.dto.MentoringApprovedEvent; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionalEventListener; + +@Component +@RequiredArgsConstructor +public class MentoringEventHandler { + + private final ChatService chatService; + + @Async + @Transactional(propagation = Propagation.REQUIRES_NEW) + @TransactionalEventListener + public void handleMentoringApproved(MentoringApprovedEvent event) { + chatService.createMentoringChatRoom(event.mentoringId(), event.mentorId(), event.menteeId()); + } +} diff --git a/src/main/resources/db/migration/V28__add_mentoring_id_to_chat_room.sql b/src/main/resources/db/migration/V28__add_mentoring_id_to_chat_room.sql new file mode 100644 index 000000000..f4ab8d815 --- /dev/null +++ b/src/main/resources/db/migration/V28__add_mentoring_id_to_chat_room.sql @@ -0,0 +1,5 @@ +ALTER TABLE chat_room + ADD COLUMN mentoring_id BIGINT, +ADD CONSTRAINT uk_chat_room_mentoring_id UNIQUE (mentoring_id), +ADD CONSTRAINT fk_chat_room_mentoring_id FOREIGN KEY (mentoring_id) REFERENCES mentoring(id); + diff --git a/src/test/java/com/example/solidconnection/chat/repository/ChatRoomRepositoryForTest.java b/src/test/java/com/example/solidconnection/chat/repository/ChatRoomRepositoryForTest.java new file mode 100644 index 000000000..7605453c6 --- /dev/null +++ b/src/test/java/com/example/solidconnection/chat/repository/ChatRoomRepositoryForTest.java @@ -0,0 +1,29 @@ +package com.example.solidconnection.chat.repository; + +import com.example.solidconnection.chat.domain.ChatRoom; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +public interface ChatRoomRepositoryForTest extends JpaRepository { + + @Query(""" + SELECT DISTINCT cr FROM ChatRoom cr + LEFT JOIN FETCH cr.chatParticipants cp + WHERE cr.isGroup = false + AND EXISTS ( + SELECT 1 FROM ChatParticipant cp1 + WHERE cp1.chatRoom = cr AND cp1.siteUserId = :mentorId + ) + AND EXISTS ( + SELECT 1 FROM ChatParticipant cp2 + WHERE cp2.chatRoom = cr AND cp2.siteUserId = :menteeId + ) + AND ( + SELECT COUNT(cp3) FROM ChatParticipant cp3 + WHERE cp3.chatRoom = cr + ) = 2 + """) + Optional findOneOnOneChatRoomByParticipants(@Param("mentorId") long mentorId, @Param("menteeId") long menteeId); +} diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java index a92925653..058388d51 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java @@ -6,7 +6,10 @@ 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.awaitility.Awaitility.await; +import com.example.solidconnection.chat.domain.ChatParticipant; +import com.example.solidconnection.chat.domain.ChatRoom; +import com.example.solidconnection.chat.repository.ChatRoomRepositoryForTest; import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.mentor.domain.Mentor; @@ -22,6 +25,9 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.time.Duration; +import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -41,6 +47,9 @@ class MentoringCommandServiceTest { @Autowired private MentoringRepository mentoringRepository; + @Autowired + private ChatRoomRepositoryForTest chatRoomRepositoryForTest; + @Autowired private SiteUserFixture siteUserFixture; @@ -115,6 +124,36 @@ class 멘토링_승인_거절_테스트 { ); } + @Test + void 멘토링_승인시_채팅방이_자동으로_생성된다() { + // given + Mentoring mentoring = mentoringFixture.대기중_멘토링(mentor1.getId(), menteeUser.getId()); + MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.APPROVED); + + Optional beforeChatRoom = chatRoomRepositoryForTest.findOneOnOneChatRoomByParticipants(mentorUser1.getId(), menteeUser.getId()); + assertThat(beforeChatRoom).isEmpty(); + + // when + mentoringCommandService.confirmMentoring(mentorUser1.getId(), mentoring.getId(), request); + + // then + ChatRoom afterChatRoom = await() + .atMost(Duration.ofSeconds(5)) + .pollInterval(Duration.ofMillis(100)) + .until(() -> chatRoomRepositoryForTest + .findOneOnOneChatRoomByParticipants(mentorUser1.getId(), menteeUser.getId()), + Optional::isPresent) + .orElseThrow(); + + List participantIds = afterChatRoom.getChatParticipants().stream() + .map(ChatParticipant::getSiteUserId) + .toList(); + assertAll( + () -> assertThat(afterChatRoom.isGroup()).isFalse(), + () -> assertThat(participantIds).containsExactly(mentorUser1.getId(), menteeUser.getId()) + ); + } + @Test void 멘토링을_성공적으로_거절한다() { // given @@ -137,6 +176,31 @@ class 멘토링_승인_거절_테스트 { ); } + @Test + void 멘토링_거절시_채팅방이_자동으로_생성되지_않는다() { + // given + Mentoring mentoring = mentoringFixture.대기중_멘토링(mentor1.getId(), menteeUser.getId()); + MentoringConfirmRequest request = new MentoringConfirmRequest(VerifyStatus.REJECTED); + + Optional beforeChatRoom = chatRoomRepositoryForTest.findOneOnOneChatRoomByParticipants(mentorUser1.getId(), menteeUser.getId()); + assertThat(beforeChatRoom).isEmpty(); + + // when + mentoringCommandService.confirmMentoring(mentorUser1.getId(), mentoring.getId(), request); + + // then + await() + .pollInterval(Duration.ofMillis(100)) + .during(Duration.ofSeconds(1)) + .until(() -> chatRoomRepositoryForTest + .findOneOnOneChatRoomByParticipants(mentorUser1.getId(), menteeUser.getId()) + .isEmpty()); + + Optional afterChatRoom = chatRoomRepositoryForTest.findOneOnOneChatRoomByParticipants(mentorUser1.getId(), menteeUser.getId()); + assertThat(afterChatRoom).isEmpty(); + + } + @Test void 다른_멘토의_멘토링을_승인할_수_없다() { // given From f78a01feb9563d6512702492f1a5be82fbe1adee Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Wed, 13 Aug 2025 02:22:24 +0900 Subject: [PATCH 71/90] =?UTF-8?q?refactor:=20=EB=A9=98=ED=86=A0=EC=9D=98?= =?UTF-8?q?=20=EB=A9=98=ED=86=A0=EB=A7=81=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C?= =?UTF-8?q?,=20confirm=20=EC=97=AC=EB=B6=80=20=ED=8F=AC=ED=95=A8=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EC=9D=91=EB=8B=B5=ED=95=98=EB=8F=84=EB=A1=9D=20(#4?= =?UTF-8?q?41)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 멘토가 멘토링 조회할 때, confirm 되었는지도 함께 응답 * refactor: 비슷한 두 코드의 형식 통일 - getMentoringsForMentee와 getMentoringsForMentor가 같은 구조를 갖도록 --- .../mentor/dto/MentoringForMentorResponse.java | 2 ++ .../solidconnection/mentor/service/MentoringQueryService.java | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMentorResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMentorResponse.java index 9ee1889f6..54622d821 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMentorResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMentorResponse.java @@ -9,6 +9,7 @@ public record MentoringForMentorResponse( String profileImageUrl, String nickname, boolean isChecked, + boolean isConfirmed, ZonedDateTime createdAt ) { @@ -18,6 +19,7 @@ public static MentoringForMentorResponse of(Mentoring mentoring, SiteUser partne partner.getProfileImageUrl(), partner.getNickname(), mentoring.getCheckedAtByMentor() != null, + mentoring.getConfirmedAt() != null, mentoring.getCreatedAt() ); } diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java index bb9deafe0..a102b9f63 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java @@ -42,10 +42,10 @@ public SliceResponse getMentoringsForMentee( throw new CustomException(ErrorCode.UNAUTHORIZED_MENTORING, "거절된 멘토링은 조회할 수 없습니다."); } Slice mentoringSlice = mentoringRepository.findAllByMenteeIdAndVerifyStatus(siteUserId, verifyStatus, pageable); - List mentorings = mentoringSlice.toList(); Map mentoringToPartnerUser = mapMentoringToPartnerUserWithBatchQuery( - mentorings, Mentoring::getMentorId + mentoringSlice.toList(), + Mentoring::getMentorId ); List content = new ArrayList<>(); From 1203d002ee3eec101dd8ceca6c392aca31002d5b Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Wed, 13 Aug 2025 03:43:04 +0900 Subject: [PATCH 72/90] =?UTF-8?q?refactor:=20=EB=A9=98=ED=86=A0=EC=9D=98?= =?UTF-8?q?=20=ED=95=A9=EA=B2=A9=20=ED=8C=81,=20=EC=9E=90=EA=B8=B0=20?= =?UTF-8?q?=EC=86=8C=EA=B0=9C=EA=B0=80=20null=20=EC=9D=84=20=ED=97=88?= =?UTF-8?q?=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20(#4?= =?UTF-8?q?43)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: not null alter 스크립트 추가 * refactor: 엔티티에 nullable=false 추가 * chore: flyway 버전업 --- .../com/example/solidconnection/mentor/domain/Mentor.java | 4 ++-- .../V29__alter_mentor_introduction_pass_tip_not_null.sql | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/db/migration/V29__alter_mentor_introduction_pass_tip_not_null.sql diff --git a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java index 008917e89..c14475b7e 100644 --- a/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java +++ b/src/main/java/com/example/solidconnection/mentor/domain/Mentor.java @@ -32,10 +32,10 @@ public class Mentor { @Column private boolean hasBadge = false; - @Column(length = 1000) + @Column(length = 1000, nullable = false) private String introduction; - @Column(length = 1000) + @Column(length = 1000, nullable = false) private String passTip; @Column diff --git a/src/main/resources/db/migration/V29__alter_mentor_introduction_pass_tip_not_null.sql b/src/main/resources/db/migration/V29__alter_mentor_introduction_pass_tip_not_null.sql new file mode 100644 index 000000000..8acb72761 --- /dev/null +++ b/src/main/resources/db/migration/V29__alter_mentor_introduction_pass_tip_not_null.sql @@ -0,0 +1,5 @@ +ALTER TABLE mentor + MODIFY introduction VARCHAR(1000) NOT NULL; + +ALTER TABLE mentor + MODIFY pass_tip VARCHAR(1000) NOT NULL; From 526e507aa2726f9bc87c913b916e43056c78922b Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Wed, 13 Aug 2025 22:58:47 +0900 Subject: [PATCH 73/90] =?UTF-8?q?refactor:=20SignUpToken=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0,=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=20(#445)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: JPA 함수로 불필요한 JPQL 제거 * refactor: SignUpService 로부터 관심 국가 저장 로직 분리 - SignUpService의 의존성이 변하자, OAuthSignUpService와 EmailSignUpService의 코드가 변했다 -> DIP 위반 * refactor: 'SignUpToken 관리'만 담당하는 클래스 생성 * refactor: SignUpTokenProvider를 조합하여 중복 코드 제거 * refactor: 회원가입 관련 코드를 한 곳으로 통합 * test: SignUpTokenProvider 테스트 코드 작성 * feat: 비밀번호 임시 저장소 구현 * test: 비밀번호 임시 저장소 테스트 작성 * refactor: 이메일 로그인 코드 가독성 개선 * refactor: transactional 어노테이션 추가 * refactor: 임시 저장 비밀번호 조회 로직 이동 - '회원가입' 서비스에 있는게 더 적절할 것이라 판단 * refactor: 회원가입에 사용된 정보 삭제 기능 추가 * test: 회원가입 토큰, 임시 저장된 비밀번호 삭제 테스트 추가 * test: 방복해서 선언되는 변수 분리 * refactor: 사용하지 않는 의존성 제거 --- .../auth/controller/AuthController.java | 18 +-- .../service/CommonSignUpTokenProvider.java | 25 ---- .../auth/service/EmailSignInService.java | 25 ++-- .../auth/service/EmailSignUpService.java | 54 --------- .../service/EmailSignUpTokenProvider.java | 80 ++----------- .../service/PasswordTemporaryStorage.java | 46 ++++++++ .../auth/service/SignUpService.java | 109 ++++++++++-------- ...Provider.java => SignUpTokenProvider.java} | 14 ++- .../auth/service/oauth/OAuthService.java | 5 +- .../service/oauth/OAuthSignUpService.java | 51 -------- .../common/exception/ErrorCode.java | 1 + .../country/repository/CountryRepository.java | 5 +- .../service/InterestedCountryService.java | 27 +++++ .../region/repository/RegionRepository.java | 5 +- .../service/InterestedRegionService.java | 27 +++++ .../auth/service/EmailSignInServiceTest.java | 4 +- .../service/PasswordTemporaryStorageTest.java | 48 ++++++++ ...Test.java => SignUpTokenProviderTest.java} | 75 ++++++------ 18 files changed, 294 insertions(+), 325 deletions(-) delete mode 100644 src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java delete mode 100644 src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java create mode 100644 src/main/java/com/example/solidconnection/auth/service/PasswordTemporaryStorage.java rename src/main/java/com/example/solidconnection/auth/service/{oauth/OAuthSignUpTokenProvider.java => SignUpTokenProvider.java} (87%) delete mode 100644 src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java create mode 100644 src/main/java/com/example/solidconnection/location/country/service/InterestedCountryService.java create mode 100644 src/main/java/com/example/solidconnection/location/region/service/InterestedRegionService.java create mode 100644 src/test/java/com/example/solidconnection/auth/service/PasswordTemporaryStorageTest.java rename src/test/java/com/example/solidconnection/auth/service/{oauth/OAuthSignUpTokenProviderTest.java => SignUpTokenProviderTest.java} (69%) diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index 085343dfd..cbcd29627 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -11,12 +11,10 @@ import com.example.solidconnection.auth.dto.oauth.OAuthResponse; import com.example.solidconnection.auth.dto.oauth.OAuthSignInResponse; import com.example.solidconnection.auth.service.AuthService; -import com.example.solidconnection.auth.service.CommonSignUpTokenProvider; import com.example.solidconnection.auth.service.EmailSignInService; -import com.example.solidconnection.auth.service.EmailSignUpService; import com.example.solidconnection.auth.service.EmailSignUpTokenProvider; +import com.example.solidconnection.auth.service.SignUpService; import com.example.solidconnection.auth.service.oauth.OAuthService; -import com.example.solidconnection.auth.service.oauth.OAuthSignUpService; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.common.resolver.AuthorizedUser; @@ -38,12 +36,10 @@ public class AuthController { private final AuthService authService; - private final OAuthSignUpService oAuthSignUpService; private final OAuthService oAuthService; + private final SignUpService signUpService; private final EmailSignInService emailSignInService; - private final EmailSignUpService emailSignUpService; private final EmailSignUpTokenProvider emailSignUpTokenProvider; - private final CommonSignUpTokenProvider commonSignUpTokenProvider; private final RefreshTokenCookieManager refreshTokenCookieManager; @PostMapping("/apple") @@ -85,8 +81,7 @@ public ResponseEntity signInWithEmail( public ResponseEntity signUpWithEmail( @Valid @RequestBody EmailSignUpTokenRequest signUpRequest ) { - emailSignUpService.validateUniqueEmail(signUpRequest.email()); - String signUpToken = emailSignUpTokenProvider.generateAndSaveSignUpToken(signUpRequest); + String signUpToken = emailSignUpTokenProvider.issueEmailSignUpToken(signUpRequest); return ResponseEntity.ok(new EmailSignUpTokenResponse(signUpToken)); } @@ -94,12 +89,7 @@ public ResponseEntity signUpWithEmail( public ResponseEntity signUp( @Valid @RequestBody SignUpRequest signUpRequest ) { - AuthType authType = commonSignUpTokenProvider.parseAuthType(signUpRequest.signUpToken()); - if (AuthType.isEmail(authType)) { - SignInResponse signInResponse = emailSignUpService.signUp(signUpRequest); - return ResponseEntity.ok(signInResponse); - } - SignInResponse signInResponse = oAuthSignUpService.signUp(signUpRequest); + SignInResponse signInResponse = signUpService.signUp(signUpRequest); return ResponseEntity.ok(signInResponse); } diff --git a/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java deleted file mode 100644 index c6930315b..000000000 --- a/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.example.solidconnection.auth.service; - -import static com.example.solidconnection.auth.service.EmailSignUpTokenProvider.AUTH_TYPE_CLAIM_KEY; -import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; - -import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.siteuser.domain.AuthType; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -@Component -@RequiredArgsConstructor -public class CommonSignUpTokenProvider { - - private final TokenProvider tokenProvider; - - public AuthType parseAuthType(String signUpToken) { - try { - String authTypeStr = tokenProvider.parseClaims(signUpToken).get(AUTH_TYPE_CLAIM_KEY, String.class); - return AuthType.valueOf(authTypeStr); - } catch (Exception e) { - throw new CustomException(SIGN_UP_TOKEN_INVALID); - } - } -} diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java index d7ee365d8..4dac56586 100644 --- a/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java +++ b/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java @@ -1,6 +1,6 @@ package com.example.solidconnection.auth.service; -import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; +import static com.example.solidconnection.common.exception.ErrorCode.SIGN_IN_FAILED; import com.example.solidconnection.auth.dto.EmailSignInRequest; import com.example.solidconnection.auth.dto.SignInResponse; @@ -8,14 +8,11 @@ import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -/* - * 보안을 위해 이메일과 비밀번호 중 무엇이 틀렸는지 구체적으로 응답하지 않는다. - * */ @Service @RequiredArgsConstructor public class EmailSignInService { @@ -24,19 +21,21 @@ public class EmailSignInService { private final SiteUserRepository siteUserRepository; private final PasswordEncoder passwordEncoder; + @Transactional(readOnly = true) public SignInResponse signIn(EmailSignInRequest signInRequest) { - Optional optionalSiteUser = siteUserRepository.findByEmailAndAuthType(signInRequest.email(), AuthType.EMAIL); - if (optionalSiteUser.isPresent()) { - SiteUser siteUser = optionalSiteUser.get(); - validatePassword(signInRequest.password(), siteUser.getPassword()); - return signInService.signIn(siteUser); - } - throw new CustomException(USER_NOT_FOUND, "이메일과 비밀번호를 확인해주세요."); + SiteUser siteUser = getEmailMatchingUserOrThrow(signInRequest.email()); + validatePassword(signInRequest.password(), siteUser.getPassword()); + return signInService.signIn(siteUser); + } + + private SiteUser getEmailMatchingUserOrThrow(String email) { + return siteUserRepository.findByEmailAndAuthType(email, AuthType.EMAIL) + .orElseThrow(() -> new CustomException(SIGN_IN_FAILED)); } private void validatePassword(String rawPassword, String encodedPassword) { if (!passwordEncoder.matches(rawPassword, encodedPassword)) { - throw new CustomException(USER_NOT_FOUND, "이메일과 비밀번호를 확인해주세요."); + throw new CustomException(SIGN_IN_FAILED); } } } diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java deleted file mode 100644 index a3436cf5d..000000000 --- a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.example.solidconnection.auth.service; - -import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_EXISTED; - -import com.example.solidconnection.auth.dto.SignUpRequest; -import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.location.country.repository.CountryRepository; -import com.example.solidconnection.location.country.repository.InterestedCountryRepository; -import com.example.solidconnection.location.region.repository.InterestedRegionRepository; -import com.example.solidconnection.location.region.repository.RegionRepository; -import com.example.solidconnection.siteuser.domain.AuthType; -import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import org.springframework.stereotype.Service; - -@Service -public class EmailSignUpService extends SignUpService { - - private final EmailSignUpTokenProvider emailSignUpTokenProvider; - - public EmailSignUpService(SignInService signInService, SiteUserRepository siteUserRepository, - RegionRepository regionRepository, InterestedRegionRepository interestedRegionRepository, - CountryRepository countryRepository, InterestedCountryRepository interestedCountryRepository, - EmailSignUpTokenProvider emailSignUpTokenProvider) { - super(signInService, siteUserRepository, regionRepository, interestedRegionRepository, countryRepository, interestedCountryRepository); - this.emailSignUpTokenProvider = emailSignUpTokenProvider; - } - - public void validateUniqueEmail(String email) { - if (siteUserRepository.existsByEmailAndAuthType(email, AuthType.EMAIL)) { - throw new CustomException(USER_ALREADY_EXISTED); - } - } - - @Override - protected void validateSignUpToken(SignUpRequest signUpRequest) { - emailSignUpTokenProvider.validateSignUpToken(signUpRequest.signUpToken()); - } - - @Override - protected void validateUserNotDuplicated(SignUpRequest signUpRequest) { - String email = emailSignUpTokenProvider.parseEmail(signUpRequest.signUpToken()); - if (siteUserRepository.existsByEmailAndAuthType(email, AuthType.EMAIL)) { - throw new CustomException(USER_ALREADY_EXISTED); - } - } - - @Override - protected SiteUser createSiteUser(SignUpRequest signUpRequest) { - String email = emailSignUpTokenProvider.parseEmail(signUpRequest.signUpToken()); - String encodedPassword = emailSignUpTokenProvider.parseEncodedPassword(signUpRequest.signUpToken()); - return signUpRequest.toEmailSiteUser(email, encodedPassword); - } -} diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java index 238c7e517..a3e2e5dc9 100644 --- a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java @@ -1,88 +1,32 @@ package com.example.solidconnection.auth.service; -import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; -import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; - -import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.EmailSignUpTokenRequest; -import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.siteuser.domain.AuthType; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; @Component @RequiredArgsConstructor public class EmailSignUpTokenProvider { - static final String PASSWORD_CLAIM_KEY = "password"; - static final String AUTH_TYPE_CLAIM_KEY = "authType"; - - private final PasswordEncoder passwordEncoder; - private final JwtProperties jwtProperties; - private final RedisTemplate redisTemplate; - private final TokenProvider tokenProvider; + private final SignUpTokenProvider signUpTokenProvider; + private final SiteUserRepository siteUserRepository; + private final PasswordTemporaryStorage passwordTemporaryStorage; - public String generateAndSaveSignUpToken(EmailSignUpTokenRequest request) { + @Transactional(readOnly = true) + public String issueEmailSignUpToken(EmailSignUpTokenRequest request) { String email = request.email(); String password = request.password(); - String encodedPassword = passwordEncoder.encode(password); - Map emailSignUpClaims = new HashMap<>(Map.of( - PASSWORD_CLAIM_KEY, encodedPassword, - AUTH_TYPE_CLAIM_KEY, AuthType.EMAIL - )); - Claims claims = Jwts.claims(emailSignUpClaims).setSubject(email); - Date now = new Date(); - Date expiredDate = new Date(now.getTime() + TokenType.SIGN_UP.getExpireTime()); - - String signUpToken = Jwts.builder() - .setClaims(claims) - .setIssuedAt(now) - .setExpiration(expiredDate) - .signWith(SignatureAlgorithm.HS512, jwtProperties.secret()) - .compact(); - return tokenProvider.saveToken(signUpToken, TokenType.SIGN_UP); - } - - public void validateSignUpToken(String token) { - validateFormatAndExpiration(token); - String email = parseEmail(token); - validateIssuedByServer(email); - } - private void validateFormatAndExpiration(String token) { - try { - Claims claims = tokenProvider.parseClaims(token); - Objects.requireNonNull(claims.getSubject()); - String encodedPassword = claims.get(PASSWORD_CLAIM_KEY, String.class); - Objects.requireNonNull(encodedPassword); - } catch (Exception e) { - throw new CustomException(SIGN_UP_TOKEN_INVALID); + if (siteUserRepository.existsByEmailAndAuthType(email, AuthType.EMAIL)) { + throw new CustomException(ErrorCode.USER_ALREADY_EXISTED); } - } - - private void validateIssuedByServer(String email) { - String key = TokenType.SIGN_UP.addPrefix(email); - if (redisTemplate.opsForValue().get(key) == null) { - throw new CustomException(SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER); - } - } - - public String parseEmail(String token) { - return tokenProvider.parseSubject(token); - } - public String parseEncodedPassword(String token) { - Claims claims = tokenProvider.parseClaims(token); - return claims.get(PASSWORD_CLAIM_KEY, String.class); + passwordTemporaryStorage.save(email, password); + return signUpTokenProvider.generateAndSaveSignUpToken(email, AuthType.EMAIL); } } diff --git a/src/main/java/com/example/solidconnection/auth/service/PasswordTemporaryStorage.java b/src/main/java/com/example/solidconnection/auth/service/PasswordTemporaryStorage.java new file mode 100644 index 000000000..adcb8bf68 --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/service/PasswordTemporaryStorage.java @@ -0,0 +1,46 @@ +package com.example.solidconnection.auth.service; + +import com.example.solidconnection.auth.domain.TokenType; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class PasswordTemporaryStorage { + + private static final String KEY_PREFIX = "password:"; + + private final RedisTemplate redisTemplate; + private final PasswordEncoder passwordEncoder; + + public void save(String email, String rawPassword) { + String encodedPassword = passwordEncoder.encode(rawPassword); + redisTemplate.opsForValue().set( + convertToKey(email), + encodedPassword, + TokenType.SIGN_UP.getExpireTime(), + TimeUnit.MILLISECONDS + ); + } + + public Optional findByEmail(String email) { + String encodedPassword = redisTemplate.opsForValue().get(convertToKey(email)); + if (encodedPassword == null) { + return Optional.empty(); + } + return Optional.of(encodedPassword); + } + + public void deleteByEmail(String email) { + String key = convertToKey(email); + redisTemplate.delete(key); + } + + private String convertToKey(String email) { + return KEY_PREFIX + email; + } +} diff --git a/src/main/java/com/example/solidconnection/auth/service/SignUpService.java b/src/main/java/com/example/solidconnection/auth/service/SignUpService.java index aeb67d037..d6feed9e1 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignUpService.java +++ b/src/main/java/com/example/solidconnection/auth/service/SignUpService.java @@ -1,19 +1,20 @@ package com.example.solidconnection.auth.service; import static com.example.solidconnection.common.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; +import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; +import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_EXISTED; import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.auth.dto.SignUpRequest; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.location.country.domain.InterestedCountry; -import com.example.solidconnection.location.country.repository.CountryRepository; -import com.example.solidconnection.location.country.repository.InterestedCountryRepository; -import com.example.solidconnection.location.region.domain.InterestedRegion; -import com.example.solidconnection.location.region.repository.InterestedRegionRepository; -import com.example.solidconnection.location.region.repository.RegionRepository; +import com.example.solidconnection.location.country.service.InterestedCountryService; +import com.example.solidconnection.location.region.service.InterestedRegionService; +import com.example.solidconnection.siteuser.domain.AuthType; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; /* @@ -23,69 +24,77 @@ * - 관심 국가와 지역은 site_user_id를 참조하므로, 사용자 저장 후 저장한다. * - 바로 로그인하도록 액세스 토큰과 리프레시 토큰을 발급한다. * */ -public abstract class SignUpService { +@Service +@RequiredArgsConstructor +public class SignUpService { - protected final SignInService signInService; - protected final SiteUserRepository siteUserRepository; - protected final RegionRepository regionRepository; - protected final InterestedRegionRepository interestedRegionRepository; - protected final CountryRepository countryRepository; - protected final InterestedCountryRepository interestedCountryRepository; - - protected SignUpService(SignInService signInService, SiteUserRepository siteUserRepository, - RegionRepository regionRepository, InterestedRegionRepository interestedRegionRepository, - CountryRepository countryRepository, InterestedCountryRepository interestedCountryRepository) { - this.signInService = signInService; - this.siteUserRepository = siteUserRepository; - this.regionRepository = regionRepository; - this.interestedRegionRepository = interestedRegionRepository; - this.countryRepository = countryRepository; - this.interestedCountryRepository = interestedCountryRepository; - } + private final SignInService signInService; + private final SiteUserRepository siteUserRepository; + private final InterestedRegionService interestedRegionService; + private final InterestedCountryService interestedCountryService; + private final SignUpTokenProvider signUpTokenProvider; + private final PasswordTemporaryStorage passwordTemporaryStorage; @Transactional public SignInResponse signUp(SignUpRequest signUpRequest) { // 검증 - validateSignUpToken(signUpRequest); - validateUserNotDuplicated(signUpRequest); - validateNicknameDuplicated(signUpRequest.nickname()); + signUpTokenProvider.validateSignUpToken(signUpRequest.signUpToken()); + String email = signUpTokenProvider.parseEmail(signUpRequest.signUpToken()); + AuthType authType = signUpTokenProvider.parseAuthType(signUpRequest.signUpToken()); + validateNicknameNotDuplicated(signUpRequest.nickname()); + validateUserNotDuplicated(email, authType); + + // 임시 저장된 비밀번호 가져오기 + String password = getTemporarySavedPassword(email, authType); // 사용자 저장 - SiteUser siteUser = siteUserRepository.save(createSiteUser(signUpRequest)); + SiteUser siteUser = siteUserRepository.save(new SiteUser( + email, + signUpRequest.nickname(), + signUpRequest.profileImageUrl(), + signUpRequest.exchangeStatus(), + Role.MENTEE, + authType, + password + )); // 관심 지역, 국가 저장 - saveInterestedRegion(signUpRequest, siteUser); - saveInterestedCountry(signUpRequest, siteUser); + interestedRegionService.saveInterestedRegion(siteUser, signUpRequest.interestedRegions()); + interestedCountryService.saveInterestedCountry(siteUser, signUpRequest.interestedCountries()); // 로그인 - return signInService.signIn(siteUser); + SignInResponse response = signInService.signIn(siteUser); + + // 회원가입을 위해 저장한 데이터(SignUpToken, 비밀번호) 삭제 + clearSignUpData(email, authType); + + return response; } - private void validateNicknameDuplicated(String nickname) { + private void validateNicknameNotDuplicated(String nickname) { if (siteUserRepository.existsByNickname(nickname)) { throw new CustomException(NICKNAME_ALREADY_EXISTED); } } - private void saveInterestedRegion(SignUpRequest signUpRequest, SiteUser savedSiteUser) { - List interestedRegionNames = signUpRequest.interestedRegions(); - List interestedRegions = regionRepository.findByKoreanNames(interestedRegionNames).stream() - .map(region -> new InterestedRegion(savedSiteUser, region)) - .toList(); - interestedRegionRepository.saveAll(interestedRegions); + private void validateUserNotDuplicated(String email, AuthType authType) { + if (siteUserRepository.existsByEmailAndAuthType(email, authType)) { + throw new CustomException(USER_ALREADY_EXISTED); + } } - private void saveInterestedCountry(SignUpRequest signUpRequest, SiteUser savedSiteUser) { - List interestedCountryNames = signUpRequest.interestedCountries(); - List interestedCountries = countryRepository.findByKoreanNames(interestedCountryNames).stream() - .map(country -> new InterestedCountry(savedSiteUser, country)) - .toList(); - interestedCountryRepository.saveAll(interestedCountries); + private String getTemporarySavedPassword(String email, AuthType authType) { + if (authType == AuthType.EMAIL) { + return passwordTemporaryStorage.findByEmail(email) + .orElseThrow(() -> new CustomException(SIGN_UP_TOKEN_INVALID)); + } + return null; } - protected abstract void validateSignUpToken(SignUpRequest signUpRequest); - - protected abstract void validateUserNotDuplicated(SignUpRequest signUpRequest); - - protected abstract SiteUser createSiteUser(SignUpRequest signUpRequest); + private void clearSignUpData(String email, AuthType authType) { + if (authType == AuthType.EMAIL) { + passwordTemporaryStorage.deleteByEmail(email); + } + signUpTokenProvider.deleteByEmail(email); + } } diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java similarity index 87% rename from src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java rename to src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java index ae359c5b8..05480b10d 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/SignUpTokenProvider.java @@ -1,10 +1,9 @@ -package com.example.solidconnection.auth.service.oauth; +package com.example.solidconnection.auth.service; import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; import com.example.solidconnection.auth.domain.TokenType; -import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.AuthType; @@ -21,9 +20,9 @@ @Component @RequiredArgsConstructor -public class OAuthSignUpTokenProvider { +public class SignUpTokenProvider { - static final String AUTH_TYPE_CLAIM_KEY = "authType"; + private static final String AUTH_TYPE_CLAIM_KEY = "authType"; private final JwtProperties jwtProperties; private final RedisTemplate redisTemplate; @@ -44,13 +43,18 @@ public String generateAndSaveSignUpToken(String email, AuthType authType) { return tokenProvider.saveToken(signUpToken, TokenType.SIGN_UP); } + public void deleteByEmail(String email) { + String key = TokenType.SIGN_UP.addPrefix(email); + redisTemplate.delete(key); + } + public void validateSignUpToken(String token) { validateFormatAndExpiration(token); String email = parseEmail(token); validateIssuedByServer(email); } - private void validateFormatAndExpiration(String token) { + private void validateFormatAndExpiration(String token) { // 파싱되는지, AuthType이 포함되어있는지 검증 try { Claims claims = tokenProvider.parseClaims(token); Objects.requireNonNull(claims.getSubject()); diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java index bb34a3739..9343bfa21 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java @@ -7,6 +7,7 @@ import com.example.solidconnection.auth.dto.oauth.OAuthUserInfoDto; import com.example.solidconnection.auth.dto.oauth.SignUpPrepareResponse; import com.example.solidconnection.auth.service.SignInService; +import com.example.solidconnection.auth.service.SignUpTokenProvider; import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; @@ -24,7 +25,7 @@ @RequiredArgsConstructor public class OAuthService { - private final OAuthSignUpTokenProvider OAuthSignUpTokenProvider; + private final SignUpTokenProvider signUpTokenProvider; private final SignInService signInService; private final SiteUserRepository siteUserRepository; private final OAuthClientMap oauthClientMap; @@ -49,7 +50,7 @@ private OAuthSignInResponse getSignInResponse(SiteUser siteUser) { } private SignUpPrepareResponse getSignUpPrepareResponse(OAuthUserInfoDto userInfoDto, AuthType authType) { - String signUpToken = OAuthSignUpTokenProvider.generateAndSaveSignUpToken(userInfoDto.getEmail(), authType); + String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(userInfoDto.getEmail(), authType); return SignUpPrepareResponse.of(userInfoDto, signUpToken); } } diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java deleted file mode 100644 index ca50442fc..000000000 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.example.solidconnection.auth.service.oauth; - -import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_EXISTED; - -import com.example.solidconnection.auth.dto.SignUpRequest; -import com.example.solidconnection.auth.service.SignInService; -import com.example.solidconnection.auth.service.SignUpService; -import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.location.country.repository.CountryRepository; -import com.example.solidconnection.location.country.repository.InterestedCountryRepository; -import com.example.solidconnection.location.region.repository.InterestedRegionRepository; -import com.example.solidconnection.location.region.repository.RegionRepository; -import com.example.solidconnection.siteuser.domain.AuthType; -import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import org.springframework.stereotype.Service; - -@Service -public class OAuthSignUpService extends SignUpService { - - private final OAuthSignUpTokenProvider oAuthSignUpTokenProvider; - - OAuthSignUpService(SignInService signInService, SiteUserRepository siteUserRepository, - RegionRepository regionRepository, InterestedRegionRepository interestedRegionRepository, - CountryRepository countryRepository, InterestedCountryRepository interestedCountryRepository, - OAuthSignUpTokenProvider oAuthSignUpTokenProvider) { - super(signInService, siteUserRepository, regionRepository, interestedRegionRepository, countryRepository, interestedCountryRepository); - this.oAuthSignUpTokenProvider = oAuthSignUpTokenProvider; - } - - @Override - protected void validateSignUpToken(SignUpRequest signUpRequest) { - oAuthSignUpTokenProvider.validateSignUpToken(signUpRequest.signUpToken()); - } - - @Override - protected void validateUserNotDuplicated(SignUpRequest signUpRequest) { - String email = oAuthSignUpTokenProvider.parseEmail(signUpRequest.signUpToken()); - AuthType authType = oAuthSignUpTokenProvider.parseAuthType(signUpRequest.signUpToken()); - if (siteUserRepository.existsByEmailAndAuthType(email, authType)) { - throw new CustomException(USER_ALREADY_EXISTED); - } - } - - @Override - protected SiteUser createSiteUser(SignUpRequest signUpRequest) { - String email = oAuthSignUpTokenProvider.parseEmail(signUpRequest.signUpToken()); - AuthType authType = oAuthSignUpTokenProvider.parseAuthType(signUpRequest.signUpToken()); - return signUpRequest.toOAuthSiteUser(email, authType); - } -} diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index b0b04829b..80b833a9c 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -56,6 +56,7 @@ public enum ErrorCode { ACCESS_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED.value(), "액세스 토큰이 만료되었습니다. 재발급 api를 호출해주세요."), REFRESH_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED.value(), "리프레시 토큰이 만료되었습니다. 다시 로그인을 진행해주세요."), ACCESS_DENIED(HttpStatus.FORBIDDEN.value(), "접근 권한이 없습니다."), + SIGN_IN_FAILED(HttpStatus.UNAUTHORIZED.value(), "로그인에 실패했습니다. 이메일과 비밀번호를 확인해주세요."), // s3 S3_SERVICE_EXCEPTION(HttpStatus.BAD_REQUEST.value(), "S3 서비스 에러 발생"), diff --git a/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java b/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java index 70477f1f4..5ba92f80a 100644 --- a/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java +++ b/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java @@ -3,11 +3,8 @@ import com.example.solidconnection.location.country.domain.Country; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; public interface CountryRepository extends JpaRepository { - @Query("SELECT c FROM Country c WHERE c.koreanName IN :names") - List findByKoreanNames(@Param(value = "names") List names); + List findAllByKoreanNameIn(List koreanNames); } diff --git a/src/main/java/com/example/solidconnection/location/country/service/InterestedCountryService.java b/src/main/java/com/example/solidconnection/location/country/service/InterestedCountryService.java new file mode 100644 index 000000000..8166de968 --- /dev/null +++ b/src/main/java/com/example/solidconnection/location/country/service/InterestedCountryService.java @@ -0,0 +1,27 @@ +package com.example.solidconnection.location.country.service; + +import com.example.solidconnection.location.country.domain.InterestedCountry; +import com.example.solidconnection.location.country.repository.CountryRepository; +import com.example.solidconnection.location.country.repository.InterestedCountryRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class InterestedCountryService { + + private final CountryRepository countryRepository; + private final InterestedCountryRepository interestedCountryRepository; + + @Transactional + public void saveInterestedCountry(SiteUser siteUser, List koreanNames) { + List interestedCountries = countryRepository.findAllByKoreanNameIn(koreanNames) + .stream() + .map(country -> new InterestedCountry(siteUser, country)) + .toList(); + interestedCountryRepository.saveAll(interestedCountries); + } +} diff --git a/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java b/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java index 656fc4377..dea93fb34 100644 --- a/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java +++ b/src/main/java/com/example/solidconnection/location/region/repository/RegionRepository.java @@ -4,13 +4,10 @@ import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; public interface RegionRepository extends JpaRepository { - @Query("SELECT r FROM Region r WHERE r.koreanName IN :names") - List findByKoreanNames(@Param(value = "names") List names); + List findAllByKoreanNameIn(List koreanNames); Optional findByKoreanName(String koreanName); } diff --git a/src/main/java/com/example/solidconnection/location/region/service/InterestedRegionService.java b/src/main/java/com/example/solidconnection/location/region/service/InterestedRegionService.java new file mode 100644 index 000000000..6dc71263e --- /dev/null +++ b/src/main/java/com/example/solidconnection/location/region/service/InterestedRegionService.java @@ -0,0 +1,27 @@ +package com.example.solidconnection.location.region.service; + +import com.example.solidconnection.location.region.domain.InterestedRegion; +import com.example.solidconnection.location.region.repository.InterestedRegionRepository; +import com.example.solidconnection.location.region.repository.RegionRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class InterestedRegionService { + + private final RegionRepository regionRepository; + private final InterestedRegionRepository interestedRegionRepository; + + @Transactional + public void saveInterestedRegion(SiteUser siteUser, List koreanNames) { + List interestedRegions = regionRepository.findAllByKoreanNameIn(koreanNames) + .stream() + .map(region -> new InterestedRegion(siteUser, region)) + .toList(); + interestedRegionRepository.saveAll(interestedRegions); + } +} diff --git a/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java index f9d481311..04b6780ad 100644 --- a/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java @@ -55,7 +55,7 @@ class 로그인에_실패한다 { // when & then assertThatCode(() -> emailSignInService.signIn(signInRequest)) .isInstanceOf(CustomException.class) - .hasMessageContaining(ErrorCode.USER_NOT_FOUND.getMessage()); + .hasMessageContaining(ErrorCode.SIGN_IN_FAILED.getMessage()); } @Test @@ -68,7 +68,7 @@ class 로그인에_실패한다 { // when & then assertThatCode(() -> emailSignInService.signIn(signInRequest)) .isInstanceOf(CustomException.class) - .hasMessageContaining(ErrorCode.USER_NOT_FOUND.getMessage()); + .hasMessageContaining(ErrorCode.SIGN_IN_FAILED.getMessage()); } } } diff --git a/src/test/java/com/example/solidconnection/auth/service/PasswordTemporaryStorageTest.java b/src/test/java/com/example/solidconnection/auth/service/PasswordTemporaryStorageTest.java new file mode 100644 index 000000000..ea3ed6355 --- /dev/null +++ b/src/test/java/com/example/solidconnection/auth/service/PasswordTemporaryStorageTest.java @@ -0,0 +1,48 @@ +package com.example.solidconnection.auth.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.example.solidconnection.support.TestContainerSpringBootTest; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; + +@DisplayName("비밀번호 임시 저장소 테스트") +@TestContainerSpringBootTest +class PasswordTemporaryStorageTest { + + @Autowired + private PasswordTemporaryStorage passwordTemporaryStorage; + + @Autowired + private PasswordEncoder passwordEncoder; + + private final String email = "test@email.com"; + private final String rawPassword = "password123"; + + @Test + void 인코딩된_비밀번호를_임시_저장소에_저장하고_조회한다() { + // when + passwordTemporaryStorage.save(email, rawPassword); + Optional foundPassword = passwordTemporaryStorage.findByEmail(email); + + // then + assertThat(foundPassword).isPresent(); + assertThat(passwordEncoder.matches(rawPassword, foundPassword.get())).isTrue(); + } + + @Test + void 임시_저장된_비밀번호를_삭제한다() { + // given + passwordTemporaryStorage.save(email, rawPassword); + + // when + passwordTemporaryStorage.deleteByEmail(email); + Optional foundPassword = passwordTemporaryStorage.findByEmail(email); + + // then + assertThat(foundPassword).isEmpty(); + } +} diff --git a/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/SignUpTokenProviderTest.java similarity index 69% rename from src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java rename to src/test/java/com/example/solidconnection/auth/service/SignUpTokenProviderTest.java index bd8833bb0..c75eac5f5 100644 --- a/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/SignUpTokenProviderTest.java @@ -1,6 +1,5 @@ -package com.example.solidconnection.auth.service.oauth; +package com.example.solidconnection.auth.service; -import static com.example.solidconnection.auth.service.oauth.OAuthSignUpTokenProvider.AUTH_TYPE_CLAIM_KEY; import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; import static org.assertj.core.api.Assertions.assertThat; @@ -8,7 +7,6 @@ import static org.junit.jupiter.api.Assertions.assertAll; import com.example.solidconnection.auth.domain.TokenType; -import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.AuthType; @@ -27,11 +25,11 @@ import org.springframework.data.redis.core.RedisTemplate; @TestContainerSpringBootTest -@DisplayName("OAuth 회원가입 토큰 제공자 테스트") -class OAuthSignUpTokenProviderTest { +@DisplayName("회원가입 토큰 제공자 테스트") +class SignUpTokenProviderTest { @Autowired - private OAuthSignUpTokenProvider OAuthSignUpTokenProvider; + private SignUpTokenProvider signUpTokenProvider; @Autowired private TokenProvider tokenProvider; @@ -42,19 +40,19 @@ class OAuthSignUpTokenProviderTest { @Autowired private JwtProperties jwtProperties; + private final String authTypeClaimKey = "authType"; + private final String email = "test@email.com"; + private final AuthType authType = AuthType.KAKAO; + @Test void 회원가입_토큰을_생성하고_저장한다() { - // given - String email = "email"; - AuthType authType = AuthType.KAKAO; - // when - String signUpToken = OAuthSignUpTokenProvider.generateAndSaveSignUpToken(email, authType); + String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType); // then Claims claims = tokenProvider.parseClaims(signUpToken); String actualSubject = claims.getSubject(); - AuthType actualAuthType = AuthType.valueOf(claims.get(AUTH_TYPE_CLAIM_KEY, String.class)); + AuthType actualAuthType = AuthType.valueOf(claims.get(authTypeClaimKey, String.class)); String signUpTokenKey = TokenType.SIGN_UP.addPrefix(email); assertAll( () -> assertThat(actualSubject).isEqualTo(email), @@ -63,19 +61,31 @@ class OAuthSignUpTokenProviderTest { ); } + @Test + void 회원가입_토큰을_삭제한다() { + // given + signUpTokenProvider.generateAndSaveSignUpToken(email, authType); + + // when + signUpTokenProvider.deleteByEmail(email); + + // then + String signUpTokenKey = TokenType.SIGN_UP.addPrefix(email); + assertThat(redisTemplate.opsForValue().get(signUpTokenKey)).isNull(); + } + @Nested class 주어진_회원가입_토큰을_검증한다 { @Test void 검증_성공한다() { // given - String email = "email@test.com"; - Map claim = new HashMap<>(Map.of(AUTH_TYPE_CLAIM_KEY, AuthType.APPLE)); + Map claim = new HashMap<>(Map.of(authTypeClaimKey, authType)); String validToken = createBaseJwtBuilder().setSubject(email).addClaims(claim).compact(); redisTemplate.opsForValue().set(TokenType.SIGN_UP.addPrefix(email), validToken); // when & then - assertThatCode(() -> OAuthSignUpTokenProvider.validateSignUpToken(validToken)).doesNotThrowAnyException(); + assertThatCode(() -> signUpTokenProvider.validateSignUpToken(validToken)).doesNotThrowAnyException(); } @Test @@ -84,7 +94,7 @@ class 주어진_회원가입_토큰을_검증한다 { String expiredToken = createExpiredToken(); // when & then - assertThatCode(() -> OAuthSignUpTokenProvider.validateSignUpToken(expiredToken)) + assertThatCode(() -> signUpTokenProvider.validateSignUpToken(expiredToken)) .isInstanceOf(CustomException.class) .hasMessageContaining(SIGN_UP_TOKEN_INVALID.getMessage()); } @@ -95,7 +105,7 @@ class 주어진_회원가입_토큰을_검증한다 { String notJwt = "not jwt"; // when & then - assertThatCode(() -> OAuthSignUpTokenProvider.validateSignUpToken(notJwt)) + assertThatCode(() -> signUpTokenProvider.validateSignUpToken(notJwt)) .isInstanceOf(CustomException.class) .hasMessageContaining(SIGN_UP_TOKEN_INVALID.getMessage()); } @@ -103,11 +113,12 @@ class 주어진_회원가입_토큰을_검증한다 { @Test void 정해진_형식에_맞지_않으면_예외가_발생한다_authType_클래스_불일치() { // given - Map wrongClaim = new HashMap<>(Map.of(AUTH_TYPE_CLAIM_KEY, "카카오")); - String wrongAuthType = createBaseJwtBuilder().addClaims(wrongClaim).compact(); + String wrongAuthType = "카카오"; + Map wrongClaim = new HashMap<>(Map.of(authTypeClaimKey, wrongAuthType)); + String wrongAuthTypeClaim = createBaseJwtBuilder().addClaims(wrongClaim).compact(); // when & then - assertThatCode(() -> OAuthSignUpTokenProvider.validateSignUpToken(wrongAuthType)) + assertThatCode(() -> signUpTokenProvider.validateSignUpToken(wrongAuthTypeClaim)) .isInstanceOf(CustomException.class) .hasMessageContaining(SIGN_UP_TOKEN_INVALID.getMessage()); } @@ -115,11 +126,11 @@ class 주어진_회원가입_토큰을_검증한다 { @Test void 정해진_형식에_맞지_않으면_예외가_발생한다_subject_누락() { // given - Map claim = new HashMap<>(Map.of(AUTH_TYPE_CLAIM_KEY, AuthType.APPLE)); + Map claim = new HashMap<>(Map.of(authTypeClaimKey, authType)); String noSubject = createBaseJwtBuilder().addClaims(claim).compact(); // when & then - assertThatCode(() -> OAuthSignUpTokenProvider.validateSignUpToken(noSubject)) + assertThatCode(() -> signUpTokenProvider.validateSignUpToken(noSubject)) .isInstanceOf(CustomException.class) .hasMessageContaining(SIGN_UP_TOKEN_INVALID.getMessage()); } @@ -127,11 +138,11 @@ class 주어진_회원가입_토큰을_검증한다 { @Test void 우리_서버에_발급된_토큰이_아니면_예외가_발생한다() { // given - Map validClaim = new HashMap<>(Map.of(AUTH_TYPE_CLAIM_KEY, AuthType.APPLE)); - String signUpToken = createBaseJwtBuilder().addClaims(validClaim).setSubject("email").compact(); + Map validClaim = new HashMap<>(Map.of(authTypeClaimKey, authType)); + String signUpToken = createBaseJwtBuilder().addClaims(validClaim).setSubject(email).compact(); // when & then - assertThatCode(() -> OAuthSignUpTokenProvider.validateSignUpToken(signUpToken)) + assertThatCode(() -> signUpTokenProvider.validateSignUpToken(signUpToken)) .isInstanceOf(CustomException.class) .hasMessageContaining(SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER.getMessage()); } @@ -140,13 +151,12 @@ class 주어진_회원가입_토큰을_검증한다 { @Test void 회원가입_토큰에서_이메일을_추출한다() { // given - String email = "email@test.com"; - Map claim = Map.of(AUTH_TYPE_CLAIM_KEY, AuthType.APPLE); + Map claim = Map.of(authTypeClaimKey, authType); String validToken = createBaseJwtBuilder().setSubject(email).addClaims(claim).compact(); redisTemplate.opsForValue().set(TokenType.SIGN_UP.addPrefix(email), validToken); // when - String extractedEmail = OAuthSignUpTokenProvider.parseEmail(validToken); + String extractedEmail = signUpTokenProvider.parseEmail(validToken); // then assertThat(extractedEmail).isEqualTo(email); @@ -155,12 +165,11 @@ class 주어진_회원가입_토큰을_검증한다 { @Test void 회원가입_토큰에서_인증_타입을_추출한다() { // given - AuthType authType = AuthType.APPLE; - Map claim = Map.of(AUTH_TYPE_CLAIM_KEY, authType); - String validToken = createBaseJwtBuilder().setSubject("email").addClaims(claim).compact(); + Map claim = Map.of(authTypeClaimKey, authType); + String validToken = createBaseJwtBuilder().setSubject(email).addClaims(claim).compact(); // when - AuthType extractedAuthType = OAuthSignUpTokenProvider.parseAuthType(validToken); + AuthType extractedAuthType = signUpTokenProvider.parseAuthType(validToken); // then assertThat(extractedAuthType).isEqualTo(authType); @@ -168,7 +177,7 @@ class 주어진_회원가입_토큰을_검증한다 { private String createExpiredToken() { return Jwts.builder() - .setSubject("subject") + .setSubject(email) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() - 1000)) .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) From 1f8e45521489d54e36c26d43fe4f061d98e3b436 Mon Sep 17 00:00:00 2001 From: seonghyeok cho <65901319+whqtker@users.noreply.github.com> Date: Thu, 14 Aug 2025 11:20:37 +0900 Subject: [PATCH 74/90] =?UTF-8?q?feat:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B3=80=EA=B2=BD=20API=20=EA=B5=AC=ED=98=84=20(#4?= =?UTF-8?q?48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 비밀번호 변경 DTO 작성 * feat: 비밀번호 변경 검증 관련 어노테이션 작성 * feat: 비밀번호 변경 API 구현 * test: 비밀번호 변경 관련 테스트 코드 작성 * chore: 코드 리포매팅 * test: 검증 방식 변경 * chore: Password 어노테이션 추가 * chore: 명확하게 의미 전달이 가능한 변수명으로 수정 * test: 테스트 코드 수정 * test: 실제 비밀번호 변경 플로우에 맞도록 테스트 코드 수정 * chore: matches 메서드 정의에 맞게 파라미터 수정 * chore: 역할을 더 잘 드러내도록 검증 메서드명 변경 * feat: 비밀번호 검증 추가 - 비밀번호는 영문, 숫자, 특수문자를 포함한 8자리 이상 * fix: Password 어노테이션 import문 추가 * fix: currentPassword가 인코딩되었던 문제 해결 --------- Co-authored-by: Gyuhyeok99 --- .../common/exception/ErrorCode.java | 3 + .../siteuser/controller/MyPageController.java | 12 +++ .../siteuser/domain/SiteUser.java | 4 + .../siteuser/dto/PasswordUpdateRequest.java | 20 +++++ .../dto/validation/PasswordConfirmation.java | 20 +++++ .../PasswordConfirmationValidator.java | 45 +++++++++++ .../siteuser/service/MyPageService.java | 21 +++++ .../PasswordConfirmationValidatorTest.java | 76 +++++++++++++++++++ .../siteuser/service/MyPageServiceTest.java | 55 +++++++++++++- 9 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/siteuser/dto/PasswordUpdateRequest.java create mode 100644 src/main/java/com/example/solidconnection/siteuser/dto/validation/PasswordConfirmation.java create mode 100644 src/main/java/com/example/solidconnection/siteuser/dto/validation/PasswordConfirmationValidator.java create mode 100644 src/test/java/com/example/solidconnection/siteuser/dto/validation/PasswordConfirmationValidatorTest.java diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index 80b833a9c..defb0230f 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -56,6 +56,9 @@ public enum ErrorCode { ACCESS_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED.value(), "액세스 토큰이 만료되었습니다. 재발급 api를 호출해주세요."), REFRESH_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED.value(), "리프레시 토큰이 만료되었습니다. 다시 로그인을 진행해주세요."), ACCESS_DENIED(HttpStatus.FORBIDDEN.value(), "접근 권한이 없습니다."), + PASSWORD_MISMATCH(HttpStatus.BAD_REQUEST.value(), "비밀번호가 일치하지 않습니다."), + PASSWORD_NOT_CHANGED(HttpStatus.BAD_REQUEST.value(), "현재 비밀번호와 새 비밀번호가 동일합니다."), + PASSWORD_NOT_CONFIRMED(HttpStatus.BAD_REQUEST.value(), "새 비밀번호가 일치하지 않습니다."), SIGN_IN_FAILED(HttpStatus.UNAUTHORIZED.value(), "로그인에 실패했습니다. 이메일과 비밀번호를 확인해주세요."), // s3 diff --git a/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java b/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java index 39dcda29a..534b6c361 100644 --- a/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java +++ b/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java @@ -3,11 +3,14 @@ import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.siteuser.dto.MyPageResponse; +import com.example.solidconnection.siteuser.dto.PasswordUpdateRequest; import com.example.solidconnection.siteuser.service.MyPageService; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -37,4 +40,13 @@ public ResponseEntity updateMyPageInfo( myPageService.updateMyPageInfo(siteUserId, imageFile, nickname); return ResponseEntity.ok().build(); } + + @PatchMapping("/password") + public ResponseEntity updatePassword( + @AuthorizedUser long siteUserId, + @RequestBody @Valid PasswordUpdateRequest request + ) { + myPageService.updatePassword(siteUserId, request); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java index 0a15317c1..9f4f7ef0f 100644 --- a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java +++ b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java @@ -115,4 +115,8 @@ public SiteUser( this.authType = authType; this.password = password; } + + public void updatePassword(String newEncodedPassword) { + this.password = newEncodedPassword; + } } diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/PasswordUpdateRequest.java b/src/main/java/com/example/solidconnection/siteuser/dto/PasswordUpdateRequest.java new file mode 100644 index 000000000..3f3d7b077 --- /dev/null +++ b/src/main/java/com/example/solidconnection/siteuser/dto/PasswordUpdateRequest.java @@ -0,0 +1,20 @@ +package com.example.solidconnection.siteuser.dto; + +import com.example.solidconnection.auth.dto.validation.Password; +import com.example.solidconnection.siteuser.dto.validation.PasswordConfirmation; +import jakarta.validation.constraints.NotBlank; + +@PasswordConfirmation +public record PasswordUpdateRequest( + @NotBlank(message = "현재 비밀번호를 입력해주세요.") + String currentPassword, + + @NotBlank(message = "새 비밀번호를 입력해주세요.") + @Password + String newPassword, + + @NotBlank(message = "새 비밀번호를 다시 한번 입력해주세요.") + String newPasswordConfirmation +) { + +} diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/validation/PasswordConfirmation.java b/src/main/java/com/example/solidconnection/siteuser/dto/validation/PasswordConfirmation.java new file mode 100644 index 000000000..cfc34638f --- /dev/null +++ b/src/main/java/com/example/solidconnection/siteuser/dto/validation/PasswordConfirmation.java @@ -0,0 +1,20 @@ +package com.example.solidconnection.siteuser.dto.validation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Constraint(validatedBy = PasswordConfirmationValidator.class) +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface PasswordConfirmation { + + String message() default "비밀번호 변경 과정에서 오류가 발생했습니다."; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/validation/PasswordConfirmationValidator.java b/src/main/java/com/example/solidconnection/siteuser/dto/validation/PasswordConfirmationValidator.java new file mode 100644 index 000000000..7524e4505 --- /dev/null +++ b/src/main/java/com/example/solidconnection/siteuser/dto/validation/PasswordConfirmationValidator.java @@ -0,0 +1,45 @@ +package com.example.solidconnection.siteuser.dto.validation; + +import static com.example.solidconnection.common.exception.ErrorCode.PASSWORD_NOT_CHANGED; +import static com.example.solidconnection.common.exception.ErrorCode.PASSWORD_NOT_CONFIRMED; + +import com.example.solidconnection.siteuser.dto.PasswordUpdateRequest; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import java.util.Objects; + +public class PasswordConfirmationValidator implements ConstraintValidator { + + @Override + public boolean isValid(PasswordUpdateRequest request, ConstraintValidatorContext context) { + context.disableDefaultConstraintViolation(); + + if (isNewPasswordNotConfirmed(request)) { + addConstraintViolation(context, PASSWORD_NOT_CONFIRMED.getMessage(), "newPasswordConfirmation"); + + return false; + } + + if (isPasswordUnchanged(request)) { + addConstraintViolation(context, PASSWORD_NOT_CHANGED.getMessage(), "newPassword"); + + return false; + } + + return true; + } + + private boolean isNewPasswordNotConfirmed(PasswordUpdateRequest request) { + return !Objects.equals(request.newPassword(), request.newPasswordConfirmation()); + } + + private boolean isPasswordUnchanged(PasswordUpdateRequest request) { + return Objects.equals(request.currentPassword(), request.newPassword()); + } + + private void addConstraintViolation(ConstraintValidatorContext context, String message, String propertyName) { + context.buildConstraintViolationWithTemplate(message) + .addPropertyNode(propertyName) + .addConstraintViolation(); + } +} diff --git a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java index 7b85bd411..1800df32b 100644 --- a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java +++ b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java @@ -2,6 +2,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; import static com.example.solidconnection.common.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; +import static com.example.solidconnection.common.exception.ErrorCode.PASSWORD_MISMATCH; import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.common.exception.CustomException; @@ -10,11 +11,13 @@ import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.MyPageResponse; +import com.example.solidconnection.siteuser.dto.PasswordUpdateRequest; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -26,6 +29,7 @@ public class MyPageService { public static final int MIN_DAYS_BETWEEN_NICKNAME_CHANGES = 7; public static final DateTimeFormatter NICKNAME_LAST_CHANGE_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + private final PasswordEncoder passwordEncoder; private final SiteUserRepository siteUserRepository; private final LikedUnivApplyInfoRepository likedUnivApplyInfoRepository; private final S3Service s3Service; @@ -87,4 +91,21 @@ private boolean isDefaultProfileImage(String profileImageUrl) { String prefix = "profile/"; return profileImageUrl == null || !profileImageUrl.startsWith(prefix); } + + @Transactional + public void updatePassword(long siteUserId, PasswordUpdateRequest request) { + SiteUser user = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); + + // 사용자의 비밀번호와 request의 currentPassword가 동일한지 검증 + validatePasswordMatch(request.currentPassword(), user.getPassword()); + + user.updatePassword(passwordEncoder.encode(request.newPassword())); + } + + private void validatePasswordMatch(String currentPassword, String userPassword) { + if (!passwordEncoder.matches(currentPassword, userPassword)) { + throw new CustomException(PASSWORD_MISMATCH); + } + } } diff --git a/src/test/java/com/example/solidconnection/siteuser/dto/validation/PasswordConfirmationValidatorTest.java b/src/test/java/com/example/solidconnection/siteuser/dto/validation/PasswordConfirmationValidatorTest.java new file mode 100644 index 000000000..c3c69f8fc --- /dev/null +++ b/src/test/java/com/example/solidconnection/siteuser/dto/validation/PasswordConfirmationValidatorTest.java @@ -0,0 +1,76 @@ +package com.example.solidconnection.siteuser.dto.validation; + +import static com.example.solidconnection.common.exception.ErrorCode.PASSWORD_NOT_CHANGED; +import static com.example.solidconnection.common.exception.ErrorCode.PASSWORD_NOT_CONFIRMED; +import static org.assertj.core.api.Assertions.assertThat; + +import com.example.solidconnection.siteuser.dto.PasswordUpdateRequest; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import java.util.Set; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +@DisplayName("비밀번호 변경 유효성 검사 테스트") +class PasswordConfirmationValidatorTest { + + private static final String MESSAGE = "message"; + + private Validator validator; + + @BeforeEach + void setUp() { + ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); + validator = factory.getValidator(); + } + + @Test + void 유효한_비밀번호_변경_요청은_검증을_통과한다() { + // given + PasswordUpdateRequest request = new PasswordUpdateRequest("currentPassword123", "newPassword123!", "newPassword123!"); + + // when + Set> violations = validator.validate(request); + + // then + assertThat(violations).isEmpty(); + } + + @Nested + class 유효하지_않은_비밀번호_변경_테스트 { + + @Test + void 새로운_비밀번호와_확인_비밀번호가_일치하지_않으면_검증에_실패한다() { + // given + PasswordUpdateRequest request = new PasswordUpdateRequest("currentPassword123", "newPassword123!", "differentPassword123!"); + + // when + Set> violations = validator.validate(request); + + // then + assertThat(violations) + .isNotEmpty() + .extracting(MESSAGE) + .contains(PASSWORD_NOT_CONFIRMED.getMessage()); + } + + @Test + void 현재_비밀번호와_새로운_비밀번호가_같으면_검증에_실패한다() { + // given + PasswordUpdateRequest request = new PasswordUpdateRequest("currentPassword123", "currentPassword123", "currentPassword123"); + + // when + Set> violations = validator.validate(request); + + // then + assertThat(violations) + .isNotEmpty() + .extracting(MESSAGE) + .contains(PASSWORD_NOT_CHANGED.getMessage()); + } + } +} diff --git a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java index 0358f8024..f470e9cfc 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java @@ -1,10 +1,13 @@ package com.example.solidconnection.siteuser.service; import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; +import static com.example.solidconnection.common.exception.ErrorCode.PASSWORD_MISMATCH; import static com.example.solidconnection.siteuser.service.MyPageService.MIN_DAYS_BETWEEN_NICKNAME_CHANGES; import static com.example.solidconnection.siteuser.service.MyPageService.NICKNAME_LAST_CHANGE_DATE_FORMAT; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.BDDMockito.any; import static org.mockito.BDDMockito.eq; import static org.mockito.BDDMockito.given; @@ -19,6 +22,7 @@ import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.MyPageResponse; +import com.example.solidconnection.siteuser.dto.PasswordUpdateRequest; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.siteuser.fixture.SiteUserFixtureBuilder; import com.example.solidconnection.siteuser.repository.SiteUserRepository; @@ -27,7 +31,6 @@ import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import java.time.LocalDateTime; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -35,6 +38,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.mock.web.MockMultipartFile; +import org.springframework.security.crypto.password.PasswordEncoder; @TestContainerSpringBootTest @DisplayName("마이페이지 서비스 테스트") @@ -61,6 +65,9 @@ class MyPageServiceTest { @Autowired private SiteUserFixtureBuilder siteUserFixtureBuilder; + @Autowired + private PasswordEncoder passwordEncoder; + private SiteUser user; @BeforeEach @@ -77,7 +84,7 @@ void setUp() { MyPageResponse response = myPageService.getMyPageInfo(user.getId()); // then - Assertions.assertAll( + assertAll( () -> assertThat(response.nickname()).isEqualTo(user.getNickname()), () -> assertThat(response.profileImageUrl()).isEqualTo(user.getProfileImageUrl()), () -> assertThat(response.role()).isEqualTo(user.getRole()), @@ -176,6 +183,50 @@ void setUp() { } } + @Nested + class 비밀번호_변경_테스트 { + + private String currentPassword; + private String newPassword; + + @BeforeEach + void setUp() { + currentPassword = "currentPassword123"; + newPassword = "newPassword123"; + + user.updatePassword(passwordEncoder.encode(currentPassword)); + siteUserRepository.save(user); + } + + @Test + void 비밀번호를_성공적으로_변경한다() { + // given + PasswordUpdateRequest request = new PasswordUpdateRequest(currentPassword, newPassword, newPassword); + + // when + myPageService.updatePassword(user.getId(), request); + + // then + SiteUser updatedUser = siteUserRepository.findById(user.getId()).get(); + assertAll( + () -> assertThat(passwordEncoder.matches(newPassword, updatedUser.getPassword())).isTrue(), + () -> assertThat(passwordEncoder.matches(currentPassword, updatedUser.getPassword())).isFalse() + ); + } + + @Test + void 현재_비밀번호가_일치하지_않으면_예외가_발생한다() { + // given + String wrongPassword = "wrongPassword"; + PasswordUpdateRequest request = new PasswordUpdateRequest(wrongPassword, newPassword, newPassword); + + // when & then + assertThatThrownBy(() -> myPageService.updatePassword(user.getId(), request)) + .isInstanceOf(CustomException.class) + .hasMessage(PASSWORD_MISMATCH.getMessage()); + } + } + private int createLikedUnivApplyInfos(SiteUser testUser) { LikedUnivApplyInfo likedUnivApplyInfo1 = new LikedUnivApplyInfo(null, univApplyInfoFixture.괌대학_A_지원_정보().getId(), testUser.getId()); LikedUnivApplyInfo likedUnivApplyInfo2 = new LikedUnivApplyInfo(null, univApplyInfoFixture.메이지대학_지원_정보().getId(), testUser.getId()); From e0dd5b7db39ca64e490576db50b8b408a9add31e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Thu, 14 Aug 2025 21:02:37 +0900 Subject: [PATCH 75/90] =?UTF-8?q?feat:=20=EB=A7=88=EC=9D=B4=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=9D=91=EB=8B=B5=20=EC=9A=94=EC=86=8C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#437)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 마이페이지 응답 요소 추가 - 멘티의 경우는 관심 국가 어디인지 - 멘토의 경우는 수학 대학교 어디인지 * test: 마이페이지 응답 요소 추가 관련 테스트 코드 작성 * refactor: null 반환 가능성 제거 * refactor: static import로 가독성 개선 * refactor: MyPageResponse 인스턴스에 대한 재할당 제거 * fix: 머지 충돌 해결 --- .../country/repository/CountryRepository.java | 13 ++ .../siteuser/dto/MyPageResponse.java | 18 +- .../siteuser/service/MyPageService.java | 26 ++- .../siteuser/service/MyPageServiceTest.java | 178 +++++++++--------- 4 files changed, 141 insertions(+), 94 deletions(-) diff --git a/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java b/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java index 5ba92f80a..8b997a2de 100644 --- a/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java +++ b/src/main/java/com/example/solidconnection/location/country/repository/CountryRepository.java @@ -3,8 +3,21 @@ import com.example.solidconnection.location.country.domain.Country; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface CountryRepository extends JpaRepository { List findAllByKoreanNameIn(List koreanNames); + + @Query(""" + SELECT DISTINCT c.koreanName + FROM Country c + WHERE c.code IN ( + SELECT ic.countryCode + FROM InterestedCountry ic + WHERE ic.siteUserId = :siteUserId + ) + """) + List findKoreanNamesBySiteUserId(@Param("siteUserId") long siteUserId); } diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/MyPageResponse.java b/src/main/java/com/example/solidconnection/siteuser/dto/MyPageResponse.java index d84ed61ec..10b8b8953 100644 --- a/src/main/java/com/example/solidconnection/siteuser/dto/MyPageResponse.java +++ b/src/main/java/com/example/solidconnection/siteuser/dto/MyPageResponse.java @@ -1,9 +1,13 @@ package com.example.solidconnection.siteuser.dto; +import static com.fasterxml.jackson.annotation.JsonInclude.Include.*; + import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; public record MyPageResponse( String nickname, @@ -15,9 +19,15 @@ public record MyPageResponse( int likedMentorCount, @JsonProperty("likedUniversityCount") - int likedUnivApplyInfoCount) { + int likedUnivApplyInfoCount, + + @JsonInclude(NON_NULL) + List interestedCountries, + + @JsonInclude(NON_NULL) + String attendedUniversity) { - public static MyPageResponse of(SiteUser siteUser, int likedUnivApplyInfoCount) { + public static MyPageResponse of(SiteUser siteUser, int likedUnivApplyInfoCount, List interestedCountries, String attendedUniversity) { return new MyPageResponse( siteUser.getNickname(), siteUser.getProfileImageUrl(), @@ -26,7 +36,9 @@ public static MyPageResponse of(SiteUser siteUser, int likedUnivApplyInfoCount) siteUser.getEmail(), 0, // TODO: 커뮤니티 기능 생기면 업데이트 필요 0, // TODO: 멘토 기능 생기면 업데이트 필요 - likedUnivApplyInfoCount + likedUnivApplyInfoCount, + interestedCountries, + attendedUniversity ); } } diff --git a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java index 1800df32b..d1d6e340c 100644 --- a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java +++ b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java @@ -1,21 +1,30 @@ package com.example.solidconnection.siteuser.service; import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; +import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; import static com.example.solidconnection.common.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; import static com.example.solidconnection.common.exception.ErrorCode.PASSWORD_MISMATCH; +import static com.example.solidconnection.common.exception.ErrorCode.UNIVERSITY_NOT_FOUND; import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.location.country.repository.CountryRepository; +import com.example.solidconnection.mentor.domain.Mentor; +import com.example.solidconnection.mentor.repository.MentorRepository; import com.example.solidconnection.s3.domain.ImgType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; +import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.MyPageResponse; import com.example.solidconnection.siteuser.dto.PasswordUpdateRequest; import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; +import com.example.solidconnection.university.repository.UniversityRepository; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -32,6 +41,9 @@ public class MyPageService { private final PasswordEncoder passwordEncoder; private final SiteUserRepository siteUserRepository; private final LikedUnivApplyInfoRepository likedUnivApplyInfoRepository; + private final CountryRepository countryRepository; + private final MentorRepository mentorRepository; + private final UniversityRepository universityRepository; private final S3Service s3Service; /* @@ -42,7 +54,19 @@ public MyPageResponse getMyPageInfo(long siteUserId) { SiteUser siteUser = siteUserRepository.findById(siteUserId) .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); int likedUnivApplyInfoCount = likedUnivApplyInfoRepository.countBySiteUserId(siteUser.getId()); - return MyPageResponse.of(siteUser, likedUnivApplyInfoCount); + + List interestedCountries = null; + String universityKoreanName = null; + if (siteUser.getRole() == Role.MENTEE) { + interestedCountries = countryRepository.findKoreanNamesBySiteUserId(siteUser.getId()); + } else if (siteUser.getRole() == Role.MENTOR) { + Mentor mentor = mentorRepository.findBySiteUserId(siteUser.getId()) + .orElseThrow(() -> new CustomException(MENTOR_NOT_FOUND)); + University university = universityRepository.findById(mentor.getUniversityId()) + .orElseThrow(() -> new CustomException(UNIVERSITY_NOT_FOUND)); + universityKoreanName = university.getKoreanName(); + } + return MyPageResponse.of(siteUser, likedUnivApplyInfoCount, interestedCountries, universityKoreanName); } /* diff --git a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java index f470e9cfc..f852909c6 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java @@ -1,13 +1,10 @@ package com.example.solidconnection.siteuser.service; import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; -import static com.example.solidconnection.common.exception.ErrorCode.PASSWORD_MISMATCH; import static com.example.solidconnection.siteuser.service.MyPageService.MIN_DAYS_BETWEEN_NICKNAME_CHANGES; import static com.example.solidconnection.siteuser.service.MyPageService.NICKNAME_LAST_CHANGE_DATE_FORMAT; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.BDDMockito.any; import static org.mockito.BDDMockito.eq; import static org.mockito.BDDMockito.given; @@ -15,6 +12,11 @@ import static org.mockito.BDDMockito.then; import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.location.country.domain.Country; +import com.example.solidconnection.location.country.domain.InterestedCountry; +import com.example.solidconnection.location.country.fixture.CountryFixture; +import com.example.solidconnection.location.country.repository.InterestedCountryRepository; +import com.example.solidconnection.mentor.fixture.MentorFixture; import com.example.solidconnection.s3.domain.ImgType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; import com.example.solidconnection.s3.service.S3Service; @@ -22,15 +24,16 @@ import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.dto.MyPageResponse; -import com.example.solidconnection.siteuser.dto.PasswordUpdateRequest; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.siteuser.fixture.SiteUserFixtureBuilder; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.support.TestContainerSpringBootTest; import com.example.solidconnection.university.domain.LikedUnivApplyInfo; +import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import java.time.LocalDateTime; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -56,9 +59,18 @@ class MyPageServiceTest { @Autowired private LikedUnivApplyInfoRepository likedUnivApplyInfoRepository; + @Autowired + private InterestedCountryRepository interestedCountryRepository; + @Autowired private SiteUserFixture siteUserFixture; + @Autowired + private MentorFixture mentorFixture; + + @Autowired + private CountryFixture countryFixture; + @Autowired private UnivApplyInfoFixture univApplyInfoFixture; @@ -76,25 +88,94 @@ void setUp() { } @Test - void 마이페이지_정보를_조회한다() { + void 멘티의_마이페이지_정보를_조회한다() { // given int likedUnivApplyInfoCount = createLikedUnivApplyInfos(user); + Country country = countryFixture.미국(); + InterestedCountry interestedCountry = new InterestedCountry(user, country); + interestedCountryRepository.save(interestedCountry); // when MyPageResponse response = myPageService.getMyPageInfo(user.getId()); // then - assertAll( + Assertions.assertAll( () -> assertThat(response.nickname()).isEqualTo(user.getNickname()), () -> assertThat(response.profileImageUrl()).isEqualTo(user.getProfileImageUrl()), () -> assertThat(response.role()).isEqualTo(user.getRole()), () -> assertThat(response.email()).isEqualTo(user.getEmail()), // () -> assertThat(response.likedPostCount()).isEqualTo(user.getLikedPostList().size()), // todo : 좋아요한 게시물 수 반환 기능 추가와 함께 수정요망 - () -> assertThat(response.likedUnivApplyInfoCount()).isEqualTo(likedUnivApplyInfoCount) + () -> assertThat(response.likedUnivApplyInfoCount()).isEqualTo(likedUnivApplyInfoCount), + () -> assertThat(response.interestedCountries().get(0)).isEqualTo(country.getKoreanName()), + () -> assertThat(response.attendedUniversity()).isNull() ); } + @Test + void 멘토의_마이페이지_정보를_조회한다() { + // given + SiteUser mentorUser = siteUserFixture.멘토(1, "mentor"); + University university = univApplyInfoFixture.괌대학_A_지원_정보().getUniversity(); + mentorFixture.멘토(mentorUser.getId(), university.getId()); + int likedUnivApplyInfoCount = createLikedUnivApplyInfos(mentorUser); + + // when + MyPageResponse response = myPageService.getMyPageInfo(mentorUser.getId()); + + // then + Assertions.assertAll( + () -> assertThat(response.nickname()).isEqualTo(mentorUser.getNickname()), + () -> assertThat(response.profileImageUrl()).isEqualTo(mentorUser.getProfileImageUrl()), + () -> assertThat(response.role()).isEqualTo(mentorUser.getRole()), + () -> assertThat(response.email()).isEqualTo(mentorUser.getEmail()), + // () -> assertThat(response.likedPostCount()).isEqualTo(user.getLikedPostList().size()), + // todo : 좋아요한 게시물 수 반환 기능 추가와 함께 수정요망 + () -> assertThat(response.likedUnivApplyInfoCount()).isEqualTo(likedUnivApplyInfoCount), + () -> assertThat(response.attendedUniversity()).isEqualTo(university.getKoreanName()), + () -> assertThat(response.interestedCountries()).isNull() + ); + } + + private int createLikedUnivApplyInfos(SiteUser testUser) { + LikedUnivApplyInfo likedUnivApplyInfo1 = new LikedUnivApplyInfo(null, univApplyInfoFixture.괌대학_A_지원_정보().getId(), testUser.getId()); + LikedUnivApplyInfo likedUnivApplyInfo2 = new LikedUnivApplyInfo(null, univApplyInfoFixture.메이지대학_지원_정보().getId(), testUser.getId()); + LikedUnivApplyInfo likedUnivApplyInfo3 = new LikedUnivApplyInfo(null, univApplyInfoFixture.코펜하겐IT대학_지원_정보().getId(), testUser.getId()); + + likedUnivApplyInfoRepository.save(likedUnivApplyInfo1); + likedUnivApplyInfoRepository.save(likedUnivApplyInfo2); + likedUnivApplyInfoRepository.save(likedUnivApplyInfo3); + return likedUnivApplyInfoRepository.countBySiteUserId(testUser.getId()); + } + + private MockMultipartFile createValidImageFile() { + return new MockMultipartFile( + "image", + "test.jpg", + "image/jpeg", + "test image content".getBytes() + ); + } + + private String createExpectedErrorMessage(LocalDateTime modifiedAt) { + String formatLastModifiedAt = String.format( + "(마지막 수정 시간 : %s)", + NICKNAME_LAST_CHANGE_DATE_FORMAT.format(modifiedAt) + ); + return CAN_NOT_CHANGE_NICKNAME_YET.getMessage() + " : " + formatLastModifiedAt; + } + + private SiteUser createSiteUserWithCustomProfile() { + return siteUserFixtureBuilder.siteUser() + .email("customProfile@example.com") + .authType(AuthType.EMAIL) + .nickname("커스텀프로필") + .profileImageUrl("profile/profileImageUrl") + .role(Role.MENTEE) + .password("customPassword123") + .create(); + } + @Nested class 프로필_이미지_수정_테스트 { @@ -182,87 +263,4 @@ void setUp() { .hasMessage(createExpectedErrorMessage(modifiedAt)); } } - - @Nested - class 비밀번호_변경_테스트 { - - private String currentPassword; - private String newPassword; - - @BeforeEach - void setUp() { - currentPassword = "currentPassword123"; - newPassword = "newPassword123"; - - user.updatePassword(passwordEncoder.encode(currentPassword)); - siteUserRepository.save(user); - } - - @Test - void 비밀번호를_성공적으로_변경한다() { - // given - PasswordUpdateRequest request = new PasswordUpdateRequest(currentPassword, newPassword, newPassword); - - // when - myPageService.updatePassword(user.getId(), request); - - // then - SiteUser updatedUser = siteUserRepository.findById(user.getId()).get(); - assertAll( - () -> assertThat(passwordEncoder.matches(newPassword, updatedUser.getPassword())).isTrue(), - () -> assertThat(passwordEncoder.matches(currentPassword, updatedUser.getPassword())).isFalse() - ); - } - - @Test - void 현재_비밀번호가_일치하지_않으면_예외가_발생한다() { - // given - String wrongPassword = "wrongPassword"; - PasswordUpdateRequest request = new PasswordUpdateRequest(wrongPassword, newPassword, newPassword); - - // when & then - assertThatThrownBy(() -> myPageService.updatePassword(user.getId(), request)) - .isInstanceOf(CustomException.class) - .hasMessage(PASSWORD_MISMATCH.getMessage()); - } - } - - private int createLikedUnivApplyInfos(SiteUser testUser) { - LikedUnivApplyInfo likedUnivApplyInfo1 = new LikedUnivApplyInfo(null, univApplyInfoFixture.괌대학_A_지원_정보().getId(), testUser.getId()); - LikedUnivApplyInfo likedUnivApplyInfo2 = new LikedUnivApplyInfo(null, univApplyInfoFixture.메이지대학_지원_정보().getId(), testUser.getId()); - LikedUnivApplyInfo likedUnivApplyInfo3 = new LikedUnivApplyInfo(null, univApplyInfoFixture.코펜하겐IT대학_지원_정보().getId(), testUser.getId()); - - likedUnivApplyInfoRepository.save(likedUnivApplyInfo1); - likedUnivApplyInfoRepository.save(likedUnivApplyInfo2); - likedUnivApplyInfoRepository.save(likedUnivApplyInfo3); - return likedUnivApplyInfoRepository.countBySiteUserId(testUser.getId()); - } - - private MockMultipartFile createValidImageFile() { - return new MockMultipartFile( - "image", - "test.jpg", - "image/jpeg", - "test image content".getBytes() - ); - } - - private String createExpectedErrorMessage(LocalDateTime modifiedAt) { - String formatLastModifiedAt = String.format( - "(마지막 수정 시간 : %s)", - NICKNAME_LAST_CHANGE_DATE_FORMAT.format(modifiedAt) - ); - return CAN_NOT_CHANGE_NICKNAME_YET.getMessage() + " : " + formatLastModifiedAt; - } - - private SiteUser createSiteUserWithCustomProfile() { - return siteUserFixtureBuilder.siteUser() - .email("customProfile@example.com") - .authType(AuthType.EMAIL) - .nickname("커스텀프로필") - .profileImageUrl("profile/profileImageUrl") - .role(Role.MENTEE) - .password("customPassword123") - .create(); - } } From 07d7dcbbca97a2ce72dc77e3a8ab179211b6afe5 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Fri, 15 Aug 2025 11:23:52 +0900 Subject: [PATCH 76/90] =?UTF-8?q?refactor:=20refreshToken=20=EC=BF=A0?= =?UTF-8?q?=ED=82=A4=EC=97=90=EC=84=9C=20=ED=99=98=EA=B2=BD=EB=B3=84?= =?UTF-8?q?=EB=A1=9C=20Domain=20=EC=84=A4=EC=A0=95=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20(#450)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 주석 가독성 개선 * chore: 쿠키 관련 환경변수 추가 * feat: 리프레시 토큰 설정 클래스 생성 * refactor: 환경에 따라 쿠키를 다르게 설정하도록 * chore: 서브모듈 업데이트 --- .../controller/RefreshTokenCookieManager.java | 11 ++++-- .../config/RefreshTokenCookieProperties.java | 21 +++++++++++ src/main/resources/secret | 2 +- .../RefreshTokenCookieManagerTest.java | 23 ++++++++++-- .../RefreshTokenCookiePropertiesTest.java | 35 +++++++++++++++++++ src/test/resources/application.yml | 3 ++ 6 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/auth/controller/config/RefreshTokenCookieProperties.java create mode 100644 src/test/java/com/example/solidconnection/auth/controller/config/RefreshTokenCookiePropertiesTest.java diff --git a/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java b/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java index 81bc45461..b0a172e2a 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java +++ b/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java @@ -1,17 +1,21 @@ package com.example.solidconnection.auth.controller; +import com.example.solidconnection.auth.controller.config.RefreshTokenCookieProperties; import com.example.solidconnection.auth.domain.TokenType; import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; import org.springframework.stereotype.Component; @Component +@RequiredArgsConstructor public class RefreshTokenCookieManager { private static final String COOKIE_NAME = "refreshToken"; private static final String PATH = "/"; - private static final String SAME_SITE = "Strict"; + + private final RefreshTokenCookieProperties properties; public void setCookie(HttpServletResponse response, String refreshToken) { long maxAge = convertExpireTimeToCookieMaxAge(TokenType.REFRESH.getExpireTime()); @@ -19,7 +23,7 @@ public void setCookie(HttpServletResponse response, String refreshToken) { } private long convertExpireTimeToCookieMaxAge(long milliSeconds) { - // jwt의 expireTime: millisecond, cookie의 maxAge: second + // jwt의 expireTime 단위인 millisecond를 cookie의 maxAge 단위인 second로 변환 return milliSeconds / 1000; } @@ -35,7 +39,8 @@ private void setRefreshTokenCookie( .secure(true) .path(PATH) .maxAge(maxAge) - .sameSite(SAME_SITE) + .domain(properties.cookieDomain()) + .sameSite(properties.sameSite()) .build(); response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); } diff --git a/src/main/java/com/example/solidconnection/auth/controller/config/RefreshTokenCookieProperties.java b/src/main/java/com/example/solidconnection/auth/controller/config/RefreshTokenCookieProperties.java new file mode 100644 index 000000000..1fa47d884 --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/controller/config/RefreshTokenCookieProperties.java @@ -0,0 +1,21 @@ +package com.example.solidconnection.auth.controller.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.web.server.Cookie.SameSite; + +@ConfigurationProperties(prefix = "token.refresh") +public record RefreshTokenCookieProperties( + String cookieDomain +) { + + public String sameSite() { + if (isDomainSet()) { + return SameSite.STRICT.attributeValue(); // 도메인을 지정한 경우 SameSite=Strict + } + return SameSite.NONE.attributeValue(); // 도메인을 지정하지 않은 경우 SameSite=None + } + + private boolean isDomainSet() { + return cookieDomain != null && !cookieDomain.isBlank(); + } +} diff --git a/src/main/resources/secret b/src/main/resources/secret index e592f6d36..0e9f5d0ce 160000 --- a/src/main/resources/secret +++ b/src/main/resources/secret @@ -1 +1 @@ -Subproject commit e592f6d36f57185c8d92a7838c0e3039603b2c57 +Subproject commit 0e9f5d0cefabab1ab9a306099b457225ce5d641e diff --git a/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java b/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java index 944be37ab..91ff13cfa 100644 --- a/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java +++ b/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java @@ -2,21 +2,35 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.BDDMockito.given; +import com.example.solidconnection.auth.controller.config.RefreshTokenCookieProperties; import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.support.TestContainerSpringBootTest; import org.junit.jupiter.api.BeforeEach; 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.MockBean; import org.springframework.mock.web.MockHttpServletResponse; @DisplayName("리프레시 토큰 쿠키 매니저 테스트") +@TestContainerSpringBootTest class RefreshTokenCookieManagerTest { + @Autowired private RefreshTokenCookieManager cookieManager; + @MockBean + private RefreshTokenCookieProperties refreshTokenCookieProperties; + + private final String sameSite = "Strict"; + private final String domain = "example.com"; + @BeforeEach void setUp() { - cookieManager = new RefreshTokenCookieManager(); + given(refreshTokenCookieProperties.cookieDomain()).willReturn(domain); + given(refreshTokenCookieProperties.sameSite()).willReturn(sameSite); } @Test @@ -37,7 +51,8 @@ void setUp() { () -> assertThat(header).contains("Secure"), () -> assertThat(header).contains("Path=/"), () -> assertThat(header).contains("Max-Age=" + TokenType.REFRESH.getExpireTime() / 1000), - () -> assertThat(header).contains("SameSite=Strict") + () -> assertThat(header).contains("Domain=" + domain), + () -> assertThat(header).contains("SameSite=" + sameSite) ); } @@ -58,7 +73,9 @@ void setUp() { () -> assertThat(header).contains("Secure"), () -> assertThat(header).contains("Path=/"), () -> assertThat(header).contains("Max-Age=0"), - () -> assertThat(header).contains("SameSite=Strict") + () -> assertThat(header).contains("SameSite=Strict"), + () -> assertThat(header).contains("Domain=" + domain), + () -> assertThat(header).contains("SameSite=" + sameSite) ); } } diff --git a/src/test/java/com/example/solidconnection/auth/controller/config/RefreshTokenCookiePropertiesTest.java b/src/test/java/com/example/solidconnection/auth/controller/config/RefreshTokenCookiePropertiesTest.java new file mode 100644 index 000000000..0a62541b1 --- /dev/null +++ b/src/test/java/com/example/solidconnection/auth/controller/config/RefreshTokenCookiePropertiesTest.java @@ -0,0 +1,35 @@ +package com.example.solidconnection.auth.controller.config; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.web.server.Cookie.SameSite; + +@DisplayName("리프레시 토큰 쿠키 설정 테스트") +class RefreshTokenCookiePropertiesTest { + + @Test + void Domain을_지정했으면_SameSite가_Strict() { + // given + RefreshTokenCookieProperties properties = new RefreshTokenCookieProperties("example.com"); + + // when + String sameSite = properties.sameSite(); + + // then + assertThat(sameSite).isEqualTo(SameSite.STRICT.attributeValue()); + } + + @Test + void Domain을_지정하지_않았으면_SameSite가_None() { + // given + RefreshTokenCookieProperties properties = new RefreshTokenCookieProperties(null); + + // when + String sameSite = properties.sameSite(); + + // then + assertThat(sameSite).isEqualTo(SameSite.NONE.attributeValue()); + } +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 7abc6949f..ce5a848cb 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -85,3 +85,6 @@ cors: - "http://localhost:8080" news: default-thumbnail-url: "default-thumbnail-url" +token: + refresh: + cookie-domain: "test.domain.com" From 151c9ad8c77e4d977654d2d0d1f53ca09acd4e36 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Fri, 15 Aug 2025 12:20:07 +0900 Subject: [PATCH 77/90] =?UTF-8?q?refactor:=20accessToken=20=EC=9E=AC?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=EC=9D=84=20refreshToken=20=EC=BF=A0=ED=82=A4?= =?UTF-8?q?=EB=A5=BC=20=ED=86=B5=ED=95=B4=20=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?(#452)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 리프레시 토큰 추출 구현 * test: 중복 사용되는 변수를 상수로 추출 * test: 리프레시 토큰 추출 테스트 * refactor: 쿠키의 리프레시 토큰으로 액세스 토큰 재발급하도록 * chore: 사용하지 않는 요청 Dto 제거 * refactor: auth 토큰에 SiteUser가 사용됨을 명시, 중복코드 제거 - AS-IS: long으로 SiteUser를 조회, SiteUser를 Subject로 만들어 authTokenProvider의 함수에 넘겨줬다. - TO-BS: authToken에는 어차피 '의미상' SiteUser라는 개념이 사용될 수 밖에 없다. 따라서 관련 로직을 함수 내부로 옮긴다. 이로 인해서 중복 코드를 줄일수도 있다. * test: 테스트 코드에 반영 * refactor: 리프레시 토큰 값이 비어있는 경우도 예외 처리 * test: 비어있는 리프레시 토큰 테스트 케이스 추가 * style: style 적용 안된 채로 rebase한 것들 reformat --- .../auth/controller/AuthController.java | 8 +-- .../controller/RefreshTokenCookieManager.java | 28 ++++++++ .../auth/dto/ReissueRequest.java | 9 --- .../auth/service/AuthService.java | 18 ++--- .../auth/service/AuthTokenProvider.java | 22 ++++-- .../auth/service/SignInService.java | 5 +- .../common/exception/ErrorCode.java | 1 + .../RefreshTokenCookieManagerTest.java | 69 ++++++++++++++++++- .../auth/service/AuthServiceTest.java | 25 +++---- .../auth/service/AuthTokenProviderTest.java | 45 ++++++------ .../WebSocketStompIntegrationTest.java | 2 +- 11 files changed, 158 insertions(+), 74 deletions(-) delete mode 100644 src/main/java/com/example/solidconnection/auth/dto/ReissueRequest.java diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index cbcd29627..f5a30bb2f 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -3,7 +3,6 @@ import com.example.solidconnection.auth.dto.EmailSignInRequest; import com.example.solidconnection.auth.dto.EmailSignUpTokenRequest; import com.example.solidconnection.auth.dto.EmailSignUpTokenResponse; -import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.auth.dto.SignUpRequest; @@ -19,6 +18,7 @@ import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.siteuser.domain.AuthType; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -118,10 +118,10 @@ public ResponseEntity quit( @PostMapping("/reissue") public ResponseEntity reissueToken( - @AuthorizedUser long siteUserId, - @Valid @RequestBody ReissueRequest reissueRequest + HttpServletRequest request ) { - ReissueResponse reissueResponse = authService.reissue(siteUserId, reissueRequest); + String refreshToken = refreshTokenCookieManager.getRefreshToken(request); + ReissueResponse reissueResponse = authService.reissue(refreshToken); return ResponseEntity.ok(reissueResponse); } diff --git a/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java b/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java index b0a172e2a..d36cca024 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java +++ b/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java @@ -1,8 +1,14 @@ package com.example.solidconnection.auth.controller; +import static com.example.solidconnection.common.exception.ErrorCode.REFRESH_TOKEN_NOT_EXISTS; + import com.example.solidconnection.auth.controller.config.RefreshTokenCookieProperties; import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.common.exception.CustomException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.util.Arrays; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; @@ -44,4 +50,26 @@ private void setRefreshTokenCookie( .build(); response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); } + + public String getRefreshToken(HttpServletRequest request) { + // 쿠키가 없거나 비어있는 경우 예외 발생 + Cookie[] cookies = request.getCookies(); + if (cookies == null || cookies.length == 0) { + throw new CustomException(REFRESH_TOKEN_NOT_EXISTS); + } + + // refreshToken 쿠키가 없는 경우 예외 발생 + Cookie refreshTokenCookie = Arrays.stream(cookies) + .filter(cookie -> COOKIE_NAME.equals(cookie.getName())) + .findFirst() + .orElseThrow(() -> new CustomException(REFRESH_TOKEN_NOT_EXISTS)); + + // 쿠키 값이 비어있는 경우 예외 발생 + String refreshToken = refreshTokenCookie.getValue(); + if (refreshToken == null || refreshToken.isBlank()) { + throw new CustomException(REFRESH_TOKEN_NOT_EXISTS); + } + return refreshToken; + } } + diff --git a/src/main/java/com/example/solidconnection/auth/dto/ReissueRequest.java b/src/main/java/com/example/solidconnection/auth/dto/ReissueRequest.java deleted file mode 100644 index 417ed32b0..000000000 --- a/src/main/java/com/example/solidconnection/auth/dto/ReissueRequest.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.solidconnection.auth.dto; - -import jakarta.validation.constraints.NotBlank; - -public record ReissueRequest( - @NotBlank(message = "리프레시 토큰과 함께 요청해주세요.") - String refreshToken) { - -} diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthService.java b/src/main/java/com/example/solidconnection/auth/service/AuthService.java index 0d54c7672..01c162002 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -3,7 +3,6 @@ import static com.example.solidconnection.common.exception.ErrorCode.REFRESH_TOKEN_EXPIRED; import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; -import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; import com.example.solidconnection.auth.token.TokenBlackListService; import com.example.solidconnection.common.exception.CustomException; @@ -28,12 +27,8 @@ public class AuthService { * - 리프레시 토큰을 삭제한다. * */ public void signOut(String token) { - Subject subject = authTokenProvider.parseSubject(token); - long siteUserId = Long.parseLong(subject.value()); - SiteUser siteUser = siteUserRepository.findById(siteUserId) - .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); - - AccessToken accessToken = authTokenProvider.generateAccessToken(subject, siteUser.getRole()); + SiteUser siteUser = authTokenProvider.parseSiteUser(token); + AccessToken accessToken = authTokenProvider.generateAccessToken(siteUser); authTokenProvider.deleteRefreshTokenByAccessToken(accessToken); tokenBlackListService.addToBlacklist(accessToken); } @@ -58,17 +53,14 @@ public void quit(long siteUserId, String token) { * - 유효한 리프레시토큰이면, 액세스 토큰을 재발급한다. * - 그렇지 않으면 예외를 발생시킨다. * */ - public ReissueResponse reissue(long siteUserId, ReissueRequest reissueRequest) { + public ReissueResponse reissue(String requestedRefreshToken) { // 리프레시 토큰 확인 - String requestedRefreshToken = reissueRequest.refreshToken(); if (!authTokenProvider.isValidRefreshToken(requestedRefreshToken)) { throw new CustomException(REFRESH_TOKEN_EXPIRED); } // 액세스 토큰 재발급 - SiteUser siteUser = siteUserRepository.findById(siteUserId) - .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); - Subject subject = authTokenProvider.parseSubject(requestedRefreshToken); - AccessToken newAccessToken = authTokenProvider.generateAccessToken(subject, siteUser.getRole()); + SiteUser siteUser = authTokenProvider.parseSiteUser(requestedRefreshToken); + AccessToken newAccessToken = authTokenProvider.generateAccessToken(siteUser); return ReissueResponse.from(newAccessToken); } } diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java index 6f335256e..8e55f77d4 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -1,8 +1,12 @@ package com.example.solidconnection.auth.service; +import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; + import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; import java.util.Map; import java.util.Objects; import lombok.RequiredArgsConstructor; @@ -17,15 +21,21 @@ public class AuthTokenProvider { private final RedisTemplate redisTemplate; private final TokenProvider tokenProvider; + private final SiteUserRepository siteUserRepository; - public AccessToken generateAccessToken(Subject subject, Role role) { + public AccessToken generateAccessToken(SiteUser siteUser) { + Subject subject = toSubject(siteUser); + Role role = siteUser.getRole(); String token = tokenProvider.generateToken( - subject.value(), Map.of(ROLE_CLAIM_KEY, role.name()), TokenType.ACCESS + subject.value(), + Map.of(ROLE_CLAIM_KEY, role.name()), + TokenType.ACCESS ); return new AccessToken(subject, role, token); } - public RefreshToken generateAndSaveRefreshToken(Subject subject) { + public RefreshToken generateAndSaveRefreshToken(SiteUser siteUser) { + Subject subject = toSubject(siteUser); String token = tokenProvider.generateToken(subject.value(), TokenType.REFRESH); tokenProvider.saveToken(token, TokenType.REFRESH); return new RefreshToken(subject, token); @@ -49,9 +59,11 @@ public void deleteRefreshTokenByAccessToken(AccessToken accessToken) { redisTemplate.delete(refreshTokenKey); } - public Subject parseSubject(String token) { + public SiteUser parseSiteUser(String token) { String subject = tokenProvider.parseSubject(token); - return new Subject(subject); + long siteUserId = Long.parseLong(subject); + return siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); } public Subject toSubject(SiteUser siteUser) { diff --git a/src/main/java/com/example/solidconnection/auth/service/SignInService.java b/src/main/java/com/example/solidconnection/auth/service/SignInService.java index 2b6a57cbe..16ec4c484 100644 --- a/src/main/java/com/example/solidconnection/auth/service/SignInService.java +++ b/src/main/java/com/example/solidconnection/auth/service/SignInService.java @@ -15,9 +15,8 @@ public class SignInService { @Transactional public SignInResponse signIn(SiteUser siteUser) { resetQuitedAt(siteUser); - Subject subject = authTokenProvider.toSubject(siteUser); - AccessToken accessToken = authTokenProvider.generateAccessToken(subject, siteUser.getRole()); - RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(subject); + AccessToken accessToken = authTokenProvider.generateAccessToken(siteUser); + RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(siteUser); return SignInResponse.of(accessToken, refreshToken); } diff --git a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java index defb0230f..4d135416e 100644 --- a/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/common/exception/ErrorCode.java @@ -56,6 +56,7 @@ public enum ErrorCode { ACCESS_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED.value(), "액세스 토큰이 만료되었습니다. 재발급 api를 호출해주세요."), REFRESH_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED.value(), "리프레시 토큰이 만료되었습니다. 다시 로그인을 진행해주세요."), ACCESS_DENIED(HttpStatus.FORBIDDEN.value(), "접근 권한이 없습니다."), + REFRESH_TOKEN_NOT_EXISTS(HttpStatus.BAD_REQUEST.value(), "리프레시 토큰이 존재하지 않습니다."), PASSWORD_MISMATCH(HttpStatus.BAD_REQUEST.value(), "비밀번호가 일치하지 않습니다."), PASSWORD_NOT_CHANGED(HttpStatus.BAD_REQUEST.value(), "현재 비밀번호와 새 비밀번호가 동일합니다."), PASSWORD_NOT_CONFIRMED(HttpStatus.BAD_REQUEST.value(), "새 비밀번호가 일치하지 않습니다."), diff --git a/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java b/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java index 91ff13cfa..677cd5854 100644 --- a/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java +++ b/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java @@ -1,23 +1,33 @@ package com.example.solidconnection.auth.controller; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.BDDMockito.given; import com.example.solidconnection.auth.controller.config.RefreshTokenCookieProperties; import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; import com.example.solidconnection.support.TestContainerSpringBootTest; +import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; @DisplayName("리프레시 토큰 쿠키 매니저 테스트") @TestContainerSpringBootTest class RefreshTokenCookieManagerTest { + private static final String REFRESH_TOKEN_COOKIE_NAME = "refreshToken"; + @Autowired private RefreshTokenCookieManager cookieManager; @@ -46,7 +56,7 @@ void setUp() { String header = response.getHeader("Set-Cookie"); assertAll( () -> assertThat(header).isNotNull(), - () -> assertThat(header).contains("refreshToken=" + refreshToken), + () -> assertThat(header).contains(REFRESH_TOKEN_COOKIE_NAME + "=" + refreshToken), () -> assertThat(header).contains("HttpOnly"), () -> assertThat(header).contains("Secure"), () -> assertThat(header).contains("Path=/"), @@ -68,14 +78,67 @@ void setUp() { String header = response.getHeader("Set-Cookie"); assertAll( () -> assertThat(header).isNotNull(), - () -> assertThat(header).contains("refreshToken="), + () -> assertThat(header).contains(REFRESH_TOKEN_COOKIE_NAME + "="), () -> assertThat(header).contains("HttpOnly"), () -> assertThat(header).contains("Secure"), () -> assertThat(header).contains("Path=/"), () -> assertThat(header).contains("Max-Age=0"), - () -> assertThat(header).contains("SameSite=Strict"), () -> assertThat(header).contains("Domain=" + domain), () -> assertThat(header).contains("SameSite=" + sameSite) ); } + + @Nested + class 쿠키에서_리프레시_토큰을_추출한다 { + + @Test + void 리프레시_토큰이_있으면_정상_반환한다() { + // given + MockHttpServletRequest request = new MockHttpServletRequest(); + String refreshToken = "test-refresh-token"; + request.setCookies(new Cookie(REFRESH_TOKEN_COOKIE_NAME, refreshToken)); + + // when + String retrievedToken = cookieManager.getRefreshToken(request); + + // then + assertThat(retrievedToken).isEqualTo(refreshToken); + } + + @Test + void 쿠키가_없으면_예외가_발생한다() { + // given + MockHttpServletRequest request = new MockHttpServletRequest(); + + // when & then + assertThatCode(() -> cookieManager.getRefreshToken(request)) + .isInstanceOf(CustomException.class) + .hasMessageContaining(ErrorCode.REFRESH_TOKEN_NOT_EXISTS.getMessage()); + } + + @Test + void 리프레시_토큰_쿠키가_없으면_예외가_발생한다() { + // given + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setCookies(new Cookie("otherCookie", "some-value")); + + // when & then + assertThatCode(() -> cookieManager.getRefreshToken(request)) + .isInstanceOf(CustomException.class) + .hasMessageContaining(ErrorCode.REFRESH_TOKEN_NOT_EXISTS.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = {"", " "}) + void 리프레시_토큰_쿠키가_비어있으면_예외가_발생한다(String token) { + // given + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setCookies(new Cookie(REFRESH_TOKEN_COOKIE_NAME, token)); + + // when & then + assertThatCode(() -> cookieManager.getRefreshToken(request)) + .isInstanceOf(CustomException.class) + .hasMessageContaining(ErrorCode.REFRESH_TOKEN_NOT_EXISTS.getMessage()); + } + } } diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java index 707c3dbbb..caedec489 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -6,7 +6,6 @@ import static org.junit.jupiter.api.Assertions.assertAll; import com.example.solidconnection.auth.domain.TokenType; -import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; import com.example.solidconnection.auth.token.TokenBlackListService; import com.example.solidconnection.common.exception.CustomException; @@ -45,14 +44,12 @@ class AuthServiceTest { private SiteUserRepository siteUserRepository; private SiteUser siteUser; - private Subject subject; private AccessToken accessToken; @BeforeEach void setUp() { siteUser = siteUserFixture.사용자(); - subject = authTokenProvider.toSubject(siteUser); - accessToken = authTokenProvider.generateAccessToken(subject, siteUser.getRole()); + accessToken = authTokenProvider.generateAccessToken(siteUser); } @Test @@ -61,7 +58,7 @@ void setUp() { authService.signOut(accessToken.token()); // then - String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value()); + String refreshTokenKey = TokenType.REFRESH.addPrefix(accessToken.subject().value()); assertAll( () -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(), () -> assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isTrue() @@ -75,7 +72,7 @@ void setUp() { // then LocalDate tomorrow = LocalDate.now().plusDays(1); - String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value()); + String refreshTokenKey = TokenType.REFRESH.addPrefix(accessToken.subject().value()); SiteUser actualSitUser = siteUserRepository.findById(siteUser.getId()).orElseThrow(); assertAll( () -> assertThat(actualSitUser.getQuitedAt()).isEqualTo(tomorrow), @@ -90,26 +87,24 @@ class 토큰을_재발급한다 { @Test void 요청의_리프레시_토큰이_저장되어_있으면_액세스_토큰을_재발급한다() { // given - RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(new Subject("subject")); - ReissueRequest reissueRequest = new ReissueRequest(refreshToken.token()); + RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(siteUser); // when - ReissueResponse reissuedAccessToken = authService.reissue(siteUser.getId(), reissueRequest); + ReissueResponse reissuedAccessToken = authService.reissue(refreshToken.token()); - // then - 요청의 리프레시 토큰과 재발급한 액세스 토큰의 subject 가 동일해야 한다. - Subject expectedSubject = authTokenProvider.parseSubject(refreshToken.token()); - Subject actualSubject = authTokenProvider.parseSubject(reissuedAccessToken.accessToken()); - assertThat(actualSubject).isEqualTo(expectedSubject); + // then - 요청의 리프레시 토큰과 재발급한 액세스 토큰의 주체가 동일해야 한다. + SiteUser actualSiteUser = authTokenProvider.parseSiteUser(refreshToken.token()); + SiteUser expectedSiteUser = authTokenProvider.parseSiteUser(reissuedAccessToken.accessToken()); + assertThat(actualSiteUser.getId()).isEqualTo(expectedSiteUser.getId()); } @Test void 요청의_리프레시_토큰이_저장되어있지_않다면_예외가_발생한다() { // given String invalidRefreshToken = accessToken.token(); - ReissueRequest reissueRequest = new ReissueRequest(invalidRefreshToken); // when, then - assertThatCode(() -> authService.reissue(siteUser.getId(), reissueRequest)) + assertThatCode(() -> authService.reissue(invalidRefreshToken)) .isInstanceOf(CustomException.class) .hasMessage(REFRESH_TOKEN_EXPIRED.getMessage()); } diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java index 6a7cc40a3..54dce4f68 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java @@ -4,7 +4,8 @@ import static org.junit.jupiter.api.Assertions.assertAll; import com.example.solidconnection.auth.domain.TokenType; -import com.example.solidconnection.siteuser.domain.Role; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -23,24 +24,27 @@ class AuthTokenProviderTest { @Autowired private RedisTemplate redisTemplate; - private Subject subject; + @Autowired + private SiteUserFixture siteUserFixture; + + private SiteUser siteUser; + private String expectedSubject; @BeforeEach void setUp() { - subject = new Subject("subject123"); + siteUser = siteUserFixture.사용자(); + expectedSubject = siteUser.getId().toString(); } @Test void 액세스_토큰을_생성한다() { // when - Role expectedRole = Role.MENTEE; - AccessToken accessToken = authTokenProvider.generateAccessToken(subject, expectedRole); + AccessToken accessToken = authTokenProvider.generateAccessToken(siteUser); // then - String actualSubject = authTokenProvider.parseSubject(accessToken.token()).value(); assertAll( - () -> assertThat(actualSubject).isEqualTo(subject.value()), - () -> assertThat(accessToken.role()).isEqualTo(expectedRole), + () -> assertThat(accessToken.subject().value()).isEqualTo(expectedSubject), + () -> assertThat(accessToken.role()).isEqualTo(siteUser.getRole()), () -> assertThat(accessToken.token()).isNotNull() ); } @@ -51,14 +55,13 @@ class 리프레시_토큰을_제공한다 { @Test void 리프레시_토큰을_생성하고_저장한다() { // when - RefreshToken actualRefreshToken = authTokenProvider.generateAndSaveRefreshToken(subject); + RefreshToken actualRefreshToken = authTokenProvider.generateAndSaveRefreshToken(siteUser); // then - String actualSubject = authTokenProvider.parseSubject(actualRefreshToken.token()).value(); - String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value()); + String refreshTokenKey = TokenType.REFRESH.addPrefix(expectedSubject); String expectedRefreshToken = redisTemplate.opsForValue().get(refreshTokenKey); assertAll( - () -> assertThat(actualSubject).isEqualTo(subject.value()), + () -> assertThat(actualRefreshToken.subject().value()).isEqualTo(expectedSubject), () -> assertThat(actualRefreshToken.token()).isEqualTo(expectedRefreshToken) ); } @@ -66,8 +69,8 @@ class 리프레시_토큰을_제공한다 { @Test void 유효한_리프레시_토큰인지_확인한다() { // given - RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(subject); - AccessToken fakeRefreshToken = authTokenProvider.generateAccessToken(subject, Role.MENTEE); + RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(siteUser); + AccessToken fakeRefreshToken = authTokenProvider.generateAccessToken(siteUser); // when, then assertAll( @@ -79,27 +82,27 @@ class 리프레시_토큰을_제공한다 { @Test void 액세스_토큰에_해당하는_리프레시_토큰을_삭제한다() { // given - authTokenProvider.generateAndSaveRefreshToken(subject); - AccessToken accessToken = authTokenProvider.generateAccessToken(subject, Role.MENTEE); + authTokenProvider.generateAndSaveRefreshToken(siteUser); + AccessToken accessToken = authTokenProvider.generateAccessToken(siteUser); // when authTokenProvider.deleteRefreshTokenByAccessToken(accessToken); // then - String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value()); + String refreshTokenKey = TokenType.REFRESH.addPrefix(expectedSubject); assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(); } } @Test - void 토큰으로부터_Subject_를_추출한다() { + void 토큰으로부터_SiteUser_를_추출한다() { // given - String accessToken = authTokenProvider.generateAccessToken(subject, Role.MENTEE).token(); + String accessToken = authTokenProvider.generateAccessToken(siteUser).token(); // when - Subject actualSubject = authTokenProvider.parseSubject(accessToken); + SiteUser actualSitUser = authTokenProvider.parseSiteUser(accessToken); // then - assertThat(actualSubject.value()).isEqualTo(subject.value()); + assertThat(actualSitUser.getId()).isEqualTo(siteUser.getId()); } } diff --git a/src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java b/src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java index c74d84534..978bfd717 100644 --- a/src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java +++ b/src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java @@ -80,7 +80,7 @@ public void handleTransportError(StompSession session, Throwable exception) { void 인증된_사용자는_핸드셰이크를_성공한다() throws Exception { // given SiteUser user = siteUserFixture.사용자(); - AccessToken accessToken = authTokenProvider.generateAccessToken(authTokenProvider.toSubject(user), user.getRole()); + AccessToken accessToken = authTokenProvider.generateAccessToken(user); WebSocketHttpHeaders handshakeHeaders = new WebSocketHttpHeaders(); handshakeHeaders.add("Authorization", "Bearer " + accessToken.token()); From afe27ab2dc6adb71371f590392d09203ebaf4bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Sat, 16 Aug 2025 18:27:01 +0900 Subject: [PATCH 78/90] =?UTF-8?q?feat:=20=EC=B1=84=ED=8C=85=EB=B0=A9=20par?= =?UTF-8?q?tner=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=ED=95=98=EB=8A=94=20API=20=EC=B6=94=EA=B0=80=20(#457)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 채팅방 partner 정보를 조회하는 API 추가 * test: 채팅방 partner 정보를 조회 테스트 추가 --- .../chat/controller/ChatController.java | 10 +++++++ .../chat/service/ChatService.java | 30 ++++++++++++------- .../chat/service/ChatServiceTest.java | 23 ++++++++++++++ 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/example/solidconnection/chat/controller/ChatController.java b/src/main/java/com/example/solidconnection/chat/controller/ChatController.java index 1c3b1a5d1..ddb1f9084 100644 --- a/src/main/java/com/example/solidconnection/chat/controller/ChatController.java +++ b/src/main/java/com/example/solidconnection/chat/controller/ChatController.java @@ -1,6 +1,7 @@ package com.example.solidconnection.chat.controller; import com.example.solidconnection.chat.dto.ChatMessageResponse; +import com.example.solidconnection.chat.dto.ChatParticipantResponse; import com.example.solidconnection.chat.dto.ChatRoomListResponse; import com.example.solidconnection.chat.service.ChatService; import com.example.solidconnection.common.dto.SliceResponse; @@ -41,6 +42,15 @@ public ResponseEntity> getChatMessages( return ResponseEntity.ok(response); } + @GetMapping("rooms/{room-id}/partner") + public ResponseEntity getChatPartner( + @AuthorizedUser long siteUserId, + @PathVariable("room-id") Long roomId + ) { + ChatParticipantResponse response = chatService.getChatPartner(siteUserId, roomId); + return ResponseEntity.ok(response); + } + @PutMapping("/rooms/{room-id}/read") public ResponseEntity markChatMessagesAsRead( @AuthorizedUser long siteUserId, diff --git a/src/main/java/com/example/solidconnection/chat/service/ChatService.java b/src/main/java/com/example/solidconnection/chat/service/ChatService.java index 4a9c02eed..874d71bd5 100644 --- a/src/main/java/com/example/solidconnection/chat/service/ChatService.java +++ b/src/main/java/com/example/solidconnection/chat/service/ChatService.java @@ -84,16 +84,6 @@ private ChatRoomResponse toChatRoomResponse(ChatRoom chatRoom, long siteUserId) return ChatRoomResponse.of(chatRoom.getId(), lastChatMessage, lastReceivedTime, partner, unReadCount); } - private ChatParticipant findPartner(ChatRoom chatRoom, long siteUserId) { - if (chatRoom.isGroup()) { - throw new CustomException(INVALID_CHAT_ROOM_STATE); - } - return chatRoom.getChatParticipants().stream() - .filter(participant -> participant.getSiteUserId() != siteUserId) - .findFirst() - .orElseThrow(() -> new CustomException(CHAT_PARTNER_NOT_FOUND)); - } - @Transactional(readOnly = true) public SliceResponse getChatMessages(long siteUserId, long roomId, Pageable pageable) { validateChatRoomParticipant(siteUserId, roomId); @@ -107,6 +97,26 @@ public SliceResponse getChatMessages(long siteUserId, long return SliceResponse.of(content, chatMessages); } + @Transactional(readOnly = true) + public ChatParticipantResponse getChatPartner(long siteUserId, Long roomId) { + ChatRoom chatRoom = chatRoomRepository.findById(roomId) + .orElseThrow(() -> new CustomException(INVALID_CHAT_ROOM_STATE)); + ChatParticipant partnerParticipant = findPartner(chatRoom, siteUserId); + SiteUser siteUser = siteUserRepository.findById(partnerParticipant.getSiteUserId()) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); + return ChatParticipantResponse.of(siteUser.getId(), siteUser.getNickname(), siteUser.getProfileImageUrl()); + } + + private ChatParticipant findPartner(ChatRoom chatRoom, long siteUserId) { + if (chatRoom.isGroup()) { + throw new CustomException(INVALID_CHAT_ROOM_STATE); + } + return chatRoom.getChatParticipants().stream() + .filter(participant -> participant.getSiteUserId() != siteUserId) + .findFirst() + .orElseThrow(() -> new CustomException(CHAT_PARTNER_NOT_FOUND)); + } + public void validateChatRoomParticipant(long siteUserId, long roomId) { boolean isParticipant = chatParticipantRepository.existsByChatRoomIdAndSiteUserId(roomId, siteUserId); if (!isParticipant) { diff --git a/src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java b/src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java index 667a02d5e..9f3c1f017 100644 --- a/src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java +++ b/src/test/java/com/example/solidconnection/chat/service/ChatServiceTest.java @@ -14,6 +14,7 @@ import com.example.solidconnection.chat.dto.ChatMessageResponse; import com.example.solidconnection.chat.dto.ChatMessageSendRequest; import com.example.solidconnection.chat.dto.ChatMessageSendResponse; +import com.example.solidconnection.chat.dto.ChatParticipantResponse; import com.example.solidconnection.chat.dto.ChatRoomListResponse; import com.example.solidconnection.chat.fixture.ChatAttachmentFixture; import com.example.solidconnection.chat.fixture.ChatMessageFixture; @@ -309,6 +310,28 @@ void setUp() { } } + @Nested + class 채팅방_파트너_정보를_조회한다 { + + @Test + void 채팅방_파트너를_정상_조회한다() { + // given + ChatRoom chatRoom = chatRoomFixture.채팅방(false); + chatParticipantFixture.참여자(user.getId(), chatRoom); + chatParticipantFixture.참여자(mentor1.getId(), chatRoom); + + // when + ChatParticipantResponse response = chatService.getChatPartner(user.getId(), chatRoom.getId()); + + // then + assertAll( + () -> assertThat(response.partnerId()).isEqualTo(mentor1.getId()), + () -> assertThat(response.nickname()).isEqualTo(mentor1.getNickname()), + () -> assertThat(response.profileUrl()).isEqualTo(mentor1.getProfileImageUrl()) + ); + } + } + @Nested class 채팅_메시지_읽음을_처리한다 { From e7fd1d4775e28404b584892b23588004ea36c49d Mon Sep 17 00:00:00 2001 From: seonghyeok cho <65901319+whqtker@users.noreply.github.com> Date: Tue, 19 Aug 2025 14:17:32 +0900 Subject: [PATCH 79/90] =?UTF-8?q?feat:=20=EA=B4=80=EC=8B=AC=20=EC=A7=80?= =?UTF-8?q?=EC=97=AD=20=EB=B0=8F=20=EA=B5=AD=EA=B0=80=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20API=20=EC=B6=94=EA=B0=80=20(#453)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 지역 변경 요청 DTO 구현 * feat: 관심 지역 변경 서비스 로직 작성 * feat: 관심 지역 변경 컨트롤러 작성 * fix: siteUser가 아니라 siteUserId로 삭제하도록 * test: 관심 지역 및 국가 변경 테스트 작성 * fix: clearAutomatically, flushAutomatically 옵션을 통해 DB와 영속성 컨텍스트 간 불일치 문제 해결 * fix: 파라미터 순서 변경 * chore: 불필요한 Modifying 어노테이션 제거 * chore: 다건 삭제는 deleteBy 대신 deleteAllBy를 사용하도록 * chore: DTO에 Valid 어노테이션 추가 * chore: 크기 검증 제거 및 미사용 repository 제거 * chore: 코드 리포매팅 * fix: conflict 해결 * refactor: 분명한 의미를 가진 엔드포인트 이름으로 변경 - /location -> /interested-location --- .../InterestedCountryRepository.java | 2 + .../service/InterestedCountryService.java | 11 ++ .../InterestedRegionRepository.java | 2 + .../service/InterestedRegionService.java | 11 ++ .../siteuser/controller/MyPageController.java | 10 ++ .../siteuser/dto/LocationUpdateRequest.java | 10 ++ .../siteuser/service/MyPageService.java | 14 ++ .../siteuser/service/MyPageServiceTest.java | 156 +++++++++++++++++- 8 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/siteuser/dto/LocationUpdateRequest.java diff --git a/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java b/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java index 0d555b7bb..b7e12a285 100644 --- a/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java +++ b/src/main/java/com/example/solidconnection/location/country/repository/InterestedCountryRepository.java @@ -7,4 +7,6 @@ public interface InterestedCountryRepository extends JpaRepository { List findAllBySiteUserId(long siteUserId); + + void deleteAllBySiteUserId(long siteUserId); } diff --git a/src/main/java/com/example/solidconnection/location/country/service/InterestedCountryService.java b/src/main/java/com/example/solidconnection/location/country/service/InterestedCountryService.java index 8166de968..9e2fa4060 100644 --- a/src/main/java/com/example/solidconnection/location/country/service/InterestedCountryService.java +++ b/src/main/java/com/example/solidconnection/location/country/service/InterestedCountryService.java @@ -24,4 +24,15 @@ public void saveInterestedCountry(SiteUser siteUser, List koreanNames) { .toList(); interestedCountryRepository.saveAll(interestedCountries); } + + @Transactional + public void updateInterestedCountry(SiteUser siteUser, List koreanNames) { + interestedCountryRepository.deleteAllBySiteUserId(siteUser.getId()); + + List interestedCountries = countryRepository.findAllByKoreanNameIn(koreanNames) + .stream() + .map(country -> new InterestedCountry(siteUser, country)) + .toList(); + interestedCountryRepository.saveAll(interestedCountries); + } } diff --git a/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java b/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java index 335218c08..367eb2164 100644 --- a/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java +++ b/src/main/java/com/example/solidconnection/location/region/repository/InterestedRegionRepository.java @@ -7,4 +7,6 @@ public interface InterestedRegionRepository extends JpaRepository { List findAllBySiteUserId(long siteUserId); + + void deleteAllBySiteUserId(long siteUserId); } diff --git a/src/main/java/com/example/solidconnection/location/region/service/InterestedRegionService.java b/src/main/java/com/example/solidconnection/location/region/service/InterestedRegionService.java index 6dc71263e..2c8c592cd 100644 --- a/src/main/java/com/example/solidconnection/location/region/service/InterestedRegionService.java +++ b/src/main/java/com/example/solidconnection/location/region/service/InterestedRegionService.java @@ -24,4 +24,15 @@ public void saveInterestedRegion(SiteUser siteUser, List koreanNames) { .toList(); interestedRegionRepository.saveAll(interestedRegions); } + + @Transactional + public void updateInterestedRegion(SiteUser siteUser, List koreanNames) { + interestedRegionRepository.deleteAllBySiteUserId(siteUser.getId()); + + List interestedRegions = regionRepository.findAllByKoreanNameIn(koreanNames) + .stream() + .map(region -> new InterestedRegion(siteUser, region)) + .toList(); + interestedRegionRepository.saveAll(interestedRegions); + } } diff --git a/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java b/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java index 534b6c361..00c3077e3 100644 --- a/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java +++ b/src/main/java/com/example/solidconnection/siteuser/controller/MyPageController.java @@ -2,6 +2,7 @@ import com.example.solidconnection.common.resolver.AuthorizedUser; +import com.example.solidconnection.siteuser.dto.LocationUpdateRequest; import com.example.solidconnection.siteuser.dto.MyPageResponse; import com.example.solidconnection.siteuser.dto.PasswordUpdateRequest; import com.example.solidconnection.siteuser.service.MyPageService; @@ -49,4 +50,13 @@ public ResponseEntity updatePassword( myPageService.updatePassword(siteUserId, request); return ResponseEntity.ok().build(); } + + @PatchMapping("/interested-location") + public ResponseEntity updateLocation( + @AuthorizedUser long siteUserId, + @RequestBody @Valid LocationUpdateRequest request + ) { + myPageService.updateLocation(siteUserId, request); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/LocationUpdateRequest.java b/src/main/java/com/example/solidconnection/siteuser/dto/LocationUpdateRequest.java new file mode 100644 index 000000000..2bb38e9cc --- /dev/null +++ b/src/main/java/com/example/solidconnection/siteuser/dto/LocationUpdateRequest.java @@ -0,0 +1,10 @@ +package com.example.solidconnection.siteuser.dto; + +import java.util.List; + +public record LocationUpdateRequest( + List interestedRegions, + List interestedCountries +) { + +} diff --git a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java index d1d6e340c..d48de9bfa 100644 --- a/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java +++ b/src/main/java/com/example/solidconnection/siteuser/service/MyPageService.java @@ -8,6 +8,8 @@ import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.location.country.service.InterestedCountryService; +import com.example.solidconnection.location.region.service.InterestedRegionService; import com.example.solidconnection.location.country.repository.CountryRepository; import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.mentor.repository.MentorRepository; @@ -16,6 +18,7 @@ import com.example.solidconnection.s3.service.S3Service; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.dto.LocationUpdateRequest; import com.example.solidconnection.siteuser.dto.MyPageResponse; import com.example.solidconnection.siteuser.dto.PasswordUpdateRequest; import com.example.solidconnection.siteuser.repository.SiteUserRepository; @@ -45,6 +48,8 @@ public class MyPageService { private final MentorRepository mentorRepository; private final UniversityRepository universityRepository; private final S3Service s3Service; + private final InterestedCountryService interestedCountryService; + private final InterestedRegionService interestedRegionService; /* * 마이페이지 정보를 조회한다. @@ -132,4 +137,13 @@ private void validatePasswordMatch(String currentPassword, String userPassword) throw new CustomException(PASSWORD_MISMATCH); } } + + @Transactional + public void updateLocation(long siteUserId, LocationUpdateRequest request) { + SiteUser siteUser = siteUserRepository.findById(siteUserId) + .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); + + interestedCountryService.updateInterestedCountry(siteUser, request.interestedCountries()); + interestedRegionService.updateInterestedRegion(siteUser, request.interestedRegions()); + } } diff --git a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java index f852909c6..d8e5c950f 100644 --- a/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java +++ b/src/test/java/com/example/solidconnection/siteuser/service/MyPageServiceTest.java @@ -1,10 +1,13 @@ package com.example.solidconnection.siteuser.service; import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; +import static com.example.solidconnection.common.exception.ErrorCode.PASSWORD_MISMATCH; import static com.example.solidconnection.siteuser.service.MyPageService.MIN_DAYS_BETWEEN_NICKNAME_CHANGES; import static com.example.solidconnection.siteuser.service.MyPageService.NICKNAME_LAST_CHANGE_DATE_FORMAT; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.BDDMockito.any; import static org.mockito.BDDMockito.eq; import static org.mockito.BDDMockito.given; @@ -16,6 +19,10 @@ import com.example.solidconnection.location.country.domain.InterestedCountry; import com.example.solidconnection.location.country.fixture.CountryFixture; import com.example.solidconnection.location.country.repository.InterestedCountryRepository; +import com.example.solidconnection.location.region.domain.InterestedRegion; +import com.example.solidconnection.location.region.domain.Region; +import com.example.solidconnection.location.region.fixture.RegionFixture; +import com.example.solidconnection.location.region.repository.InterestedRegionRepository; import com.example.solidconnection.mentor.fixture.MentorFixture; import com.example.solidconnection.s3.domain.ImgType; import com.example.solidconnection.s3.dto.UploadedFileUrlResponse; @@ -23,7 +30,9 @@ import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.Role; import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.dto.LocationUpdateRequest; import com.example.solidconnection.siteuser.dto.MyPageResponse; +import com.example.solidconnection.siteuser.dto.PasswordUpdateRequest; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.siteuser.fixture.SiteUserFixtureBuilder; import com.example.solidconnection.siteuser.repository.SiteUserRepository; @@ -33,7 +42,7 @@ import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; import java.time.LocalDateTime; -import org.junit.jupiter.api.Assertions; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -62,6 +71,9 @@ class MyPageServiceTest { @Autowired private InterestedCountryRepository interestedCountryRepository; + @Autowired + private InterestedRegionRepository interestedRegionRepository; + @Autowired private SiteUserFixture siteUserFixture; @@ -74,6 +86,9 @@ class MyPageServiceTest { @Autowired private UnivApplyInfoFixture univApplyInfoFixture; + @Autowired + private RegionFixture regionFixture; + @Autowired private SiteUserFixtureBuilder siteUserFixtureBuilder; @@ -99,7 +114,7 @@ void setUp() { MyPageResponse response = myPageService.getMyPageInfo(user.getId()); // then - Assertions.assertAll( + assertAll( () -> assertThat(response.nickname()).isEqualTo(user.getNickname()), () -> assertThat(response.profileImageUrl()).isEqualTo(user.getProfileImageUrl()), () -> assertThat(response.role()).isEqualTo(user.getRole()), @@ -124,7 +139,7 @@ void setUp() { MyPageResponse response = myPageService.getMyPageInfo(mentorUser.getId()); // then - Assertions.assertAll( + assertAll( () -> assertThat(response.nickname()).isEqualTo(mentorUser.getNickname()), () -> assertThat(response.profileImageUrl()).isEqualTo(mentorUser.getProfileImageUrl()), () -> assertThat(response.role()).isEqualTo(mentorUser.getRole()), @@ -263,4 +278,139 @@ void setUp() { .hasMessage(createExpectedErrorMessage(modifiedAt)); } } + + @Nested + class 비밀번호_변경_테스트 { + + private String currentPassword; + private String newPassword; + + @BeforeEach + void setUp() { + currentPassword = "currentPassword123"; + newPassword = "newPassword123"; + + user.updatePassword(passwordEncoder.encode(currentPassword)); + siteUserRepository.save(user); + } + + @Test + void 비밀번호를_성공적으로_변경한다() { + // given + PasswordUpdateRequest request = new PasswordUpdateRequest(currentPassword, newPassword, newPassword); + + // when + myPageService.updatePassword(user.getId(), request); + + // then + SiteUser updatedUser = siteUserRepository.findById(user.getId()).get(); + assertAll( + () -> assertThat(passwordEncoder.matches(newPassword, updatedUser.getPassword())).isTrue(), + () -> assertThat(passwordEncoder.matches(currentPassword, updatedUser.getPassword())).isFalse() + ); + } + + @Test + void 현재_비밀번호가_일치하지_않으면_예외가_발생한다() { + // given + String wrongPassword = "wrongPassword"; + PasswordUpdateRequest request = new PasswordUpdateRequest(wrongPassword, newPassword, newPassword); + + // when & then + assertThatThrownBy(() -> myPageService.updatePassword(user.getId(), request)) + .isInstanceOf(CustomException.class) + .hasMessage(PASSWORD_MISMATCH.getMessage()); + } + } + + @Nested + class 관심_지역_및_국가_변경_테스트 { + + private Country 미국; + private Country 캐나다; + private Country 일본; + private Region 영미권; + private Region 아시아; + + @BeforeEach + void setUp() { + 미국 = countryFixture.미국(); + 캐나다 = countryFixture.캐나다(); + 일본 = countryFixture.일본(); + 영미권 = regionFixture.영미권(); + 아시아 = regionFixture.아시아(); + } + + @Test + void 관심_지역과_국가를_성공적으로_수정한다() { + // given + interestedCountryRepository.save(new InterestedCountry(user, 미국)); + interestedRegionRepository.save(new InterestedRegion(user, 영미권)); + + List newCountries = List.of(캐나다.getKoreanName(), 일본.getKoreanName()); + List newRegions = List.of(아시아.getKoreanName()); + LocationUpdateRequest request = new LocationUpdateRequest(newRegions, newCountries); + + // when + myPageService.updateLocation(user.getId(), request); + + // then + List updatedCountries = interestedCountryRepository.findAllBySiteUserId(user.getId()); + List updatedRegions = interestedRegionRepository.findAllBySiteUserId(user.getId()); + + assertAll( + () -> assertThat(updatedCountries) + .extracting(InterestedCountry::getCountryCode) + .containsExactlyInAnyOrder(캐나다.getCode(), 일본.getCode()), + () -> assertThat(updatedRegions) + .extracting(InterestedRegion::getRegionCode) + .containsExactly(아시아.getCode()) + ); + } + + @Test + void 기존에_관심_지역과_국가가_없어도_성공적으로_추가된다() { + // given + List newCountries = List.of(미국.getKoreanName()); + List newRegions = List.of(영미권.getKoreanName()); + LocationUpdateRequest request = new LocationUpdateRequest(newRegions, newCountries); + + // when + myPageService.updateLocation(user.getId(), request); + + // then + List updatedCountries = interestedCountryRepository.findAllBySiteUserId(user.getId()); + List updatedRegions = interestedRegionRepository.findAllBySiteUserId(user.getId()); + + assertAll( + () -> assertThat(updatedCountries) + .extracting(InterestedCountry::getCountryCode) + .containsExactly(미국.getCode()), + () -> assertThat(updatedRegions) + .extracting(InterestedRegion::getRegionCode) + .containsExactly(영미권.getCode()) + ); + } + + @Test + void 빈_리스트를_전달하면_모든_관심_지역과_국가가_삭제된다() { + // given + interestedCountryRepository.save(new InterestedCountry(user, 미국)); + interestedRegionRepository.save(new InterestedRegion(user, 영미권)); + + LocationUpdateRequest request = new LocationUpdateRequest(List.of(), List.of()); + + // when + myPageService.updateLocation(user.getId(), request); + + // then + List updatedCountries = interestedCountryRepository.findAllBySiteUserId(user.getId()); + List updatedRegions = interestedRegionRepository.findAllBySiteUserId(user.getId()); + + assertAll( + () -> assertThat(updatedCountries).isEmpty(), + () -> assertThat(updatedRegions).isEmpty() + ); + } + } } From 9e770d101fa6ef2b60a5497aeeea05079818b2fa Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Tue, 19 Aug 2025 17:58:58 +0900 Subject: [PATCH 80/90] =?UTF-8?q?refactor:=20=EC=BF=A0=ED=82=A4=20?= =?UTF-8?q?=EC=A0=95=EC=B1=85=20=EB=B3=80=EA=B2=BD=20-=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=A5=B8=20Domain=EA=B3=BC=20Sam?= =?UTF-8?q?eSite=3DLax=20(#461)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * style: 불필요한 개행 제거 - 두 줄이 개행되어있었다. * refactor: SameSite를 Lax로 고정 * refactor: Domain에 따라 SameSite분기하던 코드 제거 * chore: 서브모듈 업데이트 --- .../controller/RefreshTokenCookieManager.java | 4 +-- .../config/RefreshTokenCookieProperties.java | 11 ------ src/main/resources/secret | 2 +- .../RefreshTokenCookieManagerTest.java | 7 ++-- .../RefreshTokenCookiePropertiesTest.java | 35 ------------------- 5 files changed, 6 insertions(+), 53 deletions(-) delete mode 100644 src/test/java/com/example/solidconnection/auth/controller/config/RefreshTokenCookiePropertiesTest.java diff --git a/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java b/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java index d36cca024..7c6f4ec04 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java +++ b/src/main/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManager.java @@ -10,6 +10,7 @@ import jakarta.servlet.http.HttpServletResponse; import java.util.Arrays; import lombok.RequiredArgsConstructor; +import org.springframework.boot.web.server.Cookie.SameSite; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; import org.springframework.stereotype.Component; @@ -46,7 +47,7 @@ private void setRefreshTokenCookie( .path(PATH) .maxAge(maxAge) .domain(properties.cookieDomain()) - .sameSite(properties.sameSite()) + .sameSite(SameSite.LAX.attributeValue()) .build(); response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); } @@ -72,4 +73,3 @@ public String getRefreshToken(HttpServletRequest request) { return refreshToken; } } - diff --git a/src/main/java/com/example/solidconnection/auth/controller/config/RefreshTokenCookieProperties.java b/src/main/java/com/example/solidconnection/auth/controller/config/RefreshTokenCookieProperties.java index 1fa47d884..ce6588f14 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/config/RefreshTokenCookieProperties.java +++ b/src/main/java/com/example/solidconnection/auth/controller/config/RefreshTokenCookieProperties.java @@ -1,21 +1,10 @@ package com.example.solidconnection.auth.controller.config; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.web.server.Cookie.SameSite; @ConfigurationProperties(prefix = "token.refresh") public record RefreshTokenCookieProperties( String cookieDomain ) { - public String sameSite() { - if (isDomainSet()) { - return SameSite.STRICT.attributeValue(); // 도메인을 지정한 경우 SameSite=Strict - } - return SameSite.NONE.attributeValue(); // 도메인을 지정하지 않은 경우 SameSite=None - } - - private boolean isDomainSet() { - return cookieDomain != null && !cookieDomain.isBlank(); - } } diff --git a/src/main/resources/secret b/src/main/resources/secret index 0e9f5d0ce..bb3bf0f41 160000 --- a/src/main/resources/secret +++ b/src/main/resources/secret @@ -1 +1 @@ -Subproject commit 0e9f5d0cefabab1ab9a306099b457225ce5d641e +Subproject commit bb3bf0f4122d10ddacab279a368cf9f06d6f6dbd diff --git a/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java b/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java index 677cd5854..a5924b860 100644 --- a/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java +++ b/src/test/java/com/example/solidconnection/auth/controller/RefreshTokenCookieManagerTest.java @@ -19,6 +19,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.web.server.Cookie.SameSite; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; @@ -34,13 +35,11 @@ class RefreshTokenCookieManagerTest { @MockBean private RefreshTokenCookieProperties refreshTokenCookieProperties; - private final String sameSite = "Strict"; private final String domain = "example.com"; @BeforeEach void setUp() { given(refreshTokenCookieProperties.cookieDomain()).willReturn(domain); - given(refreshTokenCookieProperties.sameSite()).willReturn(sameSite); } @Test @@ -62,7 +61,7 @@ void setUp() { () -> assertThat(header).contains("Path=/"), () -> assertThat(header).contains("Max-Age=" + TokenType.REFRESH.getExpireTime() / 1000), () -> assertThat(header).contains("Domain=" + domain), - () -> assertThat(header).contains("SameSite=" + sameSite) + () -> assertThat(header).contains("SameSite=" + SameSite.LAX.attributeValue()) ); } @@ -84,7 +83,7 @@ void setUp() { () -> assertThat(header).contains("Path=/"), () -> assertThat(header).contains("Max-Age=0"), () -> assertThat(header).contains("Domain=" + domain), - () -> assertThat(header).contains("SameSite=" + sameSite) + () -> assertThat(header).contains("SameSite=" + SameSite.LAX.attributeValue()) ); } diff --git a/src/test/java/com/example/solidconnection/auth/controller/config/RefreshTokenCookiePropertiesTest.java b/src/test/java/com/example/solidconnection/auth/controller/config/RefreshTokenCookiePropertiesTest.java deleted file mode 100644 index 0a62541b1..000000000 --- a/src/test/java/com/example/solidconnection/auth/controller/config/RefreshTokenCookiePropertiesTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.example.solidconnection.auth.controller.config; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.boot.web.server.Cookie.SameSite; - -@DisplayName("리프레시 토큰 쿠키 설정 테스트") -class RefreshTokenCookiePropertiesTest { - - @Test - void Domain을_지정했으면_SameSite가_Strict() { - // given - RefreshTokenCookieProperties properties = new RefreshTokenCookieProperties("example.com"); - - // when - String sameSite = properties.sameSite(); - - // then - assertThat(sameSite).isEqualTo(SameSite.STRICT.attributeValue()); - } - - @Test - void Domain을_지정하지_않았으면_SameSite가_None() { - // given - RefreshTokenCookieProperties properties = new RefreshTokenCookieProperties(null); - - // when - String sameSite = properties.sameSite(); - - // then - assertThat(sameSite).isEqualTo(SameSite.NONE.attributeValue()); - } -} From 35e979aa2d53fbd875ad71a9e4f230e2e99adf95 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Tue, 19 Aug 2025 17:59:12 +0900 Subject: [PATCH 81/90] =?UTF-8?q?refactor:=20isConfirmed=20=EB=8C=80?= =?UTF-8?q?=EC=8B=A0=20verifyStatus=EB=A5=BC=20=EC=9D=91=EB=8B=B5=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20(#463)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mentor/dto/MentoringForMentorResponse.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMentorResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMentorResponse.java index 54622d821..8a41fba84 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMentorResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMentorResponse.java @@ -1,5 +1,6 @@ package com.example.solidconnection.mentor.dto; +import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.mentor.domain.Mentoring; import com.example.solidconnection.siteuser.domain.SiteUser; import java.time.ZonedDateTime; @@ -9,7 +10,7 @@ public record MentoringForMentorResponse( String profileImageUrl, String nickname, boolean isChecked, - boolean isConfirmed, + VerifyStatus verifyStatus, ZonedDateTime createdAt ) { @@ -19,7 +20,7 @@ public static MentoringForMentorResponse of(Mentoring mentoring, SiteUser partne partner.getProfileImageUrl(), partner.getNickname(), mentoring.getCheckedAtByMentor() != null, - mentoring.getConfirmedAt() != null, + mentoring.getVerifyStatus(), mentoring.getCreatedAt() ); } From 44184e25ed22d768528818a441d2d9c891757d39 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Wed, 20 Aug 2025 10:42:37 +0900 Subject: [PATCH 82/90] =?UTF-8?q?refactor:=20=EB=A9=98=ED=8B=B0=EC=9D=98?= =?UTF-8?q?=20=EB=A9=98=ED=86=A0=EB=A7=81=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=8B=9C=20=EC=97=B0=EA=B4=80=EB=90=9C=20=EC=B1=84?= =?UTF-8?q?=ED=8C=85=EB=B0=A9=20ID=EB=A5=BC=20=ED=95=A8=EA=BB=98=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=ED=95=98=EB=8F=84=EB=A1=9D=20(#465)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 멘티의 멘토링 목록 조회 시, chatRoomId를 포함하여 응답하도록 * test: 멘토링 채팅방 픽스쳐 추가 * test: 멘티의 멘토링 목록 조회 시 채팅방 ID 포함 테스트 추가 * refactor: 멘토링 조회 순서를 보장하도록 - map이 아니라 순서가 보장되는 slice를 기준으로 순회하며 응답을 생성하도록 --- .../chat/repository/ChatRoomRepository.java | 2 ++ .../dto/MentoringForMenteeResponse.java | 8 +++-- .../mentor/service/MentoringQueryService.java | 29 +++++++++++++++---- .../chat/fixture/ChatRoomFixture.java | 7 +++++ .../chat/fixture/ChatRoomFixtureBuilder.java | 8 ++++- .../service/MentoringQueryServiceTest.java | 15 +++++++--- 6 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java b/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java index ad815dbe1..cdb0dbfa6 100644 --- a/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java +++ b/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java @@ -35,4 +35,6 @@ SELECT COUNT(cm) FROM ChatMessage cm long countUnreadMessages(@Param("chatRoomId") long chatRoomId, @Param("userId") long userId); boolean existsByMentoringId(long mentoringId); + + List findAllByMentoringIdIn(List mentoringIds); } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMenteeResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMenteeResponse.java index 7f2591555..65e1e0120 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMenteeResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringForMenteeResponse.java @@ -9,16 +9,18 @@ public record MentoringForMenteeResponse( String profileImageUrl, String nickname, boolean isChecked, - ZonedDateTime createdAt + ZonedDateTime createdAt, + Long chatRoomId ) { - public static MentoringForMenteeResponse of(Mentoring mentoring, SiteUser partner) { + public static MentoringForMenteeResponse of(Mentoring mentoring, SiteUser partner, Long chatRoomId) { return new MentoringForMenteeResponse( mentoring.getId(), partner.getProfileImageUrl(), partner.getNickname(), mentoring.getCheckedAtByMentee() != null, - mentoring.getCreatedAt() + mentoring.getCreatedAt(), + chatRoomId ); } } diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java b/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java index a102b9f63..1ef037706 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentoringQueryService.java @@ -2,6 +2,8 @@ import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; +import com.example.solidconnection.chat.domain.ChatRoom; +import com.example.solidconnection.chat.repository.ChatRoomRepository; import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.dto.SliceResponse; import com.example.solidconnection.common.exception.CustomException; @@ -17,7 +19,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.function.Function; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -33,6 +34,7 @@ public class MentoringQueryService { private final MentoringRepository mentoringRepository; private final MentorRepository mentorRepository; private final SiteUserRepository siteUserRepository; + private final ChatRoomRepository chatRoomRepository; @Transactional(readOnly = true) public SliceResponse getMentoringsForMentee( @@ -47,15 +49,30 @@ public SliceResponse getMentoringsForMentee( mentoringSlice.toList(), Mentoring::getMentorId ); + Map mentoringIdToChatRoomId = mapMentoringIdToChatRoomIdWithBatchQuery(mentoringSlice.getContent()); List content = new ArrayList<>(); - for (Entry entry : mentoringToPartnerUser.entrySet()) { - content.add(MentoringForMenteeResponse.of(entry.getKey(), entry.getValue())); + for (Mentoring mentoring : mentoringSlice) { + content.add(MentoringForMenteeResponse.of( + mentoring, + mentoringToPartnerUser.get(mentoring), + mentoringIdToChatRoomId.get(mentoring.getId()) + )); } - return SliceResponse.of(content, mentoringSlice); } + // N+1 을 해결하면서 멘토링의 채팅방 정보 조회 + private Map mapMentoringIdToChatRoomIdWithBatchQuery(List mentorings) { + List mentoringIds = mentorings.stream() + .map(Mentoring::getId) + .distinct() + .toList(); + List chatRooms = chatRoomRepository.findAllByMentoringIdIn(mentoringIds); + return chatRooms.stream() + .collect(Collectors.toMap(ChatRoom::getMentoringId, ChatRoom::getId)); + } + @Transactional(readOnly = true) public SliceResponse getMentoringsForMentor(long siteUserId, Pageable pageable) { Mentor mentor = mentorRepository.findBySiteUserId(siteUserId) @@ -68,8 +85,8 @@ public SliceResponse getMentoringsForMentor(long sit ); List content = new ArrayList<>(); - for (Entry entry : mentoringToPartnerUser.entrySet()) { - content.add(MentoringForMentorResponse.of(entry.getKey(), entry.getValue())); + for (Mentoring mentoring : mentoringSlice) { + content.add(MentoringForMentorResponse.of(mentoring, mentoringToPartnerUser.get(mentoring))); } return SliceResponse.of(content, mentoringSlice); diff --git a/src/test/java/com/example/solidconnection/chat/fixture/ChatRoomFixture.java b/src/test/java/com/example/solidconnection/chat/fixture/ChatRoomFixture.java index cf80313bc..717852a4c 100644 --- a/src/test/java/com/example/solidconnection/chat/fixture/ChatRoomFixture.java +++ b/src/test/java/com/example/solidconnection/chat/fixture/ChatRoomFixture.java @@ -15,4 +15,11 @@ public class ChatRoomFixture { .isGroup(isGroup) .create(); } + + public ChatRoom 멘토링_채팅방(long mentoringId) { + return chatRoomFixtureBuilder.chatRoom() + .mentoringId(mentoringId) + .isGroup(false) + .create(); + } } diff --git a/src/test/java/com/example/solidconnection/chat/fixture/ChatRoomFixtureBuilder.java b/src/test/java/com/example/solidconnection/chat/fixture/ChatRoomFixtureBuilder.java index bf7ed3387..9bbb3e988 100644 --- a/src/test/java/com/example/solidconnection/chat/fixture/ChatRoomFixtureBuilder.java +++ b/src/test/java/com/example/solidconnection/chat/fixture/ChatRoomFixtureBuilder.java @@ -12,6 +12,7 @@ public class ChatRoomFixtureBuilder { private final ChatRoomRepository chatRoomRepository; private boolean isGroup; + private Long mentoringId; public ChatRoomFixtureBuilder chatRoom() { return new ChatRoomFixtureBuilder(chatRoomRepository); @@ -22,8 +23,13 @@ public ChatRoomFixtureBuilder isGroup(boolean isGroup) { return this; } + public ChatRoomFixtureBuilder mentoringId(long mentoringId) { + this.mentoringId = mentoringId; + return this; + } + public ChatRoom create() { - ChatRoom chatRoom = new ChatRoom(isGroup); + ChatRoom chatRoom = new ChatRoom(mentoringId, isGroup); return chatRoomRepository.save(chatRoom); } } diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java index d8146dc51..867af6e56 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentoringQueryServiceTest.java @@ -4,6 +4,8 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.AssertionsForClassTypes.tuple; +import com.example.solidconnection.chat.domain.ChatRoom; +import com.example.solidconnection.chat.fixture.ChatRoomFixture; import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.dto.SliceResponse; import com.example.solidconnection.common.exception.CustomException; @@ -45,6 +47,9 @@ class MentoringQueryServiceTest { @Autowired private MentoringRepository mentoringRepository; + @Autowired + private ChatRoomFixture chatRoomFixture; + private SiteUser mentorUser1, mentorUser2; private SiteUser menteeUser1, menteeUser2, menteeUser3; private Mentor mentor1, mentor2, mentor3; @@ -146,10 +151,12 @@ class 멘티의_멘토링_목록_조회_테스트 { } @Test - void 승인된_멘토링_목록을_조회한다() { + void 승인된_멘토링_목록과_대응하는_채팅방을_조회한다() { // given Mentoring mentoring1 = mentoringFixture.승인된_멘토링(mentor1.getId(), menteeUser1.getId()); Mentoring mentoring2 = mentoringFixture.승인된_멘토링(mentor2.getId(), menteeUser1.getId()); + ChatRoom mentoringChatRoom1 = chatRoomFixture.멘토링_채팅방(mentoring1.getId()); + ChatRoom mentoringChatRoom2 = chatRoomFixture.멘토링_채팅방(mentoring2.getId()); mentoringFixture.대기중_멘토링(mentor3.getId(), menteeUser1.getId()); // when @@ -157,10 +164,10 @@ class 멘티의_멘토링_목록_조회_테스트 { menteeUser1.getId(), VerifyStatus.APPROVED, pageable); // then - assertThat(response.content()).extracting(MentoringForMenteeResponse::mentoringId) + assertThat(response.content()).extracting(MentoringForMenteeResponse::mentoringId, MentoringForMenteeResponse::chatRoomId) .containsExactlyInAnyOrder( - mentoring1.getId(), - mentoring2.getId() + tuple(mentoring1.getId(), mentoringChatRoom1.getId()), + tuple(mentoring2.getId(), mentoringChatRoom2.getId()) ); } From accbc93a674be824357bf53f57f174ab26508c0f Mon Sep 17 00:00:00 2001 From: seonghyeok cho <65901319+whqtker@users.noreply.github.com> Date: Wed, 20 Aug 2025 13:35:05 +0900 Subject: [PATCH 83/90] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=EC=9D=84?= =?UTF-8?q?=20Authorization=20=ED=97=A4=EB=8D=94=EA=B0=80=20=EC=95=84?= =?UTF-8?q?=EB=8B=88=EB=9D=BC=20=EC=BF=BC=EB=A6=AC=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A7=81=EC=97=90=EC=84=9C=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20(#467)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 웹소켓 연결 요청은 쿼리 파라미터에서 토큰을 읽도록 * refactor: 인터셉터가 인증에 관여하던 로직 제거 - 인터셉터는 인증된 Principal 객체를 세션에 담는데 집중한다. - 토큰 필터가 인증 작업을 처리한다. * test: 변경된 로직에 맞게 테스트 코드 수정 * refactor: 빈 문자열은 토큰으로 간주하지 않는다. * chore: 코드 리팩터링 --- .../config/WebSocketHandshakeInterceptor.java | 3 +- .../filter/TokenAuthenticationFilter.java | 21 +++++---- .../WebSocketStompIntegrationTest.java | 47 +++++-------------- 3 files changed, 26 insertions(+), 45 deletions(-) diff --git a/src/main/java/com/example/solidconnection/chat/config/WebSocketHandshakeInterceptor.java b/src/main/java/com/example/solidconnection/chat/config/WebSocketHandshakeInterceptor.java index 9e8aafe2d..e4af7a412 100644 --- a/src/main/java/com/example/solidconnection/chat/config/WebSocketHandshakeInterceptor.java +++ b/src/main/java/com/example/solidconnection/chat/config/WebSocketHandshakeInterceptor.java @@ -19,10 +19,9 @@ public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse res if (principal != null) { attributes.put("user", principal); - return true; } - return false; + return true; } @Override diff --git a/src/main/java/com/example/solidconnection/security/filter/TokenAuthenticationFilter.java b/src/main/java/com/example/solidconnection/security/filter/TokenAuthenticationFilter.java index 0a81b860e..8c8dc8f30 100644 --- a/src/main/java/com/example/solidconnection/security/filter/TokenAuthenticationFilter.java +++ b/src/main/java/com/example/solidconnection/security/filter/TokenAuthenticationFilter.java @@ -28,16 +28,21 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { public void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { - Optional token = authorizationHeaderParser.parseToken(request); - if (token.isEmpty()) { - filterChain.doFilter(request, response); - return; - } + Optional resolvedToken = resolveToken(request); - TokenAuthentication authToken = new TokenAuthentication(token.get()); - Authentication auth = authenticationManager.authenticate(authToken); - SecurityContextHolder.getContext().setAuthentication(auth); + resolvedToken.filter(token -> !token.isBlank()).ifPresent(token -> { + TokenAuthentication authToken = new TokenAuthentication(token); + Authentication auth = authenticationManager.authenticate(authToken); + SecurityContextHolder.getContext().setAuthentication(auth); + }); filterChain.doFilter(request, response); } + + private Optional resolveToken(HttpServletRequest request) { + if (request.getRequestURI().startsWith("/connect")) { + return Optional.ofNullable(request.getParameter("token")); + } + return authorizationHeaderParser.parseToken(request); + } } diff --git a/src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java b/src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java index 978bfd717..b39c91ece 100644 --- a/src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java +++ b/src/test/java/com/example/solidconnection/websocket/WebSocketStompIntegrationTest.java @@ -2,8 +2,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.ThrowableAssert.catchThrowable; -import static org.junit.jupiter.api.Assertions.assertAll; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.example.solidconnection.auth.service.AccessToken; import com.example.solidconnection.auth.service.AuthTokenProvider; @@ -11,9 +10,8 @@ import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -22,11 +20,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.messaging.converter.MappingJackson2MessageConverter; -import org.springframework.messaging.simp.stomp.StompHeaders; import org.springframework.messaging.simp.stomp.StompSession; import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter; import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.socket.WebSocketHttpHeaders; import org.springframework.web.socket.client.standard.StandardWebSocketClient; import org.springframework.web.socket.messaging.WebSocketStompClient; import org.springframework.web.socket.sockjs.client.SockJsClient; @@ -67,48 +63,29 @@ void tearDown() { @Nested class WebSocket_핸드셰이크_및_STOMP_세션_수립_테스트 { - private final BlockingQueue transportErrorQueue = new ArrayBlockingQueue<>(1); - - private final StompSessionHandlerAdapter sessionHandler = new StompSessionHandlerAdapter() { - @Override - public void handleTransportError(StompSession session, Throwable exception) { - transportErrorQueue.add(exception); - } - }; - @Test void 인증된_사용자는_핸드셰이크를_성공한다() throws Exception { // given SiteUser user = siteUserFixture.사용자(); AccessToken accessToken = authTokenProvider.generateAccessToken(user); - - WebSocketHttpHeaders handshakeHeaders = new WebSocketHttpHeaders(); - handshakeHeaders.add("Authorization", "Bearer " + accessToken.token()); + String tokenUrl = url + "?token=" + accessToken.token(); // when - stompSession = stompClient.connectAsync(url, handshakeHeaders, new StompHeaders(), sessionHandler).get(5, SECONDS); + stompSession = stompClient.connectAsync(tokenUrl, new StompSessionHandlerAdapter() { + }).get(5, SECONDS); // then - assertAll( - () -> assertThat(stompSession).isNotNull(), - () -> assertThat(transportErrorQueue).isEmpty() - ); + assertThat(stompSession.isConnected()).isTrue(); } @Test void 인증되지_않은_사용자는_핸드셰이크를_실패한다() { - // when - Throwable thrown = catchThrowable(() -> { - stompSession = stompClient.connectAsync(url, new WebSocketHttpHeaders(), new StompHeaders(), sessionHandler).get(5, SECONDS); - }); - - // then - assertAll( - () -> assertThat(thrown) - .isInstanceOf(ExecutionException.class) - .hasCauseInstanceOf(HttpClientErrorException.Unauthorized.class), - () -> assertThat(transportErrorQueue).hasSize(1) - ); + // when & then + assertThatThrownBy(() -> { + stompClient.connectAsync(url, new StompSessionHandlerAdapter() { + }).get(5, TimeUnit.SECONDS); + }).isInstanceOf(ExecutionException.class) + .hasCauseInstanceOf(HttpClientErrorException.Unauthorized.class); } } } From 70603785c19fe989b141cb253b153b14b5983cba Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Sun, 24 Aug 2025 21:27:16 +0900 Subject: [PATCH 84/90] =?UTF-8?q?refactor:=20=EA=B8=B0=ED=9A=8D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=ED=95=84?= =?UTF-8?q?=ED=84=B0=EB=A7=81=20=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#471)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 레포지토리 구현체가 아니라 인터페이스 의존하도록 - UnivApplyInfoFilterRepositoryImpl가 아니라 UnivApplyInfoRepository를 의존하게 한다. - 그렇게 해도 되는 이유는, UnivApplyInfoRepository가 UnivApplyInfoFilterRepository를 extend하고 있기 때문 * refactor: API 명세에 맞게 컨트롤러, 요청 구현 * feat: 대학지원정보 필터링 검색 기능 구현 * chore: 사용하지 않는 메서드 제거 * refactor: 람다 표현식 간소화 * chore: 주석 수정 - compare 에서 0은 같다는 의미 * test: 바뀐 로직을 테스트 코드에 반영 * test: 더 엄격한 검증 적용 --- .../controller/UnivApplyInfoController.java | 28 +- .../university/domain/LanguageTestType.java | 6 +- .../dto/UnivApplyInfoFilterSearchRequest.java | 15 + .../repository/UniversityRepository.java | 6 - .../custom/UnivApplyInfoFilterRepository.java | 3 +- .../UnivApplyInfoFilterRepositoryImpl.java | 73 +++-- .../service/UnivApplyInfoQueryService.java | 33 +-- .../UnivApplyInfoQueryServiceTest.java | 269 +++++++----------- 8 files changed, 207 insertions(+), 226 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoFilterSearchRequest.java diff --git a/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java b/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java index c3b986d40..97adc128a 100644 --- a/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java +++ b/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java @@ -1,19 +1,22 @@ package com.example.solidconnection.university.controller; import com.example.solidconnection.common.resolver.AuthorizedUser; -import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.dto.IsLikeResponse; import com.example.solidconnection.university.dto.UnivApplyInfoDetailResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoFilterSearchRequest; import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponses; import com.example.solidconnection.university.dto.UnivApplyInfoRecommendsResponse; import com.example.solidconnection.university.service.LikedUnivApplyInfoService; import com.example.solidconnection.university.service.UnivApplyInfoQueryService; import com.example.solidconnection.university.service.UnivApplyInfoRecommendService; +import jakarta.validation.Valid; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -84,16 +87,19 @@ public ResponseEntity getUnivApplyInfoDetails( return ResponseEntity.ok(univApplyInfoDetailResponse); } - // todo: return타입 UniversityInfoForApplyPreviewResponses로 추후 수정 필요 - @GetMapping("/search") - public ResponseEntity> searchUnivApplyInfo( - @RequestParam(required = false, defaultValue = "") String region, - @RequestParam(required = false, defaultValue = "") List keyword, - @RequestParam(required = false, defaultValue = "") LanguageTestType testType, - @RequestParam(required = false, defaultValue = "") String testScore + @GetMapping("/search/filter") + public ResponseEntity searchUnivApplyInfoByFilter( + @Valid @ModelAttribute UnivApplyInfoFilterSearchRequest request ) { - List univApplyInfoPreviewResponse - = univApplyInfoQueryService.searchUnivApplyInfo(region, keyword, testType, testScore).univApplyInfoPreviews(); - return ResponseEntity.ok(univApplyInfoPreviewResponse); + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request); + return ResponseEntity.ok(response); + } + + @GetMapping("/search/text") + public ResponseEntity searchUnivApplyInfoByText( + @RequestParam(required = false) String text + ) { + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text); + return ResponseEntity.ok(response); } } diff --git a/src/main/java/com/example/solidconnection/university/domain/LanguageTestType.java b/src/main/java/com/example/solidconnection/university/domain/LanguageTestType.java index f220ac3d3..af0f861db 100644 --- a/src/main/java/com/example/solidconnection/university/domain/LanguageTestType.java +++ b/src/main/java/com/example/solidconnection/university/domain/LanguageTestType.java @@ -4,8 +4,8 @@ public enum LanguageTestType { - CEFR((s1, s2) -> s1.compareTo(s2)), - JLPT((s1, s2) -> s2.compareTo(s1)), + CEFR(String::compareTo), + JLPT(Comparator.reverseOrder()), DALF(LanguageTestType::compareIntegerScores), DELF(LanguageTestType::compareIntegerScores), DUOLINGO(LanguageTestType::compareIntegerScores), @@ -16,7 +16,7 @@ public enum LanguageTestType { TOEFL_IBT(LanguageTestType::compareIntegerScores), TOEFL_ITP(LanguageTestType::compareIntegerScores), TOEIC(LanguageTestType::compareIntegerScores), - ETC((s1, s2) -> 0), // 기타 언어시험은 점수를 비교할 수 없으므로 항상 크다고 비교한다. + ETC((s1, s2) -> 0), // 기타 언어시험은 점수를 비교할 수 없으므로 항상 같다고 비교한다. ; private final Comparator comparator; diff --git a/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoFilterSearchRequest.java b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoFilterSearchRequest.java new file mode 100644 index 000000000..a49079319 --- /dev/null +++ b/src/main/java/com/example/solidconnection/university/dto/UnivApplyInfoFilterSearchRequest.java @@ -0,0 +1,15 @@ +package com.example.solidconnection.university.dto; + +import com.example.solidconnection.university.domain.LanguageTestType; +import jakarta.validation.constraints.NotNull; +import java.util.List; + +public record UnivApplyInfoFilterSearchRequest( + + @NotNull(message = "어학 시험 종류를 선택해주세요.") + LanguageTestType languageTestType, + String testScore, + List countryCode +) { + +} diff --git a/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java b/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java index 5447a14ba..15210c18d 100644 --- a/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/UniversityRepository.java @@ -4,16 +4,10 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.university.domain.University; -import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; public interface UniversityRepository extends JpaRepository { - @Query("SELECT u FROM University u WHERE u.country.code IN :countryCodes OR u.region.code IN :regionCodes") - List findByCountryCodeInOrRegionCodeIn(@Param("countryCodes") List countryCodes, @Param("regionCodes") List regionCodes); - default University getUniversityById(Long id) { return findById(id) .orElseThrow(() -> new CustomException(UNIVERSITY_NOT_FOUND)); diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java index 5c95e5d1e..9de0e98b6 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java @@ -8,6 +8,5 @@ public interface UnivApplyInfoFilterRepository { List findAllByRegionCodeAndKeywords(String regionCode, List keywords); - List findAllByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( - String regionCode, List keywords, LanguageTestType testType, String testScore, String term); + List findAllByFilter(LanguageTestType testType, String testScore, String term, List countryKoreanNames); } diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java index 9e57274ee..538074422 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java @@ -1,7 +1,6 @@ package com.example.solidconnection.university.repository.custom; import com.example.solidconnection.location.country.domain.QCountry; -import com.example.solidconnection.location.region.domain.QRegion; import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.QLanguageRequirement; import com.example.solidconnection.university.domain.QUnivApplyInfo; @@ -70,43 +69,71 @@ private BooleanExpression createKeywordCondition(StringPath namePath, List findAllByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( - String regionCode, List keywords, LanguageTestType testType, String testScore, String term) { + public List findAllByFilter( + LanguageTestType testType, String testScore, String term, List countryCodes + ) { QUniversity university = QUniversity.university; - QCountry country = QCountry.country; - QRegion region = QRegion.region; QUnivApplyInfo univApplyInfo = QUnivApplyInfo.univApplyInfo; + QCountry country = QCountry.country; + QLanguageRequirement languageRequirement = QLanguageRequirement.languageRequirement; - List filteredUnivApplyInfo = queryFactory - .selectFrom(univApplyInfo) + List filteredUnivApplyInfo = queryFactory.selectFrom(univApplyInfo) .join(univApplyInfo.university, university) .join(university.country, country) - .join(university.region, region) - .where(regionCodeEq(country, regionCode) - .and(countryOrUniversityContainsKeyword(country, university, keywords)) - .and(univApplyInfo.term.eq(term))) + .join(univApplyInfo.languageRequirements, languageRequirement) + .fetchJoin() + .where( + languageTestTypeEq(languageRequirement, testType), + termEq(univApplyInfo, term), + countryCodesIn(country, countryCodes) + ) + .distinct() .fetch(); - if (testScore == null || testScore.isEmpty()) { - if (testType != null) { - return filteredUnivApplyInfo.stream() - .filter(uai -> uai.getLanguageRequirements().stream() - .anyMatch(lr -> lr.getLanguageTestType().equals(testType))) - .toList(); - } + if (testScore == null || testScore.isBlank()) { return filteredUnivApplyInfo; } + /* + * 시험 유형에 따라 성적 비교 방식이 다르다. + * 입력된 점수가 대학에서 요구하는 최소 점수보다 높은지를 '쿼리로' 비교하기엔 쿼리가 지나치게 복잡해진다. + * 따라서 이 부분만 자바 코드로 필터링한다. + * */ return filteredUnivApplyInfo.stream() - .filter(uai -> compareMyTestScoreToMinPassScore(uai, testType, testScore) >= 0) + .filter(uai -> isGivenScoreOverMinPassScore(uai, testType, testScore)) .toList(); } - private int compareMyTestScoreToMinPassScore(UnivApplyInfo univApplyInfo, LanguageTestType testType, String testScore) { + private BooleanExpression languageTestTypeEq( + QLanguageRequirement languageRequirement, LanguageTestType givenTestType + ) { + if (givenTestType == null) { + return null; + } + return languageRequirement.languageTestType.eq(givenTestType); + } + + private BooleanExpression termEq(QUnivApplyInfo univApplyInfo, String givenTerm) { + if (givenTerm == null || givenTerm.isBlank()) { + return null; + } + return univApplyInfo.term.eq(givenTerm); + } + + private BooleanExpression countryCodesIn(QCountry country, List givenCountryCodes) { + if (givenCountryCodes == null || givenCountryCodes.isEmpty()) { + return null; + } + return country.code.in(givenCountryCodes); + } + + private boolean isGivenScoreOverMinPassScore( + UnivApplyInfo univApplyInfo, LanguageTestType givenTestType, String givenTestScore + ) { return univApplyInfo.getLanguageRequirements().stream() - .filter(languageRequirement -> languageRequirement.getLanguageTestType().equals(testType)) + .filter(languageRequirement -> languageRequirement.getLanguageTestType().equals(givenTestType)) .findFirst() - .map(requirement -> testType.compare(testScore, requirement.getMinScore())) - .orElse(-1); + .map(requirement -> givenTestType.compare(givenTestScore, requirement.getMinScore())) + .orElse(-1) >= 0; } } diff --git a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java index ff0215c63..e8ba3140b 100644 --- a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java +++ b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java @@ -1,14 +1,13 @@ package com.example.solidconnection.university.service; import com.example.solidconnection.cache.annotation.ThunderingHerdCaching; -import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.dto.UnivApplyInfoDetailResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoFilterSearchRequest; import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponses; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; -import com.example.solidconnection.university.repository.custom.UnivApplyInfoFilterRepositoryImpl; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -20,7 +19,6 @@ public class UnivApplyInfoQueryService { private final UnivApplyInfoRepository univApplyInfoRepository; - private final UnivApplyInfoFilterRepositoryImpl universityFilterRepository; // todo: 구현체 숨기고 univApplyInfoRepository만 사용하도록 @Value("${university.term}") public String term; @@ -39,22 +37,19 @@ public UnivApplyInfoDetailResponse getUnivApplyInfoDetail(Long univApplyInfoId) return UnivApplyInfoDetailResponse.of(university, univApplyInfo); } - /* - * 대학교 검색 결과를 불러온다. - * - 권역, 키워드, 언어 시험 종류, 언어 시험 점수를 조건으로 검색하여 결과를 반환한다. - * - 권역은 영어 대문자로 받는다 e.g. ASIA - * - 키워드는 국가명 또는 대학명에 포함되는 것이 조건이다. - * - 언어 시험 점수는 합격 최소 점수보다 높은 것이 조건이다. - * */ @Transactional(readOnly = true) - @ThunderingHerdCaching(key = "univApplyInfo:{0}:{1}:{2}:{3}", cacheManager = "customCacheManager", ttlSec = 86400) - public UnivApplyInfoPreviewResponses searchUnivApplyInfo( - String regionCode, List keywords, LanguageTestType testType, String testScore) { - - return new UnivApplyInfoPreviewResponses(universityFilterRepository - .findAllByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm(regionCode, keywords, testType, testScore, term) - .stream() - .map(UnivApplyInfoPreviewResponse::from) - .toList()); + public UnivApplyInfoPreviewResponses searchUnivApplyInfoByFilter(UnivApplyInfoFilterSearchRequest request) { + List responses = univApplyInfoRepository + .findAllByFilter(request.languageTestType(), request.testScore(), term, request.countryCode()) + .stream() + .map(UnivApplyInfoPreviewResponse::from) + .toList(); + return new UnivApplyInfoPreviewResponses(responses); + } + + @Transactional(readOnly = true) + public UnivApplyInfoPreviewResponses searchUnivApplyInfoByText(String text) { + // todo: 구현 + return null; } } diff --git a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java index fd6b4b26d..69fa9b619 100644 --- a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java @@ -1,24 +1,26 @@ package com.example.solidconnection.university.service; import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND; +import static com.example.solidconnection.university.domain.LanguageTestType.TOEIC; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.times; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.UnivApplyInfo; import com.example.solidconnection.university.dto.UnivApplyInfoDetailResponse; +import com.example.solidconnection.university.dto.UnivApplyInfoFilterSearchRequest; import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponse; import com.example.solidconnection.university.dto.UnivApplyInfoPreviewResponses; import com.example.solidconnection.university.fixture.LanguageRequirementFixture; import com.example.solidconnection.university.fixture.UnivApplyInfoFixture; import com.example.solidconnection.university.repository.UnivApplyInfoRepository; -import com.example.solidconnection.university.repository.custom.UnivApplyInfoFilterRepository; import java.util.List; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.SpyBean; @@ -30,9 +32,6 @@ class UnivApplyInfoQueryServiceTest { @Autowired private UnivApplyInfoQueryService univApplyInfoQueryService; - @SpyBean - private UnivApplyInfoFilterRepository univApplyInfoFilterRepository; - @SpyBean private UnivApplyInfoRepository univApplyInfoRepository; @@ -42,164 +41,110 @@ class UnivApplyInfoQueryServiceTest { @Autowired private LanguageRequirementFixture languageRequirementFixture; - @Test - void 대학_지원_정보를_상세_조회한다() { - // given - UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); - - // when - UnivApplyInfoDetailResponse response = univApplyInfoQueryService.getUnivApplyInfoDetail(괌대학_A_지원_정보.getId()); - - // then - assertThat(response.id()).isEqualTo(괌대학_A_지원_정보.getId()); - } - - @Test - void 대학_지원_정보_상세_조회시_캐시가_적용된다() { - // given - UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); - - // when - UnivApplyInfoDetailResponse firstResponse = univApplyInfoQueryService.getUnivApplyInfoDetail(괌대학_A_지원_정보.getId()); - UnivApplyInfoDetailResponse secondResponse = univApplyInfoQueryService.getUnivApplyInfoDetail(괌대학_A_지원_정보.getId()); - - // then - assertThat(firstResponse).isEqualTo(secondResponse); - then(univApplyInfoRepository).should(times(1)).getUnivApplyInfoById(괌대학_A_지원_정보.getId()); - } - - @Test - void 존재하지_않는_대학_지원_정보를_조회하면_예외가_발생한다() { - // given - Long invalidUnivApplyInfoId = 9999L; - - // when & then - assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> univApplyInfoQueryService.getUnivApplyInfoDetail(invalidUnivApplyInfoId)) - .havingRootCause() - .isInstanceOf(CustomException.class) - .withMessage(UNIV_APPLY_INFO_NOT_FOUND.getMessage()); - } - - @Test - void 전체_대학_지원_정보를_조회한다() { - // given - UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); - UnivApplyInfo 괌대학_B_지원_정보 = univApplyInfoFixture.괌대학_B_지원_정보(); - UnivApplyInfo 네바다주립대학_라스베이거스_지원_정보 = univApplyInfoFixture.네바다주립대학_라스베이거스_지원_정보(); - UnivApplyInfo 서던덴마크대학교_지원_정보 = univApplyInfoFixture.서던덴마크대학교_지원_정보(); - UnivApplyInfo 그라츠대학_지원_정보 = univApplyInfoFixture.그라츠대학_지원_정보(); - UnivApplyInfo 메이지대학_지원_정보 = univApplyInfoFixture.메이지대학_지원_정보(); - - // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfo( - null, List.of(), null, null); - - // then - assertThat(response.univApplyInfoPreviews()) - .containsExactlyInAnyOrder( - UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보), - UnivApplyInfoPreviewResponse.from(괌대학_B_지원_정보), - UnivApplyInfoPreviewResponse.from(네바다주립대학_라스베이거스_지원_정보), - UnivApplyInfoPreviewResponse.from(서던덴마크대학교_지원_정보), - UnivApplyInfoPreviewResponse.from(그라츠대학_지원_정보), - UnivApplyInfoPreviewResponse.from(메이지대학_지원_정보) - ); - } - - @Test - void 대학_지원_정보_조회시_캐시가_적용된다() { - // given - univApplyInfoFixture.괌대학_A_지원_정보(); - String regionCode = "AMERICAS"; - List keywords = List.of("괌"); - LanguageTestType testType = LanguageTestType.TOEFL_IBT; - String testScore = "70"; - String term = "2024-1"; - - // when - UnivApplyInfoPreviewResponses firstResponse = - univApplyInfoQueryService.searchUnivApplyInfo(regionCode, keywords, testType, testScore); - UnivApplyInfoPreviewResponses secondResponse = - univApplyInfoQueryService.searchUnivApplyInfo(regionCode, keywords, testType, testScore); - - // then - assertThat(firstResponse).isEqualTo(secondResponse); - then(univApplyInfoFilterRepository).should(times(1)) - .findAllByRegionCodeAndKeywordsAndLanguageTestTypeAndTestScoreAndTerm( - regionCode, keywords, testType, testScore, term); - } - - @Test - void 지역으로_대학_지원_정보를_필터링한다() { - // given - UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); - univApplyInfoFixture.코펜하겐IT대학_지원_정보(); - univApplyInfoFixture.그라츠공과대학_지원_정보(); - univApplyInfoFixture.메이지대학_지원_정보(); - - // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfo( - "AMERICAS", List.of(), null, null); - - // then - assertThat(response.univApplyInfoPreviews()) - .containsExactlyInAnyOrder(UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보)); - } - - @Test - void 키워드로_대학_지원_정보를_필터링한다() { - // given - univApplyInfoFixture.괌대학_A_지원_정보(); - UnivApplyInfo 그라츠대학_지원_정보 = univApplyInfoFixture.그라츠대학_지원_정보(); - UnivApplyInfo 메이지대학_지원_정보 = univApplyInfoFixture.메이지대학_지원_정보(); - - // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfo( - null, List.of("라", "일본"), null, null); - - // then - assertThat(response.univApplyInfoPreviews()) - .containsExactlyInAnyOrder( - UnivApplyInfoPreviewResponse.from(그라츠대학_지원_정보), - UnivApplyInfoPreviewResponse.from(메이지대학_지원_정보) - ); - } - - @Test - void 어학시험_조건으로_대학_지원_정보를_필터링한다() { - // given - UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); - languageRequirementFixture.토플_80(괌대학_A_지원_정보); - languageRequirementFixture.토익_800(괌대학_A_지원_정보); - UnivApplyInfo 괌대학_B_지원_정보 = univApplyInfoFixture.괌대학_B_지원_정보(); - languageRequirementFixture.토플_70(괌대학_B_지원_정보); - languageRequirementFixture.토익_900(괌대학_B_지원_정보); - - // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfo( - null, List.of(), LanguageTestType.TOEFL_IBT, "70"); - - // then - assertThat(response.univApplyInfoPreviews()) - .containsExactlyInAnyOrder(UnivApplyInfoPreviewResponse.from(괌대학_B_지원_정보)); + @Nested + class 대학_지원_정보_상세_조회 { + + @Test + void 대학_지원_정보를_상세_조회한다() { + // given + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + + // when + UnivApplyInfoDetailResponse response = univApplyInfoQueryService.getUnivApplyInfoDetail(괌대학_A_지원_정보.getId()); + + // then + assertThat(response.id()).isEqualTo(괌대학_A_지원_정보.getId()); + } + + @Test + void 대학_지원_정보_상세_조회시_캐시가_적용된다() { + // given + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + + // when + UnivApplyInfoDetailResponse firstResponse = univApplyInfoQueryService.getUnivApplyInfoDetail(괌대학_A_지원_정보.getId()); + UnivApplyInfoDetailResponse secondResponse = univApplyInfoQueryService.getUnivApplyInfoDetail(괌대학_A_지원_정보.getId()); + + // then + assertThat(firstResponse).isEqualTo(secondResponse); + then(univApplyInfoRepository).should(times(1)).getUnivApplyInfoById(괌대학_A_지원_정보.getId()); + } + + @Test + void 존재하지_않는_대학_지원_정보를_조회하면_예외가_발생한다() { + // given + Long invalidUnivApplyInfoId = 9999L; + + // when & then + assertThatExceptionOfType(RuntimeException.class) + .isThrownBy(() -> univApplyInfoQueryService.getUnivApplyInfoDetail(invalidUnivApplyInfoId)) + .havingRootCause() + .isInstanceOf(CustomException.class) + .withMessage(UNIV_APPLY_INFO_NOT_FOUND.getMessage()); + } } - @Test - void 모든_조건으로_대학_지원_정보를_필터링한다() { - // given - UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); - languageRequirementFixture.토플_80(괌대학_A_지원_정보); - languageRequirementFixture.토익_800(괌대학_A_지원_정보); - UnivApplyInfo 서던덴마크대학교_지원_정보 = univApplyInfoFixture.서던덴마크대학교_지원_정보(); - languageRequirementFixture.토플_70(서던덴마크대학교_지원_정보); - - // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfo( - "EUROPE", List.of(), LanguageTestType.TOEFL_IBT, "70"); - - // then - assertThat(response.univApplyInfoPreviews()) - .containsExactly(UnivApplyInfoPreviewResponse.from(서던덴마크대학교_지원_정보)); + @Nested + class 대학_지원_정보_필터링_검색 { + + @Test + void 어학_시험_종류로_필터링한다() { + // given + UnivApplyInfoFilterSearchRequest request = new UnivApplyInfoFilterSearchRequest(TOEIC, null, null); + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + languageRequirementFixture.토익_800(괌대학_A_지원_정보); + UnivApplyInfo 괌대학_B_지원_정보 = univApplyInfoFixture.괌대학_B_지원_정보(); + languageRequirementFixture.토플_70(괌대학_B_지원_정보); + + // when + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request); + + // then + assertThat(response.univApplyInfoPreviews()) + .containsExactly(UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보)); + } + + @Test + void 어학_시험_점수가_기준치_이상인_곳을_필터링한다() { + // given + UnivApplyInfoFilterSearchRequest request = new UnivApplyInfoFilterSearchRequest(TOEIC, "800", null); + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + languageRequirementFixture.토익_800(괌대학_A_지원_정보); + UnivApplyInfo 괌대학_B_지원_정보 = univApplyInfoFixture.괌대학_B_지원_정보(); + languageRequirementFixture.토익_900(괌대학_B_지원_정보); + + // when + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request); + + // then + assertThat(response.univApplyInfoPreviews()) + .containsExactly(UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보)); + } + + @Test + void 국가_코드로_필터링한다() { + // given + UnivApplyInfoFilterSearchRequest request1 = new UnivApplyInfoFilterSearchRequest(TOEIC, null, List.of("US")); + UnivApplyInfoFilterSearchRequest request2 = new UnivApplyInfoFilterSearchRequest(TOEIC, null, List.of("US", "CA")); + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + languageRequirementFixture.토익_800(괌대학_A_지원_정보); + UnivApplyInfo 메모리얼대학_세인트존스_A_지원_정보 = univApplyInfoFixture.메모리얼대학_세인트존스_A_지원_정보(); + languageRequirementFixture.토익_800(메모리얼대학_세인트존스_A_지원_정보); + + // when + UnivApplyInfoPreviewResponses response1 = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request1); + UnivApplyInfoPreviewResponses response2 = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request2); + + // then + assertAll( + () -> assertThat(response1.univApplyInfoPreviews()) + .containsExactly(UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보)), + () -> assertThat(response2.univApplyInfoPreviews()) + .containsExactlyInAnyOrder( + UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보), + UnivApplyInfoPreviewResponse.from(메모리얼대학_세인트존스_A_지원_정보) + ) + ); + } } } From f4d7fd892a22b081a8ff08d451c082dee9742487 Mon Sep 17 00:00:00 2001 From: seonghyeok cho <65901319+whqtker@users.noreply.github.com> Date: Mon, 25 Aug 2025 20:02:19 +0900 Subject: [PATCH 85/90] =?UTF-8?q?refactor:=20verify=5Fstatus=EC=9D=98=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=EC=9D=84=20=ED=86=B5=EC=9D=BC=ED=95=9C?= =?UTF-8?q?=EB=8B=A4.=20(#472)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 엔티티 코드 통일 * chore: 마이그레이션 파일 작성 - Column 어노테이션에 name 옵션을 사용하지 않으면 인덱스 생성 시 인식하지 못해 에러가 발생한다. --- .../solidconnection/application/domain/Application.java | 4 ++-- .../example/solidconnection/score/domain/GpaScore.java | 4 ++-- .../solidconnection/score/domain/LanguageTestScore.java | 4 ++-- .../V30__modify_verify_status_from_varchar_to_enum.sql | 8 ++++++++ 4 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 src/main/resources/db/migration/V30__modify_verify_status_from_varchar_to_enum.sql diff --git a/src/main/java/com/example/solidconnection/application/domain/Application.java b/src/main/java/com/example/solidconnection/application/domain/Application.java index db830ed0a..72eb0068b 100644 --- a/src/main/java/com/example/solidconnection/application/domain/Application.java +++ b/src/main/java/com/example/solidconnection/application/domain/Application.java @@ -48,9 +48,9 @@ public class Application { private LanguageTest languageTest; @Setter - @Column(columnDefinition = "varchar(50) not null default 'PENDING'", name = "verify_status") + @Column(name = "verify_status", nullable = false) @Enumerated(EnumType.STRING) - private VerifyStatus verifyStatus; + private VerifyStatus verifyStatus = VerifyStatus.PENDING; @Column(length = 100, name = "nickname_for_apply") private String nicknameForApply; diff --git a/src/main/java/com/example/solidconnection/score/domain/GpaScore.java b/src/main/java/com/example/solidconnection/score/domain/GpaScore.java index 284a815af..7e8536d83 100644 --- a/src/main/java/com/example/solidconnection/score/domain/GpaScore.java +++ b/src/main/java/com/example/solidconnection/score/domain/GpaScore.java @@ -31,9 +31,9 @@ public class GpaScore extends BaseEntity { private Gpa gpa; @Setter - @Column(columnDefinition = "varchar(50) not null default 'PENDING'") + @Column(nullable = false) @Enumerated(EnumType.STRING) - private VerifyStatus verifyStatus; + private VerifyStatus verifyStatus = VerifyStatus.PENDING; private String rejectedReason; diff --git a/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java b/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java index 04d95487b..415519b7d 100644 --- a/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java +++ b/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java @@ -31,9 +31,9 @@ public class LanguageTestScore extends BaseEntity { private LanguageTest languageTest; @Setter - @Column(columnDefinition = "varchar(50) not null default 'PENDING'") + @Column(nullable = false) @Enumerated(EnumType.STRING) - private VerifyStatus verifyStatus; + private VerifyStatus verifyStatus = VerifyStatus.PENDING; private String rejectedReason; diff --git a/src/main/resources/db/migration/V30__modify_verify_status_from_varchar_to_enum.sql b/src/main/resources/db/migration/V30__modify_verify_status_from_varchar_to_enum.sql new file mode 100644 index 000000000..c70a2b512 --- /dev/null +++ b/src/main/resources/db/migration/V30__modify_verify_status_from_varchar_to_enum.sql @@ -0,0 +1,8 @@ +ALTER TABLE application +MODIFY COLUMN verify_status ENUM('PENDING', 'REJECTED', 'APPROVED') NOT NULL DEFAULT 'PENDING'; + +ALTER TABLE gpa_score +MODIFY COLUMN verify_status ENUM('PENDING', 'REJECTED', 'APPROVED') NOT NULL DEFAULT 'PENDING'; + +ALTER TABLE language_test_score +MODIFY COLUMN verify_status ENUM('PENDING', 'REJECTED', 'APPROVED') NOT NULL DEFAULT 'PENDING'; From 3f8c493547186ebff3e2e79677ddb98e13c87254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Mon, 25 Aug 2025 20:46:28 +0900 Subject: [PATCH 86/90] =?UTF-8?q?refactor:=20=EC=86=8C=EC=8B=9D=EC=A7=80?= =?UTF-8?q?=20=EB=8B=A8=EC=9D=BC=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=ED=99=95=EC=9D=B8=20API=20=EC=A0=9C=EA=B1=B0=20(#4?= =?UTF-8?q?55)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 소식지 목록 조회 시 isLike 추가 - 로그인하지 않은 사용자에게는 isLike를 응답하지 않도록 * refactor: 소식지 목록 조회 시 좋아요 여부 확인 로직 추가 - 로그인한 사용자와 로그인하지 않은 사용자를 구분 - @AuthorizedUser(required = false) 활용 - Set을 활용하여 n + 1 문제 해결 * test: 소식지 관련 fixture 추가 * test: 소식지 조회 서비스 테스트 추가 * refactor: 사용하지 않는 api 제거 * refactor: 쿼리 2 -> 1로 개선 - jpql 프로젝션 활용 * style: isLike -> isLiked로 변경 * test: 검증 구체화 - 사이즈가 아닌 실제 id가 존재하는지 확인하도록 * refactor: from -> of로 변경 --- .../news/controller/NewsController.java | 15 +---- .../news/dto/LikedNewsResponse.java | 10 --- .../news/dto/NewsResponse.java | 10 ++- .../news/repository/NewsRepository.java | 3 +- .../custom/NewsCustomRepository.java | 9 +++ .../custom/NewsCustomRepositoryImpl.java | 38 +++++++++++ .../news/service/NewsLikeService.java | 10 --- .../news/service/NewsQueryService.java | 19 ++++-- .../news/fixture/LikedNewsFixture.java | 19 ++++++ .../news/fixture/LikedNewsFixtureBuilder.java | 36 ++++++++++ .../news/service/NewsLikeServiceTest.java | 26 -------- .../news/service/NewsQueryServiceTest.java | 66 ++++++++++++++++--- 12 files changed, 186 insertions(+), 75 deletions(-) delete mode 100644 src/main/java/com/example/solidconnection/news/dto/LikedNewsResponse.java create mode 100644 src/main/java/com/example/solidconnection/news/repository/custom/NewsCustomRepository.java create mode 100644 src/main/java/com/example/solidconnection/news/repository/custom/NewsCustomRepositoryImpl.java create mode 100644 src/test/java/com/example/solidconnection/news/fixture/LikedNewsFixture.java create mode 100644 src/test/java/com/example/solidconnection/news/fixture/LikedNewsFixtureBuilder.java diff --git a/src/main/java/com/example/solidconnection/news/controller/NewsController.java b/src/main/java/com/example/solidconnection/news/controller/NewsController.java index 6ba7b2eda..263124a18 100644 --- a/src/main/java/com/example/solidconnection/news/controller/NewsController.java +++ b/src/main/java/com/example/solidconnection/news/controller/NewsController.java @@ -1,7 +1,6 @@ package com.example.solidconnection.news.controller; import com.example.solidconnection.common.resolver.AuthorizedUser; -import com.example.solidconnection.news.dto.LikedNewsResponse; import com.example.solidconnection.news.dto.NewsCommandResponse; import com.example.solidconnection.news.dto.NewsCreateRequest; import com.example.solidconnection.news.dto.NewsListResponse; @@ -37,9 +36,10 @@ public class NewsController { // todo: 추후 Slice 적용 @GetMapping public ResponseEntity findNewsBySiteUserId( - @RequestParam(value = "site-user-id") Long siteUserId + @AuthorizedUser(required = false) Long siteUserId, + @RequestParam(value = "author-id") Long authorId ) { - NewsListResponse newsListResponse = newsQueryService.findNewsBySiteUserId(siteUserId); + NewsListResponse newsListResponse = newsQueryService.findNewsByAuthorId(siteUserId, authorId); return ResponseEntity.ok(newsListResponse); } @@ -80,15 +80,6 @@ public ResponseEntity deleteNewsById( return ResponseEntity.ok(newsCommandResponse); } - @GetMapping("/{news-id}/like") - public ResponseEntity isNewsLiked( - @AuthorizedUser long siteUserId, - @PathVariable("news-id") Long newsId - ) { - LikedNewsResponse likedNewsResponse = newsLikeService.isNewsLiked(siteUserId, newsId); - return ResponseEntity.ok(likedNewsResponse); - } - @PostMapping("/{news-id}/like") public ResponseEntity addNewsLike( @AuthorizedUser long siteUserId, diff --git a/src/main/java/com/example/solidconnection/news/dto/LikedNewsResponse.java b/src/main/java/com/example/solidconnection/news/dto/LikedNewsResponse.java deleted file mode 100644 index b854b9bf0..000000000 --- a/src/main/java/com/example/solidconnection/news/dto/LikedNewsResponse.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.example.solidconnection.news.dto; - -public record LikedNewsResponse( - boolean isLike -) { - - public static LikedNewsResponse of(boolean isLike) { - return new LikedNewsResponse(isLike); - } -} diff --git a/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java b/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java index 77d8cd3a3..d344080ba 100644 --- a/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java +++ b/src/main/java/com/example/solidconnection/news/dto/NewsResponse.java @@ -1,6 +1,9 @@ package com.example.solidconnection.news.dto; +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; + import com.example.solidconnection.news.domain.News; +import com.fasterxml.jackson.annotation.JsonInclude; import java.time.ZonedDateTime; public record NewsResponse( @@ -9,16 +12,21 @@ public record NewsResponse( String description, String thumbnailUrl, String url, + + @JsonInclude(NON_NULL) + Boolean isLiked, + ZonedDateTime updatedAt ) { - public static NewsResponse from(News news) { + public static NewsResponse of(News news, Boolean isLiked) { return new NewsResponse( news.getId(), news.getTitle(), news.getDescription(), news.getThumbnailUrl(), news.getUrl(), + isLiked, news.getUpdatedAt() ); } diff --git a/src/main/java/com/example/solidconnection/news/repository/NewsRepository.java b/src/main/java/com/example/solidconnection/news/repository/NewsRepository.java index 4ec1798df..0d3ccf3e9 100644 --- a/src/main/java/com/example/solidconnection/news/repository/NewsRepository.java +++ b/src/main/java/com/example/solidconnection/news/repository/NewsRepository.java @@ -1,10 +1,11 @@ package com.example.solidconnection.news.repository; import com.example.solidconnection.news.domain.News; +import com.example.solidconnection.news.repository.custom.NewsCustomRepository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; -public interface NewsRepository extends JpaRepository { +public interface NewsRepository extends JpaRepository, NewsCustomRepository { List findAllBySiteUserIdOrderByUpdatedAtDesc(long siteUserId); } diff --git a/src/main/java/com/example/solidconnection/news/repository/custom/NewsCustomRepository.java b/src/main/java/com/example/solidconnection/news/repository/custom/NewsCustomRepository.java new file mode 100644 index 000000000..ebba659e0 --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/repository/custom/NewsCustomRepository.java @@ -0,0 +1,9 @@ +package com.example.solidconnection.news.repository.custom; + +import com.example.solidconnection.news.dto.NewsResponse; +import java.util.List; + +public interface NewsCustomRepository { + + List findNewsByAuthorIdWithLikeStatus(long authorId, Long siteUserId); +} diff --git a/src/main/java/com/example/solidconnection/news/repository/custom/NewsCustomRepositoryImpl.java b/src/main/java/com/example/solidconnection/news/repository/custom/NewsCustomRepositoryImpl.java new file mode 100644 index 000000000..949d188bc --- /dev/null +++ b/src/main/java/com/example/solidconnection/news/repository/custom/NewsCustomRepositoryImpl.java @@ -0,0 +1,38 @@ +package com.example.solidconnection.news.repository.custom; + +import com.example.solidconnection.news.dto.NewsResponse; +import jakarta.persistence.EntityManager; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class NewsCustomRepositoryImpl implements NewsCustomRepository { + + private final EntityManager entityManager; + + @Override + public List findNewsByAuthorIdWithLikeStatus(long authorId, Long siteUserId) { + String jpql = """ + SELECT new com.example.solidconnection.news.dto.NewsResponse( + n.id, + n.title, + n.description, + n.thumbnailUrl, + n.url, + CASE WHEN ln.id IS NOT NULL THEN true ELSE false END, + n.updatedAt + ) + FROM News n + LEFT JOIN LikedNews ln ON n.id = ln.newsId AND ln.siteUserId = :siteUserId + WHERE n.siteUserId = :authorId + ORDER BY n.updatedAt DESC + """; + + return entityManager.createQuery(jpql, NewsResponse.class) + .setParameter("authorId", authorId) + .setParameter("siteUserId", siteUserId) + .getResultList(); + } +} diff --git a/src/main/java/com/example/solidconnection/news/service/NewsLikeService.java b/src/main/java/com/example/solidconnection/news/service/NewsLikeService.java index e0e2e5114..4b9435ab6 100644 --- a/src/main/java/com/example/solidconnection/news/service/NewsLikeService.java +++ b/src/main/java/com/example/solidconnection/news/service/NewsLikeService.java @@ -6,7 +6,6 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.news.domain.LikedNews; -import com.example.solidconnection.news.dto.LikedNewsResponse; import com.example.solidconnection.news.repository.LikedNewsRepository; import com.example.solidconnection.news.repository.NewsRepository; import lombok.RequiredArgsConstructor; @@ -20,15 +19,6 @@ public class NewsLikeService { private final NewsRepository newsRepository; private final LikedNewsRepository likedNewsRepository; - @Transactional(readOnly = true) - public LikedNewsResponse isNewsLiked(long siteUserId, long newsId) { - if (!newsRepository.existsById(newsId)) { - throw new CustomException(NEWS_NOT_FOUND); - } - boolean isLike = likedNewsRepository.existsByNewsIdAndSiteUserId(newsId, siteUserId); - return LikedNewsResponse.of(isLike); - } - @Transactional public void addNewsLike(long siteUserId, long newsId) { if (!newsRepository.existsById(newsId)) { diff --git a/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java b/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java index cda55c1dd..e0050643b 100644 --- a/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java +++ b/src/main/java/com/example/solidconnection/news/service/NewsQueryService.java @@ -1,6 +1,5 @@ package com.example.solidconnection.news.service; -import com.example.solidconnection.news.domain.News; import com.example.solidconnection.news.dto.NewsListResponse; import com.example.solidconnection.news.dto.NewsResponse; import com.example.solidconnection.news.repository.NewsRepository; @@ -16,11 +15,19 @@ public class NewsQueryService { private final NewsRepository newsRepository; @Transactional(readOnly = true) - public NewsListResponse findNewsBySiteUserId(long siteUserId) { - List newsList = newsRepository.findAllBySiteUserIdOrderByUpdatedAtDesc(siteUserId); - List newsResponseList = newsList.stream() - .map(NewsResponse::from) - .toList(); + public NewsListResponse findNewsByAuthorId(Long siteUserId, long authorId) { + // 로그인하지 않은 경우 + if (siteUserId == null) { + List newsResponseList = newsRepository.findAllBySiteUserIdOrderByUpdatedAtDesc(authorId) + .stream() + .map(news -> NewsResponse.of(news, null)) + .toList(); + return NewsListResponse.from(newsResponseList); + } + + // 로그인한 경우 + List newsResponseList = newsRepository.findNewsByAuthorIdWithLikeStatus(authorId, siteUserId); + return NewsListResponse.from(newsResponseList); } } diff --git a/src/test/java/com/example/solidconnection/news/fixture/LikedNewsFixture.java b/src/test/java/com/example/solidconnection/news/fixture/LikedNewsFixture.java new file mode 100644 index 000000000..acacb25b0 --- /dev/null +++ b/src/test/java/com/example/solidconnection/news/fixture/LikedNewsFixture.java @@ -0,0 +1,19 @@ +package com.example.solidconnection.news.fixture; + +import com.example.solidconnection.news.domain.LikedNews; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class LikedNewsFixture { + + private final LikedNewsFixtureBuilder likedNewsFixtureBuilder; + + public LikedNews 소식지_좋아요(long newsId, long siteUserId) { + return likedNewsFixtureBuilder.likedNews() + .newsId(newsId) + .siteUserId(siteUserId) + .create(); + } +} diff --git a/src/test/java/com/example/solidconnection/news/fixture/LikedNewsFixtureBuilder.java b/src/test/java/com/example/solidconnection/news/fixture/LikedNewsFixtureBuilder.java new file mode 100644 index 000000000..8554dc6b9 --- /dev/null +++ b/src/test/java/com/example/solidconnection/news/fixture/LikedNewsFixtureBuilder.java @@ -0,0 +1,36 @@ +package com.example.solidconnection.news.fixture; + +import com.example.solidconnection.news.domain.LikedNews; +import com.example.solidconnection.news.repository.LikedNewsRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.test.context.TestComponent; + +@TestComponent +@RequiredArgsConstructor +public class LikedNewsFixtureBuilder { + + private final LikedNewsRepository likedNewsRepository; + + private long newsId; + + private long siteUserId; + + public LikedNewsFixtureBuilder likedNews() { + return new LikedNewsFixtureBuilder(likedNewsRepository); + } + + public LikedNewsFixtureBuilder newsId(long newsId) { + this.newsId = newsId; + return this; + } + + public LikedNewsFixtureBuilder siteUserId(long siteUserId) { + this.siteUserId = siteUserId; + return this; + } + + public LikedNews create() { + LikedNews likedNews = new LikedNews(newsId, siteUserId); + return likedNewsRepository.save(likedNews); + } +} diff --git a/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java b/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java index e620fa206..2600c2891 100644 --- a/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java +++ b/src/test/java/com/example/solidconnection/news/service/NewsLikeServiceTest.java @@ -7,7 +7,6 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.news.domain.News; -import com.example.solidconnection.news.dto.LikedNewsResponse; import com.example.solidconnection.news.fixture.NewsFixture; import com.example.solidconnection.news.repository.LikedNewsRepository; import com.example.solidconnection.siteuser.domain.SiteUser; @@ -44,31 +43,6 @@ void setUp() { news = newsFixture.소식지(siteUserFixture.멘토(1, "mentor").getId()); } - @Nested - class 소식지_좋아요_상태를_조회한다 { - - @Test - void 좋아요한_소식지의_좋아요_상태를_조회한다() { - // given - newsLikeService.addNewsLike(user.getId(), news.getId()); - - // when - LikedNewsResponse response = newsLikeService.isNewsLiked(user.getId(), news.getId()); - - // then - assertThat(response.isLike()).isTrue(); - } - - @Test - void 좋아요하지_않은_소식지의_좋아요_상태를_조회한다() { - // when - LikedNewsResponse response = newsLikeService.isNewsLiked(user.getId(), news.getId()); - - // then - assertThat(response.isLike()).isFalse(); - } - } - @Nested class 소식지_좋아요를_등록한다 { diff --git a/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java b/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java index 12bfe2c59..6c69db0cf 100644 --- a/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/news/service/NewsQueryServiceTest.java @@ -6,12 +6,15 @@ import com.example.solidconnection.news.domain.News; import com.example.solidconnection.news.dto.NewsListResponse; import com.example.solidconnection.news.dto.NewsResponse; +import com.example.solidconnection.news.fixture.LikedNewsFixture; import com.example.solidconnection.news.fixture.NewsFixture; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -29,25 +32,70 @@ class NewsQueryServiceTest { @Autowired private NewsFixture newsFixture; + @Autowired + private LikedNewsFixture likedNewsFixture; + @Test - void 특정_사용자의_소식지_목록을_성공적으로_조회한다() { + void 로그인하지_않은_사용자가_특정_사용자의_소식지_목록을_성공적으로_조회한다() { // given - SiteUser user1 = siteUserFixture.멘토(1, "mentor1"); - SiteUser user2 = siteUserFixture.멘토(2, "mentor2"); - News news1 = newsFixture.소식지(user1.getId()); - News news2 = newsFixture.소식지(user1.getId()); - newsFixture.소식지(user2.getId()); + SiteUser author = siteUserFixture.멘토(1, "author"); + SiteUser otherUser = siteUserFixture.멘토(2, "other"); + + News news1 = newsFixture.소식지(author.getId()); + News news2 = newsFixture.소식지(author.getId()); + newsFixture.소식지(otherUser.getId()); List newsList = List.of(news1, news2); // when - NewsListResponse response = newsQueryService.findNewsBySiteUserId(user1.getId()); + NewsListResponse response = newsQueryService.findNewsByAuthorId(null, author.getId()); // then assertAll( - () -> assertThat(response.newsResponseList()).hasSize(newsList.size()), + () -> assertThat(response.newsResponseList()) + .extracting(NewsResponse::id) + .containsExactlyInAnyOrder(news1.getId(), news2.getId()), + () -> assertThat(response.newsResponseList()) + .extracting(NewsResponse::updatedAt) + .isSortedAccordingTo(Comparator.reverseOrder()), + () -> assertThat(response.newsResponseList()) + .extracting(NewsResponse::isLiked) + .containsOnly((Boolean) null) + ); + } + + @Test + void 로그인한_사용자가_특정_사용자의_소식지_목록을_성공적으로_조회한다() { + // given + SiteUser author = siteUserFixture.멘토(1, "author"); + SiteUser loginUser = siteUserFixture.멘토(2, "loginUser"); + + News news1 = newsFixture.소식지(author.getId()); + News news2 = newsFixture.소식지(author.getId()); + News news3 = newsFixture.소식지(author.getId()); + + likedNewsFixture.소식지_좋아요(news1.getId(), loginUser.getId()); + likedNewsFixture.소식지_좋아요(news3.getId(), loginUser.getId()); + + List newsList = List.of(news1, news2, news3); + + // when + NewsListResponse response = newsQueryService.findNewsByAuthorId(loginUser.getId(), author.getId()); + + // then + assertAll( + () -> assertThat(response.newsResponseList()) + .extracting(NewsResponse::id) + .containsExactlyInAnyOrder(news1.getId(), news2.getId(), news3.getId()), () -> assertThat(response.newsResponseList()) .extracting(NewsResponse::updatedAt) - .isSortedAccordingTo(Comparator.reverseOrder()) + .isSortedAccordingTo(Comparator.reverseOrder()), + () -> { + Map likeStatusMap = response.newsResponseList().stream() + .collect(Collectors.toMap(NewsResponse::id, NewsResponse::isLiked)); + assertThat(likeStatusMap.get(news1.getId())).isTrue(); + assertThat(likeStatusMap.get(news2.getId())).isFalse(); + assertThat(likeStatusMap.get(news3.getId())).isTrue(); + } ); } } From dbcfea321ff09ad6d5d37b290bab025b5fe7d044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=A9=EA=B7=9C=ED=98=81?= <126947828+Gyuhyeok99@users.noreply.github.com> Date: Tue, 26 Aug 2025 08:56:06 +0900 Subject: [PATCH 87/90] =?UTF-8?q?refactor:=20=EC=B1=84=ED=8C=85=EB=B0=A9?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=20=EB=8F=99=EA=B8=B0=EC=8B=9D=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20(#474)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 채팅방 생성 동기식으로 변경 * test: 비동기 테스트를 위한 코드 제거 * refactor: 사용하지 않는 코드 제거 --- .../chat/repository/ChatRoomRepository.java | 2 +- .../chat/service/ChatService.java | 11 +++++-- .../mentor/dto/MentoringApprovedEvent.java | 12 -------- .../mentor/dto/MentoringConfirmResponse.java | 12 ++++++-- .../service/MentoringCommandService.java | 10 +++---- .../mentor/service/MentoringEventHandler.java | 24 --------------- .../service/MentoringCommandServiceTest.java | 29 ++++++------------- 7 files changed, 32 insertions(+), 68 deletions(-) delete mode 100644 src/main/java/com/example/solidconnection/mentor/dto/MentoringApprovedEvent.java delete mode 100644 src/main/java/com/example/solidconnection/mentor/service/MentoringEventHandler.java diff --git a/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java b/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java index cdb0dbfa6..8c0b81a4b 100644 --- a/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java +++ b/src/main/java/com/example/solidconnection/chat/repository/ChatRoomRepository.java @@ -34,7 +34,7 @@ SELECT COUNT(cm) FROM ChatMessage cm """) long countUnreadMessages(@Param("chatRoomId") long chatRoomId, @Param("userId") long userId); - boolean existsByMentoringId(long mentoringId); + ChatRoom findByMentoringId(long mentoringId); List findAllByMentoringIdIn(List mentoringIds); } diff --git a/src/main/java/com/example/solidconnection/chat/service/ChatService.java b/src/main/java/com/example/solidconnection/chat/service/ChatService.java index 874d71bd5..ae9be659b 100644 --- a/src/main/java/com/example/solidconnection/chat/service/ChatService.java +++ b/src/main/java/com/example/solidconnection/chat/service/ChatService.java @@ -174,15 +174,20 @@ public void sendChatMessage(ChatMessageSendRequest chatMessageSendRequest, long } @Transactional - public void createMentoringChatRoom(Long mentoringId, Long mentorId, Long menteeId) { - if (chatRoomRepository.existsByMentoringId(mentoringId)) { - return; + public Long createMentoringChatRoom(Long mentoringId, Long mentorId, Long menteeId) { + ChatRoom existingChatRoom = chatRoomRepository.findByMentoringId(mentoringId); + if (existingChatRoom != null) { + return existingChatRoom.getId(); } + // 새 채팅방 생성 ChatRoom chatRoom = new ChatRoom(mentoringId, false); chatRoom = chatRoomRepository.save(chatRoom); + ChatParticipant mentorParticipant = new ChatParticipant(mentorId, chatRoom); ChatParticipant menteeParticipant = new ChatParticipant(menteeId, chatRoom); chatParticipantRepository.saveAll(List.of(mentorParticipant, menteeParticipant)); + + return chatRoom.getId(); } } diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringApprovedEvent.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringApprovedEvent.java deleted file mode 100644 index 4909a5c85..000000000 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentoringApprovedEvent.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.example.solidconnection.mentor.dto; - -public record MentoringApprovedEvent( - long mentoringId, - long mentorId, - long menteeId -) { - - public static MentoringApprovedEvent of(long mentoringId, long mentorId, long menteeId) { - return new MentoringApprovedEvent(mentoringId, mentorId, menteeId); - } -} diff --git a/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmResponse.java b/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmResponse.java index b4ed1e40f..1cf8f03ff 100644 --- a/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmResponse.java +++ b/src/main/java/com/example/solidconnection/mentor/dto/MentoringConfirmResponse.java @@ -1,12 +1,18 @@ package com.example.solidconnection.mentor.dto; +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; + import com.example.solidconnection.mentor.domain.Mentoring; +import com.fasterxml.jackson.annotation.JsonInclude; public record MentoringConfirmResponse( - long mentoringId + long mentoringId, + + @JsonInclude(NON_NULL) + Long chatRoomId ) { - public static MentoringConfirmResponse from(Mentoring mentoring) { - return new MentoringConfirmResponse(mentoring.getId()); + public static MentoringConfirmResponse from(Mentoring mentoring, Long chatRoomId) { + return new MentoringConfirmResponse(mentoring.getId(), chatRoomId); } } diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java b/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java index 15a1e2507..254323127 100644 --- a/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java +++ b/src/main/java/com/example/solidconnection/mentor/service/MentoringCommandService.java @@ -5,19 +5,18 @@ import static com.example.solidconnection.common.exception.ErrorCode.MENTOR_NOT_FOUND; import static com.example.solidconnection.common.exception.ErrorCode.UNAUTHORIZED_MENTORING; +import com.example.solidconnection.chat.service.ChatService; import com.example.solidconnection.common.VerifyStatus; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.mentor.domain.Mentor; import com.example.solidconnection.mentor.domain.Mentoring; import com.example.solidconnection.mentor.dto.MentoringApplyRequest; import com.example.solidconnection.mentor.dto.MentoringApplyResponse; -import com.example.solidconnection.mentor.dto.MentoringApprovedEvent; import com.example.solidconnection.mentor.dto.MentoringConfirmRequest; import com.example.solidconnection.mentor.dto.MentoringConfirmResponse; import com.example.solidconnection.mentor.repository.MentorRepository; import com.example.solidconnection.mentor.repository.MentoringRepository; import lombok.RequiredArgsConstructor; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -27,7 +26,7 @@ public class MentoringCommandService { private final MentoringRepository mentoringRepository; private final MentorRepository mentorRepository; - private final ApplicationEventPublisher eventPublisher; + private final ChatService chatService; @Transactional public MentoringApplyResponse applyMentoring(long siteUserId, MentoringApplyRequest mentoringApplyRequest) { @@ -49,12 +48,13 @@ public MentoringConfirmResponse confirmMentoring(long siteUserId, long mentoring mentoring.confirm(mentoringConfirmRequest.status()); + Long chatRoomId = null; if (mentoringConfirmRequest.status() == VerifyStatus.APPROVED) { mentor.increaseMenteeCount(); - eventPublisher.publishEvent(MentoringApprovedEvent.of(mentoringId, mentor.getSiteUserId(), mentoring.getMenteeId())); + chatRoomId = chatService.createMentoringChatRoom(mentoringId, mentor.getSiteUserId(), mentoring.getMenteeId()); } - return MentoringConfirmResponse.from(mentoring); + return MentoringConfirmResponse.from(mentoring, chatRoomId); } private void validateMentoringNotConfirmed(Mentoring mentoring) { diff --git a/src/main/java/com/example/solidconnection/mentor/service/MentoringEventHandler.java b/src/main/java/com/example/solidconnection/mentor/service/MentoringEventHandler.java deleted file mode 100644 index 920ff007f..000000000 --- a/src/main/java/com/example/solidconnection/mentor/service/MentoringEventHandler.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.solidconnection.mentor.service; - -import com.example.solidconnection.chat.service.ChatService; -import com.example.solidconnection.mentor.dto.MentoringApprovedEvent; -import lombok.RequiredArgsConstructor; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.event.TransactionalEventListener; - -@Component -@RequiredArgsConstructor -public class MentoringEventHandler { - - private final ChatService chatService; - - @Async - @Transactional(propagation = Propagation.REQUIRES_NEW) - @TransactionalEventListener - public void handleMentoringApproved(MentoringApprovedEvent event) { - chatService.createMentoringChatRoom(event.mentoringId(), event.mentorId(), event.menteeId()); - } -} diff --git a/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java b/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java index 058388d51..8c6a78468 100644 --- a/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java +++ b/src/test/java/com/example/solidconnection/mentor/service/MentoringCommandServiceTest.java @@ -134,23 +134,17 @@ class 멘토링_승인_거절_테스트 { assertThat(beforeChatRoom).isEmpty(); // when - mentoringCommandService.confirmMentoring(mentorUser1.getId(), mentoring.getId(), request); + MentoringConfirmResponse response = mentoringCommandService.confirmMentoring(mentorUser1.getId(), mentoring.getId(), request); // then - ChatRoom afterChatRoom = await() - .atMost(Duration.ofSeconds(5)) - .pollInterval(Duration.ofMillis(100)) - .until(() -> chatRoomRepositoryForTest - .findOneOnOneChatRoomByParticipants(mentorUser1.getId(), menteeUser.getId()), - Optional::isPresent) - .orElseThrow(); - + ChatRoom afterChatRoom = chatRoomRepositoryForTest.findOneOnOneChatRoomByParticipants(mentorUser1.getId(), menteeUser.getId()).orElseThrow(); List participantIds = afterChatRoom.getChatParticipants().stream() .map(ChatParticipant::getSiteUserId) .toList(); assertAll( () -> assertThat(afterChatRoom.isGroup()).isFalse(), - () -> assertThat(participantIds).containsExactly(mentorUser1.getId(), menteeUser.getId()) + () -> assertThat(participantIds).containsExactly(mentorUser1.getId(), menteeUser.getId()), + () -> assertThat(response.chatRoomId()).isEqualTo(afterChatRoom.getId()) ); } @@ -186,19 +180,14 @@ class 멘토링_승인_거절_테스트 { assertThat(beforeChatRoom).isEmpty(); // when - mentoringCommandService.confirmMentoring(mentorUser1.getId(), mentoring.getId(), request); + MentoringConfirmResponse response = mentoringCommandService.confirmMentoring(mentorUser1.getId(), mentoring.getId(), request); // then - await() - .pollInterval(Duration.ofMillis(100)) - .during(Duration.ofSeconds(1)) - .until(() -> chatRoomRepositoryForTest - .findOneOnOneChatRoomByParticipants(mentorUser1.getId(), menteeUser.getId()) - .isEmpty()); - Optional afterChatRoom = chatRoomRepositoryForTest.findOneOnOneChatRoomByParticipants(mentorUser1.getId(), menteeUser.getId()); - assertThat(afterChatRoom).isEmpty(); - + assertAll( + () -> assertThat(response.chatRoomId()).isNull(), + () -> assertThat(afterChatRoom).isEmpty() + ); } @Test From c60163fa227b2874784a4fa82261b0637c587a64 Mon Sep 17 00:00:00 2001 From: seonghyeok cho <65901319+whqtker@users.noreply.github.com> Date: Tue, 26 Aug 2025 09:54:42 +0900 Subject: [PATCH 88/90] =?UTF-8?q?fix:=20SiteUser=20=EA=B0=9D=EC=B2=B4=20?= =?UTF-8?q?=EB=8C=80=EC=8B=A0=20ID=EB=A5=BC=20=EB=B0=9B=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20(#485)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/report/controller/ReportController.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/example/solidconnection/report/controller/ReportController.java b/src/main/java/com/example/solidconnection/report/controller/ReportController.java index cab986f92..2bf436fc0 100644 --- a/src/main/java/com/example/solidconnection/report/controller/ReportController.java +++ b/src/main/java/com/example/solidconnection/report/controller/ReportController.java @@ -3,7 +3,6 @@ import com.example.solidconnection.common.resolver.AuthorizedUser; import com.example.solidconnection.report.dto.ReportRequest; import com.example.solidconnection.report.service.ReportService; -import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -21,10 +20,10 @@ public class ReportController { @PostMapping public ResponseEntity createReport( - @AuthorizedUser SiteUser siteUser, + @AuthorizedUser long siteUserId, @Valid @RequestBody ReportRequest reportRequest ) { - reportService.createReport(siteUser.getId(), reportRequest); + reportService.createReport(siteUserId, reportRequest); return ResponseEntity.ok().build(); } } From a16ce9e9d215135ac688802d328d22feea70724e Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Tue, 26 Aug 2025 14:27:13 +0900 Subject: [PATCH 89/90] =?UTF-8?q?feat:=20=EB=8C=80=ED=95=99=EC=A7=80?= =?UTF-8?q?=EC=9B=90=EC=A0=95=EB=B3=B4=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(#486)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 텍스트 기반 검색 레포지토리 함수 구현 * feat: 텍스트 기반 검색 서비스 함수 구현 - 캐싱 적용 * test: 테스트 코드 작성 * refactor: N+1 최소화 * chore: 오해 방지를 위해 주석 수정 * refactor: term을 검색 캐싱에 포함하도록 * fix: api 규격에 맞게 path variable 이름 변경 --- .../controller/UnivApplyInfoController.java | 10 +- .../custom/UnivApplyInfoFilterRepository.java | 2 + .../UnivApplyInfoFilterRepositoryImpl.java | 47 ++++++ .../service/UnivApplyInfoQueryService.java | 18 +-- .../fixture/UnivApplyInfoFixture.java | 8 + .../university/fixture/UniversityFixture.java | 9 ++ .../UnivApplyInfoQueryServiceTest.java | 141 +++++++++++++++++- 7 files changed, 219 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java b/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java index 97adc128a..fab050079 100644 --- a/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java +++ b/src/main/java/com/example/solidconnection/university/controller/UnivApplyInfoController.java @@ -13,6 +13,7 @@ import jakarta.validation.Valid; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -32,6 +33,9 @@ public class UnivApplyInfoController { private final LikedUnivApplyInfoService likedUnivApplyInfoService; private final UnivApplyInfoRecommendService univApplyInfoRecommendService; + @Value("${university.term}") + public String term; + @GetMapping("/recommend") public ResponseEntity getUnivApplyInfoRecommends( @AuthorizedUser(required = false) Long siteUserId @@ -91,15 +95,15 @@ public ResponseEntity getUnivApplyInfoDetails( public ResponseEntity searchUnivApplyInfoByFilter( @Valid @ModelAttribute UnivApplyInfoFilterSearchRequest request ) { - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request); + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request, term); return ResponseEntity.ok(response); } @GetMapping("/search/text") public ResponseEntity searchUnivApplyInfoByText( - @RequestParam(required = false) String text + @RequestParam(required = false) String value ) { - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text); + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(value, term); return ResponseEntity.ok(response); } } diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java index 9de0e98b6..c8a6601e3 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepository.java @@ -9,4 +9,6 @@ public interface UnivApplyInfoFilterRepository { List findAllByRegionCodeAndKeywords(String regionCode, List keywords); List findAllByFilter(LanguageTestType testType, String testScore, String term, List countryKoreanNames); + + List findAllByText(String text, String term); } diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java index 538074422..3786f697c 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UnivApplyInfoFilterRepositoryImpl.java @@ -1,14 +1,19 @@ package com.example.solidconnection.university.repository.custom; import com.example.solidconnection.location.country.domain.QCountry; +import com.example.solidconnection.location.region.domain.QRegion; import com.example.solidconnection.university.domain.LanguageTestType; import com.example.solidconnection.university.domain.QLanguageRequirement; import com.example.solidconnection.university.domain.QUnivApplyInfo; import com.example.solidconnection.university.domain.QUniversity; import com.example.solidconnection.university.domain.UnivApplyInfo; +import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.CaseBuilder; import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.NumberExpression; import com.querydsl.core.types.dsl.StringPath; +import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; import java.util.List; @@ -136,4 +141,46 @@ private boolean isGivenScoreOverMinPassScore( .map(requirement -> givenTestType.compare(givenTestScore, requirement.getMinScore())) .orElse(-1) >= 0; } + + @Override + public List findAllByText(String text, String term) { + QUnivApplyInfo univApplyInfo = QUnivApplyInfo.univApplyInfo; + QUniversity university = QUniversity.university; + QLanguageRequirement languageRequirement = QLanguageRequirement.languageRequirement; + QCountry country = QCountry.country; + QRegion region = QRegion.region; + + JPAQuery base = queryFactory.selectFrom(univApplyInfo) + .join(univApplyInfo.university, university).fetchJoin() + .join(university.country, country).fetchJoin() + .join(region).on(country.regionCode.eq(region.code)) + .leftJoin(univApplyInfo.languageRequirements, languageRequirement).fetchJoin() + .where(termEq(univApplyInfo, term)); + + // text 가 비어있다면 모든 대학 지원 정보를 id 오름차순으로 정렬하여 반환 + if (text == null || text.isBlank()) { + return base.orderBy(univApplyInfo.id.asc()).fetch(); + } + + // 매칭 조건 (대학 지원 정보명/국가명/지역명 중 하나라도 포함) + BooleanExpression univApplyInfoLike = univApplyInfo.koreanName.contains(text); + BooleanExpression countryLike = country.koreanName.contains(text); + BooleanExpression regionLike = region.koreanName.contains(text); + BooleanBuilder where = new BooleanBuilder() + .or(univApplyInfoLike) + .or(countryLike) + .or(regionLike); + + // 우선순위 랭크: 대학 지원 정보명(0) > 국가명(1) > 지역명(2) > 그 외(3) + NumberExpression rank = new CaseBuilder() + .when(univApplyInfoLike).then(0) + .when(countryLike).then(1) + .when(regionLike).then(2) + .otherwise(3); + + // 정렬 조건: 랭크 오름차순 > 대학지원정보 id 오름차순 + return base.where(where) + .orderBy(rank.asc(), univApplyInfo.id.asc()) + .fetch(); + } } diff --git a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java index e8ba3140b..bf6ec089a 100644 --- a/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java +++ b/src/main/java/com/example/solidconnection/university/service/UnivApplyInfoQueryService.java @@ -10,7 +10,6 @@ import com.example.solidconnection.university.repository.UnivApplyInfoRepository; import java.util.List; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,15 +19,12 @@ public class UnivApplyInfoQueryService { private final UnivApplyInfoRepository univApplyInfoRepository; - @Value("${university.term}") - public String term; - /* * 대학교 상세 정보를 불러온다. * - 대학교(University) 정보와 대학 지원 정보(UniversityInfoForApply) 정보를 조합하여 반환한다. * */ @Transactional(readOnly = true) - @ThunderingHerdCaching(key = "univApplyInfo:{0}", cacheManager = "customCacheManager", ttlSec = 86400) + @ThunderingHerdCaching(key = "univApplyInfo:{0}:{1}", cacheManager = "customCacheManager", ttlSec = 86400) public UnivApplyInfoDetailResponse getUnivApplyInfoDetail(Long univApplyInfoId) { UnivApplyInfo univApplyInfo = univApplyInfoRepository.getUnivApplyInfoById(univApplyInfoId); @@ -38,7 +34,7 @@ public UnivApplyInfoDetailResponse getUnivApplyInfoDetail(Long univApplyInfoId) } @Transactional(readOnly = true) - public UnivApplyInfoPreviewResponses searchUnivApplyInfoByFilter(UnivApplyInfoFilterSearchRequest request) { + public UnivApplyInfoPreviewResponses searchUnivApplyInfoByFilter(UnivApplyInfoFilterSearchRequest request, String term) { List responses = univApplyInfoRepository .findAllByFilter(request.languageTestType(), request.testScore(), term, request.countryCode()) .stream() @@ -48,8 +44,12 @@ public UnivApplyInfoPreviewResponses searchUnivApplyInfoByFilter(UnivApplyInfoFi } @Transactional(readOnly = true) - public UnivApplyInfoPreviewResponses searchUnivApplyInfoByText(String text) { - // todo: 구현 - return null; + @ThunderingHerdCaching(key = "univApplyInfoTextSearch:{0}:{1}", cacheManager = "customCacheManager", ttlSec = 86400) + public UnivApplyInfoPreviewResponses searchUnivApplyInfoByText(String text, String term) { + List responses = univApplyInfoRepository.findAllByText(text, term) + .stream() + .map(UnivApplyInfoPreviewResponse::from) + .toList(); + return new UnivApplyInfoPreviewResponses(responses); } } diff --git a/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixture.java b/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixture.java index 12c5efa07..dfd50450f 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixture.java +++ b/src/test/java/com/example/solidconnection/university/fixture/UnivApplyInfoFixture.java @@ -39,6 +39,14 @@ public class UnivApplyInfoFixture { .create(); } + public UnivApplyInfo 아칸소주립대학_지원_정보() { + return univApplyInfoFixtureBuilder.univApplyInfo() + .term(term) + .koreanName("아칸소 주립 대학") + .university(universityFixture.아칸소_주립_대학()) + .create(); + } + public UnivApplyInfo 메모리얼대학_세인트존스_A_지원_정보() { return univApplyInfoFixtureBuilder.univApplyInfo() .term(term) diff --git a/src/test/java/com/example/solidconnection/university/fixture/UniversityFixture.java b/src/test/java/com/example/solidconnection/university/fixture/UniversityFixture.java index d1c9d2dd4..bbc3fc3b4 100644 --- a/src/test/java/com/example/solidconnection/university/fixture/UniversityFixture.java +++ b/src/test/java/com/example/solidconnection/university/fixture/UniversityFixture.java @@ -32,6 +32,15 @@ public final class UniversityFixture { .create(); } + public University 아칸소_주립_대학() { + return universityFixtureBuilder.university() + .koreanName("아칸소 주립 대학") + .englishName("Arkansas State University") + .country(countryFixture.미국()) + .region(regionFixture.영미권()) + .create(); + } + public University 메모리얼_대학_세인트존스() { return universityFixtureBuilder.university() .koreanName("메모리얼 대학 세인트존스") diff --git a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java index 69fa9b619..661294363 100644 --- a/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java +++ b/src/test/java/com/example/solidconnection/university/service/UnivApplyInfoQueryServiceTest.java @@ -3,6 +3,7 @@ import static com.example.solidconnection.common.exception.ErrorCode.UNIV_APPLY_INFO_NOT_FOUND; import static com.example.solidconnection.university.domain.LanguageTestType.TOEIC; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.BDDMockito.then; @@ -23,6 +24,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.mock.mockito.SpyBean; @TestContainerSpringBootTest @@ -41,6 +43,9 @@ class UnivApplyInfoQueryServiceTest { @Autowired private LanguageRequirementFixture languageRequirementFixture; + @Value("${university.term}") + public String term; + @Nested class 대학_지원_정보_상세_조회 { @@ -97,7 +102,7 @@ class 대학_지원_정보_필터링_검색 { languageRequirementFixture.토플_70(괌대학_B_지원_정보); // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request); + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request, term); // then assertThat(response.univApplyInfoPreviews()) @@ -114,7 +119,7 @@ class 대학_지원_정보_필터링_검색 { languageRequirementFixture.토익_900(괌대학_B_지원_정보); // when - UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request); + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request, term); // then assertThat(response.univApplyInfoPreviews()) @@ -132,8 +137,8 @@ class 대학_지원_정보_필터링_검색 { languageRequirementFixture.토익_800(메모리얼대학_세인트존스_A_지원_정보); // when - UnivApplyInfoPreviewResponses response1 = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request1); - UnivApplyInfoPreviewResponses response2 = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request2); + UnivApplyInfoPreviewResponses response1 = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request1, term); + UnivApplyInfoPreviewResponses response2 = univApplyInfoQueryService.searchUnivApplyInfoByFilter(request2, term); // then assertAll( @@ -147,4 +152,132 @@ class 대학_지원_정보_필터링_검색 { ); } } + + @Nested + class 대학_지원_정보_텍스트_검색 { + + @Test + void 텍스트가_없으면_전체_대학을_id_순으로_정렬하여_반환한다() { + // given + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + UnivApplyInfo 메이지대학_지원_정보 = univApplyInfoFixture.메이지대학_지원_정보(); + + // when + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(null, term); + + // then + assertThat(response.univApplyInfoPreviews()) + .containsExactly( + UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보), + UnivApplyInfoPreviewResponse.from(메이지대학_지원_정보) + ); + } + + @Nested + class 각각의_검색_대상에_대해_검색한다 { + + @Test + void 국문_대학_지원_정보명() { + // given + String text = "메"; + UnivApplyInfo 메이지대학_지원_정보 = univApplyInfoFixture.메이지대학_지원_정보(); + UnivApplyInfo 메모리얼대학_세인트존스_A_지원_정보 = univApplyInfoFixture.메모리얼대학_세인트존스_A_지원_정보(); + univApplyInfoFixture.괌대학_A_지원_정보(); + + // when + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text, term); + + // then + assertThat(response.univApplyInfoPreviews()) + .containsExactly( + UnivApplyInfoPreviewResponse.from(메이지대학_지원_정보), + UnivApplyInfoPreviewResponse.from(메모리얼대학_세인트존스_A_지원_정보) + ); + } + + @Test + void 국문_국가명() { + // given + String text = "미국"; + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + UnivApplyInfo 괌대학_B_지원_정보 = univApplyInfoFixture.괌대학_B_지원_정보(); + univApplyInfoFixture.메이지대학_지원_정보(); + + // when + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text, term); + + // then + assertThat(response.univApplyInfoPreviews()) + .containsExactly( + UnivApplyInfoPreviewResponse.from(괌대학_A_지원_정보), + UnivApplyInfoPreviewResponse.from(괌대학_B_지원_정보) + ); + } + + @Test + void 국문_권역명() { + // given + String text = "유럽"; + UnivApplyInfo 린츠_카톨릭대학_지원_정보 = univApplyInfoFixture.린츠_카톨릭대학_지원_정보(); + UnivApplyInfo 서던덴마크대학교_지원_정보 = univApplyInfoFixture.서던덴마크대학교_지원_정보(); + univApplyInfoFixture.메이지대학_지원_정보(); + + // when + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text, term); + + // then + assertThat(response.univApplyInfoPreviews()) + .containsExactly( + UnivApplyInfoPreviewResponse.from(린츠_카톨릭대학_지원_정보), + UnivApplyInfoPreviewResponse.from(서던덴마크대학교_지원_정보) + ); + } + } + + @Test + void 대학_국가_권역_일치_순서로_정렬하여_응답한다() { + // given + String text = "아"; + UnivApplyInfo 권역_아 = univApplyInfoFixture.메이지대학_지원_정보(); + UnivApplyInfo 국가_아 = univApplyInfoFixture.그라츠대학_지원_정보(); + UnivApplyInfo 대학지원정보_아 = univApplyInfoFixture.아칸소주립대학_지원_정보(); + + // when + UnivApplyInfoPreviewResponses response = univApplyInfoQueryService.searchUnivApplyInfoByText(text, term); + + // then + assertThat(response.univApplyInfoPreviews()) + .containsExactly( + UnivApplyInfoPreviewResponse.from(대학지원정보_아), + UnivApplyInfoPreviewResponse.from(국가_아), + UnivApplyInfoPreviewResponse.from(권역_아) + ); + } + + @Test + void 캐시가_적용된다() { + // given + String text = "Guam"; + UnivApplyInfo 괌대학_A_지원_정보 = univApplyInfoFixture.괌대학_A_지원_정보(); + + // when + UnivApplyInfoPreviewResponses firstResponse = univApplyInfoQueryService.searchUnivApplyInfoByText(text, term); + UnivApplyInfoPreviewResponses secondResponse = univApplyInfoQueryService.searchUnivApplyInfoByText(text, term); + + // then + assertThatCode(() -> { + List firstResponseIds = extractIds(firstResponse); + List secondResponseIds = extractIds(secondResponse); + assertThat(firstResponseIds).isEqualTo(secondResponseIds); + }).doesNotThrowAnyException(); + then(univApplyInfoRepository).should(times(1)).findAllByText(text, term); + } + + private List extractIds(UnivApplyInfoPreviewResponses responses) { + return responses.univApplyInfoPreviews() + .stream() + .map(UnivApplyInfoPreviewResponse::id) + .toList(); + } + } } From 5d18bd284bfef191d90d32d077a23fee51cf33c3 Mon Sep 17 00:00:00 2001 From: Yeongseo Na Date: Tue, 26 Aug 2025 15:24:29 +0900 Subject: [PATCH 90/90] =?UTF-8?q?chore:=20=EC=A7=80=EC=9B=90=20=ED=95=99?= =?UTF-8?q?=EA=B8=B0=20=EB=B3=80=EA=B2=BD=20(#487)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/secret | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/secret b/src/main/resources/secret index bb3bf0f41..fd0d80ad2 160000 --- a/src/main/resources/secret +++ b/src/main/resources/secret @@ -1 +1 @@ -Subproject commit bb3bf0f4122d10ddacab279a368cf9f06d6f6dbd +Subproject commit fd0d80ad28d28698e3e27160d9d27bf4e5462238