Skip to content

version 2.16.2#330

Merged
rlagkswn00 merged 2 commits intomainfrom
develop
Jan 4, 2026
Merged

version 2.16.2#330
rlagkswn00 merged 2 commits intomainfrom
develop

Conversation

@rlagkswn00
Copy link
Member

@rlagkswn00 rlagkswn00 commented Jan 4, 2026

version 2.16.2

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 관리자용 테스트 학사일정 알림 발송 API 추가
  • Tests

    • 테스트 학사일정 알림 발송 기능 수용 테스트 추가
  • Refactor

    • 부동산학과 공지사항 처리를 표준 파서로 통합하여 중복 코드 제거
    • 알림 메시지에 메시지 타입 정보 추가
  • Chores

    • 불필요한 부동산학과 관련 설정 제거

✏️ Tip: You can customize this high-level summary in your review settings.

* [feat]: fcm 메세지 분류용 messageType 필드 추가

* [feat]: 테스트 학사일정 알림 전송 api 구현

* [test]: 테스트 학사일정 알림 전송 api 테스트 구현

* [refactor]: MessageType enum화

* [refactor]: 학사일정 테스트 step 객체로 관리

* [refactor]: putData 맵 하나로 통합
@coderabbitai
Copy link

coderabbitai bot commented Jan 4, 2026

개요

관리자 API에 학사일정 테스트 알림 발송 엔드포인트가 추가되었으며, 메시지 타입 구분을 위한 열거형이 도입되었습니다. 또한 부동산학과 공지사항 파싱 로직이 일반화된 최신 페이지 파서로 통합되었습니다.

변경사항

집단 / 파일 변경 요약
학사일정 테스트 알림 관리자 API
src/main/java/com/kustacks/kuring/admin/adapter/in/web/AdminCommandApiV2.java
POST /api/v2/admin/academic/dev 엔드포인트 추가로 테스트 학사일정 알림 발송 기능 구현
학사일정 테스트 알림 요청 DTO
src/main/java/com/kustacks/kuring/admin/adapter/in/web/dto/AcademicTestNotificationRequest.java
title, body 필드를 가진 새 레코드 타입 및 toCommand() 헬퍼 메서드 추가
이벤트 및 커맨드 정의
src/main/java/com/kustacks/kuring/message/adapter/in/event/dto/AcademicTestNotificationEvent.java,
src/main/java/com/kustacks/kuring/message/application/port/in/dto/AcademicTestNotificationCommand.java
학사일정 테스트 알림 이벤트 및 커맨드 레코드 타입 추가
포트 인터페이스 확장
src/main/java/com/kustacks/kuring/admin/application/port/in/AdminCommandUseCase.java,
src/main/java/com/kustacks/kuring/admin/application/port/out/AdminEventPort.java,
src/main/java/com/kustacks/kuring/message/application/port/in/FirebaseWithAdminUseCase.java
학사일정 테스트 알림 관련 메서드 추가
서비스 계층 구현
src/main/java/com/kustacks/kuring/admin/application/service/AdminCommandService.java,
src/main/java/com/kustacks/kuring/admin/adapter/out/event/AdminFirebaseMessageAdapter.java,
src/main/java/com/kustacks/kuring/message/adapter/in/event/MessageAdminEventListener.java
학사일정 테스트 알림 처리 로직 구현
메시지 서비스 강화
src/main/java/com/kustacks/kuring/message/application/service/FirebaseNotificationService.java,
src/main/java/com/kustacks/kuring/calendar/application/service/AcademicEventNotificationService.java
messageType 데이터 필드 추가 및 학사일정 테스트 알림 전송 메서드 구현
메시지 타입 열거형
src/main/java/com/kustacks/kuring/message/domain/MessageType.java
NOTICE, ADMIN, ACADEMIC 값을 가진 새 MessageType 열거형 도입
응답 코드 추가
src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java
ADMIN_TEST_ACADEMIC_CREATE_SUCCESS 응답 코드 추가
부동산학과 파싱 통합
src/main/java/com/kustacks/kuring/notice/domain/DepartmentName.java,
src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/real_estate/RealEstateDept.java
hostPrefix를 "rest"에서 "kure"로 변경, 제네릭 파서 사용으로 전환
부동산학과 전문 파서 제거
src/main/java/com/kustacks/kuring/worker/parser/notice/RealEstateNoticeHtmlParser.java,
src/main/java/com/kustacks/kuring/worker/scrap/client/notice/RealEstateNoticeApiClient.java,
src/main/java/com/kustacks/kuring/worker/scrap/client/notice/property/RealEstateNoticeProperties.java
부동산학과 전문 파서/클라이언트/설정 클래스 전체 삭제
파서 일반화
src/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParser.java
RealEstateDept 제외 조건 제거로 모든 부서 지원 활성화
공지사항 업데이터 수정
src/main/java/com/kustacks/kuring/worker/update/notice/DepartmentNoticeUpdater.java,
src/main/java/com/kustacks/kuring/worker/update/notice/DepartmentGraduationNoticeUpdater.java
REAL_ESTATE 부서에 대한 조건부 스킵 제거
설정 정리
src/main/resources/config/environments/common.yml
notice.real-estate 설정 항목 삭제
수용 테스트
src/test/java/com/kustacks/kuring/acceptance/AdminAcceptanceTest.java,
src/test/java/com/kustacks/kuring/acceptance/AdminStep.java
학사일정 테스트 알림 API 테스트 및 헬퍼 메서드 추가
테스트 정리
src/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.java
RealEstateNoticeHtmlParser 테스트 제거 및 와일드카드 임포트 정리

시퀀스 다이어그램

sequenceDiagram
    participant Client
    participant AdminAPI as Admin API<br/>Controller
    participant AdminService as Admin<br/>Command Service
    participant EventAdapter as Firebase<br/>Message Adapter
    participant EventBus as Spring<br/>Event Bus
    participant MessageListener as Message Admin<br/>Event Listener
    participant FirebaseService as Firebase<br/>Notification Service
    participant Firebase as Firebase<br/>Cloud Messaging

    Client->>AdminAPI: POST /api/v2/admin/academic/dev<br/>(title, body)
    AdminAPI->>AdminService: createAcademicTestNotification<br/>(command)
    AdminService->>EventAdapter: sendAcademicTestNotification<br/>(title, body)
    EventAdapter->>EventBus: raise<br/>AcademicTestNotificationEvent
    EventBus->>MessageListener: sendAcademicTestNotificationEvent<br/>(event)
    MessageListener->>FirebaseService: sendAcademicTestNotification<br/>(command)
    FirebaseService->>Firebase: send(message)<br/>to ACADEMIC_EVENT_TOPIC_dev
    Firebase-->>FirebaseService: success/exception
    FirebaseService-->>MessageListener: complete
    MessageListener-->>EventBus: async complete
    EventBus-->>EventAdapter: event processed
    AdminService-->>AdminAPI: success
    AdminAPI-->>Client: 200 OK<br/>success response
Loading

관련된 가능성 있는 PR

제안된 레이블

🐛 Bug

🐰 학사 알림의 새로운 소식,
메시지 타입으로 구분하고,
부동산학과는 표준화되어,
파서들이 하나로 통합되네!
테스트 엔드포인트도 추가되어,
관리자의 일이 더 쉬워졌어~ 🎉

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.86% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ⚠️ Warning PR 제목 'version 2.16.2'는 변경 사항의 주요 목표를 반영하지 않으며, 버전 번호만 표시하여 실제 구현 내용을 설명하지 않습니다. 제목을 'Add academic test notification API and refactor real estate notice parsing'과 같이 실제 주요 변경 사항을 명확히 나타내는 내용으로 변경하세요.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rlagkswn00 rlagkswn00 self-assigned this Jan 4, 2026
@rlagkswn00 rlagkswn00 added the 🚀 Deploy Production deploy production server label Jan 4, 2026
@sonarqubecloud
Copy link

sonarqubecloud bot commented Jan 4, 2026

@github-actions
Copy link

github-actions bot commented Jan 4, 2026

Unit Test Results

  74 files  ±0    74 suites  ±0   1m 24s ⏱️ -1s
532 tests ±0  525 ✔️ ±0  7 💤 ±0  0 ±0 
535 runs  ±0  528 ✔️ ±0  7 💤 ±0  0 ±0 

Results for commit 1d2e4d4. ± Comparison against base commit 357824e.

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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.java (1)

1-209: RealEstateDept에 대한 테스트 커버리지를 추가하세요.

RealEstateNoticeHtmlParser 관련 테스트가 제거되었지만, LatestPageNoticeHtmlParser가 이제 RealEstateDept를 지원하는지 검증하는 테스트가 추가되지 않았습니다. RealEstateDept의 HTML을 사용하여 LatestPageNoticeHtmlParser.parse()가 올바르게 작동하는지 확인하는 테스트를 추가하는 것을 권장합니다.

🔎 테스트 추가 예시

src/test/resources/notice/ 디렉토리에 real-estate-notice-2024.html 파일을 추가하고, 다음과 같은 테스트를 추가하세요:

@DisplayName("부동산학과 공지사항을 LatestPageNoticeHtmlParser로 분석한다")
@Test
void RealEstateNoticeWithLatestPageParser() throws IOException {
    // given
    Document doc = Jsoup.parse(TestFileLoader.loadHtmlFile("src/test/resources/notice/real-estate-notice-2024.html"));
    String viewUrl = "https://realestate.konkuk.ac.kr/bbs/realestate/{siteId}/{noticeId}/artclView.do";

    // when
    RowsDto rowsDto = new LatestPageNoticeHtmlParser().parse(doc);
    List<CommonNoticeFormatDto> important = rowsDto.buildImportantRowList(viewUrl);
    List<CommonNoticeFormatDto> normal = rowsDto.buildNormalRowList(viewUrl);

    // then
    assertAll(
            () -> assertThat(important).isNotEmpty(),
            () -> assertThat(normal).isNotEmpty()
            // 실제 데이터에 맞는 검증 추가
    );
}
🧹 Nitpick comments (3)
src/main/java/com/kustacks/kuring/message/application/service/FirebaseNotificationService.java (1)

70-90: 데이터 페이로드 생성 방식을 다른 메서드와 일관되게 개선하세요.

이 메서드는 Map.of()를 사용하여 데이터 페이로드를 수동으로 생성하지만, 같은 파일의 createMessageFromCommand (라인 157)와 createMessageFromDto (라인 170)는 objectMapper.convertValue()를 사용합니다.

일관성 문제:

  • Map.of()는 null 값이 있으면 NullPointerException을 발생시킵니다
  • objectMapper 방식은 null을 안전하게 처리하고 command의 모든 필드를 자동으로 포함합니다
  • 수동 매핑은 향후 필드가 추가될 때 누락될 위험이 있습니다
🔎 objectMapper를 사용한 리팩토링 제안
 @Override
 public void sendAcademicTestNotification(AcademicTestNotificationCommand command) {
     try {
         Message newMessage = Message.builder()
                 .setNotification(Notification.builder()
                         .setTitle(command.title())
                         .setBody(command.body())
                         .build())
-                .putAllData(Map.of(
-                        "title", command.title(),
-                        "body", command.body(),
-                        "messageType", ACADEMIC.getValue()
-                ))
+                .putAllData(objectMapper.convertValue(command, Map.class))
+                .putData("messageType", ACADEMIC.getValue())
                 .setTopic(serverProperties.addDevSuffix(ACADEMIC_EVENT_TOPIC))
                 .build();

         firebaseMessagingPort.send(newMessage);
     } catch (FirebaseMessagingException exception) {
         throw new FirebaseMessageSendException();
     }
 }
src/main/java/com/kustacks/kuring/notice/domain/DepartmentName.java (1)

56-56: 호스트 프리픽스 변경에 대한 실제 영향 범위 재검토 필요

코드베이스 검증 결과, 모든 REAL_ESTATE 참조는 getHostPrefix() 메서드를 통해 동적으로 호출되고 있으며, 하드코딩된 "rest" 프리픽스 참조는 발견되지 않았습니다. HOST_PREFIX_MAP은 enum 값으로부터 자동 생성되고, 모든 부서 클래스가 동일한 패턴을 따르고 있습니다.

코드 레벨에서는 프리픽스 변경이 안전하게 전파되지만, 다음 영역에 대해서만 확인이 필요합니다:

  • 외부 시스템이나 클라이언트에서 "rest" 프리픽스를 하드코딩하여 참조하는 경우
  • 배포 후 기존 캐시 또는 외부 데이터 소스 검증

코드 내 하드코딩된 참조는 없으므로, 외부 통합점과 배포 절차에 대한 확인으로 충분합니다.

src/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParser.java (1)

13-15: 사용되지 않는 파라미터를 제거하는 것을 고려하세요.

deptInfo 파라미터가 더 이상 사용되지 않습니다. 이는 인터페이스나 추상 클래스의 메서드를 오버라이드하는 경우라면 불가피하지만, 그렇지 않다면 제거를 고려해볼 수 있습니다.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 357824e and 1d2e4d4.

📒 Files selected for processing (26)
  • src/main/java/com/kustacks/kuring/admin/adapter/in/web/AdminCommandApiV2.java
  • src/main/java/com/kustacks/kuring/admin/adapter/in/web/dto/AcademicTestNotificationRequest.java
  • src/main/java/com/kustacks/kuring/admin/adapter/out/event/AdminFirebaseMessageAdapter.java
  • src/main/java/com/kustacks/kuring/admin/application/port/in/AdminCommandUseCase.java
  • src/main/java/com/kustacks/kuring/admin/application/port/out/AdminEventPort.java
  • src/main/java/com/kustacks/kuring/admin/application/service/AdminCommandService.java
  • src/main/java/com/kustacks/kuring/calendar/application/service/AcademicEventNotificationService.java
  • src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java
  • src/main/java/com/kustacks/kuring/message/adapter/in/event/MessageAdminEventListener.java
  • src/main/java/com/kustacks/kuring/message/adapter/in/event/dto/AcademicTestNotificationEvent.java
  • src/main/java/com/kustacks/kuring/message/application/port/in/FirebaseWithAdminUseCase.java
  • src/main/java/com/kustacks/kuring/message/application/port/in/dto/AcademicTestNotificationCommand.java
  • src/main/java/com/kustacks/kuring/message/application/service/FirebaseNotificationService.java
  • src/main/java/com/kustacks/kuring/message/domain/MessageType.java
  • src/main/java/com/kustacks/kuring/notice/domain/DepartmentName.java
  • src/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParser.java
  • src/main/java/com/kustacks/kuring/worker/parser/notice/RealEstateNoticeHtmlParser.java
  • src/main/java/com/kustacks/kuring/worker/scrap/client/notice/RealEstateNoticeApiClient.java
  • src/main/java/com/kustacks/kuring/worker/scrap/client/notice/property/RealEstateNoticeProperties.java
  • src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/real_estate/RealEstateDept.java
  • src/main/java/com/kustacks/kuring/worker/update/notice/DepartmentGraduationNoticeUpdater.java
  • src/main/java/com/kustacks/kuring/worker/update/notice/DepartmentNoticeUpdater.java
  • src/main/resources/config/environments/common.yml
  • src/test/java/com/kustacks/kuring/acceptance/AdminAcceptanceTest.java
  • src/test/java/com/kustacks/kuring/acceptance/AdminStep.java
  • src/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.java
💤 Files with no reviewable changes (6)
  • src/main/java/com/kustacks/kuring/worker/parser/notice/RealEstateNoticeHtmlParser.java
  • src/main/resources/config/environments/common.yml
  • src/main/java/com/kustacks/kuring/worker/update/notice/DepartmentNoticeUpdater.java
  • src/main/java/com/kustacks/kuring/worker/update/notice/DepartmentGraduationNoticeUpdater.java
  • src/main/java/com/kustacks/kuring/worker/scrap/client/notice/RealEstateNoticeApiClient.java
  • src/main/java/com/kustacks/kuring/worker/scrap/client/notice/property/RealEstateNoticeProperties.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.java (1)
src/main/java/com/kustacks/kuring/worker/parser/notice/RowsDto.java (1)
  • RowsDto (10-55)
src/test/java/com/kustacks/kuring/acceptance/AdminAcceptanceTest.java (1)
src/test/java/com/kustacks/kuring/acceptance/AdminStep.java (1)
  • AdminStep (14-157)
⏰ 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 and analyze
🔇 Additional comments (23)
src/main/java/com/kustacks/kuring/common/dto/ResponseCodeAndMessages.java (2)

33-33: 새로운 응답 코드 추가가 적절합니다.

ADMIN_TEST_ACADEMIC_CREATE_SUCCESS 상수가 기존 패턴(Line 27-28의 다른 ADMIN_TEST_ 상수들)과 일관되게 추가되었으며, 메시지가 명확합니다.


56-56: 포매팅 변경이 확인되었습니다.

의미론적 변경 없이 포매팅만 개선되었습니다.

src/test/java/com/kustacks/kuring/acceptance/AdminAcceptanceTest.java (1)

155-175: 학사일정 테스트 알림 acceptance 테스트가 적절합니다.

테스트가 given-when-then 패턴을 따르며, 다른 테스트들(Line 98-122, Line 129-153)과 구조적으로 일관성이 있습니다. 응답 검증도 적절하게 수행됩니다.

src/main/java/com/kustacks/kuring/calendar/application/service/AcademicEventNotificationService.java (1)

19-19: FCM 메시지에 메시지 타입 필드 추가는 적절합니다.

ACADEMIC 타입의 메시지를 분류하기 위해 messageType 필드를 FCM 메시지에 추가한 변경사항입니다. 이는 기존의 ADMIN 및 NOTICE 타입과 함께 일관성 있게 적용되는 패턴입니다.

  • MessageType.ACADEMIC enum 상수가 올바르게 정의됨 (값: "academic")
  • getValue() 메서드가 Lombok의 @Getter를 통해 제공됨
  • Static import가 유효한 상수를 올바르게 참조함
  • 다른 알림 서비스(FirebaseNotificationService)와 동일한 패턴으로 구현됨
src/main/java/com/kustacks/kuring/admin/application/port/out/AdminEventPort.java (1)

17-17: 학사일정 알림용 포트 메서드가 적절히 추가되었습니다.

인터페이스 메서드 sendAcademicTestNotification의 설계가 예상대로 구현되어 있습니다. 구현체(AdminFirebaseMessageAdapter)에서 AcademicTestNotificationEvent(title, body)를 발행하므로 URL 파라미터가 필요 없으며, 학사일정 알림 특성상 FirebaseMessageSendException 발생 및 선언이 불필요합니다. 메서드 시그니처는 구현 요구사항과 일치하고 기존 이벤트 기반 패턴을 일관되게 따릅니다.

src/main/java/com/kustacks/kuring/admin/application/service/AdminCommandService.java (1)

86-92: 테스트 학사일정 알림 메서드 구현이 명확하며 다른 admin 메서드들과 패턴이 일관적입니다.

비밀번호 검증이 누락된 것처럼 보이나, 이는 설계 의도입니다. 엔드포인트를 확인해보니 /notices/dev (테스트 공지)와 /academic/dev (테스트 학사일정)는 역할 기반 접근 제어(@Secured(AdminRole.ROLE_ROOT))로만 보호되고, /notices/prod (실제 공지)만 추가로 비밀번호 검증을 수행합니다. 테스트 엔드포인트에서는 개발 목적상 추가 검증을 요구하지 않는 일관된 패턴이므로 현재 구현이 적절합니다.

src/main/java/com/kustacks/kuring/admin/adapter/out/event/AdminFirebaseMessageAdapter.java (2)

5-5: LGTM!

새로운 이벤트 타입에 대한 import가 올바르게 추가되었습니다.


42-45: 이 코드는 문제가 없습니다. 예외 처리 일관성이 확인되었습니다.

sendAcademicTestNotificationFirebaseMessageSendException을 선언하지 않은 것은 의도적인 설계 선택입니다. AdminEventPort 인터페이스(라인 17)에서 명확하게 예외를 선언하지 않으며, 이는 메서드가 비동기 이벤트 발행 패턴(Events.raise())을 사용하기 때문입니다. 반면 sendTestNotificationByAdmin(라인 8-15)은 동기식 직접 실행이므로 예외를 선언합니다. 두 메서드는 서로 다른 아키텍처 패턴을 따르고 있으며, 구현이 인터페이스와 정확히 일치합니다.

src/main/java/com/kustacks/kuring/admin/application/port/in/AdminCommandUseCase.java (1)

7-7: LGTM!

인터페이스 메서드 추가가 기존 패턴을 잘 따르고 있으며, 명명 규칙도 일관성 있게 적용되었습니다.

Also applies to: 23-24

src/main/java/com/kustacks/kuring/message/adapter/in/event/dto/AcademicTestNotificationEvent.java (1)

1-12: LGTM!

이벤트 레코드가 간결하게 잘 구현되었으며, toCommand() 메서드를 통한 변환 로직도 명확합니다.

src/test/java/com/kustacks/kuring/acceptance/AdminStep.java (1)

3-3: LGTM!

테스트 헬퍼 메서드가 파일의 기존 패턴을 일관성 있게 따르고 있으며, RestAssured 설정도 적절합니다.

Also applies to: 142-155

src/main/java/com/kustacks/kuring/message/domain/MessageType.java (1)

1-15: ACADEMIC 타입의 통합이 올바르게 구현되었습니다.

검증 결과, MessageType 열거형의 새로운 ACADEMIC 타입이 코드베이스에 일관되게 적용되어 있습니다. switch 문이 사용되지 않으므로 누락된 케이스가 없으며, 모든 메시지 타입(NOTICE, ADMIN, ACADEMIC)에서 getValue() 메서드가 일관되게 호출됩니다. FirebaseNotificationService와 AcademicEventNotificationService에서 ACADEMIC 타입이 올바르게 임포트되고 사용되고 있습니다.

src/main/java/com/kustacks/kuring/admin/adapter/in/web/dto/AcademicTestNotificationRequest.java (1)

9-11: LGTM!

커맨드 변환 로직이 명확하고 단순합니다.

src/main/java/com/kustacks/kuring/message/application/port/in/FirebaseWithAdminUseCase.java (1)

3-3: LGTM!

새로운 메서드 추가가 기존 인터페이스 패턴과 일관성 있게 구현되었습니다.

Also applies to: 12-12

src/main/java/com/kustacks/kuring/message/application/port/in/dto/AcademicTestNotificationCommand.java (1)

3-6: LGTM!

커맨드 DTO가 단순하고 명확합니다. 다만 입력 검증은 어댑터 레이어(AcademicTestNotificationRequest)에서 수행되어야 합니다.

src/main/java/com/kustacks/kuring/admin/adapter/in/web/AdminCommandApiV2.java (1)

80-83: LGTM!

엔드포인트의 보안 설정과 API 문서화가 적절합니다. ROLE_ROOT 권한으로 제한되어 있고, 테스트 용도임이 명확하게 표시되어 있습니다.

src/main/java/com/kustacks/kuring/message/adapter/in/event/MessageAdminEventListener.java (1)

42-48: @async 사용이 sendTestNotificationEvent와는 일관성 있지만, sendAlertEvent와의 차이를 명확히 문서화하세요.

sendAcademicTestNotificationEventsendTestNotificationEvent와 동일하게 @Async로 표시되어 있습니다. 다만, 같은 클래스의 sendAlertEvent는 동기 방식으로 처리됩니다.

  • sendNotificationEvent: @async
  • sendTestNotificationEvent: @async
  • sendAcademicTestNotificationEvent: @async
  • sendAlertEvent: 동기

테스트 알림의 비동기 처리가 의도된 설계인지, 아니면 alert 처리 시간 임계성에 따른 구분인지 코드나 문서에 명시되지 않았습니다. 현재 구현은 테스트 알림끼리는 일관성 있으나, 이러한 선택 기준을 주석으로 명확히 기록해야 합니다.

src/main/java/com/kustacks/kuring/message/application/service/FirebaseNotificationService.java (2)

158-158: 좋은 개선 사항입니다!

관리자 알림에 messageType 필드를 추가하여 클라이언트가 메시지 유형을 구분할 수 있게 되었습니다. 구현이 명확하고 올바릅니다.


171-171: 좋은 개선 사항입니다!

공지 알림에 messageType 필드를 추가하여 클라이언트가 메시지 유형을 구분할 수 있게 되었습니다. 라인 158의 관리자 알림과 일관된 패턴을 따르고 있습니다.

src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/real_estate/RealEstateDept.java (3)

3-3: LGTM! 특정 클래스에서 범용 클래스로의 리팩토링

RealEstateNoticeApiClientRealEstateNoticeHtmlParser에서 LatestPageNoticeApiClientLatestPageNoticeHtmlParser로의 전환은 코드 재사용성을 개선하는 좋은 리팩토링입니다. 생성자 파라미터가 올바르게 업데이트되었고 필드 할당도 일치합니다.

Also applies to: 5-5, 18-26


30-30: LGTM! 하드코딩된 값 제거로 유지보수성 향상

staffScrapInfo 초기화에서 하드코딩된 "kure" 문자열을 REAL_ESTATE.getHostPrefix()로 대체한 것은 좋은 개선입니다. 이제 라인 31, 33과 일관된 패턴을 따르며 DRY 원칙을 준수합니다.


34-34: LGTM! 누락된 필드 할당 추가

latestPageGraduateNoticeApiClient 필드 할당이 적절하게 추가되었습니다. 생성자 파라미터가 올바르게 필드에 할당됩니다.

src/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.java (1)

4-6: 명시적 임포트로 변경한 점이 좋습니다.

와일드카드 임포트를 명시적 임포트로 변경하여 코드 가독성과 유지보수성이 향상되었습니다.

Comment on lines +80 to +89
@Operation(summary = "테스트 학사일정 알림 전송", description = "테스트 학사일정 알림을 전송합니다, 실제 운영시 사용하지 않습니다")
@SecurityRequirement(name = "JWT")
@Secured(AdminRole.ROLE_ROOT)
@PostMapping("/academic/dev")
public ResponseEntity<BaseResponse<String>> sendAcademicTest(
@RequestBody AcademicTestNotificationRequest request
) {
adminCommandUseCase.createAcademicTestNotification(request.toCommand());
return ResponseEntity.ok().body(new BaseResponse<>(ADMIN_TEST_ACADEMIC_CREATE_SUCCESS, null));
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

요청 검증 어노테이션이 누락되었습니다.

@RequestBody@Valid 어노테이션이 없어 AcademicTestNotificationRequest의 검증 규칙이 실행되지 않습니다. 이는 첫 번째 파일에서 지적한 검증 문제와 연관되어 있습니다.

🔎 수정 제안
+import jakarta.validation.Valid;
+
     @Operation(summary = "테스트 학사일정 알림 전송", description = "테스트 학사일정 알림을 전송합니다, 실제 운영시 사용하지 않습니다")
     @SecurityRequirement(name = "JWT")
     @Secured(AdminRole.ROLE_ROOT)
     @PostMapping("/academic/dev")
     public ResponseEntity<BaseResponse<String>> sendAcademicTest(
-            @RequestBody AcademicTestNotificationRequest request
+            @Valid @RequestBody AcademicTestNotificationRequest request
     ) {
         adminCommandUseCase.createAcademicTestNotification(request.toCommand());
         return ResponseEntity.ok().body(new BaseResponse<>(ADMIN_TEST_ACADEMIC_CREATE_SUCCESS, null));
     }
🤖 Prompt for AI Agents
In src/main/java/com/kustacks/kuring/admin/adapter/in/web/AdminCommandApiV2.java
around lines 80 to 89, the controller method parameter for
AcademicTestNotificationRequest is missing the @Valid annotation so its
validation constraints won't run; add @Valid before @RequestBody (i.e., change
to @Valid @RequestBody AcademicTestNotificationRequest request) and ensure the
correct javax.validation.Valid or jakarta.validation.Valid import is present at
the top of the file.

Comment on lines +5 to +8
public record AcademicTestNotificationRequest(
String title,
String body
) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

입력 검증이 누락되었습니다.

titlebody 필드에 검증 어노테이션이 없어 null이나 빈 문자열이 전달될 수 있습니다. 이는 Firebase 알림 전송 시 런타임 오류를 발생시킬 수 있습니다.

🔎 검증 추가를 위한 제안
 package com.kustacks.kuring.admin.adapter.in.web.dto;

 import com.kustacks.kuring.message.application.port.in.dto.AcademicTestNotificationCommand;
+import jakarta.validation.constraints.NotBlank;

 public record AcademicTestNotificationRequest(
+        @NotBlank(message = "제목은 필수입니다")
         String title,
+        @NotBlank(message = "본문은 필수입니다")
         String body
 ) {
     public AcademicTestNotificationCommand toCommand() {
         return new AcademicTestNotificationCommand(title, body);
     }
 }

API 컨트롤러에도 @Valid 어노테이션을 추가해야 합니다:

public ResponseEntity<BaseResponse<String>> sendAcademicTest(
        @Valid @RequestBody AcademicTestNotificationRequest request
)
🤖 Prompt for AI Agents
In
src/main/java/com/kustacks/kuring/admin/adapter/in/web/dto/AcademicTestNotificationRequest.java
around lines 5 to 8, the record lacks input validation for title and body
allowing null/empty values; add validation annotations (e.g. @NotBlank) to both
fields and import the appropriate javax.validation constraints so the request
object enforces non-null/non-empty strings, and update the API controller method
that receives this DTO to annotate the parameter with @Valid (and keep
@RequestBody) so Spring triggers validation on incoming requests.

Comment on lines 13 to 15
public boolean support(DeptInfo deptInfo) {
return !(deptInfo instanceof RealEstateDept);
return true;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 파일 구조 탐색
echo "=== 프로젝트 구조 ==="
find . -type f -name "*.java" | grep -E "(RealEstate|LatestPageNotice)" | head -20

echo -e "\n=== 전체 parser 관련 파일 목록 ==="
git ls-files | grep -E "parser.*notice" | head -20

Repository: ku-ring/ku-ring-backend-web

Length of output: 1344


🏁 Script executed:

#!/bin/bash
# RealEstateDept 정의 확인
echo "=== RealEstateDept.java 내용 ==="
cat -n ./src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/real_estate/RealEstateDept.java

echo -e "\n=== LatestPageNoticeHtmlParser.java 전체 내용 ==="
cat -n ./src/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParser.java

Repository: ku-ring/ku-ring-backend-web

Length of output: 3781


🏁 Script executed:

#!/bin/bash
# 테스트 파일 확인
echo "=== LatestPageNoticeApiClientTest.java 내용 ==="
cat -n ./src/test/java/com/kustacks/kuring/worker/scrap/client/notice/LatestPageNoticeApiClientTest.java

echo -e "\n=== 다른 parser 구현 비교 (NoticeHtmlParserTemplate 및 다른 구현체) ==="
rg -nA 20 "class.*NoticeHtmlParser" src/main/java/com/kustacks/kuring/worker/parser/notice/ --type=java

echo -e "\n=== DeptInfo와 support 메서드 사용처 ==="
rg -n "support\(" src/main/java/com/kustacks/kuring/ --type=java | grep -E "(support|DeptInfo)" | head -20

Repository: ku-ring/ku-ring-backend-web

Length of output: 11288


🏁 Script executed:

#!/bin/bash
# NoticeHtmlParserTemplate의 support 메서드 추상화 확인
echo "=== NoticeHtmlParserTemplate.java 전체 ==="
cat -n ./src/main/java/com/kustacks/kuring/worker/parser/notice/NoticeHtmlParserTemplate.java

echo -e "\n=== RealEstateDept HTML 구조 테스트 자료 확인 ==="
find . -type f -name "*.html" | grep -i real
find . -type f -name "*.html" | grep -i estate

echo -e "\n=== 테스트 리소스 전체 ==="
find ./src/test/resources -type f -name "*.html" | head -20

Repository: ku-ring/ku-ring-backend-web

Length of output: 3299


🏁 Script executed:

#!/bin/bash
# RealEstate 테스트 HTML 구조 확인
echo "=== realestate.html 첫 200줄 ==="
head -200 ./src/test/resources/notice/realestate.html

echo -e "\n=== 테이블 구조 검색 ==="
grep -n "board-table\|tbody\|notice" ./src/test/resources/notice/realestate.html | head -30

echo -e "\n=== onclick 속성 및 파싱 관련 요소 검색 ==="
grep -n "jf_viewArtcl\|onclick" ./src/test/resources/notice/realestate.html | head -20

Repository: ku-ring/ku-ring-backend-web

Length of output: 10128


🏁 Script executed:

#!/bin/bash
# RealEstate HTML 테이블 구조 상세 확인
echo "=== RealEstate HTML 테이블 행 구조 ==="
sed -n '180,250p' ./src/test/resources/notice/realestate.html

echo -e "\n=== 파서가 찾는 .board-table 클래스 존재 여부 ==="
grep "board-table" ./src/test/resources/notice/realestate.html

echo -e "\n=== CSE 테스트 HTML (다른 부서)와 비교 - 테이블 클래스 ==="
grep -n "board-table\|<table" ./src/test/resources/notice/cse-notice-2024.html | head -10

echo -e "\n=== CSE HTML의 실제 테이블 구조 ==="
sed -n '1,100p' ./src/test/resources/notice/cse-notice-2024.html | grep -A 30 "<table"

Repository: ku-ring/ku-ring-backend-web

Length of output: 1954


🏁 Script executed:

#!/bin/bash
# CSE HTML 테이블 구조 전체 확인
echo "=== CSE HTML 테이블 및 tbody 구조 ==="
sed -n '1,150p' ./src/test/resources/notice/cse-notice-2024.html | grep -A 50 '<table'

echo -e "\n=== RealEstate 파일에서 .notice 클래스 확인 ==="
grep -n "notice" ./src/test/resources/notice/realestate.html

echo -e "\n=== LatestPageNoticeHtmlParser 테스트 확인 ==="
find . -type f -name "*Test.java" | xargs grep -l "LatestPageNoticeHtmlParser\|LatestPageNoticeApiClient" 2>/dev/null

echo -e "\n=== 현재 사용 중인 parser 선택 로직 ==="
rg -n "filter.*support\(" src/main/ --type=java -A 2 -B 2

Repository: ku-ring/ku-ring-backend-web

Length of output: 5370


🏁 Script executed:

#!/bin/bash
# 공지 파서 선택 로직 확인
echo "=== NoticeScraper 또는 유사 파일 찾기 ==="
find . -type f -name "*Scraper.java" | head -20

echo -e "\n=== 공지 관련 scraper/client 파일 ==="
find . -type f -name "*Notice*.java" -path "*/scrap/*" | head -20

echo -e "\n=== NoticeHtmlParserTemplateTest 내용 ==="
cat -n ./src/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.java

echo -e "\n=== parser 선택 로직 찾기 ==="
rg -n "NoticeHtmlParser\|notice.*parser\|htmlParser" src/main/java/com/kustacks/kuring/worker/scrap/ --type=java | grep -i "filter\|stream\|select" | head -20

Repository: ku-ring/ku-ring-backend-web

Length of output: 17562


🏁 Script executed:

#!/bin/bash
# DepartmentNoticeScraperTemplate 확인
echo "=== DepartmentNoticeScraperTemplate.java ==="
cat -n ./src/main/java/com/kustacks/kuring/worker/scrap/DepartmentNoticeScraperTemplate.java

echo -e "\n=== RealEstate parser 현재 구현 확인 ==="
rg -n "class.*RealEstate" src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/real_estate/ --type=java -A 20 | head -80

Repository: ku-ring/ku-ring-backend-web

Length of output: 7397


🏁 Script executed:

#!/bin/bash
# CSE HTML 구조와 selectors 검증
echo "=== CSE HTML에서 .board-table 확인 ==="
grep -A 50 'class="board-table' ./src/test/resources/notice/cse-notice-2024.html | head -60

echo -e "\n=== .notice 클래스를 갖는 행 찾기 (CSE) ==="
grep -n 'class.*notice' ./src/test/resources/notice/cse-notice-2024.html

echo -e "\n=== onclick 속성과 jf_viewArtcl 확인 (CSE) ==="
grep -n "jf_viewArtcl\|onclick" ./src/test/resources/notice/cse-notice-2024.html | head -20

echo -e "\n=== DeptInfo의 parse 메서드 구현 ==="
rg -n "public.*RowsDto parse\|protected.*RowsDto parse" src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/ --type=java -A 5

Repository: ku-ring/ku-ring-backend-web

Length of output: 3627


🏁 Script executed:

#!/bin/bash
# DeptInfo parse 메서드 확인
echo "=== DeptInfo 클래스 확인 ==="
find . -type f -name "DeptInfo.java" -path "*/deptinfo/*"
cat -n ./src/main/java/com/kustacks/kuring/worker/scrap/deptinfo/DeptInfo.java | head -100

echo -e "\n=== RealEstate HTML 첫 번째 행의 링크 구조 ==="
sed -n '188,210p' ./src/test/resources/notice/realestate.html

echo -e "\n=== CSE HTML 첫 번째 중요 공지의 td 구조 ==="
sed -n '380,410p' ./src/test/resources/notice/cse-notice-2024.html

echo -e "\n=== RealEstate HTML 파싱 테스트 여부 ==="
grep -n "realestate\|RealEstate" src/test/java/com/kustacks/kuring/worker/parser/NoticeHtmlParserTemplateTest.java

Repository: ku-ring/ku-ring-backend-web

Length of output: 5997


🏁 Script executed:

#!/bin/bash
# RealEstate HTML 상세 구조 확인
echo "=== RealEstate HTML에서 tbody, tr, 클래스 구조 확인 ==="
sed -n '175,220p' ./src/test/resources/notice/realestate.html

echo -e "\n=== RealEstate의 td 개수 및 구조 ==="
sed -n '183,197p' ./src/test/resources/notice/realestate.html | grep -E "td|<strong|<a"

echo -e "\n=== CSE HTML의 td 개수 확인 ==="
sed -n '380,410p' ./src/test/resources/notice/cse-notice-2024.html | grep -E "td|<strong|<a"

echo -e "\n=== extractNoticeFromRow에서 사용하는 인덱스 확인 ==="
grep -A 15 "extractNoticeFromRow" ./src/main/java/com/kustacks/kuring/worker/parser/notice/LatestPageNoticeHtmlParser.java

echo -e "\n=== RealEstate가 사용할 수 있는 파서 있는지 확인 ==="
find src/main/java/com/kustacks/kuring/worker/parser -name "*HtmlParser.java" -exec grep -l "support" {} \; | xargs -I {} sh -c 'echo "=== {} ===" && grep -A 3 "public.*support"' {}

Repository: ku-ring/ku-ring-backend-web

Length of output: 2780


🏁 Script executed:

#!/bin/bash
# 최종 검증: RealEstate HTML 구조와 파서의 호환성 확인
echo "=== RealEstate HTML에 .board-table 클래스가 있는지 확인 ==="
grep -c "class=\"board-table" ./src/test/resources/notice/realestate.html || echo "Not found: 0 matches"

echo -e "\n=== RealEstate HTML 테이블 클래스 확인 ==="
grep -n "<table" ./src/test/resources/notice/realestate.html

echo -e "\n=== RealEstate 행의 정확한 td 구조 (첫 번째 행) ==="
python3 << 'EOF'
import re

with open('./src/test/resources/notice/realestate.html', 'r', encoding='utf-8') as f:
    content = f.read()

# Find first tr in tbody
tbody_match = re.search(r'<tbody>.*?<tr>(.*?)</tr>', content, re.DOTALL)
if tbody_match:
    first_row = tbody_match.group(1)
    tds = re.findall(r'<td[^>]*>(.*?)</td>', first_row, re.DOTALL)
    for i, td in enumerate(tds):
        print(f"td[{i}]: {td[:100]}...")
        
# Check for onclick attributes
onclick_matches = re.findall(r'onclick="[^"]*"', content)
print(f"\nonclick 속성 개수: {len(onclick_matches)}")
if onclick_matches:
    print(f"예시: {onclick_matches[0]}")

# Check for jf_viewArtcl
jf_matches = re.findall(r'jf_viewArtcl\([^)]*\)', content)
print(f"jf_viewArtcl 호출 개수: {len(jf_matches)}")
EOF

echo -e "\n=== CSE HTML와 RealEstate HTML 구조 비교 ==="
python3 << 'EOF'
import re

# CSE
with open('./src/test/resources/notice/cse-notice-2024.html', 'r', encoding='utf-8') as f:
    cse_content = f.read()
cse_table = re.search(r'<table[^>]*class="([^"]*)"', cse_content)
cse_jf = len(re.findall(r'jf_viewArtcl', cse_content))

# RealEstate
with open('./src/test/resources/notice/realestate.html', 'r', encoding='utf-8') as f:
    re_content = f.read()
re_table = re.search(r'<table[^>]*>', re_content)
re_jf = len(re.findall(r'jf_viewArtcl', re_content))

print(f"CSE table class: {cse_table.group(1) if cse_table else 'No class'}")
print(f"CSE jf_viewArtcl 개수: {cse_jf}")
print(f"\nRealEstate table: {re_table.group(0) if re_table else 'Not found'}")
print(f"RealEstate jf_viewArtcl 개수: {re_jf}")
EOF

Repository: ku-ring/ku-ring-backend-web

Length of output: 157


🏁 Script executed:

#!/bin/bash
# 간단한 확인
echo "=== RealEstate HTML에 board-table 클래스 있는지 ==="
grep "board-table" ./src/test/resources/notice/realestate.html && echo "FOUND" || echo "NOT FOUND"

echo -e "\n=== RealEstate HTML 테이블 태그 ==="
grep -m1 "<table" ./src/test/resources/notice/realestate.html

echo -e "\n=== RealEstate HTML에 jf_viewArtcl 있는지 ==="
grep "jf_viewArtcl" ./src/test/resources/notice/realestate.html && echo "FOUND" || echo "NOT FOUND"

echo -e "\n=== CSE에 jf_viewArtcl 있는지 ==="
grep -c "jf_viewArtcl" ./src/test/resources/notice/cse-notice-2024.html

echo -e "\n=== RealEstate 행의 정확한 td 개수 ==="
sed -n '183,202p' ./src/test/resources/notice/realestate.html | wc -l

Repository: ku-ring/ku-ring-backend-web

Length of output: 289


RealEstateDept HTML 구조와 파서 호환성 문제를 검증하세요.

RealEstateDept의 HTML 구조가 이 파서와 호환되지 않습니다. RealEstate 페이지에는 .board-table 클래스가 없고 jf_viewArtcl() 함수 호출도 없으며, 링크는 단순 href 속성만 포함합니다. 반면 LatestPageNoticeHtmlParser.board-table > tbody > tr 선택자와 onclick 속성의 jf_viewArtcl() 파싱에 의존합니다. support() 메서드가 return true로 변경되면 RealEstateDept가 이 파서를 사용하게 되어 파싱이 실패합니다.

파서 코드
    protected Elements selectImportantRows(Document document) {
        return document.select(".board-table > tbody > tr").select(".notice");
    }
    
    protected String[] extractNoticeFromRow(Element row) {
        Elements tds = row.getElementsByTag("td");
        String[] splitResults = tds.get(1).select("a").attr("onclick")
                .replace("jf_viewArtcl('", "")
                .replace("')", "")
                .split("', '");
        ...
    }

@rlagkswn00 rlagkswn00 changed the title version 2.16.1 version 2.16.2 Jan 4, 2026
@rlagkswn00 rlagkswn00 merged commit b653184 into main Jan 4, 2026
7 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Feb 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🚀 Deploy Production deploy production server

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants