-
Notifications
You must be signed in to change notification settings - Fork 25
6주차 미션 / 서버 5조 조영탁 #55
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
base: main
Are you sure you want to change the base?
Conversation
리퀘스트 핸들링을 위한 DispatcherServlet 추가 및 기존 컨트롤러의 @WebServlet 어노테이션 제거, 구현을 위한 HttpServletRequest 필요 메서드 확인
…ontrollers for redirection handling
…oot configuration
…Question and User models
…ing KeyHolder parameter and adding transactional support
개요Servlet 기반의 레거시 웹 애플리케이션을 Spring Boot 기반의 현대적인 아키텍처로 마이그레이션했습니다. 수동 JDBC 연결 관리 및 임베디드 Tomcat 설정을 Spring Boot 스타터 및 JPA로 대체하고, 컨트롤러를 Spring MVC 스타일로 리팩토링했습니다. 변경사항
시퀀스 다이어그램sequenceDiagram
participant User as 사용자
participant Browser as 브라우저
participant SpringBoot as Spring Boot<br/>애플리케이션
participant Controller as 컨트롤러<br/>(MVC)
participant DAO as DAO<br/>(JPA)
participant DB as H2 데이터베이스
User->>Browser: 홈페이지 접속
Browser->>SpringBoot: GET /
SpringBoot->>Controller: HomeController.home()
Controller->>DAO: questionDao.findAll()
DAO->>DB: SELECT * FROM QUESTIONS
DB-->>DAO: 질문 목록
DAO-->>Controller: List<Question>
Controller->>Controller: Model에 질문 추가
Controller-->>Browser: home.jsp 렌더링
Browser-->>User: 질문 목록 표시
User->>Browser: 질문 작성 클릭
Browser->>SpringBoot: GET /qna/form
SpringBoot->>Controller: QnaController.getQuestionForm()
Controller->>Controller: 세션 확인
Controller-->>Browser: qna/form.jsp 렌더링
Browser-->>User: 질문 작성 양식 표시
User->>Browser: 질문 제출
Browser->>SpringBoot: POST /qna/create
SpringBoot->>Controller: QnaController.createQuestion()
Controller->>DAO: questionDao.insert(Question)
DAO->>DB: INSERT INTO QUESTIONS
DB-->>DAO: 삽입 완료
Controller-->>Browser: /qna/show?questionId=N으로 리다이렉트
Browser->>SpringBoot: GET /qna/show?questionId=N
SpringBoot->>Controller: QnaController.getShow()
Controller->>DAO: questionDao.findQuestionById(N)
DAO->>DB: SELECT * FROM QUESTIONS WHERE id=N
DB-->>DAO: Question
Controller-->>Browser: qna/show.jsp 렌더링
Browser-->>User: 질문 상세 페이지 표시
예상 코드 리뷰 노력🎯 4 (복잡함) | ⏱️ ~60분 주의 깊게 검토가 필요한 영역:
시
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
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: 14
🧹 Nitpick comments (11)
src/main/java/core/filter/ResourceFilter.java (1)
16-16: 변수명 오타를 수정하는 것을 고려해보세요.
resourcePrefixs는resourcePrefixes가 올바른 철자입니다. 기능에는 영향이 없지만, 코드 가독성 향상을 위해 수정을 권장합니다.- private static final List<String> resourcePrefixs = new ArrayList<>(); + private static final List<String> resourcePrefixes = new ArrayList<>();그리고 Line 46의 참조도 함께 수정:
- private boolean isResourceUrl(String url) { - for (String prefix : resourcePrefixs) { + private boolean isResourceUrl(String url) { + for (String prefix : resourcePrefixes) {src/main/java/jwp/WebServerLauncher.java (1)
10-10: 사용하지 않는 Logger를 제거하세요.Logger가 선언되었지만 코드 내에서 사용되지 않습니다.
다음 diff를 적용하여 제거하세요:
- private static final Logger logger = Logger.getLogger(WebServerLauncher.class.getName()); - public static void main(String[] args) throws Exception {src/main/java/jwp/model/User.java (1)
24-28: update() 메서드 검토가 필요합니다.@Setter 어노테이션으로 모든 필드에 setter가 생성되므로 update() 메서드가 중복될 수 있습니다. 그러나 update()는 특정 필드만 업데이트하는 비즈니스 로직을 표현하므로, 의도적인 설계라면 @Setter를 제거하고 필요한 setter만 명시적으로 작성하는 것이 좋습니다.
src/main/java/jwp/model/Question.java (1)
24-24: 날짜 필드에 적절한 타입을 사용하세요.createdDate를 String으로 저장하면 날짜 연산, 정렬, 포맷팅이 어렵습니다. LocalDateTime 또는 Date 타입으로 변경하는 것을 권장합니다.
다음과 같이 수정하는 것을 고려하세요:
+import java.time.LocalDateTime; +import javax.persistence.Column; + @Getter @Entity @Table(name = "QUESTIONS") @NoArgsConstructor @AllArgsConstructor @ToString public class Question { @GeneratedValue(strategy = GenerationType.IDENTITY) @Id private Long questionId; private String writer; private String title; private String contents; - private String createdDate; + @Column(name = "createdDate") + private LocalDateTime createdDate; private int countOfAnswer; }src/main/java/jwp/support/KeyHolder.java (1)
3-13: Lombok을 사용하여 일관성을 높이세요.프로젝트의 다른 모델 클래스들(User, Question)은 Lombok을 사용하고 있습니다. KeyHolder도 Lombok을 적용하면 코드 일관성이 향상됩니다.
다음 diff를 적용하는 것을 고려하세요:
package jwp.support; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter public class KeyHolder { private Long id; - - public void setId(Long id) { - this.id = id; - } - - public Long getId() { - return id; - } }src/main/webapp/home.jsp (2)
37-57: 정적 페이지네이션 구현 확인 필요페이지네이션 UI가 정적으로 구현되어 있어 실제 동작하지 않습니다. 현재 단계에서 의도적인 것이라면 문제없지만, 향후 서버 측 페이징 로직과 연동이 필요합니다.
61-61: Bootstrap 버전 고려Bootstrap 5.3.0-alpha1 알파 버전을 사용 중입니다. 학습용 프로젝트라면 문제없지만, 프로덕션 환경에서는 안정 버전 사용을 권장합니다.
src/main/java/jwp/dao/QuestionDao.java (1)
20-22: JPQL 쿼리 변수명 개선 권장JPQL에서
u를 별칭으로 사용하고 있는데,Question엔티티에 대해서는q가 더 명확합니다.u는 일반적으로User에 사용됩니다.다음과 같이 수정할 수 있습니다:
- return em.createQuery("select u from Question u", Question.class).getResultList(); + return em.createQuery("select q from Question q", Question.class).getResultList();src/main/java/jwp/controller/QnaController.java (2)
28-37: 디버그 출력문 제거 권장Line 32의
System.out.println은 프로덕션 코드에 적합하지 않습니다. 로거를 사용하거나 제거하는 것을 권장합니다.
56-63: 예외 처리 및 디버그 출력 개선 필요
- Line 58:
Long.parseLong()는 잘못된 입력에 대해NumberFormatException을 발생시킬 수 있습니다. 예외 처리를 추가하거나 Spring의@RequestParam을 사용하는 것을 권장합니다.- Line 60:
System.out.println은 로거로 대체하거나 제거해야 합니다.- Spring MVC를 사용하고 있으므로
HttpServletRequest.setAttribute대신Model을 사용하는 것이 더 권장됩니다.src/main/java/jwp/controller/UserController.java (1)
40-47: 디버그 출력문 제거 권장Line 43의
System.out.println을 로거로 대체하거나 제거하세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
src/main/webapp/favicon.icois excluded by!**/*.icosrc/main/webapp/img/KUIT.pngis excluded by!**/*.pngsrc/main/webapp/img/picture.jpegis excluded by!**/*.jpeg
📒 Files selected for processing (26)
build.gradle(1 hunks)java-webMVC.ipr(3 hunks)java-webMVC.iws(1 hunks)src/main/java/WebServerLauncher.java(0 hunks)src/main/java/core/db/MemoryUserRepository.java(0 hunks)src/main/java/core/db/Repository.java(0 hunks)src/main/java/core/filter/CharacterEncodingFilter.java(1 hunks)src/main/java/core/filter/ResourceFilter.java(1 hunks)src/main/java/core/jdbc/ConnectionManager.java(0 hunks)src/main/java/jwp/WebServerLauncher.java(1 hunks)src/main/java/jwp/controller/HomeController.java(1 hunks)src/main/java/jwp/controller/QnaController.java(1 hunks)src/main/java/jwp/controller/UserController.java(1 hunks)src/main/java/jwp/dao/QuestionDao.java(1 hunks)src/main/java/jwp/dao/UserDao.java(1 hunks)src/main/java/jwp/model/Question.java(1 hunks)src/main/java/jwp/model/User.java(1 hunks)src/main/java/jwp/support/KeyHolder.java(1 hunks)src/main/java/jwp/support/context/ContextLoaderListener.java(0 hunks)src/main/resources/application.properties(1 hunks)src/main/webapp/home.jsp(1 hunks)src/main/webapp/qna/form.jsp(1 hunks)src/main/webapp/qna/show.jsp(1 hunks)webapp/home.jsp(0 hunks)webapp/qna/form.jsp(0 hunks)webapp/qna/show.jsp(0 hunks)
💤 Files with no reviewable changes (8)
- webapp/home.jsp
- src/main/java/core/db/MemoryUserRepository.java
- src/main/java/core/db/Repository.java
- webapp/qna/show.jsp
- src/main/java/jwp/support/context/ContextLoaderListener.java
- src/main/java/WebServerLauncher.java
- webapp/qna/form.jsp
- src/main/java/core/jdbc/ConnectionManager.java
🧰 Additional context used
🧬 Code graph analysis (7)
src/main/java/jwp/dao/QuestionDao.java (2)
src/main/java/jwp/support/KeyHolder.java (1)
KeyHolder(3-13)src/main/java/jwp/dao/UserDao.java (1)
Repository(13-35)
src/main/java/jwp/model/Question.java (1)
src/main/java/jwp/model/User.java (1)
Getter(9-82)
src/main/java/jwp/controller/HomeController.java (1)
src/main/java/jwp/controller/QnaController.java (1)
Controller(22-64)
src/main/java/jwp/controller/UserController.java (1)
src/main/java/jwp/controller/QnaController.java (1)
Controller(22-64)
src/main/java/jwp/dao/UserDao.java (1)
src/main/java/jwp/dao/QuestionDao.java (1)
Repository(16-32)
src/main/java/jwp/controller/QnaController.java (3)
src/main/java/jwp/support/KeyHolder.java (1)
KeyHolder(3-13)src/main/java/jwp/controller/UserController.java (1)
Controller(22-113)src/main/java/jwp/controller/HomeController.java (1)
Controller(11-20)
src/main/java/jwp/model/User.java (1)
src/main/java/jwp/model/Question.java (1)
Getter(12-26)
🔇 Additional comments (18)
src/main/java/core/filter/CharacterEncodingFilter.java (1)
1-1: 패키지 재구성이 잘 적용되었습니다.
CharacterEncodingFilter도core.filter패키지로 이동되어 필터 클래스들이 일관되게 구조화되었습니다. 코드 변경 사항이 없고 패키지 이동만 이루어졌습니다.src/main/java/core/filter/ResourceFilter.java (1)
1-1: 패키지 재구성이 올바르게 적용되었습니다.검증 결과:
- 기존
core.web.filter패키지에 대한 참조 없음 (마이그레이션 완료)- 새
core.filter패키지로 완전히 통합됨 (ResourceFilter.java, CharacterEncodingFilter.java)- 패키지 변경이 일관되게 적용됨
모든 필터 클래스가 새 패키지 구조로 올바르게 정렬되었습니다.
java-webMVC.ipr (1)
1-116: LGTM - IDE 설정 파일입니다.IntelliJ IDEA 프로젝트 설정 파일로 자동 생성된 구성입니다. Java 17 설정이 적용되어 있습니다.
build.gradle (1)
17-38: LGTM - Spring Boot 의존성 구성이 잘 되어 있습니다.Spring Boot 2.7.17 기반의 웹 애플리케이션 구성이 적절합니다. Web, JPA, JSP, 데이터베이스 드라이버, Lombok, 테스트 의존성이 모두 포함되어 있습니다.
src/main/webapp/qna/form.jsp (1)
13-27: LGTM - 폼 구조가 적절합니다.작성자 필드가 세션에서 읽기 전용으로 설정되어 보안이 잘 고려되었습니다. POST 방식으로 /qna/create에 제출하는 구조도 적절합니다.
src/main/webapp/home.jsp (2)
1-8: LGTM!페이지 설정과 include 구조가 적절합니다.
14-35: LGTM!질문 목록 표시 로직이 올바르게 구현되었습니다. JSTL을 사용한 반복문과 데이터 바인딩이 적절합니다.
src/main/resources/application.properties (1)
3-18: LGTM!Spring Boot 설정이 적절합니다. H2 데이터베이스, JPA, JSP 뷰 리졸버 설정이 올바르게 구성되어 있습니다.
src/main/java/jwp/dao/QuestionDao.java (2)
24-27: LGTM!
@Transactional을 사용한 insert 메서드가 올바르게 구현되었습니다.
29-31: LGTM!JPA EntityManager의
find메서드를 사용한 조회 로직이 적절합니다.src/main/java/jwp/dao/UserDao.java (3)
18-21: LGTM!트랜잭션 내에서 사용자 등록이 올바르게 구현되었습니다.
28-30: LGTM!JPQL을 사용한 전체 사용자 조회 로직이 올바릅니다.
32-34: LGTM!ID 기반 사용자 조회가 적절하게 구현되었습니다.
src/main/java/jwp/controller/HomeController.java (1)
11-19: LGTM!Spring MVC 패턴을 따른 깔끔한 홈 컨트롤러 구현입니다.
Model을 사용하여 뷰에 데이터를 전달하는 방식이 적절합니다.src/main/java/jwp/controller/UserController.java (4)
29-38: LGTM!세션 기반 접근 제어와 사용자 목록 조회 로직이 올바르게 구현되었습니다.
66-73: LGTM!로그아웃 로직이 명확하고 올바르게 구현되었습니다.
75-86: 업데이트 로직 검토 필요Line 82-83의 업데이트 패턴이 일반적이지 않습니다.
userById.update()가 내부적으로 필드를 변경하고UserDao.update()를 호출하는 것으로 보이는데, 이는 혼란스러울 수 있습니다. 또한UserDao.update()에@Transactional이 누락되어 변경사항이 커밋되지 않을 수 있습니다.
User.update()메서드의 구현을 확인하여 트랜잭션 처리가 올바른지 검증이 필요합니다.
88-101: LGTM!사용자가 본인의 정보만 수정할 수 있도록 하는 권한 검증이 적절합니다.
| version '1.0-SNAPSHOT' | ||
| group = 'org.example' | ||
| version = '1.0-SNAPSHOT' | ||
| sourceCompatibility = '11' // 사용하는 자바 버전에 맞게 설정 (예: 11) |
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.
🧩 Analysis chain
Java 버전 불일치를 확인하세요.
build.gradle에서 Java 11로 설정되어 있지만, java-webMVC.ipr에서는 Java 17이 설정되어 있습니다. 프로젝트 전체에서 일관된 Java 버전을 사용하는지 확인하세요.
다음 명령으로 프로젝트에서 사용 중인 Java 버전을 확인하세요:
🏁 Script executed:
#!/bin/bash
# Description: Check Java version consistency across project files
# Check Gradle wrapper properties
cat gradle/wrapper/gradle-wrapper.properties 2>/dev/null || echo "No Gradle wrapper found"
# Check for any Java version references in project files
rg -n "JavaSDK|sourceCompatibility|targetCompatibility|java.*version" --type=gradleLength of output: 271
필요한 정보를 더 수집하겠습니다. java-webMVC.ipr 파일과 추가 Java 버전 설정을 확인하겠습니다.
---
🏁 Script executed:
```shell
#!/bin/bash
# Find .ipr files and check for Java version references
find . -name "*.ipr" -type f 2>/dev/null | head -20
# Check java-webMVC.ipr content if it exists
if [ -f "java-webMVC.ipr" ]; then
echo "=== Checking java-webMVC.ipr ==="
cat java-webMVC.ipr | grep -i "java\|sdk" | head -20
fi
# Check for targetCompatibility in build.gradle
echo "=== Checking for targetCompatibility ==="
rg -n "targetCompatibility" --type=gradle
Length of output: 398
Java 버전 불일치 문제를 수정해야 합니다.
프로젝트에서 Java 버전이 일관되지 않게 설정되어 있습니다:
build.gradle: Java 11 (sourceCompatibility = '11')java-webMVC.ipr: Java 17 (languageLevel="JDK_17",project-jdk-name="17")
build.gradle의 sourceCompatibility와 java-webMVC.ipr의 languageLevel 및 project-jdk-name을 동일한 Java 버전으로 맞춰주세요. 프로젝트에서 사용할 Java 버전을 결정한 후 두 파일 모두 업데이트하시기 바랍니다.
🤖 Prompt for AI Agents
In build.gradle around line 11 and in java-webMVC.ipr (the IDE project file),
the Java versions are inconsistent (build.gradle uses sourceCompatibility = '11'
while the .ipr uses JDK_17/project-jdk-name="17"); choose the project Java
version (either 11 or 17) and update both files to match: set
sourceCompatibility (and targetCompatibility if present) in build.gradle to the
chosen version string and update languageLevel and project-jdk-name in
java-webMVC.ipr to the same version identifier so both Gradle and the IDE use
the identical Java version.
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project version="4"> | ||
| <component name="ChangeListManager"> | ||
| <option name="TRACKING_ENABLED" value="true"/> | ||
| <option name="SHOW_DIALOG" value="false"/> | ||
| <option name="HIGHLIGHT_CONFLICTS" value="true"/> | ||
| <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false"/> | ||
| <option name="LAST_RESOLUTION" value="IGNORE"/> | ||
| <list default="true" id="e01f72a1-cc99-4225-ba36-2f9ffd49b507" name="변경" comment="" /> | ||
| <option name="SHOW_DIALOG" value="false" /> | ||
| <option name="HIGHLIGHT_CONFLICTS" value="true" /> | ||
| <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> | ||
| <option name="LAST_RESOLUTION" value="IGNORE" /> | ||
| </component> | ||
| <component name="ChangesViewManager" flattened_view="true" show_ignored="false"/> | ||
| <component name="ChangesViewManager" flattened_view="true" show_ignored="false" /> | ||
| <component name="CreatePatchCommitExecutor"> | ||
| <option name="PATCH_PATH" value=""/> | ||
| <option name="REVERSE_PATCH" value="false"/> | ||
| <option name="PATCH_PATH" value="" /> | ||
| <option name="REVERSE_PATCH" value="false" /> | ||
| </component> | ||
| <component name="DaemonCodeAnalyzer"> | ||
| <disable_hints/> | ||
| <disable_hints /> | ||
| </component> | ||
| <component name="DebuggerManager"> | ||
| <breakpoint_any> | ||
| <breakpoint_any converted="true"> | ||
| <breakpoint> | ||
| <option name="NOTIFY_CAUGHT" value="true"/> | ||
| <option name="NOTIFY_UNCAUGHT" value="true"/> | ||
| <option name="ENABLED" value="false"/> | ||
| <option name="LOG_ENABLED" value="false"/> | ||
| <option name="LOG_EXPRESSION_ENABLED" value="false"/> | ||
| <option name="SUSPEND_POLICY" value="SuspendAll"/> | ||
| <option name="COUNT_FILTER_ENABLED" value="false"/> | ||
| <option name="COUNT_FILTER" value="0"/> | ||
| <option name="CONDITION_ENABLED" value="false"/> | ||
| <option name="CLASS_FILTERS_ENABLED" value="false"/> | ||
| <option name="INSTANCE_FILTERS_ENABLED" value="false"/> | ||
| <option name="CONDITION" value=""/> | ||
| <option name="LOG_MESSAGE" value=""/> | ||
| <option name="NOTIFY_CAUGHT" value="true" /> | ||
| <option name="NOTIFY_UNCAUGHT" value="true" /> | ||
| <option name="ENABLED" value="false" /> | ||
| <option name="LOG_ENABLED" value="false" /> | ||
| <option name="LOG_EXPRESSION_ENABLED" value="false" /> | ||
| <option name="SUSPEND_POLICY" value="SuspendAll" /> | ||
| <option name="COUNT_FILTER_ENABLED" value="false" /> | ||
| <option name="COUNT_FILTER" value="0" /> | ||
| <option name="CONDITION_ENABLED" value="false" /> | ||
| <option name="CLASS_FILTERS_ENABLED" value="false" /> | ||
| <option name="INSTANCE_FILTERS_ENABLED" value="false" /> | ||
| <option name="CONDITION" value="" /> | ||
| <option name="LOG_MESSAGE" value="" /> | ||
| </breakpoint> | ||
| <breakpoint> | ||
| <option name="NOTIFY_CAUGHT" value="true"/> | ||
| <option name="NOTIFY_UNCAUGHT" value="true"/> | ||
| <option name="ENABLED" value="false"/> | ||
| <option name="LOG_ENABLED" value="false"/> | ||
| <option name="LOG_EXPRESSION_ENABLED" value="false"/> | ||
| <option name="SUSPEND_POLICY" value="SuspendAll"/> | ||
| <option name="COUNT_FILTER_ENABLED" value="false"/> | ||
| <option name="COUNT_FILTER" value="0"/> | ||
| <option name="CONDITION_ENABLED" value="false"/> | ||
| <option name="CLASS_FILTERS_ENABLED" value="false"/> | ||
| <option name="INSTANCE_FILTERS_ENABLED" value="false"/> | ||
| <option name="CONDITION" value=""/> | ||
| <option name="LOG_MESSAGE" value=""/> | ||
| <option name="NOTIFY_CAUGHT" value="true" /> | ||
| <option name="NOTIFY_UNCAUGHT" value="true" /> | ||
| <option name="ENABLED" value="false" /> | ||
| <option name="LOG_ENABLED" value="false" /> | ||
| <option name="LOG_EXPRESSION_ENABLED" value="false" /> | ||
| <option name="SUSPEND_POLICY" value="SuspendAll" /> | ||
| <option name="COUNT_FILTER_ENABLED" value="false" /> | ||
| <option name="COUNT_FILTER" value="0" /> | ||
| <option name="CONDITION_ENABLED" value="false" /> | ||
| <option name="CLASS_FILTERS_ENABLED" value="false" /> | ||
| <option name="INSTANCE_FILTERS_ENABLED" value="false" /> | ||
| <option name="CONDITION" value="" /> | ||
| <option name="LOG_MESSAGE" value="" /> | ||
| </breakpoint> | ||
| </breakpoint_any> | ||
| <breakpoint_rules/> | ||
| <ui_properties/> | ||
| <breakpoint_rules converted="true" /> | ||
| <ui_properties converted="true" /> | ||
| </component> | ||
| <component name="ModuleEditorState"> | ||
| <option name="LAST_EDITED_MODULE_NAME"/> | ||
| <option name="LAST_EDITED_TAB_NAME"/> | ||
| <option name="LAST_EDITED_MODULE_NAME" /> | ||
| <option name="LAST_EDITED_TAB_NAME" /> | ||
| </component> | ||
| <component name="ProjectColorInfo"><![CDATA[{ | ||
| "associatedIndex": 5 | ||
| }]]></component> | ||
| <component name="ProjectId" id="33lcJAKYePq0puHzG8dnjXD2D5k" /> | ||
| <component name="ProjectInspectionProfilesVisibleTreeState"> | ||
| <entry key="Project Default"> | ||
| <profile-state/> | ||
| <profile-state /> | ||
| </entry> | ||
| </component> | ||
| <component name="ProjectLevelVcsManager"> | ||
| <OptionsSetting value="true" id="Add"/> | ||
| <OptionsSetting value="true" id="Remove"/> | ||
| <OptionsSetting value="true" id="Checkout"/> | ||
| <OptionsSetting value="true" id="Update"/> | ||
| <OptionsSetting value="true" id="Status"/> | ||
| <OptionsSetting value="true" id="Edit"/> | ||
| <ConfirmationsSetting value="0" id="Add"/> | ||
| <ConfirmationsSetting value="0" id="Remove"/> | ||
| </component> | ||
| <component name="ProjectReloadState"> | ||
| <option name="STATE" value="0"/> | ||
| <option name="STATE" value="0" /> | ||
| </component> | ||
| <component name="PropertiesComponent"> | ||
| <property name="GoToFile.includeJavaFiles" value="false"/> | ||
| <property name="GoToClass.toSaveIncludeLibraries" value="false"/> | ||
| <property name="MemberChooser.sorted" value="false"/> | ||
| <property name="MemberChooser.showClasses" value="true"/> | ||
| <property name="GoToClass.includeLibraries" value="false"/> | ||
| <property name="MemberChooser.copyJavadoc" value="false"/> | ||
| <component name="ProjectViewState"> | ||
| <option name="showLibraryContents" value="true" /> | ||
| <option name="showScratchesAndConsoles" value="false" /> | ||
| </component> | ||
| <component name="PropertiesComponent"><![CDATA[{ | ||
| "keyToString": { | ||
| "RunOnceActivity.ShowReadmeOnStart": "true", | ||
| "ignore.virus.scanning.warn.message": "true", | ||
| "kotlin-language-version-configured": "true", | ||
| "last_opened_file_path": "C:/Users/andyc/OneDrive/바탕 화면/Projects/KUIT6_Server-webMVC", | ||
| "nodejs_package_manager_path": "npm", | ||
| "vue.rearranger.settings.migration": "true" | ||
| } | ||
| }]]></component> | ||
| <component name="RunManager"> | ||
| <configuration default="true" type="Remote" factoryName="Remote"> | ||
| <option name="USE_SOCKET_TRANSPORT" value="true"/> | ||
| <option name="SERVER_MODE" value="false"/> | ||
| <option name="SHMEM_ADDRESS" value="javadebug"/> | ||
| <option name="HOST" value="localhost"/> | ||
| <option name="PORT" value="5005"/> | ||
| <method> | ||
| <option name="BuildArtifacts" enabled="false"/> | ||
| </method> | ||
| </configuration> | ||
| <configuration default="true" type="Applet" factoryName="Applet"> | ||
| <module name=""/> | ||
| <option name="MAIN_CLASS_NAME"/> | ||
| <option name="HTML_FILE_NAME"/> | ||
| <option name="HTML_USED" value="false"/> | ||
| <option name="WIDTH" value="400"/> | ||
| <option name="HEIGHT" value="300"/> | ||
| <option name="POLICY_FILE" value="$APPLICATION_HOME_DIR$/bin/appletviewer.policy"/> | ||
| <option name="VM_PARAMETERS"/> | ||
| <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false"/> | ||
| <option name="ALTERNATIVE_JRE_PATH"/> | ||
| <module name="" /> | ||
| <option name="MAIN_CLASS_NAME" /> | ||
| <option name="HTML_FILE_NAME" /> | ||
| <option name="HTML_USED" value="false" /> | ||
| <option name="WIDTH" value="400" /> | ||
| <option name="HEIGHT" value="300" /> | ||
| <option name="POLICY_FILE" value="$APPLICATION_HOME_DIR$/bin/appletviewer.policy" /> | ||
| <option name="VM_PARAMETERS" /> | ||
| <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> | ||
| <option name="ALTERNATIVE_JRE_PATH" /> | ||
| <method> | ||
| <option name="BuildArtifacts" enabled="false"/> | ||
| <option name="Make" enabled="true"/> | ||
| <option name="BuildArtifacts" enabled="false" /> | ||
| <option name="Make" enabled="true" /> | ||
| </method> | ||
| </configuration> | ||
| <configuration name="<template>" type="WebApp" default="true" selected="false"> | ||
| <Host>localhost</Host> | ||
| <Port>5050</Port> | ||
| </configuration> | ||
| <configuration default="true" type="Application" factoryName="Application"> | ||
| <extension name="coverage" enabled="false" merge="false"/> | ||
| <option name="MAIN_CLASS_NAME"/> | ||
| <option name="VM_PARAMETERS"/> | ||
| <option name="PROGRAM_PARAMETERS"/> | ||
| <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$"/> | ||
| <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false"/> | ||
| <option name="ALTERNATIVE_JRE_PATH"/> | ||
| <option name="ENABLE_SWING_INSPECTOR" value="false"/> | ||
| <option name="ENV_VARIABLES"/> | ||
| <option name="PASS_PARENT_ENVS" value="true"/> | ||
| <module name=""/> | ||
| <envs/> | ||
| <method> | ||
| <option name="BuildArtifacts" enabled="false"/> | ||
| <option name="Make" enabled="true"/> | ||
| <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> | ||
| <method v="2"> | ||
| <option name="BuildArtifacts" enabled="false" /> | ||
| <option name="Make" enabled="true" /> | ||
| </method> | ||
| </configuration> | ||
| <configuration default="true" type="JUnit" factoryName="JUnit"> | ||
| <extension name="coverage" enabled="false" merge="false"/> | ||
| <module name=""/> | ||
| <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false"/> | ||
| <option name="ALTERNATIVE_JRE_PATH"/> | ||
| <option name="PACKAGE_NAME"/> | ||
| <option name="MAIN_CLASS_NAME"/> | ||
| <option name="METHOD_NAME"/> | ||
| <option name="TEST_OBJECT" value="class"/> | ||
| <option name="VM_PARAMETERS"/> | ||
| <option name="PARAMETERS"/> | ||
| <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$"/> | ||
| <option name="ENV_VARIABLES"/> | ||
| <option name="PASS_PARENT_ENVS" value="true"/> | ||
| <option name="TEST_OBJECT" value="class" /> | ||
| <option name="VM_PARAMETERS" /> | ||
| <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" /> | ||
| <option name="TEST_SEARCH_SCOPE"> | ||
| <value defaultName="moduleWithDependencies"/> | ||
| <value defaultName="moduleWithDependencies" /> | ||
| </option> | ||
| <envs/> | ||
| <method> | ||
| <option name="BuildArtifacts" enabled="false"/> | ||
| <option name="Make" enabled="true"/> | ||
| <method v="2"> | ||
| <option name="BuildArtifacts" enabled="false" /> | ||
| <option name="Make" enabled="true" /> | ||
| </method> | ||
| </configuration> | ||
| <list size="0"/> | ||
| <configuration name="<template>" type="WebApp" default="true" selected="false"> | ||
| <Host>localhost</Host> | ||
| <Port>5050</Port> | ||
| <configuration default="true" type="Remote"> | ||
| <option name="USE_SOCKET_TRANSPORT" value="true" /> | ||
| <option name="SERVER_MODE" value="false" /> | ||
| <option name="SHMEM_ADDRESS" value="javadebug" /> | ||
| <option name="HOST" value="localhost" /> | ||
| <option name="PORT" value="5005" /> | ||
| <option name="AUTO_RESTART" value="false" /> | ||
| <method v="2"> | ||
| <option name="BuildArtifacts" enabled="false" /> | ||
| </method> | ||
| </configuration> | ||
| </component> | ||
| <component name="ShelveChangesManager" show_recycled="false"/> | ||
| <component name="SharedIndexes"> | ||
| <attachedChunks> | ||
| <set> | ||
| <option value="bundled-jdk-9823dce3aa75-a94e463ab2e7-intellij.indexing.shared.core-IU-243.25659.59" /> | ||
| <option value="bundled-js-predefined-d6986cc7102b-1632447f56bf-JavaScript-IU-243.25659.59" /> | ||
| </set> | ||
| </attachedChunks> | ||
| </component> | ||
| <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="애플리케이션 수준" UseSingleDictionary="true" transferred="true" /> | ||
| <component name="SvnConfiguration" maxAnnotateRevisions="500"> | ||
| <option name="USER" value=""/> | ||
| <option name="PASSWORD" value=""/> | ||
| <option name="LAST_MERGED_REVISION"/> | ||
| <option name="UPDATE_RUN_STATUS" value="false"/> | ||
| <option name="MERGE_DRY_RUN" value="false"/> | ||
| <option name="MERGE_DIFF_USE_ANCESTRY" value="true"/> | ||
| <option name="UPDATE_LOCK_ON_DEMAND" value="false"/> | ||
| <option name="IGNORE_SPACES_IN_MERGE" value="false"/> | ||
| <option name="DETECT_NESTED_COPIES" value="true"/> | ||
| <option name="IGNORE_SPACES_IN_ANNOTATE" value="true"/> | ||
| <option name="SHOW_MERGE_SOURCES_IN_ANNOTATE" value="true"/> | ||
| <option name="USER" value="" /> | ||
| <option name="PASSWORD" value="" /> | ||
| <option name="LAST_MERGED_REVISION" /> | ||
| <option name="UPDATE_RUN_STATUS" value="false" /> | ||
| <option name="MERGE_DRY_RUN" value="false" /> | ||
| <option name="MERGE_DIFF_USE_ANCESTRY" value="true" /> | ||
| <option name="UPDATE_LOCK_ON_DEMAND" value="false" /> | ||
| <option name="IGNORE_SPACES_IN_MERGE" value="false" /> | ||
| <option name="DETECT_NESTED_COPIES" value="true" /> | ||
| <option name="IGNORE_SPACES_IN_ANNOTATE" value="true" /> | ||
| <option name="SHOW_MERGE_SOURCES_IN_ANNOTATE" value="true" /> | ||
| <myIsUseDefaultProxy>false</myIsUseDefaultProxy> | ||
| </component> | ||
| <component name="TaskManager"> | ||
| <task active="true" id="Default" summary="Default task"/> | ||
| <servers/> | ||
| <task active="true" id="Default" summary="Default task"> | ||
| <changelist id="e01f72a1-cc99-4225-ba36-2f9ffd49b507" name="변경" comment="" /> | ||
| <created>1759895024866</created> | ||
| <option name="number" value="Default" /> | ||
| <option name="presentableId" value="Default" /> | ||
| <updated>1759895024866</updated> | ||
| <workItem from="1759895025810" duration="59000" /> | ||
| </task> | ||
| <servers /> | ||
| </component> | ||
| <component name="VcsManagerConfiguration"> | ||
| <option name="OFFER_MOVE_TO_ANOTHER_CHANGELIST_ON_PARTIAL_COMMIT" value="true"/> | ||
| <option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="true"/> | ||
| <option name="PERFORM_UPDATE_IN_BACKGROUND" value="true"/> | ||
| <option name="PERFORM_COMMIT_IN_BACKGROUND" value="true"/> | ||
| <option name="PERFORM_EDIT_IN_BACKGROUND" value="true"/> | ||
| <option name="PERFORM_CHECKOUT_IN_BACKGROUND" value="true"/> | ||
| <option name="PERFORM_ADD_REMOVE_IN_BACKGROUND" value="true"/> | ||
| <option name="PERFORM_ROLLBACK_IN_BACKGROUND" value="false"/> | ||
| <option name="CHECK_LOCALLY_CHANGED_CONFLICTS_IN_BACKGROUND" value="false"/> | ||
| <option name="ENABLE_BACKGROUND_PROCESSES" value="false"/> | ||
| <option name="CHANGED_ON_SERVER_INTERVAL" value="60"/> | ||
| <option name="FORCE_NON_EMPTY_COMMENT" value="false"/> | ||
| <option name="LAST_COMMIT_MESSAGE"/> | ||
| <option name="MAKE_NEW_CHANGELIST_ACTIVE" value="true"/> | ||
| <option name="OPTIMIZE_IMPORTS_BEFORE_PROJECT_COMMIT" value="false"/> | ||
| <option name="CHECK_FILES_UP_TO_DATE_BEFORE_COMMIT" value="false"/> | ||
| <option name="REFORMAT_BEFORE_PROJECT_COMMIT" value="false"/> | ||
| <option name="REFORMAT_BEFORE_FILE_COMMIT" value="false"/> | ||
| <option name="FILE_HISTORY_DIALOG_COMMENTS_SPLITTER_PROPORTION" value="0.8"/> | ||
| <option name="FILE_HISTORY_DIALOG_SPLITTER_PROPORTION" value="0.5"/> | ||
| <option name="ACTIVE_VCS_NAME"/> | ||
| <option name="UPDATE_GROUP_BY_PACKAGES" value="false"/> | ||
| <option name="UPDATE_GROUP_BY_CHANGELIST" value="false"/> | ||
| <option name="SHOW_FILE_HISTORY_AS_TREE" value="false"/> | ||
| <option name="FILE_HISTORY_SPLITTER_PROPORTION" value="0.6"/> | ||
| <component name="TypeScriptGeneratedFilesManager"> | ||
| <option name="version" value="3" /> | ||
| </component> | ||
| <component name="XDebuggerManager"> | ||
| <breakpoint-manager/> | ||
| <component name="VcsManagerConfiguration"> | ||
| <option name="MAKE_NEW_CHANGELIST_ACTIVE" value="true" /> | ||
| </component> | ||
| </project> | ||
| </project> |
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.
IDE 전용 파일은 커밋 대상에서 제외해주세요.
java-webMVC.iws는 IntelliJ 개인 워크스페이스 설정으로, 개인 경로(C:/Users/andyc/...)와 로컬 환경 정보가 포함되어 있습니다. 저장소에 포함되면 팀원 환경이 뒤섞이고 불필요한 충돌이 발생하며, 개인 정보 노출 우려도 있습니다. PR에서 이 파일을 삭제하고, .gitignore 등에 추가해 추후 커밋되지 않도록 해주세요.
🤖 Prompt for AI Agents
In the file java-webMVC.iws across all lines (1-176), this is an IDE-specific
workspace file containing personal and local environment settings with sensitive
user paths. Remove this file from the pull request entirely to avoid committing
personal data and environment-specific details. Additionally, add
java-webMVC.iws to the project's .gitignore file to prevent it from being
tracked and committed in the future.
| @PostMapping("/create") | ||
| public String createQuestion(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, SQLException { | ||
| String writer = req.getParameter("writer"); | ||
| String title = req.getParameter("title"); | ||
| String contents = req.getParameter("contents"); | ||
| KeyHolder keyHolder = new KeyHolder(); | ||
| DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); | ||
| String createdDate = LocalDateTime.now().format(formatter); | ||
| questionDao.insert(new Question(null, writer, title, contents,createdDate , 0)); | ||
|
|
||
| Question question = questionDao.findQuestionById(keyHolder.getId()); | ||
| if(question == null){ | ||
| return "redirect:/"; | ||
| } | ||
| return "redirect:/qna/show?questionId=" + question.getQuestionId(); | ||
| } |
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.
KeyHolder 사용 로직 오류
Line 44에서 KeyHolder를 생성하지만 ID를 설정하지 않아, Line 49의 keyHolder.getId()는 항상 null을 반환합니다. 이로 인해 질문 생성 후 항상 홈으로 리다이렉트되며, 생성된 질문 페이지로 이동하지 않습니다.
JPA를 사용하면 persist 후 엔티티 자체에 생성된 ID가 할당됩니다. 다음과 같이 수정하세요:
@PostMapping("/create")
public String createQuestion(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, SQLException {
String writer = req.getParameter("writer");
String title = req.getParameter("title");
String contents = req.getParameter("contents");
- KeyHolder keyHolder = new KeyHolder();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String createdDate = LocalDateTime.now().format(formatter);
- questionDao.insert(new Question(null, writer, title, contents,createdDate , 0));
+ Question newQuestion = new Question(null, writer, title, contents, createdDate, 0);
+ questionDao.insert(newQuestion);
- Question question = questionDao.findQuestionById(keyHolder.getId());
- if(question == null){
+ if(newQuestion.getQuestionId() == null){
return "redirect:/";
}
- return "redirect:/qna/show?questionId=" + question.getQuestionId();
+ return "redirect:/qna/show?questionId=" + newQuestion.getQuestionId();
}📝 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.
| @PostMapping("/create") | |
| public String createQuestion(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, SQLException { | |
| String writer = req.getParameter("writer"); | |
| String title = req.getParameter("title"); | |
| String contents = req.getParameter("contents"); | |
| KeyHolder keyHolder = new KeyHolder(); | |
| DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); | |
| String createdDate = LocalDateTime.now().format(formatter); | |
| questionDao.insert(new Question(null, writer, title, contents,createdDate , 0)); | |
| Question question = questionDao.findQuestionById(keyHolder.getId()); | |
| if(question == null){ | |
| return "redirect:/"; | |
| } | |
| return "redirect:/qna/show?questionId=" + question.getQuestionId(); | |
| } | |
| @PostMapping("/create") | |
| public String createQuestion(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, SQLException { | |
| String writer = req.getParameter("writer"); | |
| String title = req.getParameter("title"); | |
| String contents = req.getParameter("contents"); | |
| DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); | |
| String createdDate = LocalDateTime.now().format(formatter); | |
| Question newQuestion = new Question(null, writer, title, contents, createdDate, 0); | |
| questionDao.insert(newQuestion); | |
| if(newQuestion.getQuestionId() == null){ | |
| return "redirect:/"; | |
| } | |
| return "redirect:/qna/show?questionId=" + newQuestion.getQuestionId(); | |
| } |
🤖 Prompt for AI Agents
In src/main/java/jwp/controller/QnaController.java around lines 39-54, you
create a new KeyHolder but never set its ID so keyHolder.getId() is null; update
the flow so the DAO returns the generated ID (or the persisted Question) from
insert instead of relying on an unused KeyHolder. Change questionDao.insert(...)
to return the generated id (or Question), use that returned id to call
findQuestionById (or use the returned Question directly) and remove the unused
KeyHolder variable; then redirect using the real questionId.
| @PostMapping("/login") | ||
| public String login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, SQLException { | ||
| String userId = req.getParameter("userId"); | ||
| String password = req.getParameter("password"); | ||
| User user = userDao.findUserById(userId); | ||
| if (user!=null && password.equals(user.getPassword())){ | ||
| HttpSession session = req.getSession(); | ||
| session.setAttribute("user", user); | ||
| return "redirect:" + "/"; | ||
| } | ||
| else{ | ||
| HttpSession session = req.getSession(); | ||
| session.removeAttribute("user"); | ||
| return "user/loginFailed"; | ||
| } | ||
| } |
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.
평문 패스워드 비교 보안 취약점
Line 54에서 패스워드를 평문으로 비교하고 있습니다. 프로덕션 환경에서는 BCrypt 등을 사용한 해시 비교가 필요합니다. 학습 목적이라면 현재 구현도 괜찮지만, 추후 개선이 필요합니다.
🤖 Prompt for AI Agents
In src/main/java/jwp/controller/UserController.java around lines 49 to 64, the
login method compares plaintext passwords (line 54); replace this with a secure
hash check using BCrypt: ensure stored passwords are hashed (update
registration/create-user flow to bcrypt-hash passwords before persisting), and
in the login flow retrieve the hashed password and validate with
BCrypt.checkpw(plainPassword, hashedPassword) (or equivalent from your chosen
bcrypt library); remove any plaintext password equality checks and keep only
session logic on successful bcrypt verification.
| @GetMapping("/form)") | ||
| public String getForm(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, SQLException { | ||
| HttpSession session = req.getSession(); | ||
| User user = (User) session.getAttribute("user"); | ||
| System.out.println("user = " + user); | ||
| if (user!= null) { | ||
| return "qna/form"; | ||
| } | ||
| return "redirect:/user/loginForm"; | ||
| } |
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.
URL 매핑 오타 및 잘못된 뷰 반환
- Line 103:
@GetMapping("/form)")에 닫는 괄호가 잘못 위치했습니다./form)이 아닌/form이어야 합니다. - Line 109:
/user/form엔드포인트에서qna/form뷰를 반환하는 것은 잘못된 것으로 보입니다.user/form이어야 할 가능성이 높습니다. - Line 107: 디버그 출력문을 제거하세요.
다음과 같이 수정하세요:
- @GetMapping("/form)")
+ @GetMapping("/form")
public String getForm(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, SQLException {
HttpSession session = req.getSession();
User user = (User) session.getAttribute("user");
- System.out.println("user = " + user);
if (user!= null) {
- return "qna/form";
+ return "user/form";
}
return "redirect:/user/loginForm";
}📝 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.
| @GetMapping("/form)") | |
| public String getForm(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, SQLException { | |
| HttpSession session = req.getSession(); | |
| User user = (User) session.getAttribute("user"); | |
| System.out.println("user = " + user); | |
| if (user!= null) { | |
| return "qna/form"; | |
| } | |
| return "redirect:/user/loginForm"; | |
| } | |
| @GetMapping("/form") | |
| public String getForm(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, SQLException { | |
| HttpSession session = req.getSession(); | |
| User user = (User) session.getAttribute("user"); | |
| if (user!= null) { | |
| return "user/form"; | |
| } | |
| return "redirect:/user/loginForm"; | |
| } |
🤖 Prompt for AI Agents
In src/main/java/jwp/controller/UserController.java around lines 103 to 112, fix
the URL mapping and returned view and remove the debug print: change the
@GetMapping value from "/form)" to "/form", remove the System.out.println("user
= " + user) line, and when an authenticated user is present return the correct
user view (e.g., "user/form") instead of "qna/form"; keep the redirect to
"/user/loginForm" for unauthenticated requests and preserve the session/User
null check and thrown exceptions.
| <span class="article-header-time">${question.createdDate}</span> | ||
| <fmt:formatDate pattern="yyyy-MM-dd HH:mm:ss" value="${question.createdDate}"/> |
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.
중복된 날짜 표시를 제거하세요.
Line 22에서 ${question.createdDate}를 직접 출력한 후 Line 23에서 포맷된 날짜를 다시 출력하고 있습니다. 두 값이 모두 화면에 표시됩니다.
다음 diff를 적용하여 수정하세요:
<div class="article-header-text">
<span class="article-author-name">${question.writer}</span>
- <span class="article-header-time">${question.createdDate}</span>
- <fmt:formatDate pattern="yyyy-MM-dd HH:mm:ss" value="${question.createdDate}"/>
+ <span class="article-header-time">
+ <fmt:formatDate pattern="yyyy-MM-dd HH:mm:ss" value="${question.createdDate}"/>
+ </span>
<i class="icon-link"></i>📝 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.
| <span class="article-header-time">${question.createdDate}</span> | |
| <fmt:formatDate pattern="yyyy-MM-dd HH:mm:ss" value="${question.createdDate}"/> | |
| <span class="article-header-time"> | |
| <fmt:formatDate pattern="yyyy-MM-dd HH:mm:ss" value="${question.createdDate}"/> | |
| </span> |
🤖 Prompt for AI Agents
In src/main/webapp/qna/show.jsp around lines 22-23, the raw
${question.createdDate} and the fmt:formatDate output are both rendered causing
duplicate date display; remove the direct ${question.createdDate} output and
ensure only the formatted date is shown (either delete the span containing
${question.createdDate} or replace its contents with the fmt:formatDate result
so the page displays a single, formatted date).
| <div class="article-util"> | ||
| <ul class="article-util-list"> | ||
| <c:if test="${not empty sessionScope.user}"> | ||
| <c:if test="${sessionScope.user.name == question.writer}"> |
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.
보안 강화를 위해 사용자 ID로 비교하세요.
현재 사용자 이름(name)으로 작성자를 비교하고 있습니다. 동명이인이 있을 수 있으므로 userId로 비교하는 것이 더 안전합니다.
다음 diff를 적용하세요:
- <c:if test="${sessionScope.user.name == question.writer}">
+ <c:if test="${sessionScope.user.userId == question.writer}">Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/main/webapp/qna/show.jsp around line 34, the template currently compares
sessionScope.user.name to question.writer; change this to compare the user's
unique ID instead. Update the conditional to use sessionScope.user.userId (or
the actual session user ID property) and the question's stored writer ID field
(e.g., question.writerId or question.writer.userId) so the check uses IDs rather
than names; ensure the property names used match the backend model and adjust
any related JSP EL references accordingly.
| <form class="submit-write"> | ||
| <div class="form-group" style="padding:14px;"> | ||
| <textarea class="form-control" placeholder="Update your status"></textarea> | ||
| </div> | ||
| <button class="btn btn-primary pull-right" type="button">답변하기</button> | ||
| <div class="clearfix" /> | ||
| </form> |
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.
답변 작성 폼이 동작하지 않습니다.
폼에 action 속성이 없고 버튼의 type이 "button"으로 설정되어 있어 제출이 되지 않습니다. 또한 writer 정보를 전송할 hidden input이 없습니다.
다음 diff를 적용하여 수정하세요:
<div class="answerWrite">
- <form class="submit-write">
+ <form class="submit-write" action="/api/qna/addAnswer" method="POST">
+ <input type="hidden" name="questionId" value="${question.questionId}"/>
+ <input type="hidden" name="writer" value="${sessionScope.user.userId}"/>
<div class="form-group" style="padding:14px;">
- <textarea class="form-control" placeholder="Update your status"></textarea>
+ <textarea name="contents" class="form-control" placeholder="답변을 입력하세요"></textarea>
</div>
- <button class="btn btn-primary pull-right" type="button">답변하기</button>
+ <button class="btn btn-primary pull-right" type="submit">답변하기</button>
<div class="clearfix" />📝 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.
| <form class="submit-write"> | |
| <div class="form-group" style="padding:14px;"> | |
| <textarea class="form-control" placeholder="Update your status"></textarea> | |
| </div> | |
| <button class="btn btn-primary pull-right" type="button">답변하기</button> | |
| <div class="clearfix" /> | |
| </form> | |
| <form class="submit-write" action="/api/qna/addAnswer" method="POST"> | |
| <input type="hidden" name="questionId" value="${question.questionId}"/> | |
| <input type="hidden" name="writer" value="${sessionScope.user.userId}"/> | |
| <div class="form-group" style="padding:14px;"> | |
| <textarea name="contents" class="form-control" placeholder="답변을 입력하세요"></textarea> | |
| </div> | |
| <button class="btn btn-primary pull-right" type="submit">답변하기</button> | |
| <div class="clearfix" /> | |
| </form> |
| <textarea class="form-control" placeholder="Update your status"></textarea> | ||
| </div> | ||
| <button class="btn btn-primary pull-right" type="button">답변하기</button> | ||
| <div class="clearfix" /> |
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.
HTML5에서 유효하지 않은 self-closing div 태그입니다.
<div class="clearfix" />는 HTML5에서 유효하지 않습니다.
다음과 같이 수정하세요:
- <div class="clearfix" />
+ <div class="clearfix"></div>📝 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.
| <div class="clearfix" /> | |
| <div class="clearfix"></div> |
🤖 Prompt for AI Agents
In src/main/webapp/qna/show.jsp around line 96, the element uses an invalid
self-closing div (<div class="clearfix" />); replace it with a proper opening
and closing tag by changing it to <div class="clearfix"></div> to conform to
HTML5.
| <script type="text/template" id="answerTemplate"> | ||
| <article class="article"> | ||
| <div class="article-header"> | ||
| <div class="article-header-thumb"> | ||
| <img src="/img/picture.jpeg" class="article-author-thumb" alt=""> | ||
| </div> | ||
| <div class="article-header-text"> | ||
| {0} | ||
| <div class="article-header-time">{1}</div> | ||
| </div> | ||
| </div> | ||
| <div class="article-doc comment-doc"> | ||
| {2} | ||
| </div> | ||
| <div class="article-util"> | ||
| <ul class="article-util-list"> | ||
| <li> | ||
| <a class="link-modify-article" href="/api/qna/updateAnswer/{3}">수정</a> | ||
| </li> | ||
| <li> | ||
| <form class="form-delete" action="/api/qna/deleteAnswer" method="POST"> | ||
| <input type="hidden" name="answerId" value="{4}"/> | ||
| <button type="submit" class="link-delete-article">삭제</button> | ||
| </form> | ||
| </li> | ||
| </ul> | ||
| </div> | ||
| </article> | ||
| </script> |
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.
🧩 Analysis chain
답변 템플릿을 활용하는 JavaScript 코드를 추가하세요.
answerTemplate이 정의되어 있지만 이를 사용하는 JavaScript 코드가 없습니다. 동적으로 답변을 추가하려면 관련 스크립트가 필요합니다.
scripts.js 파일에 답변 추가 기능이 구현되어 있는지 확인하세요:
🏁 Script executed:
#!/bin/bash
# Description: Check if answer template is used in JavaScript files
# Search for answerTemplate usage in JS files
rg -n "answerTemplate" --type=jsLength of output: 0
추가 검증이 필요합니다. 더 광범위하게 검색하겠습니다.
🏁 Script executed:
#!/bin/bash
# Search for answerTemplate in all file types
echo "=== Searching for answerTemplate in all files ==="
rg -n "answerTemplate"
echo -e "\n=== Checking for answer-related AJAX/dynamic operations ==="
rg -n "addAnswer|deleteAnswer|updateAnswer" --type=js -A 2 -B 2
echo -e "\n=== Checking for inline scripts in show.jsp ==="
rg -n "script" src/main/webapp/qna/show.jsp -A 5 -B 2Length of output: 833
🏁 Script executed:
#!/bin/bash
# Check scripts.js for any answer-related functionality
echo "=== Examining scripts.js for answer handling ==="
cat src/main/webapp/js/scripts.js | head -100
echo -e "\n=== Search for answer in scripts.js ==="
rg -n "answer" src/main/webapp/js/scripts.js -i
echo -e "\n=== Search for form handling or template rendering ==="
rg -n "template|append|html|\$\(" src/main/webapp/js/scripts.js -i | head -20Length of output: 145
답변 템플릿을 사용하는 JavaScript 코드가 실제로 구현되지 않았습니다.
검증 결과, answerTemplate이 정의되어 있지만 전체 코드베이스에서 어디에도 참조되지 않습니다. scripts.js 파일도 답변 추가/삭제/수정 관련 기능이 없으며, AJAX 핸들러도 존재하지 않습니다. 동적으로 답변을 렌더링하는 JavaScript 구현이 필수적으로 필요합니다.
🤖 Prompt for AI Agents
In src/main/webapp/qna/show.jsp around lines 106 to 134, the answerTemplate HTML
exists but no JS uses it and there are no AJAX handlers; implement a small
script (e.g., in scripts.js or an inline module) that: 1) selects the
#answerTemplate, provides a render function to substitute placeholders and
append/prepend rendered answer nodes into the answers container; 2) loads
initial answers via GET /api/qna/answers?questionId=... or binds to existing
server data, and uses fetch/POST/PUT/DELETE for add, edit and delete actions to
/api/qna endpoints; 3) attach event listeners (use event delegation) for the
modify link to open an edit form and for the .form-delete submit to intercept,
call DELETE via fetch, and remove the DOM node on success; 4) handle errors and
update timestamps/content safely, ensuring CSRF/token headers if required.
Summary by CodeRabbit
릴리스 노트
새 기능
개선