Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public record DiscussionResponse(
@Schema(description = "관련 문제 ID", example = "45", requiredMode = REQUIRED)
Long problemId,

@Schema(description = "언어 ID", example = "1", requiredMode = REQUIRED)
Long languageId,

@Schema(description = "토론 내용", example = "이 문제는 이렇게 풀 수 있습니다...", requiredMode = REQUIRED)
String content,

Expand Down Expand Up @@ -50,7 +53,8 @@ public static DiscussionResponse fromEntity(Discussion discussion) {
return new DiscussionResponse(
discussion.getId(),
SimpleUserInfoResponse.fromEntity(discussion.getUser()),
discussion.getProblem().getId(),
discussion.getProblemId(),
discussion.getLanguageId(),
discussion.getContent(),
discussion.getCreatedAt(),
null,
Expand All @@ -66,6 +70,7 @@ public static DiscussionResponse from(DiscussionQueryResult result) {
result.getDiscussionId(),
result.getUserInfo(),
result.getProblemId(),
result.getLanguageId(),
result.getContent(),
result.getCreatedAt(),
result.getUpvoteCount(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public class DiscussionQueryResult {

private final Long problemId;

private final Long languageId;

private final String content;

private final LocalDateTime createdAt;
Expand All @@ -37,6 +39,7 @@ public DiscussionQueryResult(
Long discussionId,
SimpleUserInfoResponse userInfo,
Long problemId,
Long languageId,
String content,
LocalDateTime createdAt,
Long upvoteCount,
Expand All @@ -48,6 +51,7 @@ public DiscussionQueryResult(
this.discussionId = discussionId;
this.userInfo = userInfo;
this.problemId = problemId;
this.languageId = languageId;
this.content = content;
this.createdAt = createdAt;
this.upvoteCount = upvoteCount;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,7 @@ public Long getProblemId() {
return this.problem.getId();
}

public Long getLanguageId() {
return this.language.getId();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@ public List<Long> findDiscussionIdsByProblemId(Long problemId, String sortBy, Pa

NumberExpression<Long> bestScore = upvoteCount.subtract(downvoteCount);

long offset = pageable.getOffset();
int pageNumber = pageable.getPageNumber();
int pageSize = pageable.getPageSize();

return jpaQueryFactory
.select(discussion.id)
.from(discussion)
Expand Down Expand Up @@ -91,6 +87,7 @@ public List<DiscussionQueryResult> findDiscussionsByIds(List<Long> discussionIds
user.profileImageUrl
),
discussion.problem.id,
discussion.language.id,
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

언어 ID 필드 추가가 올바르게 구현되었습니다.

discussion.language.id가 쿼리 프로젝션에 적절히 추가되었습니다. QDiscussionQueryResult 생성자의 매개변수 순서도 올바릅니다.

AI 요약에 따르면 groupBy 절에도 discussion.language.id가 추가되어야 한다고 언급되어 있는데, 현재 diff에서는 확인되지 않습니다. 다음 스크립트로 groupBy 절이 올바르게 업데이트되었는지 확인해주세요:


🏁 Script executed:

#!/bin/bash
# groupBy 절에 discussion.language.id가 포함되어 있는지 확인
ast-grep --pattern $'groupBy(
  $$$,
  discussion.language.id,
  $$$
)'

Length of output: 72


다음 스크립트로 DiscussionQueryRepositoryImpl.java 파일 내 groupBy 절에 discussion.language.id가 포함되었는지 확인해 보겠습니다.


🏁 Script executed:

#!/bin/bash
# DiscussionQueryRepositoryImpl.java에서 groupBy 절 검색
rg "groupBy" -A5 -B2 src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/community/discussion/DiscussionQueryRepositoryImpl.java

Length of output: 685


언어 ID 필드 프로젝션은 올바르게 추가되었으나 groupBy 절에도 반영이 필요합니다.

  • 위치: src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/community/discussion/DiscussionQueryRepositoryImpl.java
    두 번째 쿼리의 groupBy 블록

변경 전:

.groupBy(
    discussion.id,
    user.id,
    user.nickname,
    user.tier,
    user.profileImageUrl,
    // …
)

변경 제안:

.groupBy(
    discussion.id,
+   discussion.language.id,
    user.id,
    user.nickname,
    user.tier,
    user.profileImageUrl,
    // …
)

이렇게 하면 프로젝션에 추가한 discussion.language.id에 대해 그룹핑도 일관되게 처리됩니다.

🤖 Prompt for AI Agents
In
src/main/java/org/ezcode/codetest/infrastructure/persistence/repository/community/discussion/DiscussionQueryRepositoryImpl.java
at line 90, the discussion.language.id field is correctly added to the select
projection but missing from the groupBy clause in the second query. To fix this,
add discussion.language.id to the groupBy block alongside the other grouped
fields to ensure consistent grouping with the projection.

discussion.content,
discussion.createdAt,
upvoteCount,
Expand Down
106 changes: 92 additions & 14 deletions src/main/resources/templates/test-submit.html
Original file line number Diff line number Diff line change
Expand Up @@ -372,16 +372,18 @@
}

.discussion-item {
background: #222;
border-radius: 10px;
background: #23272f;
border-radius: 12px;
margin-bottom: 18px;
padding: 18px 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.10);
transition: box-shadow 0.2s;
padding: 22px 18px 18px 18px;
box-shadow: 0 4px 18px rgba(0,0,0,0.18);
border: 1.5px solid #2d323c;
transition: box-shadow 0.2s, border 0.2s;
}

.discussion-item:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
box-shadow: 0 8px 28px rgba(0,0,0,0.28);
border-color: #00d084;
}

/* Header (profile/nickname) */
Expand All @@ -390,6 +392,7 @@
align-items: center;
gap: 8px;
margin-bottom: 2px;
margin-top: 8px;
}

.discussion-header img {
Expand Down Expand Up @@ -779,6 +782,19 @@
border-color: #00d084;
font-weight: 700;
}
.lang-tag {
display: inline-block;
background: #2d323c;
color: #00e09e;
font-size: 13px;
font-weight: 600;
border-radius: 12px;
padding: 4px 16px;
margin-bottom: 14px;
margin-top: 2px;
margin-right: 0;
letter-spacing: 0.5px;
}
</style>
</head>

Expand All @@ -800,6 +816,10 @@
<li>메모리 제한: <span id="prob-mem">--</span> KB</li>
<li>카테고리: <span id="prob-cats">--</span></li>
</ul>

<div id="prob-image-wrapper">
<img id="prob-image" src="" alt="문제 이미지" style="max-width: 100%; height: auto;" />
</div>
</div>

<div id="discussion-content" class="left-content">
Expand All @@ -816,10 +836,6 @@
<div id="discussion-pagination"></div>
</div>

<div id="prob-image-wrapper">
<img id="prob-image" src="" alt="문제 이미지" style="max-width: 100%; height: auto;" />
</div>

</div>
<div class="right">
<div class="section">
Expand Down Expand Up @@ -873,12 +889,48 @@ <h3>채점 결과</h3>

const DEFAULT_PROFILE_IMG = "/static/images/user-icon.png"; // 임의 프사 경로

// ====== 언어 목록 불러오기 및 전역 저장 ======
async function loadLanguages() {
try {
const res = await fetch('/api/languages');
const data = await res.json();
if (data.success) {
window.languageList = data.result;
} else {
window.languageList = [];
}
} catch {
window.languageList = [];
}
}
// 언어 드롭다운 HTML 생성
function getLanguageSelectHtml(selectedId) {
if (!window.languageList) return '';
return `
<select class="discussion-language-select">
${window.languageList.map(lang =>
`<option value="${lang.id}" ${lang.id === selectedId ? 'selected' : ''}>
${lang.name} ${lang.version}
</option>`
).join('')}
</select>
`;
}
// 언어 id로 name+version 반환
function getLanguageTagById(id) {
if (!window.languageList) return '';
const lang = window.languageList.find(l => l.id === id);
return lang ? `${lang.name} ${lang.version}` : '';
}

function tokenHeader() {
const token = sessionStorage.getItem('accessToken');
return token ? { 'Authorization': token.startsWith('Bearer ') ? token : 'Bearer ' + token } : {};
}

window.addEventListener('DOMContentLoaded', () => {
window.addEventListener('DOMContentLoaded', async () => {
await loadLanguages();
renderDiscussionCreateBox(); // 언어 목록 로드 후 호출
const pid = new URLSearchParams(location.search).get('problemId');
if (pid) {
window.problemId = pid;
Expand Down Expand Up @@ -1180,6 +1232,7 @@ <h3>채점 결과</h3>
listEl.innerHTML = discussions.map(d => {
return `
<div class="discussion-item" data-discussion-id="${d.discussionId}">
<span class="lang-tag">${getLanguageTagById(d.languageId)}</span>
<div class="discussion-header">
<img src="${d.userInfo.profileImageUrl || DEFAULT_PROFILE_IMG}" alt="profile">
<span class="nickname">${d.userInfo.nickname}</span>
Expand Down Expand Up @@ -1409,10 +1462,22 @@ <h3>채점 결과</h3>
const oldContent = contentEl.textContent;
// 이미 수정창이 열려있으면 return
if (item.querySelector('.edit-input-box')) return;
// textarea + 취소/저장 버튼
// 기존 언어 id (discussion-item만)
let oldLangId = 1;
if (item.classList.contains('discussion-item')) {
// lang-tag에서 id 추출
const langTag = item.querySelector('.lang-tag');
if (langTag) {
const text = langTag.textContent.trim();
const found = window.languageList?.find(l => text.startsWith(l.name));
if (found) oldLangId = found.id;
}
}
// textarea + 언어 드롭다운 + 취소/저장 버튼
const box = document.createElement('div');
box.className = 'edit-input-box';
box.innerHTML = `
<div style="margin-bottom:8px;">${item.classList.contains('discussion-item') ? getLanguageSelectHtml(oldLangId) : ''}</div>
<textarea class="edit-input-text" style="width:100%;min-height:60px;">${oldContent}</textarea>
<div style="display:flex;gap:8px;justify-content:flex-end;">
<button class="edit-cancel-btn">취소</button>
Expand All @@ -1431,13 +1496,19 @@ <h3>채점 결과</h3>
// 저장
box.querySelector('.edit-save-btn').onclick = async () => {
const newContent = box.querySelector('.edit-input-text').value.trim();
let newLangId = oldLangId;
if (item.classList.contains('discussion-item')) {
newLangId = +box.querySelector('.discussion-language-select').value;
}
if (!newContent) return alert('내용을 입력하세요.');
let url, method = 'PUT', payload = { languageId: 1, content: newContent };
let url, method = 'PUT', payload = { languageId: newLangId, content: newContent };
if (item.classList.contains('discussion-item')) {
url = `/api/problems/${window.problemId}/discussions/${e.target.dataset.id}`;
} else {
const discussionId = item.closest('.discussion-item').dataset.discussionId;
url = `/api/problems/${window.problemId}/discussions/${discussionId}/replies/${e.target.dataset.id}`;
// 대댓글/댓글은 언어 변경 불가 (기존대로 1)
payload.languageId = 1;
}
const res = await fetch(url, {
method,
Expand Down Expand Up @@ -1659,7 +1730,13 @@ <h3>채점 결과</h3>

function renderDiscussionCreateBox() {
const box = document.getElementById('discussion-create-box');
if (!window.languageList || window.languageList.length === 0) {
box.innerHTML = '<div style="color:#aaa;">언어 목록을 불러오는 중...</div>';
return;
}
// 언어 드롭다운 + textarea
box.innerHTML = `
<div style="margin-bottom:8px;">${getLanguageSelectHtml(window.languageList?.[0]?.id || 1)}</div>
<textarea id="discussion-create-text" class="reply-input-text" rows="4" placeholder="토론글을 작성해보세요"></textarea>
<div style="display:flex;gap:10px;justify-content:flex-end;">
<button id="discussion-create-cancel" class="reply-cancel-btn">취소</button>
Expand All @@ -1673,6 +1750,7 @@ <h3>채점 결과</h3>
// 작성
box.querySelector('#discussion-create-submit').onclick = async () => {
const content = box.querySelector('#discussion-create-text').value.trim();
const langId = +box.querySelector('.discussion-language-select').value;
if (!content) return alert('내용을 입력하세요.');
const token = sessionStorage.getItem('accessToken');
const res = await fetch(`/api/problems/${window.problemId}/discussions`, {
Expand All @@ -1681,7 +1759,7 @@ <h3>채점 결과</h3>
'Content-Type': 'application/json',
...tokenHeader()
},
body: JSON.stringify({ languageId: 1, content })
body: JSON.stringify({ languageId: langId, content })
});
if (res.ok) {
// 성공: 입력창 비우고 목록 새로고침
Expand Down