feat: 오답노트 — 질문 북마크 + 모아보기 (B-4) - #178
Merged
Merged
Conversation
다시 볼 질문을 표시해 두고 나중에 모아 복습한다.
- `PUT /api/sessions/{sid}/messages/{mid}/bookmark` — 표시/해제
- `GET /api/users/me/bookmarks` — 모아보기
## 설계 판단
**질문(INTERVIEWER)에만 걸 수 있다** (`MESSAGE_NOT_BOOKMARKABLE`, 422). 답변을 표시해 봐야
복습할 게 없다.
**토글이 아니라 명시적 상태를 받는다.** 토글이면 재전송·더블클릭이 상태를 뒤집어 놓는다.
같은 요청이 두 번 와도 결과가 같다.
**목록은 질문 + 그때 내 답변 + 모범답안/코칭을 한 묶음으로** 반환한다. 질문만 모아 봐야
"내가 뭘 틀렸는지"를 알 수 없다. 답변은 `findByParentMessage_IdIn` 으로 한 번에 받아
매핑한다 — 질문마다 조회하면 N+1 이다.
**별도 테이블을 만들지 않았다.** V26 으로 `interview_messages.bookmarked` + 부분 인덱스
(`WHERE bookmarked = TRUE`). 북마크의 수명은 질문의 수명과 같다 — 세션을 지우면 그 질문도
사라지는 게 맞다(목록 쿼리가 `session.deleted = false` 로 거른다).
엔티티 변경 메서드는 `applyBookmark` 다. `setBookmarked` 로 두면 ArchUnit 의
"엔티티 public setter 금지" 룰에 걸린다.
`QuestionBookmarkController` 는 URL 이 `/api/users/me/*` 지만 `UserStatsController` 와 같은
이유로 session 슬라이스에 둔다(user → session 직접 의존 회피).
## 테스트
`QuestionBookmarkServiceTest` 신설 (8) — 표시, 멱등, 답변 표시 거부, 타인 세션 거부,
**다른 세션의 메시지 id 끼워넣기 거부**, 답변·코칭 결합, 답변 없는 질문도 목록에 노출,
북마크 0건일 때 답변 조회 스킵.
- 종료 세션 기록(`InterviewTranscript`)의 질문 버블에 별 버튼. **라이브 중엔 안 띄운다** — 답변에 집중해야 할 때 끼어드는 조작이다(`bookmarkable` prop 으로 게이팅) - 워크스페이스에 '오답노트' 메뉴 + `/workspace/bookmarks` - **모범 답안은 접혀 있다.** 먼저 스스로 떠올려 보고 펼치는 게 복습의 핵심이라, 펼치기 전에는 질문과 평가 관점만 보인다 - 답변·피드백 기록이 없는 질문(답변 전에 담았거나 피드백 전)은 빈 아코디언 대신 이유를 적어 준다 - 담기/빼기 모두 명시적 상태를 보낸다(서버 계약과 동일 — 재전송이 상태를 뒤집지 않게) 북마크는 면접 질문이라 별도 feature 슬라이스로 쪼개지 않고 `features/interview` 에 뒀다. 쪼갰다면 질문 버블의 토글이 feature 간 import 가 돼 FSD 규칙에 걸린다. 테스트: `BookmarkList.test.tsx`(5) — 빈 상태, 모범답안 접힘→펼침, 복습 재료 없을 때 안내, 출처 면접 링크, 빼기 인자. `QuestionBubble.test.tsx` 신설(3) — 기본은 별 미노출, 담기/빼기 인자. vitest 98/98 · eslint 0 · tsc · build 통과.
- `docs/database.md` — interview_messages.bookmarked(V26) + 부분 인덱스 - `backend/CLAUDE.md` — 엔드포인트·설계 판단(질문 전용, 명시적 상태, N+1 회피, 컨트롤러 위치) - `frontend/src/features/CLAUDE.md` — interview 슬라이스 책임에 오답노트 추가와 그 이유
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
변경 사항
감사 백로그 B-4. 다시 볼 질문을 표시해 두고 나중에 모아 복습한다.
PUT /api/sessions/{sid}/messages/{mid}/bookmark— 표시/해제GET /api/users/me/bookmarks— 모아보기/workspace/bookmarks설계 판단
질문(INTERVIEWER)에만 걸 수 있다 (
MESSAGE_NOT_BOOKMARKABLE, 422). 답변을 표시해 봐야 복습할 게 없다.토글이 아니라 명시적 상태를 받는다.
{"bookmarked": true|false}. 토글이면 재전송·더블클릭이 상태를 뒤집어 놓는다. 같은 요청이 두 번 와도 결과가 같다.목록은 질문 + 그때 내 답변 + 모범답안/코칭을 한 묶음으로 반환한다. 질문만 모아 봐야 "내가 뭘 틀렸는지"를 알 수 없다. 답변은
findByParentMessage_IdIn으로 한 번에 받아 매핑한다 — 질문마다 조회하면 N+1 이다.별도 테이블을 만들지 않았다. V26 으로
interview_messages.bookmarked+ 부분 인덱스(WHERE bookmarked = TRUE). 북마크의 수명은 질문의 수명과 같다 — 세션을 지우면 그 질문도 사라지는 게 맞고, 목록 쿼리가session.deleted = false로 거른다.엔티티 변경 메서드 이름은
applyBookmark다.setBookmarked로 두면 ArchUnit 의 "엔티티 public setter 금지" 룰에 걸린다.UX 판단
라이브 면접 중에는 별을 띄우지 않는다. 답변에 집중해야 할 때 끼어드는 조작이라, 종료 세션 기록(
InterviewTranscript)에서만 켠다(bookmarkableprop).모범 답안은 접혀 있다. 먼저 스스로 떠올려 보고 펼치는 게 복습의 핵심이다. 펼치기 전에는 질문과 평가 관점만 보인다.
답변·피드백 기록이 없는 질문(답변 전에 담았거나 피드백 생성 전)은 빈 아코디언 대신 이유를 적어 준다.
practice 러너 재사용에 대해
원래 백로그에는 "practice 러너 재사용"이라고 적었지만, 실제로 보니
PracticeRunner는 정적 QuestionBank 를 로드하는usePracticeSession(track)에 묶인 전체 화면 레이아웃(헤더의 나가기, 푸터)이었다. 워크스페이스 안에 끼워 넣으려면 상태 훅과 표현을 갈라내는 리팩터가 필요한데, 그 리팩터를 이 PR 에 섞으면 연습 모드까지 회귀 위험을 지게 된다.대신 목록에서 모범 답안을 접어 두는 것으로 같은 효과(회상 → 확인)를 냈다. 한 문제씩 넘기는 드릴이 필요하면 러너 상태 훅(
useQuestionRunner) 추출을 별도 PR 로 하는 게 안전하다.테스트
QuestionBookmarkServiceTest(8)BookmarkList.test.tsx(5)QuestionBubble.test.tsx(3)backend
test통과(ArchUnit 포함) · frontend vitest 98/98 · eslint 0 · tsc · build.영향 범위
MessageResponse.bookmarked)MESSAGE_NOT_FOUND(404),MESSAGE_NOT_BOOKMARKABLE(422)리뷰어 체크포인트