임동욱 임동욱 08-21
refactor: 퀴즈 수정 화면이 단건 조회 API를 쓴다
단건 조회가 없어 수정 화면이 목록을 500건까지 받아 와 그 안에서 찾고
있었다. 백엔드가 GET /api/v1/mngr/cntntns/quiz/{quizSn}을 열었으므로
스캔을 걷어낸다. 500건을 넘는 퀴즈가 쌓여도 수정 화면이 빈 화면이 되지
않는다.

같은 개편에서 등록 INSERT의 값 목록과 keyProperty도 고쳐져, 파일 머리말의
결함 목록에서 함께 지운다. 검색어가 키워드를 보지 않는 것과 난이도 필터가
SQL에 없는 것은 그대로다.

Co-Authored-By: Claude Opus 5 
@8934963de1932970f4e73a74b581fd0760e0bc7f
lib/data/repositories/content-quiz-repository.ts
--- lib/data/repositories/content-quiz-repository.ts
+++ lib/data/repositories/content-quiz-repository.ts
@@ -11,6 +11,7 @@
  *
  * ```
  * GET    /api/v1/mngr/cntntns/quiz/pagination      학교·학년·난이도·검색어
+ * GET    /api/v1/mngr/cntntns/quiz/{quizSn}        단건
  * POST   /api/v1/mngr/cntntns/quiz                 어노테이션 없음 → form(중첩 목록은 인덱스 표기)
  * PUT    /api/v1/mngr/cntntns/quiz/{quizSn}        @RequestBody(JSON)
  * DELETE /api/v1/mngr/cntntns/quiz/{quizSn}        soft delete(DEL_YN)
@@ -21,23 +22,13 @@
  * 같은 모양이라 화면 쪽 취급도 같다.
  *
  * ⚠️ 백엔드 결함(보고함, 미수정)
- * 1. **등록 INSERT의 컬럼 수와 값 수가 다르다** — 컬럼은 11개인데 값은 10개고 `USE_YN` 자리가
- *    비어 있다. 지금은 등록이 SQL 오류로 끝난다. 프론트는 `useYn`을 정상적으로 보내고 있으므로
- *    백엔드가 값 목록만 맞추면 그대로 동작한다.
- * 2. 등록 INSERT의 `keyProperty`/`keyColumn`이 `cntntsSn`/`CNTNTS_SN`이다(이 테이블은 `QUIZ_SN`).
- *    채번된 키가 VO에 담기지 않아 학교·학년 매핑이 `quiz_sn` 없이 저장된다.
- * 3. 단건 조회 API가 없다 — 매퍼·서비스에는 `selectOne`이 있는데 컨트롤러가 열지 않았다.
- *    그래서 수정 화면은 목록에서 찾는다(아래 `fetchQuizDetail`).
- * 4. 검색어가 질문·정답만 본다(`QUIZ_ANSWER`·`QUIZ_QUESTION`). 화면의 「키워드」 검색은
+ * 1. 검색어가 질문·정답만 본다(`QUIZ_ANSWER`·`QUIZ_QUESTION`). 화면의 「키워드」 검색은
  *    `QUIZ_TITLE`을 봐야 하는데 조건에 없어 결과가 비어 온다.
- * 5. 난이도 필터가 SQL에 없다 — `searchLevelCd`를 받지만 `searchWhere`가 쓰지 않는다.
+ * 2. 난이도 필터가 SQL에 없다 — `searchLevelCd`를 받지만 `searchWhere`가 쓰지 않는다.
  *    화면 선택지는 기획대로 두고 값만 실어 보낸다.
  */
 
 const QUIZ_PATH = '/api/v1/mngr/cntntns/quiz';
-
-/** 목록에서 단건을 찾을 때 한 번에 받아올 최대 행 수(단건 조회 API가 없다 — 결함 3). */
-const DETAIL_SCAN_LIMIT = 500;
 
 function isRecord(value: unknown): value is Record<string, unknown> {
   return value !== null && typeof value === 'object';
@@ -123,7 +114,7 @@
   return {
     searchSchulGradeCd: query.school,
     searchGradeCd: query.grade,
-    // 백엔드 SQL이 아직 쓰지 않는다(결함 5). 화면 선택지는 기획대로 두고 값만 보낸다.
+    // 백엔드 SQL이 아직 쓰지 않는다(결함 2). 화면 선택지는 기획대로 두고 값만 보낸다.
     searchLevelCd: query.level,
     searchKeyword: keyword,
     pageIndex,
@@ -189,24 +180,27 @@
   };
 }
 
-/** TODO: 단건 조회 API(결함 3)가 열리면 이 스캔을 `GET {base}/{quizSn}` 호출로 교체한다. */
 export async function fetchQuizDetail(quizSn: string): Promise<QuizItem | null> {
-  const { items } = await requestList(
+  const accessToken = await getSessionAccessToken();
+
+  const result = await backendFetch<unknown>(
+    `${QUIZ_PATH}/${encodeURIComponent(quizSn)}`,
     {
-      school: '',
-      grade: '',
-      level: '',
-      searchField: '',
-      keyword: '',
-      sort: 'created',
-      page: 1,
-      pageSize: 10,
-    },
-    1,
-    DETAIL_SCAN_LIMIT
+      method: 'GET',
+      accessToken: accessToken ?? undefined,
+      cache: 'no-store',
+      canHaveNullData: true,
+    }
   );
 
-  return items.find((item) => item.quizSn === quizSn) ?? null;
+  if (!result.ok) {
+    throw new BackendRequestError(result);
+  }
+  if (result.data === null || result.data === undefined) {
+    return null;
+  }
+
+  return toQuizItem(result.data);
 }
 
 type QuizWritePayload = {
Add a comment
List