Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 230 additions & 0 deletions debug_complete.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PETTY 로그인 디버깅</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.debug-section {
background: white;
padding: 20px;
margin-bottom: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.status {
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.success { background-color: #d4edda; color: #155724; }
.error { background-color: #f8d7da; color: #721c24; }
.warning { background-color: #fff3cd; color: #856404; }
button {
background-color: #FF9933;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:hover { background-color: #e67e00; }
.code {
background-color: #f8f9fa;
padding: 10px;
border-radius: 4px;
font-family: monospace;
white-space: pre-wrap;
}
</style>
</head>
<body>
<h1>🔍 PETTY 로그인 상태 디버깅</h1>

<div class="debug-section">
<h2>1. JWT 토큰 상태</h2>
<div id="jwtStatus"></div>
<button onclick="checkJWT()">JWT 확인</button>
<button onclick="clearJWT()">로그아웃</button>
</div>

<div class="debug-section">
<h2>2. 사용자 정보 확인</h2>
<div id="userStatus"></div>
<button onclick="checkUser()">사용자 정보 확인</button>
</div>

<div class="debug-section">
<h2>3. API 엔드포인트 테스트</h2>
<div id="apiStatus"></div>
<button onclick="testAPI()">API 테스트</button>
</div>

<div class="debug-section">
<h2>4. 로그인 테스트</h2>
<div>
<input type="text" id="username" placeholder="사용자명" style="margin: 5px; padding: 8px;">
<input type="password" id="password" placeholder="비밀번호" style="margin: 5px; padding: 8px;">
<button onclick="testLogin()">로그인 테스트</button>
</div>
<div id="loginStatus"></div>
</div>

<script>
function checkJWT() {
const statusDiv = document.getElementById("jwtStatus");

// HttpOnly 쿠키는 JavaScript로 직접 접근할 수 없음
statusDiv.innerHTML = `
<div class="status warning">⚠️ HttpOnly 쿠키 방식으로 변경됨</div>
<div class="code">쿠키는 JavaScript로 직접 접근할 수 없으므로 /api/users/me로 인증 상태를 확인합니다.</div>
`;

// 대신 사용자 정보 API로 인증 상태 확인
checkUser();
}

function clearJWT() {
// HttpOnly 쿠키는 JavaScript로 삭제할 수 없으므로 로그아웃 API 호출
fetch('/logout', {
method: 'POST',
credentials: 'include'
}).then(() => {
document.getElementById("jwtStatus").innerHTML = `
<div class="status warning">🗑️ 로그아웃 요청을 보냈습니다</div>
`;
checkUser();
}).catch(err => {
document.getElementById("jwtStatus").innerHTML = `
<div class="status error">❌ 로그아웃 실패: ${err.message}</div>
`;
});
}

async function checkUser() {
const statusDiv = document.getElementById("userStatus");

try {
const res = await fetch('http://localhost:8080/api/users/me', {
credentials: 'include' // 쿠키 포함
});

if (res.ok) {
const user = await res.json();
statusDiv.innerHTML = `
<div class="status success">✅ 사용자 정보 조회 성공 (로그인됨)</div>
<div class="code">${JSON.stringify(user, null, 2)}</div>
`;
} else if (res.status === 401) {
statusDiv.innerHTML = `
<div class="status error">❌ 로그인되지 않음 (401)</div>
`;
} else {
statusDiv.innerHTML = `
<div class="status error">❌ 사용자 정보 조회 실패 (${res.status})</div>
<div class="code">응답: ${await res.text()}</div>
`;
}
} catch (err) {
statusDiv.innerHTML = `
<div class="status error">❌ 네트워크 오류</div>
<div class="code">${err.message}</div>
`;
}
}

async function testAPI() {
const statusDiv = document.getElementById("apiStatus");
const tests = [
{ name: "메인 페이지", url: "http://localhost:8080/" },
{ name: "로그인 페이지", url: "http://localhost:8080/login" },
{ name: "API 상태", url: "http://localhost:8080/api/posts?type=QNA&page=0&size=5" }
];

statusDiv.innerHTML = "<div>API 테스트 중...</div>";

let results = "<h3>API 테스트 결과:</h3>";

for (const test of tests) {
try {
const res = await fetch(test.url);
if (res.ok) {
results += `<div class="status success">✅ ${test.name}: 성공 (${res.status})</div>`;
} else {
results += `<div class="status error">❌ ${test.name}: 실패 (${res.status})</div>`;
}
} catch (err) {
results += `<div class="status error">❌ ${test.name}: 네트워크 오류</div>`;
}
}

statusDiv.innerHTML = results;
}

async function testLogin() {
const username = document.getElementById("username").value;
const password = document.getElementById("password").value;
const statusDiv = document.getElementById("loginStatus");

if (!username || !password) {
statusDiv.innerHTML = `
<div class="status warning">⚠️ 사용자명과 비밀번호를 입력해주세요</div>
`;
return;
}

try {
// Spring Security LoginFilter의 기본 경로로 요청
const res = await fetch('http://localhost:8080/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include', // 쿠키 포함
body: JSON.stringify({ username, password })
});

if (res.ok) {
const result = await res.json();
statusDiv.innerHTML = `
<div class="status success">✅ 로그인 성공!</div>
<div class="code">쿠키에 토큰이 저장되었습니다</div>
<div class="code">${JSON.stringify(result, null, 2)}</div>
`;

// 로그인 후 사용자 정보 자동 확인
setTimeout(() => {
checkJWT();
checkUser();
}, 500);
} else {
const responseText = await res.text();
statusDiv.innerHTML = `
<div class="status error">❌ 로그인 실패 (${res.status})</div>
<div class="code">${responseText}</div>
`;
}
} catch (err) {
statusDiv.innerHTML = `
<div class="status error">❌ 네트워크 오류</div>
<div class="code">${err.message}</div>
`;
}
}

// 페이지 로드 시 자동으로 상태 확인
window.addEventListener('DOMContentLoaded', () => {
checkJWT();
checkUser();
});
</script>
</body>
</html>
Comment on lines +1 to +230
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

프로덕션 배포 시 보안 위험이 있습니다.

이 디버깅 페이지는 개발용으로는 유용하지만 다음과 같은 보안 우려사항이 있습니다:

  1. 프로덕션 환경에 배포되면 안 됩니다
  2. 하드코딩된 localhost URL들이 포함되어 있습니다
  3. 로그인 테스트 기능이 악용될 수 있습니다

다음과 같은 보안 조치를 권장합니다:

+<!-- 개발 환경에서만 사용하는 디버깅 페이지입니다. 프로덕션 배포 시 제거해야 합니다. -->
+<script>
+    // 프로덕션 환경 체크
+    if (window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
+        document.body.innerHTML = '<h1>이 페이지는 개발 환경에서만 사용 가능합니다.</h1>';
+    }
+</script>

또한 프로덕션 빌드 시 이 파일을 제외하는 설정을 추가하거나, /debug/ 경로로 이동시켜 프로덕션에서 접근을 차단하는 것을 고려해주세요.

📝 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
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PETTY 로그인 디버깅</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.debug-section {
background: white;
padding: 20px;
margin-bottom: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.status {
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.success { background-color: #d4edda; color: #155724; }
.error { background-color: #f8d7da; color: #721c24; }
.warning { background-color: #fff3cd; color: #856404; }
button {
background-color: #FF9933;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:hover { background-color: #e67e00; }
.code {
background-color: #f8f9fa;
padding: 10px;
border-radius: 4px;
font-family: monospace;
white-space: pre-wrap;
}
</style>
</head>
<body>
<h1>🔍 PETTY 로그인 상태 디버깅</h1>
<div class="debug-section">
<h2>1. JWT 토큰 상태</h2>
<div id="jwtStatus"></div>
<button onclick="checkJWT()">JWT 확인</button>
<button onclick="clearJWT()">로그아웃</button>
</div>
<div class="debug-section">
<h2>2. 사용자 정보 확인</h2>
<div id="userStatus"></div>
<button onclick="checkUser()">사용자 정보 확인</button>
</div>
<div class="debug-section">
<h2>3. API 엔드포인트 테스트</h2>
<div id="apiStatus"></div>
<button onclick="testAPI()">API 테스트</button>
</div>
<div class="debug-section">
<h2>4. 로그인 테스트</h2>
<div>
<input type="text" id="username" placeholder="사용자명" style="margin: 5px; padding: 8px;">
<input type="password" id="password" placeholder="비밀번호" style="margin: 5px; padding: 8px;">
<button onclick="testLogin()">로그인 테스트</button>
</div>
<div id="loginStatus"></div>
</div>
<script>
function checkJWT() {
const statusDiv = document.getElementById("jwtStatus");
// HttpOnly 쿠키는 JavaScript로 직접 접근할 수 없음
statusDiv.innerHTML = `
<div class="status warning">⚠️ HttpOnly 쿠키 방식으로 변경됨</div>
<div class="code">쿠키는 JavaScript로 직접 접근할 수 없으므로 /api/users/me로 인증 상태를 확인합니다.</div>
`;
// 대신 사용자 정보 API로 인증 상태 확인
checkUser();
}
function clearJWT() {
// HttpOnly 쿠키는 JavaScript로 삭제할 수 없으므로 로그아웃 API 호출
fetch('/logout', {
method: 'POST',
credentials: 'include'
}).then(() => {
document.getElementById("jwtStatus").innerHTML = `
<div class="status warning">🗑️ 로그아웃 요청을 보냈습니다</div>
`;
checkUser();
}).catch(err => {
document.getElementById("jwtStatus").innerHTML = `
<div class="status error">❌ 로그아웃 실패: ${err.message}</div>
`;
});
}
async function checkUser() {
const statusDiv = document.getElementById("userStatus");
try {
const res = await fetch('http://localhost:8080/api/users/me', {
credentials: 'include' // 쿠키 포함
});
if (res.ok) {
const user = await res.json();
statusDiv.innerHTML = `
<div class="status success">✅ 사용자 정보 조회 성공 (로그인됨)</div>
<div class="code">${JSON.stringify(user, null, 2)}</div>
`;
} else if (res.status === 401) {
statusDiv.innerHTML = `
<div class="status error">❌ 로그인되지 않음 (401)</div>
`;
} else {
statusDiv.innerHTML = `
<div class="status error">❌ 사용자 정보 조회 실패 (${res.status})</div>
<div class="code">응답: ${await res.text()}</div>
`;
}
} catch (err) {
statusDiv.innerHTML = `
<div class="status error">❌ 네트워크 오류</div>
<div class="code">${err.message}</div>
`;
}
}
async function testAPI() {
const statusDiv = document.getElementById("apiStatus");
const tests = [
{ name: "메인 페이지", url: "http://localhost:8080/" },
{ name: "로그인 페이지", url: "http://localhost:8080/login" },
{ name: "API 상태", url: "http://localhost:8080/api/posts?type=QNA&page=0&size=5" }
];
statusDiv.innerHTML = "<div>API 테스트 중...</div>";
let results = "<h3>API 테스트 결과:</h3>";
for (const test of tests) {
try {
const res = await fetch(test.url);
if (res.ok) {
results += `<div class="status success">✅ ${test.name}: 성공 (${res.status})</div>`;
} else {
results += `<div class="status error">❌ ${test.name}: 실패 (${res.status})</div>`;
}
} catch (err) {
results += `<div class="status error">❌ ${test.name}: 네트워크 오류</div>`;
}
}
statusDiv.innerHTML = results;
}
async function testLogin() {
const username = document.getElementById("username").value;
const password = document.getElementById("password").value;
const statusDiv = document.getElementById("loginStatus");
if (!username || !password) {
statusDiv.innerHTML = `
<div class="status warning">⚠️ 사용자명과 비밀번호를 입력해주세요</div>
`;
return;
}
try {
// Spring Security LoginFilter의 기본 경로로 요청
const res = await fetch('http://localhost:8080/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include', // 쿠키 포함
body: JSON.stringify({ username, password })
});
if (res.ok) {
const result = await res.json();
statusDiv.innerHTML = `
<div class="status success">✅ 로그인 성공!</div>
<div class="code">쿠키에 토큰이 저장되었습니다</div>
<div class="code">${JSON.stringify(result, null, 2)}</div>
`;
// 로그인 후 사용자 정보 자동 확인
setTimeout(() => {
checkJWT();
checkUser();
}, 500);
} else {
const responseText = await res.text();
statusDiv.innerHTML = `
<div class="status error">❌ 로그인 실패 (${res.status})</div>
<div class="code">${responseText}</div>
`;
}
} catch (err) {
statusDiv.innerHTML = `
<div class="status error">❌ 네트워크 오류</div>
<div class="code">${err.message}</div>
`;
}
}
// 페이지 로드 시 자동으로 상태 확인
window.addEventListener('DOMContentLoaded', () => {
checkJWT();
checkUser();
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PETTY 로그인 디버깅</title>
<!-- …existing styles… -->
</head>
<body>
<!-- 개발 환경에서만 사용하는 디버깅 페이지입니다. 프로덕션 배포 시 제거해야 합니다. -->
<script>
// 프로덕션 환경 체크
if (window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
document.body.innerHTML = '<h1>이 페이지는 개발 환경에서만 사용 가능합니다.</h1>';
}
</script>
<h1>🔍 PETTY 로그인 상태 디버깅</h1>
<div class="debug-section">
<h2>1. JWT 토큰 상태</h2>
<div id="jwtStatus"></div>
<button onclick="checkJWT()">JWT 확인</button>
<button onclick="clearJWT()">로그아웃</button>
</div>
<!-- …the rest of your debug page… -->
</body>
</html>
🤖 Prompt for AI Agents
In debug_complete.html lines 1 to 230, the debugging page contains hardcoded
localhost URLs and a login test feature that pose security risks if deployed to
production. To fix this, remove or disable this page from the production build
by excluding it in the build configuration or moving it under a protected path
like /debug/ that is blocked in production. Also, replace hardcoded URLs with
environment-based configurations and restrict or remove the login test
functionality to prevent misuse.

Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,22 @@ public ResponseEntity<List<CommentResponse>> getComments(@PathVariable Long post
public ResponseEntity<?> addComment(@PathVariable Long postId,
@RequestBody CommentRequest request,
@AuthenticationPrincipal CustomUserDetails userDetails) {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
Long commentId = commentService.addComment(postId, request, user);
return ResponseEntity.ok().body(commentId);
try {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);

System.out.println("🔥 댓글 등록 시작 - postId: " + postId + ", user: " + username);

Long commentId = commentService.addComment(postId, request, user);

System.out.println("✅ 댓글 등록 완료 - commentId: " + commentId);

return ResponseEntity.ok().body(commentId);
} catch (Exception e) {
System.err.println("❌ 댓글 등록 실패: " + e.getMessage());
e.printStackTrace();
return ResponseEntity.badRequest().body("댓글 등록에 실패했습니다: " + e.getMessage());
}
Comment on lines +32 to +47
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

일관성 없는 예외 처리 개선 필요

addComment 메서드에 예외 처리가 추가되었지만, updateComment 메서드(50-58라인)에는 동일한 처리가 없어 일관성이 부족합니다.

또한 다음 개선사항들을 적용해주세요:

-            System.out.println("🔥 댓글 등록 시작 - postId: " + postId + ", user: " + username);
-            System.out.println("✅ 댓글 등록 완료 - commentId: " + commentId);
+            log.info("댓글 등록 시작 - postId: {}, user: {}", postId, username);
+            log.info("댓글 등록 완료 - commentId: {}", commentId);
-        } catch (Exception e) {
-            System.err.println("❌ 댓글 등록 실패: " + e.getMessage());
-            e.printStackTrace();
-            return ResponseEntity.badRequest().body("댓글 등록에 실패했습니다: " + e.getMessage());
+        } catch (Exception e) {
+            log.error("댓글 등록 실패 - postId: {}, user: {}", postId, username, e);
+            return ResponseEntity.badRequest().body("댓글 등록에 실패했습니다.");
📝 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
try {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
System.out.println("🔥 댓글 등록 시작 - postId: " + postId + ", user: " + username);
Long commentId = commentService.addComment(postId, request, user);
System.out.println("✅ 댓글 등록 완료 - commentId: " + commentId);
return ResponseEntity.ok().body(commentId);
} catch (Exception e) {
System.err.println("❌ 댓글 등록 실패: " + e.getMessage());
e.printStackTrace();
return ResponseEntity.badRequest().body("댓글 등록에 실패했습니다: " + e.getMessage());
}
try {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
log.info("댓글 등록 시작 - postId: {}, user: {}", postId, username);
Long commentId = commentService.addComment(postId, request, user);
log.info("댓글 등록 완료 - commentId: {}", commentId);
return ResponseEntity.ok().body(commentId);
} catch (Exception e) {
log.error("댓글 등록 실패 - postId: {}, user: {}", postId, username, e);
return ResponseEntity.badRequest().body("댓글 등록에 실패했습니다.");
}
🤖 Prompt for AI Agents
In src/main/java/io/github/petty/community/controller/CommentController.java
around lines 32 to 47, the addComment method has try-catch exception handling
but the updateComment method (lines 50 to 58) lacks it, causing inconsistency.
To fix this, wrap the updateComment method's logic in a try-catch block similar
to addComment, logging errors and returning appropriate ResponseEntity responses
on failure to maintain consistent exception handling across methods.

🛠️ Refactor suggestion

예외 처리 추가는 좋지만 로깅 방식을 개선해주세요.

try-catch 블록을 통한 예외 처리는 좋은 접근이지만, 다음 개선사항들을 고려해보세요:

  1. 프로덕션 환경에 적합한 로깅 사용: System.out.println 대신 SLF4J Logger 사용
  2. 더 구체적인 예외 처리: 일반적인 Exception 대신 특정 예외 타입들 처리

다음과 같이 개선해보세요:

+import lombok.extern.slf4j.Slf4j;

+@Slf4j
@RestController
@RequiredArgsConstructor
public class CommentController {

    @PostMapping("/api/posts/{postId}/comments")
    public ResponseEntity<?> addComment(@PathVariable Long postId,
                                        @RequestBody CommentRequest request,
                                        @AuthenticationPrincipal CustomUserDetails userDetails) {
        try {
            String username = userDetails.getUsername();
            Users user = usersRepository.findByUsername(username);
            
-           System.out.println("🔥 댓글 등록 시작 - postId: " + postId + ", user: " + username);
+           log.info("댓글 등록 시작 - postId: {}, user: {}", postId, username);
            
            Long commentId = commentService.addComment(postId, request, user);
            
-           System.out.println("✅ 댓글 등록 완료 - commentId: " + commentId);
+           log.info("댓글 등록 완료 - commentId: {}", commentId);
            
            return ResponseEntity.ok().body(commentId);
-       } catch (Exception e) {
-           System.err.println("❌ 댓글 등록 실패: " + e.getMessage());
-           e.printStackTrace();
+       } catch (IllegalArgumentException e) {
+           log.warn("댓글 등록 실패 - 잘못된 요청: {}", e.getMessage());
+           return ResponseEntity.badRequest().body("잘못된 요청입니다: " + e.getMessage());
+       } catch (Exception e) {
+           log.error("댓글 등록 실패", e);
            return ResponseEntity.badRequest().body("댓글 등록에 실패했습니다: " + e.getMessage());
        }
    }
📝 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
try {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
System.out.println("🔥 댓글 등록 시작 - postId: " + postId + ", user: " + username);
Long commentId = commentService.addComment(postId, request, user);
System.out.println("✅ 댓글 등록 완료 - commentId: " + commentId);
return ResponseEntity.ok().body(commentId);
} catch (Exception e) {
System.err.println("❌ 댓글 등록 실패: " + e.getMessage());
e.printStackTrace();
return ResponseEntity.badRequest().body("댓글 등록에 실패했습니다: " + e.getMessage());
}
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class CommentController {
@PostMapping("/api/posts/{postId}/comments")
public ResponseEntity<?> addComment(@PathVariable Long postId,
@RequestBody CommentRequest request,
@AuthenticationPrincipal CustomUserDetails userDetails) {
try {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
log.info("댓글 등록 시작 - postId: {}, user: {}", postId, username);
Long commentId = commentService.addComment(postId, request, user);
log.info("댓글 등록 완료 - commentId: {}", commentId);
return ResponseEntity.ok().body(commentId);
} catch (IllegalArgumentException e) {
log.warn("댓글 등록 실패 - 잘못된 요청: {}", e.getMessage());
return ResponseEntity.badRequest().body("잘못된 요청입니다: " + e.getMessage());
} catch (Exception e) {
log.error("댓글 등록 실패", e);
return ResponseEntity.badRequest().body("댓글 등록에 실패했습니다: " + e.getMessage());
}
}
}
🤖 Prompt for AI Agents
In src/main/java/io/github/petty/community/controller/CommentController.java
around lines 32 to 47, replace all System.out.println and System.err.println
calls with SLF4J Logger methods like logger.info and logger.error for proper
logging. Also, instead of catching the generic Exception, catch more specific
exceptions that the code might throw to handle errors more precisely. Initialize
a Logger instance for the class and use it consistently for all log messages.

}

@PutMapping("/api/comments/{commentId}")
Expand All @@ -48,9 +60,21 @@ public ResponseEntity<?> updateComment(@PathVariable Long commentId,
@DeleteMapping("/api/comments/{commentId}")
public ResponseEntity<?> deleteComment(@PathVariable Long commentId,
@AuthenticationPrincipal CustomUserDetails userDetails) {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
commentService.deleteComment(commentId, user);
return ResponseEntity.noContent().build();
try {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);

System.out.println("🔥 댓글 삭제 시작 - commentId: " + commentId + ", user: " + username);

commentService.deleteComment(commentId, user);

System.out.println("✅ 댓글 삭제 완료 - commentId: " + commentId);

return ResponseEntity.noContent().build();
} catch (Exception e) {
System.err.println("❌ 댓글 삭제 실패: " + e.getMessage());
e.printStackTrace();
return ResponseEntity.badRequest().body("댓글 삭제에 실패했습니다: " + e.getMessage());
}
Comment on lines +63 to +78
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

로깅 프레임워크 사용 권장

System.out.println 대신 SLF4J 로거를 사용하고, 예외 메시지 노출을 제한해주세요.

클래스 상단에 로거 추가:

+import lombok.extern.slf4j.Slf4j;

+@Slf4j
 @RestController
 @RequiredArgsConstructor
 public class CommentController {

그리고 예외 처리 개선:

-            System.out.println("🔥 댓글 삭제 시작 - commentId: " + commentId + ", user: " + username);
-            System.out.println("✅ 댓글 삭제 완료 - commentId: " + commentId);
+            log.info("댓글 삭제 시작 - commentId: {}, user: {}", commentId, username);
+            log.info("댓글 삭제 완료 - commentId: {}", commentId);
-        } catch (Exception e) {
-            System.err.println("❌ 댓글 삭제 실패: " + e.getMessage());
-            e.printStackTrace();
-            return ResponseEntity.badRequest().body("댓글 삭제에 실패했습니다: " + e.getMessage());
+        } catch (Exception e) {
+            log.error("댓글 삭제 실패 - commentId: {}, user: {}", commentId, username, e);
+            return ResponseEntity.badRequest().body("댓글 삭제에 실패했습니다.");
📝 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
try {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
System.out.println("🔥 댓글 삭제 시작 - commentId: " + commentId + ", user: " + username);
commentService.deleteComment(commentId, user);
System.out.println("✅ 댓글 삭제 완료 - commentId: " + commentId);
return ResponseEntity.noContent().build();
} catch (Exception e) {
System.err.println("❌ 댓글 삭제 실패: " + e.getMessage());
e.printStackTrace();
return ResponseEntity.badRequest().body("댓글 삭제에 실패했습니다: " + e.getMessage());
}
// At the top of the file, add the import and annotation:
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
public class CommentController {
@DeleteMapping("/{commentId}")
public ResponseEntity<?> deleteComment(@PathVariable Long commentId,
@AuthenticationPrincipal UserDetails userDetails) {
try {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
- System.out.println("🔥 댓글 삭제 시작 - commentId: " + commentId + ", user: " + username);
+ log.info("댓글 삭제 시작 - commentId: {}, user: {}", commentId, username);
commentService.deleteComment(commentId, user);
- System.out.println("✅ 댓글 삭제 완료 - commentId: " + commentId);
+ log.info("댓글 삭제 완료 - commentId: {}", commentId);
return ResponseEntity.noContent().build();
- } catch (Exception e) {
- System.err.println("❌ 댓글 삭제 실패: " + e.getMessage());
- e.printStackTrace();
- return ResponseEntity.badRequest().body("댓글 삭제에 실패했습니다: " + e.getMessage());
+ } catch (Exception e) {
+ log.error("댓글 삭제 실패 - commentId: {}, user: {}", commentId, username, e);
+ return ResponseEntity.badRequest().body("댓글 삭제에 실패했습니다.");
}
}
}
🤖 Prompt for AI Agents
In src/main/java/io/github/petty/community/controller/CommentController.java
around lines 63 to 78, replace all System.out.println and System.err.println
calls with SLF4J logger calls by adding a private static final Logger instance
at the class level. Use logger.info for normal logs and logger.error for
exceptions. Modify the catch block to log the exception without exposing the
full exception message to the client; instead, return a generic error message in
the response body to avoid leaking sensitive information.

🛠️ Refactor suggestion

deleteComment 메서드의 로깅도 개선해주세요.

addComment 메서드와 동일한 로깅 개선사항이 적용되어야 합니다.

앞서 제안한 것과 동일하게 SLF4J Logger를 사용하도록 개선해주세요:

@DeleteMapping("/api/comments/{commentId}")
public ResponseEntity<?> deleteComment(@PathVariable Long commentId,
                                       @AuthenticationPrincipal CustomUserDetails userDetails) {
    try {
        String username = userDetails.getUsername();
        Users user = usersRepository.findByUsername(username);
        
-       System.out.println("🔥 댓글 삭제 시작 - commentId: " + commentId + ", user: " + username);
+       log.info("댓글 삭제 시작 - commentId: {}, user: {}", commentId, username);
        
        commentService.deleteComment(commentId, user);
        
-       System.out.println("✅ 댓글 삭제 완료 - commentId: " + commentId);
+       log.info("댓글 삭제 완료 - commentId: {}", commentId);
        
        return ResponseEntity.noContent().build();
-   } catch (Exception e) {
-       System.err.println("❌ 댓글 삭제 실패: " + e.getMessage());
-       e.printStackTrace();
+   } catch (IllegalArgumentException e) {
+       log.warn("댓글 삭제 실패 - 잘못된 요청: {}", e.getMessage());
+       return ResponseEntity.badRequest().body("잘못된 요청입니다: " + e.getMessage());
+   } catch (Exception e) {
+       log.error("댓글 삭제 실패", e);
        return ResponseEntity.badRequest().body("댓글 삭제에 실패했습니다: " + e.getMessage());
    }
}
📝 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
try {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
System.out.println("🔥 댓글 삭제 시작 - commentId: " + commentId + ", user: " + username);
commentService.deleteComment(commentId, user);
System.out.println("✅ 댓글 삭제 완료 - commentId: " + commentId);
return ResponseEntity.noContent().build();
} catch (Exception e) {
System.err.println("❌ 댓글 삭제 실패: " + e.getMessage());
e.printStackTrace();
return ResponseEntity.badRequest().body("댓글 삭제에 실패했습니다: " + e.getMessage());
}
try {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
log.info("댓글 삭제 시작 - commentId: {}, user: {}", commentId, username);
commentService.deleteComment(commentId, user);
log.info("댓글 삭제 완료 - commentId: {}", commentId);
return ResponseEntity.noContent().build();
} catch (IllegalArgumentException e) {
log.warn("댓글 삭제 실패 - 잘못된 요청: {}", e.getMessage());
return ResponseEntity.badRequest().body("잘못된 요청입니다: " + e.getMessage());
} catch (Exception e) {
log.error("댓글 삭제 실패", e);
return ResponseEntity.badRequest().body("댓글 삭제에 실패했습니다: " + e.getMessage());
}
🤖 Prompt for AI Agents
In src/main/java/io/github/petty/community/controller/CommentController.java
around lines 63 to 78, replace all System.out.println and System.err.println
calls with SLF4J Logger calls. Inject a Logger instance for the class and use
logger.info for normal logs and logger.error for error logs, including the
exception stack trace in the error log. This aligns the logging style with the
addComment method and improves consistency and log management.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.github.petty.users.dto.CustomUserDetails;
import io.github.petty.users.entity.Users;
import io.github.petty.users.repository.UsersRepository;
import jakarta.validation.Valid;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

컴파일 오류: 잘못된 validation 패키지 import

파이프라인 오류에 따르면 jakarta.validation 패키지가 존재하지 않습니다. Spring Boot 버전을 확인하고 적절한 패키지를 사용하세요.

Spring Boot 2.x를 사용 중이라면:

-import jakarta.validation.Valid;
+import javax.validation.Valid;

Spring Boot 3.x를 사용 중이라면 의존성을 확인하세요:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
🧰 Tools
🪛 GitHub Actions: Java CI with Gradle

[error] 10-10: Compilation failed: package 'jakarta.validation' does not exist. Missing import for @Valid annotation.

🤖 Prompt for AI Agents
In src/main/java/io/github/petty/community/controller/PostController.java at
line 10, the import statement uses jakarta.validation.Valid, which causes a
compile error because the package is missing. Check the Spring Boot version: if
using Spring Boot 2.x, replace the import with javax.validation.Valid; if using
Spring Boot 3.x, ensure the spring-boot-starter-validation dependency is added
to your build configuration to provide jakarta.validation. Adjust the import
accordingly based on the Spring Boot version and dependencies.

import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand All @@ -26,17 +27,27 @@ public class PostController {
private final UsersRepository usersRepository;

@PostMapping
public ResponseEntity<?> create(@RequestBody PostRequest request,
public ResponseEntity<?> create(@RequestBody @Valid PostRequest request,
@AuthenticationPrincipal CustomUserDetails userDetails) {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
Long id = postService.save(request, user);
return ResponseEntity.ok(Map.of("id", id));
try {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
if (user == null) {
return ResponseEntity.status(401).body("사용자를 찾을 수 없습니다.");
}

Long id = postService.save(request, user);
return ResponseEntity.ok(Map.of("id", id));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(e.getMessage());
} catch (Exception e) {
return ResponseEntity.status(500).body("게시글 작성에 실패했습니다.");
}
}

@PutMapping("/{id}")
public ResponseEntity<?> update(@PathVariable Long id,
@RequestBody PostRequest request,
@RequestBody @Valid PostRequest request,
@AuthenticationPrincipal CustomUserDetails userDetails) {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
Expand All @@ -50,8 +61,8 @@ public ResponseEntity<?> delete(@PathVariable Long id,
@AuthenticationPrincipal CustomUserDetails userDetails) {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
postService.delete(id, user);
return ResponseEntity.ok().build();
String postType = postService.delete(id, user); // 🔥 삭제된 게시글의 타입 반환
return ResponseEntity.ok(Map.of("postType", postType));
}

@GetMapping
Expand All @@ -70,9 +81,26 @@ public ResponseEntity<PostDetailResponse> getPost(@PathVariable Long id) {
@PostMapping("/{id}/like")
public ResponseEntity<?> likePost(@PathVariable Long id,
@AuthenticationPrincipal CustomUserDetails userDetails) {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
int newCount = postService.toggleLike(id, user); // 좋아요 또는 취소
return ResponseEntity.ok(Map.of("likeCount", newCount));
try {
String username = userDetails.getUsername();
Users user = usersRepository.findByUsername(username);
if (user == null) {
return ResponseEntity.status(401).body("사용자를 찾을 수 없습니다.");
}

int newCount = postService.toggleLike(id, user);
return ResponseEntity.ok(Map.of("likeCount", newCount));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(e.getMessage());
} catch (Exception e) {
return ResponseEntity.status(500).body("좋아요 처리에 실패했습니다.");
}
}

// 🔥 기존 데이터 업데이트를 위한 임시 엔드포인트 (관리자용)
@PostMapping("/update-counts")
public ResponseEntity<?> updateAllPostCounts() {
postService.updateAllPostCounts();
return ResponseEntity.ok(Map.of("message", "모든 게시글의 댓글 수와 좋아요 수가 업데이트되었습니다."));
}
Comment on lines +100 to 105
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

보안 취약점: 관리자 권한 체크가 없습니다!

이 엔드포인트는 모든 게시글의 카운트를 업데이트하는 중요한 작업을 수행하지만, 권한 체크가 없어 누구나 호출할 수 있습니다.

관리자 권한 체크를 추가하세요:

 @PostMapping("/update-counts")
+@PreAuthorize("hasRole('ADMIN')")
 public ResponseEntity<?> updateAllPostCounts() {
     postService.updateAllPostCounts();
     return ResponseEntity.ok(Map.of("message", "모든 게시글의 댓글 수와 좋아요 수가 업데이트되었습니다."));
 }

또는 요청자의 권한을 확인하는 로직을 추가하세요:

 @PostMapping("/update-counts")
-public ResponseEntity<?> updateAllPostCounts() {
+public ResponseEntity<?> updateAllPostCounts(@AuthenticationPrincipal CustomUserDetails userDetails) {
+    // 관리자 권한 확인
+    if (!userDetails.getAuthorities().stream()
+            .anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
+        return ResponseEntity.status(403).body("관리자 권한이 필요합니다.");
+    }
     postService.updateAllPostCounts();
     return ResponseEntity.ok(Map.of("message", "모든 게시글의 댓글 수와 좋아요 수가 업데이트되었습니다."));
 }
📝 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("/update-counts")
public ResponseEntity<?> updateAllPostCounts() {
postService.updateAllPostCounts();
return ResponseEntity.ok(Map.of("message", "모든 게시글의 댓글 수와 좋아요 수가 업데이트되었습니다."));
}
// 🔥 기존 데이터 업데이트를 위한 임시 엔드포인트 (관리자용)
@PostMapping("/update-counts")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> updateAllPostCounts() {
postService.updateAllPostCounts();
return ResponseEntity.ok(Map.of("message", "모든 게시글의 댓글 수와 좋아요 수가 업데이트되었습니다."));
}
Suggested change
// 🔥 기존 데이터 업데이트를 위한 임시 엔드포인트 (관리자용)
@PostMapping("/update-counts")
public ResponseEntity<?> updateAllPostCounts() {
postService.updateAllPostCounts();
return ResponseEntity.ok(Map.of("message", "모든 게시글의 댓글 수와 좋아요 수가 업데이트되었습니다."));
}
// 🔥 기존 데이터 업데이트를 위한 임시 엔드포인트 (관리자용)
@PostMapping("/update-counts")
public ResponseEntity<?> updateAllPostCounts(@AuthenticationPrincipal CustomUserDetails userDetails) {
// 관리자 권한 확인
if (!userDetails.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
return ResponseEntity.status(403).body("관리자 권한이 필요합니다.");
}
postService.updateAllPostCounts();
return ResponseEntity.ok(Map.of("message", "모든 게시글의 댓글 수와 좋아요 수가 업데이트되었습니다."));
}
🤖 Prompt for AI Agents
In src/main/java/io/github/petty/community/controller/PostController.java around
lines 100 to 105, the updateAllPostCounts endpoint lacks any admin authorization
check, allowing unrestricted access. Add a security annotation such as
@PreAuthorize("hasRole('ADMIN')") above the method or implement explicit logic
to verify the requester's admin role before executing the update. This ensures
only authorized administrators can invoke this sensitive operation.

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package io.github.petty.community.dto;

import jakarta.validation.constraints.NotBlank;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

검증 어노테이션 패키지 불일치 문제

jakarta.validation.constraintsjavax.validation.constraints 패키지를 혼용하고 있어 컴파일 에러가 발생합니다. Spring Boot 3.x에서는 모든 검증 어노테이션을 jakarta 패키지에서 가져와야 합니다.

다음과 같이 수정해주세요:

-import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
-import javax.validation.constraints.Size;

Also applies to: 7-7

🧰 Tools
🪛 GitHub Actions: Java CI with Gradle

[error] 3-3: Compilation failed: package 'jakarta.validation.constraints' does not exist. Missing import for NotBlank annotation.

🤖 Prompt for AI Agents
In src/main/java/io/github/petty/community/dto/CommentRequest.java at lines 3
and 7, there is a package mismatch between jakarta.validation.constraints and
javax.validation.constraints causing compilation errors. Replace all imports and
usages of javax.validation.constraints with jakarta.validation.constraints to
ensure consistency with Spring Boot 3.x requirements.

import lombok.Getter;
import lombok.Setter;

import javax.validation.constraints.Size;

@Getter
@Setter
public class CommentRequest {
@NotBlank
@Size(max=500)
private String content;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ public class CommentResponse {
private Long id;
private String writer;
private String content;
private String userName;
private LocalDateTime createdAt;
}
Loading
Loading