Skip to content

Feat/#14#23

Merged
Hgyeol merged 9 commits intodevelopfrom
feat/#14
Jul 9, 2025
Merged

Feat/#14#23
Hgyeol merged 9 commits intodevelopfrom
feat/#14

Conversation

@fixgramwork
Copy link
Member

@fixgramwork fixgramwork commented Jul 8, 2025

+시간표 API Webclient로 변경

Summary by CodeRabbit

  • Chores
    • 새로운 환경 변수 NEIS_API_KEY가 추가되었습니다.
    • 빌드 구성에 WebFlux, validation, Mockito, Reactor 테스트 관련 의존성이 추가되었습니다.
    • PostgreSQL 데이터 디렉토리의 버전 정보 파일이 삭제되었습니다.

@coderabbitai
Copy link

coderabbitai bot commented Jul 8, 2025

Walkthrough

이번 변경에서는 .env 파일에 새로운 API 키 환경 변수가 추가되었고, build.gradle에 WebFlux, validation, Mockito, Reactor 테스트 관련 의존성이 추가되었습니다. 또한 PostgreSQL 데이터 디렉터리 내 버전 정보를 나타내는 PG_VERSION 파일 두 개가 삭제되었습니다.

Changes

파일/경로 변경 요약
.env NEIS_API_KEY 환경 변수 추가
build.gradle WebFlux, validation, Mockito, Reactor 테스트 의존성 추가
postgres-data/PG_VERSION,
postgres-data/base/1/PG_VERSION
PostgreSQL 버전 정보 파일 삭제

Poem

토끼는 귀를 쫑긋 세우고,
새로운 키를 살짝 추가해요.
빌드에선 의존성 친구들이 늘었고,
데이터 버전 파일은 조용히 사라졌죠.
변화의 발자국, 살금살금 남기며,
오늘도 코드는 한 뼘 성장해요! 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c33ae88 and d5b3d16.

📒 Files selected for processing (3)
  • build.gradle (2 hunks)
  • postgres-data/PG_VERSION (0 hunks)
  • postgres-data/base/1/PG_VERSION (0 hunks)
💤 Files with no reviewable changes (2)
  • postgres-data/PG_VERSION
  • postgres-data/base/1/PG_VERSION
🚧 Files skipped from review as they are similar to previous changes (1)
  • build.gradle
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

🔭 Outside diff range comments (1)
postgres-data/postmaster.pid (1)

1-9: 런타임 아티팩트를 버전 관리에서 제외하세요.

postgres-data/postmaster.pid 파일은 PostgreSQL 데이터베이스의 런타임 아티팩트입니다. 이런 파일들은 데이터베이스가 시작될 때마다 변경되며, 버전 관리 시스템에 포함되어서는 안 됩니다.

해결 방안:

  1. postgres-data/ 전체 디렉토리를 .gitignore에 추가
  2. 기존 커밋에서 이 파일 제거
  3. 데이터베이스 관련 런타임 파일들이 실수로 커밋되지 않도록 방지
🧹 Nitpick comments (4)
src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java (1)

17-23: 가독성과 인자 순서 오류 방지를 위해 빌더 패턴 사용 권장
생성자에 다섯 개 이상의 파라미터를 순서대로 넘기면 실수 가능성이 높습니다. UserEntity.builder() 혹은 정적 팩토리 메서드를 도입하면 필드명을 명시적으로 적을 수 있어 유지보수에 용이합니다.
예)

UserEntity userEntity = UserEntity.builder()
        .studentId(userDTO.getStudentId())
        .username(userDTO.getUsername())
        .password(encodedPassword)
        .email(userDTO.getEmail())
        .role(userDTO.getRole())
        .build();
src/main/java/hello/cluebackend/domain/timetable/presentation/dto/request/TimetableRequestDto.java (1)

7-10: 사용하지 않는 import 제거 필요

사용하지 않는 import들이 포함되어 있습니다.

-import lombok.Data;
-import lombok.Setter;
-import org.hibernate.annotations.processing.Pattern;
src/main/java/hello/cluebackend/domain/timetable/service/TimetableService.java (1)

100-116: 주석으로 처리된 올바른 코드 제거 필요

주석으로 처리된 코드는 코드베이스를 어지럽힙니다. 위의 수정사항을 적용한 후 이 주석 블록을 제거해야 합니다.

-//            .uri(uriBuilder -> uriBuilder
-//                    .scheme("https")
-//                    .host(neisApiHost)
-//                    .path(TIMETABLE_PATH)
-//                    .queryParam("KEY", neisApiKey)
-//                    .queryParam("Type", "json")
-//                    .queryParam("pIndex", "1")
-//                    .queryParam("pSize", "100")
-//                    .queryParam("ATPT_OFCDC_SC_CODE", atptCode)
-//                    .queryParam("SD_SCHUL_CODE", schoolCode)
-//                    .queryParam("AY", year)
-//                    .queryParam("SEM", semester)
-//                    .queryParam("GRADE", request.getGrade())
-//                    .queryParam("CLASS_NM", request.getClassNumber())
-//                    .queryParam("TI_FROM_YMD", from)
-//                    .queryParam("TI_TO_YMD", to)
-//                    .build())
src/main/java/hello/cluebackend/domain/timetable/presentation/TimetableController.java (1)

13-16: API 문서화 어노테이션 추가 권장

컨트롤러에 Swagger/OpenAPI 문서화 어노테이션을 추가하면 API 사용성이 향상됩니다.

+@Tag(name = "Timetable", description = "시간표 조회 API")
@RestController
@RequestMapping("/api/timetable")
@RequiredArgsConstructor
public class TimetableController {

각 엔드포인트에도 @Operation 어노테이션을 추가하세요.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9eee2d5 and 1a03625.

📒 Files selected for processing (14)
  • .env (1 hunks)
  • build.gradle (2 hunks)
  • postgres-data/postmaster.pid (1 hunks)
  • src/main/java/hello/cluebackend/domain/timetable/presentation/TimetableController.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/timetable/presentation/dto/request/TimetableRequestDto.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/timetable/presentation/dto/response/TimetableResponseDto.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/timetable/service/TimetableService.java (1 hunks)
  • src/main/java/hello/cluebackend/domain/user/domain/repository/UserRepository.java (0 hunks)
  • src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java (0 hunks)
  • src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java (1 hunks)
  • src/main/java/hello/cluebackend/global/config/SecurityConfig.java (2 hunks)
  • src/main/java/hello/cluebackend/global/config/WebClientConfig.java (1 hunks)
  • src/main/resources/application.yaml (2 hunks)
  • src/test/java/hello/cluebackend/ClueBackendApplicationTests.java (0 hunks)
💤 Files with no reviewable changes (3)
  • src/main/java/hello/cluebackend/domain/user/presentation/RegisterController.java
  • src/main/java/hello/cluebackend/domain/user/domain/repository/UserRepository.java
  • src/test/java/hello/cluebackend/ClueBackendApplicationTests.java
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/main/java/hello/cluebackend/domain/timetable/presentation/dto/response/TimetableResponseDto.java (1)
src/main/java/hello/cluebackend/domain/timetable/presentation/dto/request/TimetableRequestDto.java (1)
  • Getter (12-28)
🪛 Gitleaks (8.26.0)
src/main/resources/application.yaml

41-41: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (5)
src/main/resources/application.yaml (1)

5-5: 데이터베이스 호스트명 변경이 적절합니다.

PostgreSQL 호스트명을 my_postgres에서 postgresql로 변경한 것은 Docker 서비스 이름 표준화에 맞는 좋은 변경입니다.

build.gradle (2)

33-35: 리액티브 프로그래밍 의존성 추가가 적절합니다.

WebFlux와 validation 스타터, 그리고 reactor-test 의존성을 추가한 것은 새로운 시간표 API의 리액티브 기능을 지원하기 위한 올바른 선택입니다.


60-61: 테스트 의존성 추가가 좋습니다.

Mockito 의존성을 추가하여 서비스 계층 테스트를 위한 모킹 기능을 지원하는 것은 좋은 테스트 관행입니다.

src/main/java/hello/cluebackend/global/config/SecurityConfig.java (2)

77-77: CORS 설정 개선이 적절합니다.

setAllowedOrigins에서 setAllowedOriginPatterns로 변경한 것은 패턴 기반 원본 매칭을 위한 올바른 업데이트입니다.


117-117: 시간표 API 엔드포인트 허용 설정이 적절합니다.

시간표 API 엔드포인트 /api/timetable/**를 인증 없이 접근 가능하도록 설정한 것은 공개 API의 특성상 적절한 보안 정책입니다.

Comment on lines +17 to +23
UserEntity userEntity = new UserEntity(
userDTO.getStudentId(),
userDTO.getUsername(),
userDTO.getStudentId()+userDTO.getUsername(),
userDTO.getEmail(),
userDTO.getRole()
);
Copy link

@coderabbitai coderabbitai bot Jul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

평문(plain-text) 패스워드 저장은 심각한 보안 취약점입니다
현재 password 파라미터에 studentId + username을 그대로 넣고 있으며, 암호화(BCrypt 등)가 전혀 적용되지 않았습니다.
사용자 정보 DB 유출 시 즉시 계정 탈취가 가능하므로, 반드시 패스워드 필드를 DTO로 받아서 해시 후 저장하도록 수정해 주세요.

-        UserEntity userEntity = new UserEntity(
-                userDTO.getStudentId(),
-                userDTO.getUsername(),
-                userDTO.getStudentId()+userDTO.getUsername(),
-                userDTO.getEmail(),
-                userDTO.getRole()
-        );
+        // 예시: Spring Security PasswordEncoder 주입 후 사용
+        String encodedPassword = passwordEncoder.encode(userDTO.getPassword());
+        UserEntity userEntity = new UserEntity(
+                userDTO.getStudentId(),
+                userDTO.getUsername(),
+                encodedPassword,
+                userDTO.getEmail(),
+                userDTO.getRole()
+        );
🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java
around lines 17 to 23, the password is currently set as a plain-text
concatenation of studentId and username without any encryption. To fix this,
modify the code to accept the password from the DTO, then hash it securely using
a strong algorithm like BCrypt before storing it in the UserEntity. Replace the
plain-text password assignment with the hashed password to ensure secure
password storage.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oauth2를 이용해서 비밀번호 따위는 존재하지 않음

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fixgramwork OAuth2를 사용한다면 패스워드 저장이 필요 없다는 점을 이해했습니다.

하지만 현재 코드에서는 여전히 studentId + username을 패스워드 필드에 저장하고 있습니다. OAuth2 전용 인증을 사용한다면 다음과 같은 개선을 제안합니다:

public void registerUser(DefaultRegisterUserDTO userDTO) {
    UserEntity userEntity = new UserEntity(
            userDTO.getStudentId(),
            userDTO.getUsername(),
            null, // OAuth2 사용으로 패스워드 불필요
            userDTO.getEmail(),
            userDTO.getRole()
    );
    userRepository.save(userEntity);
}

또는 UserEntity 구조를 수정하여 패스워드 필드를 아예 제거하는 것이 더 명확할 것 같습니다.


✏️ Learnings added
Learnt from: fixgramwork
PR: EscapeFrame/clueBackend#23
File: src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java:17-23
Timestamp: 2025-07-08T23:38:29.625Z
Learning: This project uses OAuth2 authentication and does not require traditional password storage or validation. The RegisterUserService in src/main/java/hello/cluebackend/domain/user/service/RegisterUserService.java should not store passwords since OAuth2 handles authentication externally.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@@ -0,0 +1 @@
NEIS_API_KEY=8ba18e6d2b1f42f9be762009bc3dfa90 No newline at end of file
Copy link

@coderabbitai coderabbitai bot Jul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

보안 경고: API 키가 버전 관리 시스템에 노출되었습니다.

NEIS API 키가 .env 파일에 하드코딩되어 버전 관리 시스템에 커밋되었습니다. 이는 심각한 보안 위험을 초래할 수 있습니다.

해결 방안:

  1. 이 파일을 .gitignore에 추가하여 향후 커밋을 방지
  2. 기존 커밋 히스토리에서 API 키 제거
  3. 노출된 API 키 교체 검토
  4. 프로덕션 환경에서는 환경 변수나 시크릿 관리 시스템 사용
🤖 Prompt for AI Agents
In the .env file at line 1, the NEIS_API_KEY is hardcoded and committed to
version control, posing a security risk. To fix this, add the .env file to
.gitignore to prevent future commits, remove the API key from the git history
using tools like git filter-branch or BFG Repo-Cleaner, replace the exposed API
key with a new one, and ensure that in production the API key is managed
securely via environment variables or a secrets management system.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NEIS_API_KEY 잘 쓸게요.
감사합니다 ㅎㅎ

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pdh0128 네, 알겠습니다! 🐰

API 키를 잘 활용하시길 바랍니다. 향후 프로덕션 환경에서는 보안을 위해 환경 변수나 시크릿 관리 시스템 사용을 고려해보시면 좋을 것 같습니다.

(´∀`)♡


http
.securityMatcher("/first-register", "/register")
.securityMatcher("/first-register", "/register","api/timetable/**")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

보안 매처 경로 패턴 불일치를 확인하세요.

securityMatcher에서 "api/timetable/**"로 설정했지만, 실제 요청 경로는 /api/timetable/**입니다. 앞의 슬래시(/) 누락으로 인해 매처가 올바르게 작동하지 않을 수 있습니다.

다음 스크립트를 실행하여 애플리케이션의 다른 부분에서 일관된 경로 패턴을 사용하는지 확인하세요:


🏁 Script executed:

#!/bin/bash
# 설명: 시간표 API 경로 패턴의 일관성을 확인합니다.

# 테스트: 프로젝트 내에서 시간표 API 경로 사용을 검색합니다.
echo "=== 시간표 API 경로 패턴 검색 ==="
rg -A 2 -B 2 "timetable" --type java

echo "=== 보안 매처 패턴 검색 ==="
rg -A 2 -B 2 "securityMatcher" --type java

Length of output: 7496


보안 매처 경로 패턴 수정 필요

securityMatcher에 등록된 "api/timetable/**" 패턴은 앞에 슬래시가 빠져 있어 실제 컨트롤러 매핑(/api/timetable/**)과 불일치합니다. 다음 위치를 변경해 주세요:

  • 파일: src/main/java/hello/cluebackend/global/config/SecurityConfig.java
  • 라인: 약 122

변경 전:

.securityMatcher("/first-register", "/register","api/timetable/**")

변경 후:

-.securityMatcher("/first-register", "/register","api/timetable/**")
+.securityMatcher("/first-register", "/register", "/api/timetable/**")
🤖 Prompt for AI Agents
In src/main/java/hello/cluebackend/global/config/SecurityConfig.java at line
122, the securityMatcher method includes the pattern "api/timetable/**" missing
a leading slash, causing a mismatch with the actual controller mapping
"/api/timetable/**". Fix this by adding the leading slash to the pattern so it
reads "/api/timetable/**" to correctly match the controller paths.

Comment on lines +14 to +22
@NotBlank(message = "학년은 필수입니다.")
@Min(value = 1)
@Max(value = 3)
private String grade;

@NotBlank(message = "반은 필수입니다.")
@Min(value = 1)
@Max(value = 4)
private String classNumber;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

String 필드에 대한 잘못된 validation 수정 필요

@Min@Max 어노테이션은 String 타입에 적용되지 않습니다. String 값의 숫자 범위를 검증하려면 @Pattern 어노테이션을 사용해야 합니다.

-  @NotBlank(message = "학년은 필수입니다.")
-  @Min(value = 1)
-  @Max(value = 3)
-  private String grade;
-
-  @NotBlank(message = "반은 필수입니다.")
-  @Min(value = 1)
-  @Max(value = 4)
-  private String classNumber;
+  @NotBlank(message = "학년은 필수입니다.")
+  @Pattern(regexp = "^[1-3]$", message = "학년은 1~3 사이여야 합니다.")
+  private String grade;
+
+  @NotBlank(message = "반은 필수입니다.")
+  @Pattern(regexp = "^[1-4]$", message = "반은 1~4 사이여야 합니다.")
+  private String classNumber;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@NotBlank(message = "학년은 필수입니다.")
@Min(value = 1)
@Max(value = 3)
private String grade;
@NotBlank(message = "반은 필수입니다.")
@Min(value = 1)
@Max(value = 4)
private String classNumber;
@NotBlank(message = "학년은 필수입니다.")
@Pattern(regexp = "^[1-3]$", message = "학년은 1~3 사이여야 합니다.")
private String grade;
@NotBlank(message = "반은 필수입니다.")
@Pattern(regexp = "^[1-4]$", message = "반은 1~4 사이여야 합니다.")
private String classNumber;
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/timetable/presentation/dto/request/TimetableRequestDto.java
around lines 14 to 22, the fields grade and classNumber are String types but
incorrectly use @Min and @Max annotations which only apply to numeric types.
Replace these with @Pattern annotations that enforce the numeric range as a
regex pattern to validate the string values correctly.

Comment on lines +86 to +99
return webClient.get()
.uri("https://" + neisApiHost + TIMETABLE_PATH +
"?KEY=" + neisApiKey +
"&Type=json" +
"&pIndex=1" +
"&pSize=100" +
"&ATPT_OFCDC_SC_CODE=" + atptCode +
"&SD_SCHUL_CODE=" + schoolCode +
"&AY=" + year +
"&SEM=" + semester +
"&GRADE=" + request.getGrade() +
"&CLASS_NM=" + request.getClassNumber() +
"&TI_FROM_YMD=" + from +
"&TI_TO_YMD=" + to)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

수동 URI 생성 대신 URI Builder 사용 필요

문자열 연결을 통한 URI 생성은 오류가 발생하기 쉽고 보안에 취약합니다. 주석으로 처리된 올바른 URI 빌더 방식을 사용해야 합니다.

-            .uri("https://" + neisApiHost + TIMETABLE_PATH +
-                    "?KEY=" + neisApiKey +
-                    "&Type=json" +
-                    "&pIndex=1" +
-                    "&pSize=100" +
-                    "&ATPT_OFCDC_SC_CODE=" + atptCode +
-                    "&SD_SCHUL_CODE=" + schoolCode +
-                    "&AY=" + year +
-                    "&SEM=" + semester +
-                    "&GRADE=" + request.getGrade() +
-                    "&CLASS_NM=" + request.getClassNumber() +
-                    "&TI_FROM_YMD=" + from +
-                    "&TI_TO_YMD=" + to)
+            .uri(uriBuilder -> uriBuilder
+                    .scheme("https")
+                    .host(neisApiHost)
+                    .path(TIMETABLE_PATH)
+                    .queryParam("KEY", neisApiKey)
+                    .queryParam("Type", "json")
+                    .queryParam("pIndex", "1")
+                    .queryParam("pSize", "100")
+                    .queryParam("ATPT_OFCDC_SC_CODE", atptCode)
+                    .queryParam("SD_SCHUL_CODE", schoolCode)
+                    .queryParam("AY", year)
+                    .queryParam("SEM", semester)
+                    .queryParam("GRADE", request.getGrade())
+                    .queryParam("CLASS_NM", request.getClassNumber())
+                    .queryParam("TI_FROM_YMD", from)
+                    .queryParam("TI_TO_YMD", to)
+                    .build())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return webClient.get()
.uri("https://" + neisApiHost + TIMETABLE_PATH +
"?KEY=" + neisApiKey +
"&Type=json" +
"&pIndex=1" +
"&pSize=100" +
"&ATPT_OFCDC_SC_CODE=" + atptCode +
"&SD_SCHUL_CODE=" + schoolCode +
"&AY=" + year +
"&SEM=" + semester +
"&GRADE=" + request.getGrade() +
"&CLASS_NM=" + request.getClassNumber() +
"&TI_FROM_YMD=" + from +
"&TI_TO_YMD=" + to)
return webClient.get()
.uri(uriBuilder -> uriBuilder
.scheme("https")
.host(neisApiHost)
.path(TIMETABLE_PATH)
.queryParam("KEY", neisApiKey)
.queryParam("Type", "json")
.queryParam("pIndex", "1")
.queryParam("pSize", "100")
.queryParam("ATPT_OFCDC_SC_CODE", atptCode)
.queryParam("SD_SCHUL_CODE", schoolCode)
.queryParam("AY", year)
.queryParam("SEM", semester)
.queryParam("GRADE", request.getGrade())
.queryParam("CLASS_NM", request.getClassNumber())
.queryParam("TI_FROM_YMD", from)
.queryParam("TI_TO_YMD", to)
.build())
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/timetable/service/TimetableService.java
lines 86 to 99, the URI for the webClient.get() call is constructed using string
concatenation, which is error-prone and insecure. Replace this manual string
building with the URI builder approach provided by WebClient, using
uriBuilder.queryParam() methods to safely add each query parameter. This ensures
proper encoding and improves security and maintainability.

Comment on lines +132 to +137
.retryWhen(Retry.backoff(RETRY_ATTEMPTS, RETRY_DELAY)
.filter(throwable -> !(throwable instanceof WebClientResponseException.BadRequest))
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
log.error("NEIS API 재시도 횟수 초과");
return new RuntimeException("NEIS API 호출 실패 - 호출 시간 초과");
}))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

재시도 로직 개선 필요

현재 재시도 로직이 BadRequest만 제외하고 있지만, 다른 4xx 에러들도 재시도하면 안 됩니다.

-                    .filter(throwable -> !(throwable instanceof WebClientResponseException.BadRequest))
+                    .filter(throwable -> {
+                        if (throwable instanceof WebClientResponseException) {
+                            WebClientResponseException ex = (WebClientResponseException) throwable;
+                            return !ex.getStatusCode().is4xxClientError();
+                        }
+                        return true;
+                    })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.retryWhen(Retry.backoff(RETRY_ATTEMPTS, RETRY_DELAY)
.filter(throwable -> !(throwable instanceof WebClientResponseException.BadRequest))
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
log.error("NEIS API 재시도 횟수 초과");
return new RuntimeException("NEIS API 호출 실패 - 호출 시간 초과");
}))
.retryWhen(Retry.backoff(RETRY_ATTEMPTS, RETRY_DELAY)
.filter(throwable -> {
if (throwable instanceof WebClientResponseException) {
WebClientResponseException ex = (WebClientResponseException) throwable;
return !ex.getStatusCode().is4xxClientError();
}
return true;
})
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
log.error("NEIS API 재시도 횟수 초과");
return new RuntimeException("NEIS API 호출 실패 - 호출 시간 초과");
}))
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/timetable/service/TimetableService.java
around lines 132 to 137, the retry logic currently excludes only BadRequest
(400) errors from retrying, but other 4xx client errors should also not trigger
retries. Update the filter in the retryWhen clause to exclude all 4xx HTTP
errors by checking if the throwable is an instance of WebClientResponseException
and its status code is in the 4xx range, and prevent retries for those errors.

Comment on lines +25 to +28
public Mono<ResponseEntity<List<TimetableResponseDto>>> getTodayTimetable(
@RequestParam(required = true) String grade,
@RequestParam(required = true) String classNumber
) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

컨트롤러 레벨 validation 추가 필요

요청 파라미터에 대한 검증이 없습니다. @Valid 어노테이션과 함께 적절한 검증을 추가해야 합니다.

  @GetMapping("/today")
  public Mono<ResponseEntity<List<TimetableResponseDto>>> getTodayTimetable(
-          @RequestParam(required = true) String grade,
-          @RequestParam(required = true) String classNumber
+          @RequestParam @Pattern(regexp = "^[1-3]$", message = "학년은 1~3 사이여야 합니다.") String grade,
+          @RequestParam @Pattern(regexp = "^[1-4]$", message = "반은 1~4 사이여야 합니다.") String classNumber
) {

동일한 패턴을 /weekly 엔드포인트에도 적용하세요.

Also applies to: 37-40

🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/timetable/presentation/TimetableController.java
around lines 25-28 and 37-40, the request parameters lack validation. Add
validation annotations such as @Valid and appropriate constraints (e.g.,
@NotBlank, @NotNull) to the method parameters or wrap them in a validated DTO
class. Apply the same validation pattern to both the getTodayTimetable and the
/weekly endpoint methods to ensure request parameters are properly validated at
the controller level.

return timetableService.getTodayTimetable(request)
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.noContent().build())
.onErrorResume(e -> Mono.just(ResponseEntity.internalServerError().build()));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

에러 처리 개선 필요

현재 모든 에러를 500 Internal Server Error로 처리하고 있습니다. 에러 타입에 따라 적절한 HTTP 상태 코드를 반환해야 합니다.

-            .onErrorResume(e -> Mono.just(ResponseEntity.internalServerError().build()));
+            .onErrorResume(e -> {
+                if (e instanceof IllegalArgumentException) {
+                    return Mono.just(ResponseEntity.badRequest().build());
+                }
+                return Mono.just(ResponseEntity.internalServerError().build());
+            });

Also applies to: 45-45

🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/timetable/presentation/TimetableController.java
at lines 33 and 45, the error handling currently returns a generic 500 Internal
Server Error for all exceptions. Modify the onErrorResume handlers to inspect
the exception type and return appropriate HTTP status codes based on the error,
such as 404 for not found or 400 for bad requests, instead of always returning
500.

Comment on lines +19 to +22
@GetMapping("/test")
public String test(){
return "Test";
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

테스트 엔드포인트 제거 필요

프로덕션 코드에 테스트 목적의 엔드포인트가 포함되어 있습니다. 보안상 위험하므로 제거해야 합니다.

-  @GetMapping("/test")
-  public String test(){
-    return "Test";
-  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@GetMapping("/test")
public String test(){
return "Test";
}
🤖 Prompt for AI Agents
In
src/main/java/hello/cluebackend/domain/timetable/presentation/TimetableController.java
around lines 19 to 22, there is a test endpoint mapped to "/test" returning a
simple string. This test endpoint should be removed entirely from the production
code to eliminate potential security risks. Delete the entire method annotated
with @GetMapping("/test") including its signature and body.

@Hgyeol Hgyeol merged commit eb1fc55 into develop Jul 9, 2025
2 checks passed
@fixgramwork fixgramwork deleted the feat/#14 branch July 9, 2025 00:21
@coderabbitai coderabbitai bot mentioned this pull request Jul 31, 2025
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants