-
Notifications
You must be signed in to change notification settings - Fork 3
feat : 알림, 커뮤니티 프론트 구현 및 동작 테스트 #157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- 수정, 삭제 권한을 프론트 측에서 알기 위해서 isAuthor 필드 추가
…re/front-comm-noti # Conflicts: # src/main/resources/templates/test-submit.html
|
""" Walkthrough이 변경사항은 실시간 알림 기능(웹소켓 기반)과 알림 UI를 헤더 및 별도 페이지에 추가하고, 토론글 및 댓글에 "작성자 여부"( Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Header as "Header (알림 벨)"
participant WS as "WebSocket(STOMP)"
participant Server as "Backend"
participant Modal as "알림 모달 UI"
User->>Header: 알림 벨 클릭
Header->>Modal: 알림 모달 표시/숨김
Note over Header,WS: 페이지 로드 시
Header->>WS: WebSocket 연결 및 구독
WS-->>Header: 실시간 알림 수신
Header->>Modal: 알림 목록 갱신/뱃지 표시
User->>Modal: "더보기" 클릭
Modal->>Server: REST로 알림 추가 조회
Server-->>Modal: 알림 데이터 반환
Modal->>Modal: 알림 목록 렌더링
sequenceDiagram
actor User
participant SubmitPage as "제출 페이지"
participant Server as "Backend"
User->>SubmitPage: 토론 탭 클릭
SubmitPage->>Server: 토론글 목록 조회 API
Server-->>SubmitPage: 토론글+isAuthor 반환
SubmitPage->>User: 토론글 목록 렌더링
User->>SubmitPage: 댓글/대댓글/투표/편집/삭제 등
SubmitPage->>Server: 해당 API 호출
Server-->>SubmitPage: 처리 결과 반환
SubmitPage->>User: UI 갱신
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
""" 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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 11
🧹 Nitpick comments (3)
src/main/resources/static/js/headerNotification.js (1)
3-5: 전역 변수 사용으로 인한 네임스페이스 오염 가능성이 있습니다.전역 변수들이 다른 스크립트와 충돌할 수 있습니다. 네임스페이스 객체 내에서 관리하는 것을 권장합니다.
-let headerStompClient; -let notifications = []; // 알림 목록 (최초 1페이지) -let unreadCount = 0; // 읽지 않은 알림 개수 +const HeaderNotification = { + stompClient: null, + notifications: [], + unreadCount: 0 +};src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/community/discussion/DiscussionQueryRepositoryImpl.java (1)
44-46: 사용되지 않는 변수들을 제거하세요.페이지네이션 관련 변수들(
offset,pageNumber,pageSize)이 선언되었지만 실제로는 사용되지 않고 있습니다. 코드의 가독성을 위해 제거하는 것이 좋습니다.- long offset = pageable.getOffset(); - int pageNumber = pageable.getPageNumber(); - int pageSize = pageable.getPageSize(); -src/main/resources/templates/test-notifications.html (1)
172-178: DOM 조작 시 메모리 누수 방지를 위한 개선이 필요합니다.
setTimeout을 사용한 애니메이션 클래스 제거 로직에서 요소가 이미 DOM에서 제거된 경우에 대한 체크가 없습니다.if (newlyAddedCount > 0) { setTimeout(() => { document.querySelectorAll('.page-notification-item.newly-added').forEach(el => { + if (el.parentNode) { el.classList.remove('newly-added'); + } }); }, 1200); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
src/main/java/org/ezcode/codetest/application/community/dto/response/DiscussionResponse.java(3 hunks)src/main/java/org/ezcode/codetest/application/community/dto/response/ReplyResponse.java(3 hunks)src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java(2 hunks)src/main/java/org/ezcode/codetest/domain/community/dto/DiscussionQueryResult.java(3 hunks)src/main/java/org/ezcode/codetest/domain/community/dto/ReplyQueryResult.java(3 hunks)src/main/java/org/ezcode/codetest/domain/community/exception/CommunityExceptionCode.java(1 hunks)src/main/java/org/ezcode/codetest/domain/community/service/ReplyDomainService.java(3 hunks)src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/community/discussion/DiscussionQueryRepositoryImpl.java(5 hunks)src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/community/reply/ReplyQueryRepositoryImpl.java(4 hunks)src/main/java/org/ezcode/codetest/presentation/view/ViewController.java(1 hunks)src/main/resources/static/html/header.html(5 hunks)src/main/resources/static/js/headerNotification.js(1 hunks)src/main/resources/static/js/headerUtils.js(1 hunks)src/main/resources/templates/search-page.html(2 hunks)src/main/resources/templates/test-main.html(2 hunks)src/main/resources/templates/test-mypage.html(2 hunks)src/main/resources/templates/test-notifications.html(1 hunks)src/main/resources/templates/test-problems.html(2 hunks)src/main/resources/templates/test-ranking.html(2 hunks)src/main/resources/templates/test-submit.html(5 hunks)
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: chat26666
PR: ezcode-my/backend#37
File: src/main/java/org/ezcode/codetest/infrastructure/elasticsearch/repository/ProblemElasticsearchRepositoryDsl.java:10-10
Timestamp: 2025-06-06T19:56:27.759Z
Learning: When user chat26666 mentions that code was implemented in a previous PR but explanation was missing, they may submit a follow-up PR just to add documentation/explanation without the actual implementation code.
src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java (1)
Learnt from: NCookies
PR: ezcode-my/backend#17
File: src/main/java/org/ezcode/codetest/presentation/problemmanagement/ProblemAdminController.java:25-25
Timestamp: 2025-06-02T06:57:37.929Z
Learning: 이 프로젝트에서는 아직 Spring Security가 적용되지 않아서 @PreAuthorize 어노테이션이 주석 처리되어 있음. Spring Security 도입 전까지는 권한 검증 어노테이션들이 비활성화 상태임.
src/main/resources/templates/search-page.html (1)
Learnt from: chat26666
PR: ezcode-my/backend#31
File: src/main/resources/templates/search-page.html:198-233
Timestamp: 2025-06-04T15:14:15.092Z
Learning: src/main/resources/templates/search-page.html은 임시용 뷰로, 곧 폐기될 예정임
src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/community/reply/ReplyQueryRepositoryImpl.java (1)
Learnt from: chat26666
PR: ezcode-my/backend#64
File: src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/game/EncounterChoiceRepositoryImpl.java:0-0
Timestamp: 2025-06-15T04:37:29.231Z
Learning: EncounterChoiceRepositoryImpl in src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/game/EncounterChoiceRepositoryImpl.java is intentionally a skeleton implementation that is work-in-progress and will be completed later.
src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/community/discussion/DiscussionQueryRepositoryImpl.java (1)
Learnt from: chat26666
PR: ezcode-my/backend#64
File: src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/game/EncounterChoiceRepositoryImpl.java:0-0
Timestamp: 2025-06-15T04:37:29.231Z
Learning: EncounterChoiceRepositoryImpl in src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/game/EncounterChoiceRepositoryImpl.java is intentionally a skeleton implementation that is work-in-progress and will be completed later.
src/main/resources/templates/test-problems.html (2)
Learnt from: thezz9
PR: ezcode-my/backend#114
File: src/main/java/org/ezcode/codetest/infrastructure/github/GitHubApiClient.java:159-165
Timestamp: 2025-06-28T02:03:38.693Z
Learning: User thezz9's service (ezcode) targets Korean users exclusively ("찐 한국인밖에 안 옴"), so Korean language hardcoding in commit messages and UI elements is intentional and appropriate for their use case.
Learnt from: thezz9
PR: ezcode-my/backend#73
File: src/main/resources/templates/submit-test.html:135-135
Timestamp: 2025-06-18T11:41:58.457Z
Learning: src/main/resources/templates/submit-test.html 파일은 테스트용 파일로, isCorrect 값의 하드코딩은 의도적이며 사용자가 UI 요소를 통해 정답/오답 상태를 선택할 수 있도록 개선 예정입니다.
src/main/resources/templates/test-notifications.html (1)
Learnt from: thezz9
PR: ezcode-my/backend#73
File: src/main/resources/templates/submit-test.html:135-135
Timestamp: 2025-06-18T11:41:58.457Z
Learning: src/main/resources/templates/submit-test.html 파일은 테스트용 파일로, isCorrect 값의 하드코딩은 의도적이며 사용자가 UI 요소를 통해 정답/오답 상태를 선택할 수 있도록 개선 예정입니다.
src/main/resources/templates/test-submit.html (2)
Learnt from: thezz9
PR: ezcode-my/backend#73
File: src/main/resources/templates/submit-test.html:135-135
Timestamp: 2025-06-18T11:41:58.457Z
Learning: src/main/resources/templates/submit-test.html 파일은 테스트용 파일로, isCorrect 값의 하드코딩은 의도적이며 사용자가 UI 요소를 통해 정답/오답 상태를 선택할 수 있도록 개선 예정입니다.
Learnt from: thezz9
PR: ezcode-my/backend#114
File: src/main/java/org/ezcode/codetest/infrastructure/github/GitHubApiClient.java:159-165
Timestamp: 2025-06-28T02:03:38.693Z
Learning: User thezz9's service (ezcode) targets Korean users exclusively ("찐 한국인밖에 안 옴"), so Korean language hardcoding in commit messages and UI elements is intentional and appropriate for their use case.
🔇 Additional comments (40)
src/main/java/org/ezcode/codetest/domain/community/exception/CommunityExceptionCode.java (1)
18-18: 댓글 계층 구조 검증을 위한 적절한 예외 코드 추가대댓글의 부모 댓글이 이미 존재하는 경우를 처리하는 예외 코드가 올바르게 추가되었습니다. 메시지가 명확하고 적절한 HTTP 상태 코드를 사용하여 기존 패턴과 일관성을 유지합니다.
src/main/java/org/ezcode/codetest/presentation/view/ViewController.java (1)
53-56: 알림 페이지를 위한 적절한 컨트롤러 메서드 추가알림 페이지를 제공하는 새로운 엔드포인트가 기존 패턴과 일관성 있게 추가되었습니다. 단순한 뷰 반환 로직으로 적절합니다.
src/main/resources/static/js/headerUtils.js (1)
9-11: 알림 WebSocket 연결 초기화 로직 추가헤더 로드 후 알림 관련 함수들을 안전하게 호출하는 로직이 추가되었습니다. 함수 존재 여부를 확인하여 오류를 방지하는 좋은 패턴입니다.
src/main/resources/templates/test-ranking.html (2)
9-10: 알림 기능을 위한 WebSocket 라이브러리 추가실시간 알림 기능을 위한 SockJS와 STOMP 라이브러리가 추가되었습니다. CDN에서 라이브러리를 로드하는 일반적인 방법을 사용합니다.
219-219: 알림 처리 스크립트 추가헤더 알림 기능을 위한 JavaScript 파일이 추가되었습니다. 다른 페이지들과 일관성 있는 패턴으로 추가되었습니다.
src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java (2)
40-40: 코드 정리 및 불필요한 빈 줄 제거코드 가독성을 위한 적절한 정리 작업입니다.
81-81: 언어 API 공개 엔드포인트 추가
/api/languages엔드포인트를 공개 GET 요청으로 추가한 것은 적절합니다. 언어 목록은 일반적으로 인증 없이도 접근 가능해야 하는 리소스입니다.src/main/resources/templates/test-main.html (2)
10-11: WebSocket 라이브러리 추가 승인실시간 알림 기능을 위한 SockJS와 STOMP.js 라이브러리가 적절히 추가되었습니다. 버전이 고정되어 있어 안정성이 보장됩니다.
32-32: 알림 기능 스크립트 추가 승인헤더 알림 기능을 위한 스크립트가 적절한 위치에 추가되었습니다. 다른 템플릿들과 일관성을 유지하고 있습니다.
src/main/resources/templates/search-page.html (2)
8-9: WebSocket 라이브러리 추가 승인실시간 알림 기능을 위한 SockJS와 STOMP.js 라이브러리가 추가되었습니다. 다만 이 파일은 임시용으로 곧 폐기될 예정이므로 우선순위가 낮습니다.
313-314: 알림 및 푸터 기능 스크립트 추가 승인헤더 알림 기능과 푸터 로드 기능이 적절히 추가되었습니다. 다른 템플릿들과 일관성을 유지하고 있습니다.
Also applies to: 317-317
src/main/java/org/ezcode/codetest/domain/community/service/ReplyDomainService.java (2)
32-32: 댓글 중복 부모 검증 로직 추가 승인대댓글의 대댓글을 방지하는 검증 로직이 적절히 추가되었습니다. 댓글 계층 구조를 2단계로 제한하는 것은 UX 관점에서 합리적입니다.
110-115: 검증 메서드 구현 승인
checkNoExistingParentForReply메서드가 명확하고 간결하게 구현되었습니다. 예외 처리도 적절하며 메서드명이 의도를 명확히 나타냅니다.src/main/resources/templates/test-problems.html (3)
11-12: WebSocket 라이브러리 추가 승인실시간 알림 기능을 위한 SockJS와 STOMP.js 라이브러리가 적절히 추가되었습니다. 다른 템플릿들과 일관성을 유지하고 있습니다.
481-481: 알림 기능 스크립트 추가 승인헤더 알림 기능을 위한 스크립트가 적절한 위치에 추가되었습니다. 전체적인 알림 시스템 구현에 일관성을 보여줍니다.
5-5: 코드 포맷팅 개선 승인메타 태그와 Google Fonts 링크의 포맷팅이 개선되어 가독성이 향상되었습니다.
Also applies to: 9-10
src/main/resources/templates/test-mypage.html (2)
10-11: WebSocket 라이브러리 추가 승인실시간 알림 기능을 위한 SockJS와 STOMP.js 라이브러리가 적절히 추가되었습니다. 전체 애플리케이션의 알림 시스템 일관성을 유지하고 있습니다.
82-83: 알림 및 푸터 기능 스크립트 추가 승인헤더 알림 기능과 푸터 로드 기능이 적절히 추가되었습니다. 마이페이지의 사용자 경험이 향상될 것으로 예상됩니다.
Also applies to: 86-86
src/main/java/org/ezcode/codetest/application/community/dto/response/DiscussionResponse.java (3)
42-46: 작성자 여부 필드 추가가 적절하게 구현되었습니다.
isAuthor필드가 적절한 Swagger 문서화와 함께 추가되었고, 기존 구조와 일관성 있게 구현되었습니다.
49-62: 팩토리 메서드 업데이트가 올바르게 구현되었습니다.
fromEntity메서드에서isAuthor를false로 기본값 설정하는 것이 합리적입니다.
64-77: 쿼리 결과 기반 팩토리 메서드가 올바르게 구현되었습니다.
DiscussionQueryResult에서isAuthor()메서드를 통해 값을 가져오는 것이 적절합니다.src/main/java/org/ezcode/codetest/domain/community/dto/DiscussionQueryResult.java (3)
33-33: 작성자 여부 필드가 올바르게 추가되었습니다.
boolean isAuthor필드가 적절히 선언되었습니다.
45-45: 생성자 파라미터 추가가 올바르게 구현되었습니다.
isAuthor파라미터가 기존 파라미터들과 일관성 있게 추가되었습니다.
65-65: 필드 할당이 올바르게 구현되었습니다.생성자에서
isAuthor필드에 파라미터 값을 올바르게 할당하고 있습니다.src/main/java/org/ezcode/codetest/domain/community/dto/ReplyQueryResult.java (3)
35-35: 작성자 여부 필드가 일관성 있게 추가되었습니다.
DiscussionQueryResult와 동일한 패턴으로isAuthor필드가 추가되어 일관성이 유지되었습니다.
48-50: 생성자 파라미터 업데이트가 올바르게 구현되었습니다.
isAuthor파라미터가 적절한 위치에 추가되었습니다.
70-70: 필드 할당이 올바르게 구현되었습니다.생성자에서
isAuthor필드 할당이 올바르게 처리되었습니다.src/main/java/org/ezcode/codetest/application/community/dto/response/ReplyResponse.java (3)
47-48: 작성자 여부 필드가 적절하게 추가되었습니다.
DiscussionResponse와 동일한 패턴으로isAuthor필드가 추가되어 일관성이 유지되었습니다.
51-70: 팩토리 메서드 업데이트가 올바르게 구현되었습니다.
fromEntity메서드에서isAuthor를false로 기본값 설정하는 것이 합리적입니다.
72-87: 쿼리 결과 기반 팩토리 메서드가 올바르게 구현되었습니다.
ReplyQueryResult에서isAuthor()메서드를 통해 값을 가져오는 방식이 적절합니다.src/main/resources/static/js/headerNotification.js (2)
65-74: 시간 포맷팅 함수가 올바르게 구현되었습니다.날짜 파싱과 상대 시간 계산이 적절히 구현되었습니다.
76-92: 이벤트 리스너 바인딩이 적절하게 구현되었습니다.클릭 이벤트 처리와 모달 토글 로직이 올바르게 구현되었습니다.
src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/community/discussion/DiscussionQueryRepositoryImpl.java (2)
52-52: 논리적 삭제 필터링이 적절히 구현되었습니다.
isDeleted.isFalse()조건을 통해 삭제된 토론글을 올바르게 필터링하고 있으며, 목록 조회와 카운트 쿼리 모두에 일관되게 적용되어 있습니다.Also applies to: 126-126
73-73: 작성자 여부 판단 로직이 올바르게 구현되었습니다.현재 사용자 ID와 토론글 작성자 ID를 비교하여
isAuthor값을 설정하는 로직이 적절하며, null 체크도 포함되어 있어 안전합니다.Also applies to: 78-78, 81-81, 99-100
src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/community/reply/ReplyQueryRepositoryImpl.java (2)
69-69: 작성자 여부 판단 로직이 일관되게 잘 구현되었습니다.토론글 repository와 동일한 패턴으로
isAuthor로직이 구현되어 있으며, null 체크와 함께 안전하게 처리되고 있습니다. projection에도 올바르게 포함되어 있습니다.Also applies to: 74-74, 77-77, 96-97
103-103: 논리적 삭제 필터링이 모든 관련 쿼리에 일관되게 적용되었습니다.댓글 조회, 토론별 댓글 카운트, 부모 댓글별 카운트 쿼리 모두에
isDeleted.isFalse()조건이 추가되어 삭제된 댓글이 올바르게 제외됩니다.Also applies to: 116-116, 125-125
src/main/resources/static/html/header.html (2)
75-93: 알림 UI 스타일링이 잘 구현되었습니다.알림 버튼의 읽지 않은 상태 표시, 모달 스타일링, 호버 효과 등이 일관된 디자인 시스템으로 잘 구현되어 있습니다. 접근성과 사용자 경험을 고려한 스타일입니다.
Also applies to: 95-151
163-170: 알림 기능 HTML 구조가 적절합니다.SVG 아이콘을 사용한 알림 버튼과 모달 구조가 시맨틱하고 접근 가능하게 구현되어 있습니다. ID와 클래스명도 명확하게 명명되었습니다.
Also applies to: 253-256
src/main/resources/templates/test-submit.html (2)
275-736: CSS 스타일이 포괄적으로 잘 구현되었습니다.탭 인터페이스, 토론 목록, 투표 버튼, 페이지네이션 등 모든 UI 요소에 대한 스타일이 일관된 디자인 시스템으로 구현되어 있습니다. 호버 효과와 상태 변화도 적절히 처리되었습니다.
744-771: 탭 기반 UI 구조가 적절히 구현되었습니다.문제 설명과 토론글을 분리한 탭 구조가 사용자 경험을 개선하며, 데이터 속성을 활용한 탭 연결도 깔끔하게 구현되었습니다.
| // 최초 알림 목록 요청 (REST API) | ||
| fetch('/api/notifications?page=0', { | ||
| headers: { 'Authorization': sessionStorage.getItem('accessToken') } | ||
| }); | ||
| }, err => console.error('[STOMP] 연결 오류:', err)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REST API 호출에 오류 처리가 누락되었습니다.
네트워크 오류나 서버 오류에 대한 처리가 없습니다.
- fetch('/api/notifications?page=0', {
- headers: { 'Authorization': sessionStorage.getItem('accessToken') }
- });
+ fetch('/api/notifications?page=0', {
+ headers: { 'Authorization': sessionStorage.getItem('accessToken') }
+ }).catch(error => {
+ console.error('알림 목록 조회 오류:', error);
+ });📝 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.
| // 최초 알림 목록 요청 (REST API) | |
| fetch('/api/notifications?page=0', { | |
| headers: { 'Authorization': sessionStorage.getItem('accessToken') } | |
| }); | |
| }, err => console.error('[STOMP] 연결 오류:', err)); | |
| } | |
| // 최초 알림 목록 요청 (REST API) | |
| fetch('/api/notifications?page=0', { | |
| headers: { 'Authorization': sessionStorage.getItem('accessToken') } | |
| }).catch(error => { | |
| console.error('알림 목록 조회 오류:', error); | |
| }); | |
| }, err => console.error('[STOMP] 연결 오류:', err)); | |
| } |
🤖 Prompt for AI Agents
In src/main/resources/static/js/headerNotification.js around lines 30 to 35, the
fetch call to the REST API lacks error handling for network or server errors.
Add a .then() block to check the response status and handle non-OK responses
appropriately, and include a .catch() block to catch and log any fetch errors.
This will ensure that errors during the API call are properly detected and
handled.
| headerStompClient.subscribe('/user/queue/notifications', msg => { | ||
| const data = JSON.parse(msg.body); | ||
| notifications = data.content || []; | ||
| unreadCount = notifications.filter(n => !n.isRead).length; | ||
| updateNotificationUI(); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
알림 목록 구독에서도 오류 처리가 필요합니다.
JSON 파싱 실패와 데이터 구조 검증이 누락되었습니다.
- headerStompClient.subscribe('/user/queue/notifications', msg => {
- const data = JSON.parse(msg.body);
- notifications = data.content || [];
- unreadCount = notifications.filter(n => !n.isRead).length;
- updateNotificationUI();
- });
+ headerStompClient.subscribe('/user/queue/notifications', msg => {
+ try {
+ const data = JSON.parse(msg.body);
+ if (data && Array.isArray(data.content)) {
+ notifications = data.content;
+ unreadCount = notifications.filter(n => !n.isRead).length;
+ updateNotificationUI();
+ }
+ } catch (error) {
+ console.error('알림 목록 파싱 오류:', error);
+ }
+ });📝 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.
| headerStompClient.subscribe('/user/queue/notifications', msg => { | |
| const data = JSON.parse(msg.body); | |
| notifications = data.content || []; | |
| unreadCount = notifications.filter(n => !n.isRead).length; | |
| updateNotificationUI(); | |
| }); | |
| headerStompClient.subscribe('/user/queue/notifications', msg => { | |
| try { | |
| const data = JSON.parse(msg.body); | |
| if (data && Array.isArray(data.content)) { | |
| notifications = data.content; | |
| unreadCount = notifications.filter(n => !n.isRead).length; | |
| updateNotificationUI(); | |
| } | |
| } catch (error) { | |
| console.error('알림 목록 파싱 오류:', error); | |
| } | |
| }); |
🤖 Prompt for AI Agents
In src/main/resources/static/js/headerNotification.js around lines 23 to 28, the
subscription callback lacks error handling for JSON parsing and data structure
validation. Wrap the JSON.parse call in a try-catch block to handle parsing
errors gracefully. After parsing, verify that the data object has the expected
structure before accessing its properties to prevent runtime errors. Add
appropriate fallback or error logging if the data is invalid.
| headerStompClient.subscribe('/user/queue/notification', msg => { | ||
| const notif = JSON.parse(msg.body); | ||
| notifications.unshift(notif); // 새 알림 맨 앞에 추가 | ||
| if (!notif.isRead) unreadCount++; | ||
| updateNotificationUI(); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
메시지 파싱 중 오류 처리가 누락되었습니다.
JSON 파싱 실패나 잘못된 메시지 구조로 인한 오류가 발생할 수 있습니다.
- headerStompClient.subscribe('/user/queue/notification', msg => {
- const notif = JSON.parse(msg.body);
- notifications.unshift(notif); // 새 알림 맨 앞에 추가
- if (!notif.isRead) unreadCount++;
- updateNotificationUI();
- });
+ headerStompClient.subscribe('/user/queue/notification', msg => {
+ try {
+ const notif = JSON.parse(msg.body);
+ if (notif && typeof notif === 'object') {
+ notifications.unshift(notif);
+ if (!notif.isRead) unreadCount++;
+ updateNotificationUI();
+ }
+ } catch (error) {
+ console.error('알림 메시지 파싱 오류:', error);
+ }
+ });📝 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.
| headerStompClient.subscribe('/user/queue/notification', msg => { | |
| const notif = JSON.parse(msg.body); | |
| notifications.unshift(notif); // 새 알림 맨 앞에 추가 | |
| if (!notif.isRead) unreadCount++; | |
| updateNotificationUI(); | |
| }); | |
| headerStompClient.subscribe('/user/queue/notification', msg => { | |
| try { | |
| const notif = JSON.parse(msg.body); | |
| if (notif && typeof notif === 'object') { | |
| notifications.unshift(notif); // 새 알림 맨 앞에 추가 | |
| if (!notif.isRead) unreadCount++; | |
| updateNotificationUI(); | |
| } | |
| } catch (error) { | |
| console.error('알림 메시지 파싱 오류:', error); | |
| } | |
| }); |
🤖 Prompt for AI Agents
In src/main/resources/static/js/headerNotification.js around lines 15 to 20, the
JSON.parse call on msg.body lacks error handling, which can cause runtime errors
if the message is malformed. Wrap the JSON.parse call in a try-catch block to
catch parsing errors, and handle them gracefully, such as logging the error and
skipping the faulty message to prevent the application from crashing.
| listEl.innerHTML = notifications.map(n => ` | ||
| <div class="notification-item${n.isRead ? '' : ' unread'}"> | ||
| <div class="message">${n.message}</div> | ||
| <div class="time">${formatTime(n.createdAt)}</div> | ||
| </div> | ||
| `).join(''); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XSS 취약점이 존재합니다.
innerHTML을 사용하여 알림 메시지를 직접 삽입하고 있어 XSS 공격에 취약합니다.
- listEl.innerHTML = notifications.map(n => `
- <div class="notification-item${n.isRead ? '' : ' unread'}">
- <div class="message">${n.message}</div>
- <div class="time">${formatTime(n.createdAt)}</div>
- </div>
- `).join('');
+ listEl.innerHTML = notifications.map(n => {
+ const div = document.createElement('div');
+ div.className = `notification-item${n.isRead ? '' : ' unread'}`;
+
+ const messageDiv = document.createElement('div');
+ messageDiv.className = 'message';
+ messageDiv.textContent = n.message; // XSS 방지
+
+ const timeDiv = document.createElement('div');
+ timeDiv.className = 'time';
+ timeDiv.textContent = formatTime(n.createdAt);
+
+ div.appendChild(messageDiv);
+ div.appendChild(timeDiv);
+ return div.outerHTML;
+ }).join('');📝 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.
| listEl.innerHTML = notifications.map(n => ` | |
| <div class="notification-item${n.isRead ? '' : ' unread'}"> | |
| <div class="message">${n.message}</div> | |
| <div class="time">${formatTime(n.createdAt)}</div> | |
| </div> | |
| `).join(''); | |
| listEl.innerHTML = notifications.map(n => { | |
| const div = document.createElement('div'); | |
| div.className = `notification-item${n.isRead ? '' : ' unread'}`; | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = 'message'; | |
| messageDiv.textContent = n.message; // XSS 방지 | |
| const timeDiv = document.createElement('div'); | |
| timeDiv.className = 'time'; | |
| timeDiv.textContent = formatTime(n.createdAt); | |
| div.appendChild(messageDiv); | |
| div.appendChild(timeDiv); | |
| return div.outerHTML; | |
| }).join(''); |
🤖 Prompt for AI Agents
In src/main/resources/static/js/headerNotification.js around lines 57 to 62, the
code uses innerHTML to insert notification messages directly, which exposes an
XSS vulnerability. To fix this, avoid using innerHTML for inserting untrusted
content; instead, create DOM elements programmatically and set text content
using textContent or similar safe methods to ensure messages are properly
escaped and not interpreted as HTML.
| window.connectNotificationWS = function() { | ||
| const tokenOnly = (sessionStorage.getItem('accessToken') || '').replace(/^Bearer /, ''); | ||
| if (!tokenOnly) return; | ||
| const socket = new SockJS(`/ws?token=${encodeURIComponent(tokenOnly)}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
토큰 추출 및 검증 로직을 개선해야 합니다.
토큰 존재 여부만 확인하고 있으나, 토큰 형식이나 만료 여부에 대한 검증이 부족합니다.
- const tokenOnly = (sessionStorage.getItem('accessToken') || '').replace(/^Bearer /, '');
- if (!tokenOnly) return;
+ const token = sessionStorage.getItem('accessToken');
+ if (!token || token.trim() === '') {
+ console.warn('알림 서비스: 액세스 토큰이 없습니다.');
+ return;
+ }
+ const tokenOnly = token.replace(/^Bearer /, '');📝 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.
| window.connectNotificationWS = function() { | |
| const tokenOnly = (sessionStorage.getItem('accessToken') || '').replace(/^Bearer /, ''); | |
| if (!tokenOnly) return; | |
| const socket = new SockJS(`/ws?token=${encodeURIComponent(tokenOnly)}`); | |
| window.connectNotificationWS = function() { | |
| const token = sessionStorage.getItem('accessToken'); | |
| if (!token || token.trim() === '') { | |
| console.warn('알림 서비스: 액세스 토큰이 없습니다.'); | |
| return; | |
| } | |
| const tokenOnly = token.replace(/^Bearer /, ''); | |
| const socket = new SockJS(`/ws?token=${encodeURIComponent(tokenOnly)}`); |
🤖 Prompt for AI Agents
In src/main/resources/static/js/headerNotification.js around lines 7 to 10, the
current code only checks if the token exists but does not validate its format or
expiration. Enhance the token validation by verifying the token structure (e.g.,
JWT format) and checking its expiration before proceeding. Implement a function
to decode and validate the token payload and ensure the token is still valid
before establishing the WebSocket connection.
| function connectPageNotificationWS() { | ||
| const tokenOnly = (sessionStorage.getItem('accessToken') || '').replace(/^Bearer /, ''); | ||
| if (!tokenOnly) return; | ||
| const socket = new SockJS(`/ws?token=${encodeURIComponent(tokenOnly)}`); | ||
| pageStompClient = Stomp.over(socket); | ||
|
|
||
| pageStompClient.connect({}, () => { | ||
| pageStompClient.subscribe('/user/queue/notifications', msg => { | ||
| const data = JSON.parse(msg.body); | ||
| pageData = { | ||
| page: data.page ?? 0, | ||
| size: data.size ?? 10, | ||
| totalElements: data.totalElements ?? 0, | ||
| totalPages: data.totalPages ?? Math.ceil((data.totalElements ?? 0) / (data.size ?? 10)) | ||
| }; | ||
| let newlyAddedCount = 0; | ||
| if (pageData.page === 0) { | ||
| allNotifications = data.content || []; | ||
| newlyAddedCount = allNotifications.length; | ||
| } else { | ||
| newlyAddedCount = (data.content || []).length; | ||
| allNotifications = allNotifications.concat(data.content || []); | ||
| } | ||
| renderNotificationList(allNotifications, newlyAddedCount); | ||
| updateMoreBtn(); | ||
| isLoading = false; | ||
| }); | ||
| fetchNotifications(0); | ||
| }); | ||
| } | ||
|
|
||
| function fetchNotifications(page) { | ||
| if (isLoading) return; | ||
| isLoading = true; | ||
| fetch(`/api/notifications?page=${page}`, { | ||
| headers: { 'Authorization': sessionStorage.getItem('accessToken') } | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
WebSocket 연결 및 API 호출에서 에러 핸들링이 부족합니다.
WebSocket 연결 실패나 API 호출 실패에 대한 에러 핸들링이 없어서 사용자에게 적절한 피드백을 제공하지 못할 수 있습니다.
에러 핸들링을 추가하세요:
pageStompClient.connect({}, () => {
// success callback
+ }, (error) => {
+ console.error('WebSocket 연결 실패:', error);
+ // 사용자에게 알림 표시 로직 추가
});
function fetchNotifications(page) {
if (isLoading) return;
isLoading = true;
- fetch(`/api/notifications?page=${page}`, {
+ fetch(`/api/notifications?page=${page}`, {
headers: { 'Authorization': sessionStorage.getItem('accessToken') }
- });
+ }).catch(error => {
+ console.error('알림 조회 실패:', error);
+ isLoading = false;
+ // 에러 메시지 표시
+ });
}📝 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.
| function connectPageNotificationWS() { | |
| const tokenOnly = (sessionStorage.getItem('accessToken') || '').replace(/^Bearer /, ''); | |
| if (!tokenOnly) return; | |
| const socket = new SockJS(`/ws?token=${encodeURIComponent(tokenOnly)}`); | |
| pageStompClient = Stomp.over(socket); | |
| pageStompClient.connect({}, () => { | |
| pageStompClient.subscribe('/user/queue/notifications', msg => { | |
| const data = JSON.parse(msg.body); | |
| pageData = { | |
| page: data.page ?? 0, | |
| size: data.size ?? 10, | |
| totalElements: data.totalElements ?? 0, | |
| totalPages: data.totalPages ?? Math.ceil((data.totalElements ?? 0) / (data.size ?? 10)) | |
| }; | |
| let newlyAddedCount = 0; | |
| if (pageData.page === 0) { | |
| allNotifications = data.content || []; | |
| newlyAddedCount = allNotifications.length; | |
| } else { | |
| newlyAddedCount = (data.content || []).length; | |
| allNotifications = allNotifications.concat(data.content || []); | |
| } | |
| renderNotificationList(allNotifications, newlyAddedCount); | |
| updateMoreBtn(); | |
| isLoading = false; | |
| }); | |
| fetchNotifications(0); | |
| }); | |
| } | |
| function fetchNotifications(page) { | |
| if (isLoading) return; | |
| isLoading = true; | |
| fetch(`/api/notifications?page=${page}`, { | |
| headers: { 'Authorization': sessionStorage.getItem('accessToken') } | |
| }); | |
| } | |
| function connectPageNotificationWS() { | |
| const tokenOnly = (sessionStorage.getItem('accessToken') || '').replace(/^Bearer /, ''); | |
| if (!tokenOnly) return; | |
| const socket = new SockJS(`/ws?token=${encodeURIComponent(tokenOnly)}`); | |
| pageStompClient = Stomp.over(socket); | |
| pageStompClient.connect({}, () => { | |
| pageStompClient.subscribe('/user/queue/notifications', msg => { | |
| const data = JSON.parse(msg.body); | |
| pageData = { | |
| page: data.page ?? 0, | |
| size: data.size ?? 10, | |
| totalElements: data.totalElements ?? 0, | |
| totalPages: data.totalPages ?? Math.ceil((data.totalElements ?? 0) / (data.size ?? 10)) | |
| }; | |
| let newlyAddedCount = 0; | |
| if (pageData.page === 0) { | |
| allNotifications = data.content || []; | |
| newlyAddedCount = allNotifications.length; | |
| } else { | |
| newlyAddedCount = (data.content || []).length; | |
| allNotifications = allNotifications.concat(data.content || []); | |
| } | |
| renderNotificationList(allNotifications, newlyAddedCount); | |
| updateMoreBtn(); | |
| isLoading = false; | |
| }); | |
| fetchNotifications(0); | |
| }, (error) => { | |
| console.error('WebSocket 연결 실패:', error); | |
| // 사용자에게 알림 표시 로직 추가 | |
| }); | |
| } | |
| function fetchNotifications(page) { | |
| if (isLoading) return; | |
| isLoading = true; | |
| fetch(`/api/notifications?page=${page}`, { | |
| headers: { 'Authorization': sessionStorage.getItem('accessToken') } | |
| }).catch(error => { | |
| console.error('알림 조회 실패:', error); | |
| isLoading = false; | |
| // 에러 메시지 표시 | |
| }); | |
| } |
🤖 Prompt for AI Agents
In src/main/resources/templates/test-notifications.html between lines 119 and
156, the WebSocket connection and fetch API calls lack error handling, which can
lead to silent failures and poor user feedback. Add error callbacks to the
WebSocket connection to handle connection failures and subscription errors, and
include catch blocks or then handlers on the fetchNotifications API call to
handle HTTP errors or network issues. Provide appropriate user feedback or
logging in these error handlers to improve robustness.
| box.querySelector('.edit-save-btn').onclick = async () => { | ||
| const newContent = box.querySelector('.edit-input-text').value.trim(); | ||
| if (!newContent) return alert('내용을 입력하세요.'); | ||
| let url, method = 'PUT', payload = { languageId: 1, content: newContent }; | ||
| if (item.classList.contains('discussion-item')) { | ||
| url = `/api/problems/${window.problemId}/discussions/${e.target.dataset.id}`; | ||
| } else { | ||
| const discussionId = item.closest('.discussion-item').dataset.discussionId; | ||
| url = `/api/problems/${window.problemId}/discussions/${discussionId}/replies/${e.target.dataset.id}`; | ||
| } | ||
| const res = await fetch(url, { | ||
| method, | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| ...tokenHeader() | ||
| }, | ||
| body: JSON.stringify(payload) | ||
| }); | ||
| if (res.ok) { | ||
| // 성공: 목록 새로고침 | ||
| if (item.classList.contains('discussion-item')) { | ||
| loadDiscussions(window.problemId, 0, 10, document.getElementById('discussion-sort')?.value || 'best'); | ||
| } else { | ||
| const discussionId = item.closest('.discussion-item').dataset.discussionId; | ||
| const container = document.getElementById(`replies-for-${discussionId}`); | ||
| if (container) { | ||
| container.dataset.loaded = ''; | ||
| const replies = await fetchReplies(discussionId); | ||
| renderReplies(container, replies, discussionId, 0); | ||
| } | ||
| } | ||
| } else { | ||
| alert('수정에 실패했습니다.'); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
수정/삭제 기능에서 에러 핸들링과 사용자 피드백이 부족합니다.
API 호출 실패 시 단순히 alert만 표시하고 있으며, 로딩 상태나 상세한 에러 정보를 제공하지 않습니다.
더 나은 에러 핸들링과 사용자 피드백을 추가하세요:
const res = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
...tokenHeader()
},
body: JSON.stringify(payload)
});
- if (res.ok) {
+ if (res.ok) {
// 성공 로직
+ } else {
+ const errorData = await res.json();
+ const message = errorData.message || '수정에 실패했습니다.';
+ alert(message);
} else {
- alert('수정에 실패했습니다.');
}Also applies to: 1402-1425
🤖 Prompt for AI Agents
In src/main/resources/templates/test-submit.html around lines 1355 to 1389 and
similarly at 1402 to 1425, the API call error handling only shows a simple alert
without detailed error info or loading state feedback. Enhance the code by
adding a loading indicator before the fetch call and removing it after
completion. Catch fetch errors explicitly and display detailed error messages to
the user. Also, disable the save button during the operation to prevent
duplicate submissions and re-enable it afterward.
| document.addEventListener('click', async function(e) { | ||
| // 댓글/대댓글 추천/비추천 (reply-item 먼저!) | ||
| if (e.target.closest('.upvote-btn') && e.target.closest('.reply-item')) { | ||
| const btn = e.target.closest('.upvote-btn'); | ||
| const replyId = btn.dataset.id; | ||
| const discussionId = btn.closest('.discussion-item').dataset.discussionId; | ||
| const voteStatus = btn.dataset.votestatus; | ||
| await voteReply(discussionId, replyId, voteStatus === 'UP' ? 'NONE' : 'UP'); | ||
| return; | ||
| } | ||
| if (e.target.closest('.downvote-btn') && e.target.closest('.reply-item')) { | ||
| const btn = e.target.closest('.downvote-btn'); | ||
| const replyId = btn.dataset.id; | ||
| const discussionId = btn.closest('.discussion-item').dataset.discussionId; | ||
| const voteStatus = btn.dataset.votestatus; | ||
| await voteReply(discussionId, replyId, voteStatus === 'DOWN' ? 'NONE' : 'DOWN'); | ||
| return; | ||
| } | ||
| // 토론글 추천/비추천 (discussion-item는 reply-item보다 나중에 체크) | ||
| if (e.target.closest('.upvote-btn') && e.target.closest('.discussion-item')) { | ||
| const btn = e.target.closest('.upvote-btn'); | ||
| const discussionId = btn.dataset.id; | ||
| const voteStatus = btn.dataset.votestatus; | ||
| await voteDiscussion(discussionId, voteStatus === 'UP' ? 'NONE' : 'UP'); | ||
| return; | ||
| } | ||
| if (e.target.closest('.downvote-btn') && e.target.closest('.discussion-item')) { | ||
| const btn = e.target.closest('.downvote-btn'); | ||
| const discussionId = btn.dataset.id; | ||
| const voteStatus = btn.dataset.votestatus; | ||
| await voteDiscussion(discussionId, voteStatus === 'DOWN' ? 'NONE' : 'DOWN'); | ||
| return; | ||
| } | ||
| // 댓글 달기 | ||
| if (e.target.classList.contains('reply-btn') && !e.target.closest('.reply-item')) { | ||
| const discussionId = e.target.dataset.id; | ||
| showReplyInput(discussionId, null, e.target); | ||
| } | ||
| // 대댓글 달기 | ||
| if (e.target.classList.contains('reply-btn') && e.target.closest('.reply-item')) { | ||
| const replyId = e.target.dataset.id; | ||
| const discussionId = e.target.closest('.discussion-item').dataset.discussionId; | ||
| showReplyInput(discussionId, replyId, e.target); | ||
| } | ||
| // 토론글/댓글/대댓글 수정 | ||
| if (e.target.classList.contains('edit-btn')) { | ||
| const item = e.target.closest('.discussion-item, .reply-item'); | ||
| const contentEl = item.querySelector('.discussion-content'); | ||
| const oldContent = contentEl.textContent; | ||
| // 이미 수정창이 열려있으면 return | ||
| if (item.querySelector('.edit-input-box')) return; | ||
| // textarea + 취소/저장 버튼 | ||
| const box = document.createElement('div'); | ||
| box.className = 'edit-input-box'; | ||
| box.innerHTML = ` | ||
| <textarea class="edit-input-text" style="width:100%;min-height:60px;">${oldContent}</textarea> | ||
| <div style="display:flex;gap:8px;justify-content:flex-end;"> | ||
| <button class="edit-cancel-btn">취소</button> | ||
| <button class="edit-save-btn">저장</button> | ||
| </div> | ||
| `; | ||
| contentEl.style.display = 'none'; | ||
| contentEl.after(box); | ||
|
|
||
| // 취소 | ||
| box.querySelector('.edit-cancel-btn').onclick = () => { | ||
| box.remove(); | ||
| contentEl.style.display = ''; | ||
| }; | ||
|
|
||
| // 저장 | ||
| box.querySelector('.edit-save-btn').onclick = async () => { | ||
| const newContent = box.querySelector('.edit-input-text').value.trim(); | ||
| if (!newContent) return alert('내용을 입력하세요.'); | ||
| let url, method = 'PUT', payload = { languageId: 1, content: newContent }; | ||
| if (item.classList.contains('discussion-item')) { | ||
| url = `/api/problems/${window.problemId}/discussions/${e.target.dataset.id}`; | ||
| } else { | ||
| const discussionId = item.closest('.discussion-item').dataset.discussionId; | ||
| url = `/api/problems/${window.problemId}/discussions/${discussionId}/replies/${e.target.dataset.id}`; | ||
| } | ||
| const res = await fetch(url, { | ||
| method, | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| ...tokenHeader() | ||
| }, | ||
| body: JSON.stringify(payload) | ||
| }); | ||
| if (res.ok) { | ||
| // 성공: 목록 새로고침 | ||
| if (item.classList.contains('discussion-item')) { | ||
| loadDiscussions(window.problemId, 0, 10, document.getElementById('discussion-sort')?.value || 'best'); | ||
| } else { | ||
| const discussionId = item.closest('.discussion-item').dataset.discussionId; | ||
| const container = document.getElementById(`replies-for-${discussionId}`); | ||
| if (container) { | ||
| container.dataset.loaded = ''; | ||
| const replies = await fetchReplies(discussionId); | ||
| renderReplies(container, replies, discussionId, 0); | ||
| } | ||
| } | ||
| } else { | ||
| alert('수정에 실패했습니다.'); | ||
| } | ||
| }; | ||
| } | ||
| // 삭제 | ||
| if (e.target.classList.contains('delete-btn')) { | ||
| if (!confirm('정말 삭제하시겠습니까?')) return; | ||
| const item = e.target.closest('.discussion-item, .reply-item'); | ||
| let url, method = 'DELETE'; | ||
| if (item.classList.contains('discussion-item')) { | ||
| url = `/api/problems/${window.problemId}/discussions/${e.target.dataset.id}`; | ||
| } else { | ||
| const discussionId = item.closest('.discussion-item').dataset.discussionId; | ||
| url = `/api/problems/${window.problemId}/discussions/${discussionId}/replies/${e.target.dataset.id}`; | ||
| } | ||
| (async () => { | ||
| const res = await fetch(url, { | ||
| method, | ||
| headers: { | ||
| ...tokenHeader() | ||
| } | ||
| }); | ||
| if (res.ok) { | ||
| if (item.classList.contains('discussion-item')) { | ||
| loadDiscussions(window.problemId, 0, 10, document.getElementById('discussion-sort')?.value || 'best'); | ||
| } else { | ||
| const discussionId = item.closest('.discussion-item').dataset.discussionId; | ||
| const container = document.getElementById(`replies-for-${discussionId}`); | ||
| if (container) { | ||
| container.dataset.loaded = ''; | ||
| const replies = await fetchReplies(discussionId); | ||
| renderReplies(container, replies, discussionId, 0); | ||
| } | ||
| } | ||
| } else { | ||
| alert('삭제에 실패했습니다.'); | ||
| } | ||
| })(); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
이벤트 위임에서 중복 코드와 복잡성이 문제입니다.
단일 이벤트 리스너에서 모든 상호작용을 처리하고 있어 코드가 매우 복잡하고 유지보수가 어렵습니다. 또한 비슷한 로직이 반복되고 있습니다.
기능별로 이벤트 핸들러를 분리하고 공통 로직을 함수로 추출하세요:
// 별도의 이벤트 핸들러들로 분리
function handleVoteClick(e) {
// 투표 관련 로직만 처리
}
function handleReplyClick(e) {
// 댓글 관련 로직만 처리
}
function handleEditDeleteClick(e) {
// 수정/삭제 관련 로직만 처리
}
// 각각을 별도로 등록
document.addEventListener('click', handleVoteClick);
document.addEventListener('click', handleReplyClick);
document.addEventListener('click', handleEditDeleteClick);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/main/resources/templates/test-submit.html between lines 1284 and 1426,
the single event listener handling all click events causes code duplication and
complexity. Refactor by splitting the event handling into separate functions for
each feature (e.g., voting, replying, editing, deleting) and extract common
logic like fetching dataset IDs and updating UI into reusable helper functions.
Then, have the main event listener delegate to these smaller handlers based on
the event target, improving readability and maintainability.
| async function loadDiscussions(pid, page = 0, size = 10, sortBy) { | ||
| sortBy = sortBy || (window.currentDiscussionSortBy || 'best'); | ||
| window.currentDiscussionSortBy = sortBy; | ||
| const res = await fetch(`/api/problems/${pid}/discussions?page=${page}&size=${size}&sortBy=${sortBy}`, { | ||
| headers: { | ||
| 'Accept': 'application/json', | ||
| ...tokenHeader() | ||
| } | ||
| }); | ||
| const data = await res.json(); | ||
| if (!data.success) { | ||
| document.getElementById('discussion-list').innerHTML = "토론글을 불러오지 못했습니다."; | ||
| return; | ||
| } | ||
| renderDiscussionList(data.result.content); | ||
| renderDiscussionPagination(data.result, pid); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
API 응답 구조 검증이 부족합니다.
API 응답의 구조를 충분히 검증하지 않고 접근하고 있어 런타임 에러가 발생할 수 있습니다.
const data = await res.json();
- if (!data.success) {
+ if (!data || !data.success) {
document.getElementById('discussion-list').innerHTML = "토론글을 불러오지 못했습니다.";
return;
}
+ if (!data.result || !Array.isArray(data.result.content)) {
+ document.getElementById('discussion-list').innerHTML = "토론글 데이터 형식이 올바르지 않습니다.";
+ return;
+ }
renderDiscussionList(data.result.content);📝 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.
| async function loadDiscussions(pid, page = 0, size = 10, sortBy) { | |
| sortBy = sortBy || (window.currentDiscussionSortBy || 'best'); | |
| window.currentDiscussionSortBy = sortBy; | |
| const res = await fetch(`/api/problems/${pid}/discussions?page=${page}&size=${size}&sortBy=${sortBy}`, { | |
| headers: { | |
| 'Accept': 'application/json', | |
| ...tokenHeader() | |
| } | |
| }); | |
| const data = await res.json(); | |
| if (!data.success) { | |
| document.getElementById('discussion-list').innerHTML = "토론글을 불러오지 못했습니다."; | |
| return; | |
| } | |
| renderDiscussionList(data.result.content); | |
| renderDiscussionPagination(data.result, pid); | |
| } | |
| async function loadDiscussions(pid, page = 0, size = 10, sortBy) { | |
| sortBy = sortBy || (window.currentDiscussionSortBy || 'best'); | |
| window.currentDiscussionSortBy = sortBy; | |
| const res = await fetch(`/api/problems/${pid}/discussions?page=${page}&size=${size}&sortBy=${sortBy}`, { | |
| headers: { | |
| 'Accept': 'application/json', | |
| ...tokenHeader() | |
| } | |
| }); | |
| const data = await res.json(); | |
| if (!data || !data.success) { | |
| document.getElementById('discussion-list').innerHTML = "토론글을 불러오지 못했습니다."; | |
| return; | |
| } | |
| if (!data.result || !Array.isArray(data.result.content)) { | |
| document.getElementById('discussion-list').innerHTML = "토론글 데이터 형식이 올바르지 않습니다."; | |
| return; | |
| } | |
| renderDiscussionList(data.result.content); | |
| renderDiscussionPagination(data.result, pid); | |
| } |
🤖 Prompt for AI Agents
In src/main/resources/templates/test-submit.html around lines 1079 to 1095, the
code assumes the API response has a specific structure without verifying it,
which can cause runtime errors. Add checks to confirm that data, data.success,
data.result, and data.result.content exist and are of expected types before
accessing them. If any check fails, handle the error gracefully by showing an
appropriate message or fallback UI instead of proceeding with rendering.
| function showReplyInput(discussionId, parentReplyId, btnEl) { | ||
| // 이미 열려있으면 return | ||
| const next = btnEl.closest('.comment-actions').nextElementSibling; | ||
| if (next && next.classList.contains('reply-input-box')) return; | ||
|
|
||
| const box = document.createElement('div'); | ||
| box.className = 'reply-input-box'; | ||
| box.innerHTML = ` | ||
| <textarea class="reply-input-text" rows="3" style="width:100%;resize:vertical;margin-bottom:6px;"></textarea> | ||
| <div style="display:flex;gap:8px;"> | ||
| <button class="reply-cancel-btn">취소</button> | ||
| <button class="reply-submit-btn">작성</button> | ||
| </div> | ||
| `; | ||
| btnEl.closest('.comment-actions').after(box); | ||
|
|
||
| // 취소 | ||
| box.querySelector('.reply-cancel-btn').onclick = () => box.remove(); | ||
|
|
||
| // 작성 | ||
| box.querySelector('.reply-submit-btn').onclick = async () => { | ||
| const content = box.querySelector('.reply-input-text').value.trim(); | ||
| if (!content) return alert('내용을 입력하세요.'); | ||
| const payload = parentReplyId ? { parentReplyId: Number(parentReplyId), content } : { content }; | ||
| const token = sessionStorage.getItem('accessToken'); | ||
| const res = await fetch(`/api/problems/${window.problemId}/discussions/${discussionId}/replies`, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| ...tokenHeader() | ||
| }, | ||
| body: JSON.stringify(payload) | ||
| }); | ||
| if (res.ok) { | ||
| if (parentReplyId) { | ||
| // 대댓글: 해당 댓글의 대댓글만 새로고침 | ||
| const childContainer = document.getElementById(`child-replies-for-${parentReplyId}`); | ||
| if (childContainer) { | ||
| childContainer.dataset.loaded = 'true'; | ||
| const discussionId = btnEl.closest('.discussion-item').dataset.discussionId; | ||
| const childReplies = await fetchChildReplies(discussionId, parentReplyId); | ||
| renderReplies(childContainer, childReplies, discussionId, 1); | ||
| // 펼쳐진 상태로 유지 | ||
| childContainer.style.display = 'block'; | ||
| // 버튼 텍스트/상태 갱신 | ||
| const toggleBtn = btnEl.closest('.reply-item').querySelector('.toggle-child-replies-btn'); | ||
| if (toggleBtn) { | ||
| toggleBtn.textContent = '대댓글 접기'; | ||
| toggleBtn.dataset.open = 'true'; | ||
| } | ||
| } | ||
| } else { | ||
| // 댓글: 전체 댓글 새로고침 | ||
| const container = document.getElementById(`replies-for-${discussionId}`); | ||
| if (container) { | ||
| container.dataset.loaded = 'true'; | ||
| const replies = await fetchReplies(discussionId); | ||
| renderReplies(container, replies, discussionId, 0); | ||
| // 펼쳐진 상태로 유지 | ||
| container.style.display = 'block'; | ||
| // 버튼 텍스트/상태 갱신 | ||
| const toggleBtn = btnEl.closest('.discussion-item').querySelector('.toggle-replies-btn'); | ||
| if (toggleBtn) { | ||
| toggleBtn.textContent = '댓글 접기'; | ||
| toggleBtn.dataset.open = 'true'; | ||
| } | ||
| } | ||
| } | ||
| box.remove(); | ||
| } else { | ||
| alert('작성에 실패했습니다.'); | ||
| } | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
댓글 입력 기능에서 XSS 공격 가능성이 있습니다.
사용자 입력을 그대로 DOM에 삽입하고 있어 XSS 공격에 취약할 수 있습니다. 또한 입력 검증이 단순합니다.
입력 검증과 XSS 방지를 강화하세요:
box.querySelector('.reply-submit-btn').onclick = async () => {
const content = box.querySelector('.reply-input-text').value.trim();
- if (!content) return alert('내용을 입력하세요.');
+ if (!content) return alert('내용을 입력하세요.');
+ if (content.length > 1000) return alert('내용이 너무 깁니다. (최대 1000자)');
+
+ // 서버에서 sanitization 처리되어야 하지만, 클라이언트에서도 기본적인 검증
+ const sanitizedContent = content.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/main/resources/templates/test-submit.html between lines 1508 and 1581,
the showReplyInput function inserts user input directly into the DOM without
proper sanitization, making it vulnerable to XSS attacks. To fix this, implement
input sanitization by escaping or removing potentially dangerous HTML tags or
characters from the user input before rendering it. Additionally, enhance input
validation to reject or sanitize inputs containing scripts or HTML elements that
could lead to XSS, ensuring only safe content is accepted and displayed.
There was a problem hiding this 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
♻️ Duplicate comments (1)
src/main/resources/templates/test-submit.html (1)
1284-1426: 단일 이벤트 위임 블록이 과도하게 비대함이 범위는 클릭 이벤트를 전부 처리하면서 조건 분기가 많아 가독성과 유지보수가 떨어집니다. 이전 리뷰(1079 라인 등)에서 이미 동일 지적을 드렸습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/main/resources/templates/test-submit.html(5 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: chat26666
PR: ezcode-my/backend#37
File: src/main/java/org/ezcode/codetest/infrastructure/elasticsearch/repository/ProblemElasticsearchRepositoryDsl.java:10-10
Timestamp: 2025-06-06T19:56:27.759Z
Learning: When user chat26666 mentions that code was implemented in a previous PR but explanation was missing, they may submit a follow-up PR just to add documentation/explanation without the actual implementation code.
src/main/resources/templates/test-submit.html (2)
Learnt from: thezz9
PR: ezcode-my/backend#73
File: src/main/resources/templates/submit-test.html:135-135
Timestamp: 2025-06-18T11:41:58.457Z
Learning: src/main/resources/templates/submit-test.html 파일은 테스트용 파일로, isCorrect 값의 하드코딩은 의도적이며 사용자가 UI 요소를 통해 정답/오답 상태를 선택할 수 있도록 개선 예정입니다.
Learnt from: thezz9
PR: ezcode-my/backend#114
File: src/main/java/org/ezcode/codetest/infrastructure/github/GitHubApiClient.java:159-165
Timestamp: 2025-06-28T02:03:38.693Z
Learning: User thezz9's service (ezcode) targets Korean users exclusively ("찐 한국인밖에 안 옴"), so Korean language hardcoding in commit messages and UI elements is intentional and appropriate for their use case.
⏰ 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
| window.addEventListener('DOMContentLoaded', () => { | ||
| const pid = new URLSearchParams(location.search).get('problemId'); | ||
| if (pid) { | ||
| window.problemId = pid; | ||
| loadProblem(pid); | ||
| } | ||
| }); | ||
|
|
||
| document.addEventListener('DOMContentLoaded', () => { | ||
| const tabs = document.querySelectorAll('.tabbar button'); | ||
| const contents = document.querySelectorAll('.left-content'); | ||
|
|
||
| tabs.forEach(tab => { | ||
| tab.addEventListener('click', () => { | ||
| // 1. 모든 탭과 콘텐츠에서 'active' 클래스 제거 | ||
| tabs.forEach(t => t.classList.remove('active')); | ||
| contents.forEach(c => c.classList.remove('active')); | ||
|
|
||
| // 2. 클릭된 탭에 'active' 클래스 추가 | ||
| tab.classList.add('active'); | ||
|
|
||
| // 3. 클릭된 탭과 연결된 콘텐츠를 찾아 'active' 클래스 추가 | ||
| const targetContent = document.querySelector(tab.dataset.target); | ||
| if (targetContent) { | ||
| targetContent.classList.add('active'); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| document.getElementById('discussion-tab').addEventListener('click', () => { | ||
| if (window.problemId) loadDiscussions(window.problemId, 0, 10, document.getElementById('discussion-sort')?.value || 'best'); | ||
| }); | ||
|
|
||
| // 정렬 드롭다운 변경 시 토론글 새로고침 | ||
| const sortEl = document.getElementById('discussion-sort'); | ||
| if (sortEl) { | ||
| sortEl.addEventListener('change', (e) => { | ||
| if (window.problemId) loadDiscussions(window.problemId, 0, 10, e.target.value); | ||
| }); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
DOMContentLoaded 리스너가 두 번 등록됨 – 중복 초기화 우려
831838, 839871 두 블록 모두 DOMContentLoaded 에서 탭/문제/정렬 로직을 초기화합니다. 중복 등록으로 같은 코드가 두 번 실행되어 불필요한 API 호출, 이벤트 바인딩 중복, 상태 꼬임이 발생할 수 있습니다.
하나의 초기화 함수로 합치고 한 번만 호출하도록 수정하세요.
🤖 Prompt for AI Agents
In src/main/resources/templates/test-submit.html between lines 831 and 871,
there are two separate DOMContentLoaded event listeners initializing tabs,
problem loading, and sorting logic, causing duplicate event bindings and
redundant API calls. Combine the code from both listeners into a single
DOMContentLoaded event listener function that performs all initialization steps
once, ensuring event handlers and API calls are set up only one time to avoid
conflicts and inefficiencies.
| if (isOpen) { | ||
| childContainer.style.display = 'none'; | ||
| this.textContent = '대댓글 접기'; | ||
| this.dataset.open = 'false'; | ||
| } else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
토글 버튼 텍스트 반대 방향으로 설정되는 버그
닫기 동작일 때도 '대댓글 접기' 를 설정하고 있습니다. 접힘 상태에서는 '대댓글 펼치기' 가 되어야 합니다.
- childContainer.style.display = 'none';
- this.textContent = '대댓글 접기';
+ childContainer.style.display = 'none';
+ this.textContent = '대댓글 펼치기';📝 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.
| if (isOpen) { | |
| childContainer.style.display = 'none'; | |
| this.textContent = '대댓글 접기'; | |
| this.dataset.open = 'false'; | |
| } else { | |
| if (isOpen) { | |
| childContainer.style.display = 'none'; | |
| this.textContent = '대댓글 펼치기'; | |
| this.dataset.open = 'false'; | |
| } else { |
🤖 Prompt for AI Agents
In src/main/resources/templates/test-submit.html around lines 1219 to 1223, the
toggle button text is incorrectly set to '대댓글 접기' even when closing the comment
thread. Update the code so that when isOpen is true (closing), set the
textContent to '대댓글 펼치기' instead of '대댓글 접기' to reflect the collapsed state
correctly.
| <div class="discussion-item" data-discussion-id="${d.discussionId}"> | ||
| <div class="discussion-header"> | ||
| <img src="${d.userInfo.profileImageUrl || DEFAULT_PROFILE_IMG}" alt="profile"> | ||
| <span class="nickname">${d.userInfo.nickname}</span> | ||
| <span style="flex:1"></span> | ||
| ${d.isAuthor ? ` | ||
| <button class="edit-btn" data-id="${d.discussionId}">수정</button> | ||
| <button class="delete-btn" data-id="${d.discussionId}">삭제</button> | ||
| ` : ''} | ||
| </div> | ||
| <div class="discussion-content">${d.content}</div> | ||
| <div class="comment-actions"> | ||
| <button class="icon-btn upvote-btn" data-id="${d.discussionId}" data-votestatus="${d.voteStatus}"> | ||
| ${getUpvoteIcon(d.voteStatus === 'UP')} | ||
| </button> | ||
| <span class="vote-count">${d.upvoteCount}</span> | ||
| <button class="icon-btn downvote-btn" data-id="${d.discussionId}" data-votestatus="${d.voteStatus}"> | ||
| ${getDownvoteIcon(d.voteStatus === 'DOWN')} | ||
| </button> | ||
| <button class="reply-btn" data-id="${d.discussionId}">댓글 달기</button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
사용자 입력을 innerHTML에 그대로 주입 – XSS 취약점 가능성
d.content 값(서버-측 Markdown 또는 사용자 입력)이 아무 필터 없이 innerHTML로 삽입됩니다. 악의적인 스크립트 태그가 포함될 경우 클라이언트에서 실행될 수 있습니다.
- <div class="discussion-content">${d.content}</div>
+ <div class="discussion-content">${DOMPurify.sanitize(d.content)}</div>또는 서버에서 HTML escape 후 textContent 로 렌더링하세요.
외부 라이브러리를 쓰지 못한다면 최소한 태그 제거 정규식이라도 적용해야 합니다.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/main/resources/templates/test-submit.html around lines 1105 to 1124, the
user input d.content is directly injected into the HTML using innerHTML, which
creates an XSS vulnerability. To fix this, sanitize d.content before inserting
it by either escaping HTML characters on the server side and rendering it with
textContent instead of innerHTML, or if server-side changes are not possible,
apply a client-side tag removal or escaping function to strip or encode any HTML
tags or scripts before rendering.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (4)
src/main/resources/templates/test-submit.html (4)
1264-1270: 토글 버튼 텍스트가 접힘·펼침 상태를 혼동합니다닫을 때도
'대댓글 접기'를 설정하고 있어 UX 오류가 발생합니다.- childContainer.style.display = 'none'; - this.textContent = '대댓글 접기'; + childContainer.style.display = 'none'; + this.textContent = '대댓글 펼치기';
1331-1473: 단일 전역 클릭 리스너가 너무 많은 책임을 집니다기능별로 별도 핸들러로 분리하고 공통 로직을 helper 함수로 추출하면 가독성과 유지보수가 크게 향상됩니다. 이전 리뷰와 동일 제안이므로 상세 설명은 생략합니다.
877-918:DOMContentLoaded리스너가 여전히 두 번 등록되어 초기화 로직이 중복 실행됩니다기존 리뷰에서도 동일한 문제가 지적되었으나(중복 초기화·이벤트 바인딩·API 호출 위험), 수정되지 않았습니다. 하나의 공용
init()함수로 합친 뒤DOMContentLoaded에서 한 번만 호출하도록 리팩터링하세요.Also applies to: 1667-1669
1150-1170: 사용자 입력을 그대로innerHTML에 주입 → XSS 취약점이 계속 존재합니다
d.content/r.content를 필터링 없이 렌더링하고 있어 악성 스크립트 실행 위험이 큽니다. 이전 리뷰에서도 동일 취약점이 보고되었으나 아직 해결되지 않았습니다.- <div class="discussion-content">${d.content}</div> + <div class="discussion-content">${DOMPurify.sanitize(d.content)}</div>DOMPurify를 사용할 수 없다면 최소한 서버에서 escape 후
textContent로 출력하세요.Also applies to: 1230-1240
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/main/resources/templates/test-submit.html(6 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: chat26666
PR: ezcode-my/backend#37
File: src/main/java/org/ezcode/codetest/infrastructure/elasticsearch/repository/ProblemElasticsearchRepositoryDsl.java:10-10
Timestamp: 2025-06-06T19:56:27.759Z
Learning: When user chat26666 mentions that code was implemented in a previous PR but explanation was missing, they may submit a follow-up PR just to add documentation/explanation without the actual implementation code.
src/main/resources/templates/test-submit.html (2)
Learnt from: thezz9
PR: ezcode-my/backend#73
File: src/main/resources/templates/submit-test.html:135-135
Timestamp: 2025-06-18T11:41:58.457Z
Learning: src/main/resources/templates/submit-test.html 파일은 테스트용 파일로, isCorrect 값의 하드코딩은 의도적이며 사용자가 UI 요소를 통해 정답/오답 상태를 선택할 수 있도록 개선 예정입니다.
Learnt from: thezz9
PR: ezcode-my/backend#114
File: src/main/java/org/ezcode/codetest/infrastructure/github/GitHubApiClient.java:159-165
Timestamp: 2025-06-28T02:03:38.693Z
Learning: User thezz9's service (ezcode) targets Korean users exclusively ("찐 한국인밖에 안 옴"), so Korean language hardcoding in commit messages and UI elements is intentional and appropriate for their use case.
⏰ 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
| if (!newContent) return alert('내용을 입력하세요.'); | ||
| let url, method = 'PUT', payload = { languageId: 1, content: newContent }; | ||
| if (item.classList.contains('discussion-item')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
edit 요청 payload 에 불필요한 languageId: 1 하드코딩
수정 API에서 언어ID가 의미가 없거나 서버가 무시한다면 해당 필드를 제거하세요.
만약 필수라면 실제 편집 대상의 언어를 바인딩해야 합니다.
🤖 Prompt for AI Agents
In src/main/resources/templates/test-submit.html around lines 1404 to 1406, the
edit request payload includes a hardcoded languageId: 1 which is unnecessary or
ignored by the server. Remove the languageId field from the payload if it is not
required, or replace it with the actual language ID of the item being edited if
it is mandatory. Adjust the payload construction accordingly to avoid sending
irrelevant or incorrect data.
작업 내용
Summary by CodeRabbit
신규 기능
버그 수정
스타일
문서화
기타