임동욱 임동욱 08-21
merge: 콘텐츠·퀴즈 등록 JSON 통일
충돌은 `content-repository.ts`의 쓰기 로그 한 곳이다. hub가 로그를 "실패만 기록"으로 좁혔고
(`logWrite` → `logWriteFailure`), 이쪽은 form 경로를 없애며 `form` 인자를 걷어냈다. 둘 다
살린다 — 실패만 기록하되 인자는 body만 받는다.
@ea16c256a8a244d950a018110089f39229f3216f
lib/data/repositories/content-quiz-repository.ts
--- lib/data/repositories/content-quiz-repository.ts
+++ lib/data/repositories/content-quiz-repository.ts
@@ -7,11 +7,11 @@
 
 /**
  * 금융OX퀴즈 Repository — 쇼츠·만화와 테이블(`TB_COM_QUIZ`)은 다르지만 콘텐츠관리 API 아래로
- * 들어와 규약은 같다(등록은 form, 수정은 JSON, 삭제는 soft delete).
+ * 들어와 규약은 같다(등록·수정 모두 JSON, 삭제는 soft delete).
  *
  * ```
  * GET    /api/v1/mngr/cntntns/quiz/pagination      학교·학년·난이도·검색어
- * POST   /api/v1/mngr/cntntns/quiz                 어노테이션 없음 → form(중첩 목록은 인덱스 표기)
+ * POST   /api/v1/mngr/cntntns/quiz                 @RequestBody(JSON)
  * PUT    /api/v1/mngr/cntntns/quiz/{quizSn}        @RequestBody(JSON)
  * DELETE /api/v1/mngr/cntntns/quiz/{quizSn}        soft delete(DEL_YN)
  * GET    /api/v1/mngr/cntntns/quiz/excel/download  엑셀 다운로드
@@ -236,40 +236,17 @@
   };
 }
 
-/** 등록만 form이다 — 컨트롤러가 `@RequestBody` 없이 받는다(쇼츠·만화와 같은 갈림). */
-function toFormParams(
-  payload: QuizWritePayload
-): Record<string, string | number | undefined> {
-  const params: Record<string, string | number | undefined> = {
-    quizTitle: payload.quizTitle,
-    quizLev: payload.quizLev,
-    quizQuestion: payload.quizQuestion,
-    quizExplan: payload.quizExplan,
-    quizAnswer: payload.quizAnswer,
-    useYn: payload.useYn,
-  };
-
-  payload.schulGrdList.forEach((item, index) => {
-    params[`schulGrdList[${index}].schulCd`] = item.schulCd;
-    // undefined면 파라미터 자체가 빠져 백엔드에서 null이 된다.
-    params[`schulGrdList[${index}].grdCd`] = item.grdCd ?? undefined;
-  });
-
-  return params;
-}
 
 async function sendWrite(
   path: string,
   method: 'POST' | 'PUT' | 'DELETE',
-  body?: unknown,
-  form?: Record<string, string | number | undefined>
+  body?: unknown
 ): Promise<void> {
   const accessToken = await getSessionAccessToken();
 
   const result = await backendFetch<unknown>(path, {
     method,
     ...(body === undefined ? {} : { body }),
-    ...(form === undefined ? {} : { form }),
     accessToken: accessToken ?? undefined,
     cache: 'no-store',
     canHaveNullData: true,
@@ -282,7 +259,7 @@
 }
 
 export async function createQuiz(values: QuizFormValues): Promise<void> {
-  await sendWrite(QUIZ_PATH, 'POST', undefined, toFormParams(buildQuizPayload(values)));
+  await sendWrite(QUIZ_PATH, 'POST', buildQuizPayload(values));
 }
 
 export async function updateQuiz(
lib/data/repositories/content-repository.ts
--- lib/data/repositories/content-repository.ts
+++ lib/data/repositories/content-repository.ts
@@ -17,7 +17,7 @@
  * ```
  * GET    /api/v1/mngr/cntntns/pagination    searchCntntsType 필수
  * GET    /api/v1/mngr/cntntns/{cntntsSn}    단건
- * POST   /api/v1/mngr/cntntns               어노테이션 없음 → form(중첩 목록은 인덱스 표기)
+ * POST   /api/v1/mngr/cntntns               @RequestBody(JSON)
  * PUT    /api/v1/mngr/cntntns/{cntntsSn}    @RequestBody(JSON)
  * DELETE /api/v1/mngr/cntntns/{cntntsSn}    soft delete(DEL_YN)
  * ```
@@ -317,34 +317,6 @@
   };
 }
 
-/**
- * 등록은 `@RequestBody`가 없어 form으로 간다. 중첩 목록은 Spring이 읽는 인덱스 표기
- * (`keywordList[0].cntntsKeyword`)로 편다 — JSON으로 보내면 한 칸도 바인딩되지 않는다.
- */
-function toFormParams(
-  payload: ContentWritePayload & { cntntsType?: string }
-): Record<string, string | number | undefined> {
-  const params: Record<string, string | number | undefined> = {
-    cntntsType: payload.cntntsType,
-    cntntsNm: payload.cntntsNm,
-    cntntsTitle: payload.cntntsTitle,
-    atchFileId: payload.atchFileId,
-    thumbAtchFileId: payload.thumbAtchFileId,
-    useYn: payload.useYn,
-  };
-
-  payload.keywordList.forEach((item, index) => {
-    params[`keywordList[${index}].cntntsKeyword`] = item.cntntsKeyword;
-    params[`keywordList[${index}].cntntsExpln`] = item.cntntsExpln;
-  });
-  payload.schulGrdList.forEach((item, index) => {
-    params[`schulGrdList[${index}].schulCd`] = item.schulCd;
-    // undefined면 파라미터 자체가 빠져 백엔드에서 null이 된다.
-    params[`schulGrdList[${index}].grdCd`] = item.grdCd ?? undefined;
-  });
-
-  return params;
-}
 
 /**
  * 실패한 쓰기의 사유와 보낸 본문을 남긴다. 요청 자체는 테스트 환경에서 backend-fetch가
@@ -354,19 +326,11 @@
   method: string,
   path: string,
   body: unknown,
-  form: Record<string, string | number | undefined> | undefined,
   failure: { code: number; message: string }
 ): void {
   const lines = [
     `[contents] ${method} ${path} 실패 (code ${failure.code}: ${failure.message})`,
     body === undefined ? null : `body(JSON):\n${JSON.stringify(body, null, 2)}`,
-    form === undefined
-      ? null
-      : `form:\n${new URLSearchParams(
-          Object.entries(form).flatMap(([key, value]) =>
-            value === undefined ? [] : [[key, String(value)] as [string, string]]
-          )
-        ).toString()}`,
   ].filter(Boolean);
 
   console.error(lines.join('\n'));
@@ -375,15 +339,13 @@
 async function sendWrite(
   path: string,
   method: 'POST' | 'PUT' | 'DELETE',
-  body?: unknown,
-  form?: Record<string, string | number | undefined>
+  body?: unknown
 ): Promise<string | null> {
   const accessToken = await getSessionAccessToken();
 
   const result = await backendFetch<unknown>(path, {
     method,
     ...(body === undefined ? {} : { body }),
-    ...(form === undefined ? {} : { form }),
     accessToken: accessToken ?? undefined,
     cache: 'no-store',
     canHaveNullData: true,
@@ -391,7 +353,7 @@
   });
 
   if (!result.ok) {
-    logWriteFailure(method, path, body, form, result);
+    logWriteFailure(method, path, body, result);
     throw new BackendRequestError(result);
   }
 
@@ -403,7 +365,7 @@
   payload: ContentWritePayload,
   cntntsType: string
 ): Promise<string | null> {
-  return sendWrite(CONTENT_BASE_PATH, 'POST', undefined, toFormParams({ ...payload, cntntsType }));
+  return sendWrite(CONTENT_BASE_PATH, 'POST', { ...payload, cntntsType });
 }
 
 export async function updateContent(
Add a comment
List