Skip to content

Conversation

@Tak002
Copy link

@Tak002 Tak002 commented Nov 5, 2025

Summary by CodeRabbit

릴리스 노트

  • 새 기능

    • Q&A 질문 작성, 조회 및 상세 보기 기능 추가
    • 사용자 계정 관리 기능 확장 (회원가입, 로그인, 정보 수정)
    • 세션 기반 사용자 인증 시스템 구현
  • 개선

    • 애플리케이션 아키텍처 현대화 및 최신 프레임워크 적용
    • 데이터베이스 연결 및 설정 최적화
    • 사용자 인터페이스 및 뷰 개선

Tak002 added 22 commits October 8, 2025 13:55
리퀘스트 핸들링을 위한 DispatcherServlet 추가 및 기존 컨트롤러의 @WebServlet 어노테이션 제거, 구현을 위한 HttpServletRequest 필요 메서드 확인
…ing KeyHolder parameter and adding transactional support
@coderabbitai
Copy link

coderabbitai bot commented Nov 5, 2025

개요

Servlet 기반의 레거시 웹 애플리케이션을 Spring Boot 기반의 현대적인 아키텍처로 마이그레이션했습니다. 수동 JDBC 연결 관리 및 임베디드 Tomcat 설정을 Spring Boot 스타터 및 JPA로 대체하고, 컨트롤러를 Spring MVC 스타일로 리팩토링했습니다.

변경사항

응집 / 파일(들) 변경 요약
빌드 설정
build.gradle
Spring Boot 플러그인 및 의존성 관리 플러그인 추가; 스프링 부트 스타터(web, data-jpa), Lombok, JSP 지원 라이브러리로 기존 JDBC/서블릿 의존성 교체; sourceCompatibility = '11' 추가
IDE 프로젝트 구성
java-webMVC.ipr
마이그레이션 상태 서비스, 검사 프로필 관리자, 프로젝트 ID 컴포넌트 추가; XML 포매팅 정규화 및 프로젝트 루트 매니저 속성 업데이트
IDE 작업공간 구성
java-webMVC.iws
변경 목록 관리자 업데이트, 런 설정 확장(WebApp/Remote 템플릿), 디버거 중단점 포매팅 변경, 새로운 작업 관리자 구조 및 SVN 설정 추가
레거시 서블릿 제거
WebServerLauncher.java (삭제), core/db/MemoryUserRepository.java (삭제), core/db/Repository.java (삭제), core/jdbc/ConnectionManager.java (삭제), jwp/support/context/ContextLoaderListener.java (삭제)
임베디드 Tomcat 시작 코드, 인메모리 저장소, JDBC 연결 풀 관리자, 서블릿 리스너 제거
Spring Boot 애플리케이션 진입점
jwp/WebServerLauncher.java
@SpringBootApplication 주석과 함께 새로운 Spring Boot 애플리케이션 메인 클래스 추가
Spring MVC 컨트롤러
jwp/controller/HomeController.java, jwp/controller/QnaController.java, jwp/controller/UserController.java
HttpServlet 기반의 컨트롤러를 @Controller@GetMapping/@PostMapping 주석을 사용한 Spring MVC 스타일로 리팩토링; 세션 관리, 요청 처리 로직 추가
Spring Data JPA DAO
jwp/dao/QuestionDao.java, jwp/dao/UserDao.java
EntityManager를 사용한 새로운 Spring Repository DAO 클래스; findAll(), insert(), update(), findById() 메서드 제공
JPA 엔티티 모델
jwp/model/Question.java, jwp/model/User.java
@Entity 및 Lombok 주석(@Getter, @Setter, @NoArgsConstructor, @AllArgsConstructor, @ToString) 적용; 명시적 생성자 및 접근자 메서드 제거
지원 유틸리티
jwp/support/KeyHolder.java
삽입된 엔티티 ID를 보유하기 위한 새로운 유틸리티 클래스 추가
패키지 재구성
core/filter/CharacterEncodingFilter.java, core/filter/ResourceFilter.java
필터 클래스의 패키지를 core.web.filter에서 core.filter로 이동
Spring Boot 설정
application.properties
서버 포트(8080), H2 데이터베이스 설정, JPA/Hibernate 구성, MVC 뷰 접두사/접미사(.jsp) 지정, 로깅 레벨 설정
JSP 뷰 페이지
src/main/webapp/home.jsp, src/main/webapp/qna/form.jsp, src/main/webapp/qna/show.jsp
동적 질문 목록, 질문 작성 양식, 질문 상세 페이지를 렌더링하는 새로운 JSP 뷰 추가
레거시 JSP 뷰 제거
webapp/home.jsp, webapp/qna/form.jsp, webapp/qna/show.jsp (삭제)
정적 콘텐츠와 임시 위치에 있던 기존 JSP 페이지 제거

시퀀스 다이어그램

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: 질문 상세 페이지 표시
Loading

예상 코드 리뷰 노력

🎯 4 (복잡함) | ⏱️ ~60분

주의 깊게 검토가 필요한 영역:

  • build.gradle: Spring Boot 의존성 마이그레이션의 완전성 검증, 버전 호환성 및 빠진 라이브러리 확인
  • Controller 클래스들 (HomeController, QnaController, UserController): 세션 관리, 요청 매개변수 처리, 데이터 검증 로직의 정확성 및 보안 고려사항
  • DAO 클래스들 (UserDao, QuestionDao): JPA 쿼리의 정확성, @Transactional 어노테이션의 적절한 사용, 트랜잭션 경계
  • 엔티티 모델: @Entity 어노테이션 설정, @Id 필드 정의, 테이블 매핑의 정확성
  • application.properties: 데이터베이스 연결 설정, SQL 초기화 모드, Hibernate 명명 전략의 적절성
  • JSP 뷰: JSTL 사용, 모델 바인딩의 정확성, 보안 취약점(XSS, CSRF) 확인
  • IDE 구성 파일 변경: 작은 변경이지만 이전 구성과의 호환성 영향 범위 검토

🐰 묵은 서블릿을 벗어나고,
스프링 부트 날개로 솟아나네!
JPA의 마법으로 데이터 춤을 추고,
컨트롤러들은 우아하게 요청을 맞이하네.
여기 새로운 시작, 더 나은 길! 🌱✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning 제목이 PR의 주요 변경사항을 명확하게 설명하지 못하고 있습니다. '6주차 미션'은 일반적인 표현이고 '서버 5조 조영탁'은 작성자 정보일 뿐 실제 코드 변경사항을 나타내지 않습니다. 제목을 'Spring Boot로 마이그레이션 및 MVC 컨트롤러 구현' 등으로 변경하여 주요 변경사항(Spring Boot 적용, Servlet에서 Spring MVC로 마이그레이션)을 명확히 나타내세요.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

🧹 Nitpick comments (11)
src/main/java/core/filter/ResourceFilter.java (1)

16-16: 변수명 오타를 수정하는 것을 고려해보세요.

resourcePrefixsresourcePrefixes가 올바른 철자입니다. 기능에는 영향이 없지만, 코드 가독성 향상을 위해 수정을 권장합니다.

-    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: 예외 처리 및 디버그 출력 개선 필요

  1. Line 58: Long.parseLong()는 잘못된 입력에 대해 NumberFormatException을 발생시킬 수 있습니다. 예외 처리를 추가하거나 Spring의 @RequestParam을 사용하는 것을 권장합니다.
  2. Line 60: System.out.println은 로거로 대체하거나 제거해야 합니다.
  3. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6a27f59 and 9867c0f.

⛔ Files ignored due to path filters (3)
  • src/main/webapp/favicon.ico is excluded by !**/*.ico
  • src/main/webapp/img/KUIT.png is excluded by !**/*.png
  • src/main/webapp/img/picture.jpeg is 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: 패키지 재구성이 잘 적용되었습니다.

CharacterEncodingFiltercore.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)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

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=gradle

Length 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.gradlesourceCompatibilityjava-webMVC.iprlanguageLevelproject-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.

Comment on lines 1 to +176
<?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="&lt;template&gt;" 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="&lt;template&gt;" 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>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +39 to +54
@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();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Suggested change
@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.

Comment on lines +49 to +64
@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";
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

평문 패스워드 비교 보안 취약점

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.

Comment on lines +103 to +112
@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";
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

URL 매핑 오타 및 잘못된 뷰 반환

  1. Line 103: @GetMapping("/form)")에 닫는 괄호가 잘못 위치했습니다. /form)이 아닌 /form이어야 합니다.
  2. Line 109: /user/form 엔드포인트에서 qna/form 뷰를 반환하는 것은 잘못된 것으로 보입니다. user/form이어야 할 가능성이 높습니다.
  3. 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.

Suggested change
@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.

Comment on lines +22 to +23
<span class="article-header-time">${question.createdDate}</span>
<fmt:formatDate pattern="yyyy-MM-dd HH:mm:ss" value="${question.createdDate}"/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

중복된 날짜 표시를 제거하세요.

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.

Suggested change
<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}">
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

보안 강화를 위해 사용자 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.

Comment on lines +91 to +97
<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>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

답변 작성 폼이 동작하지 않습니다.

폼에 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.

Suggested change
<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" />
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
<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.

Comment on lines +106 to +134
<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>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

답변 템플릿을 활용하는 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=js

Length 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 2

Length 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 -20

Length 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant