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
... | ... | @@ -7,11 +7,11 @@ |
| 7 | 7 |
|
| 8 | 8 |
/** |
| 9 | 9 |
* 금융OX퀴즈 Repository — 쇼츠·만화와 테이블(`TB_COM_QUIZ`)은 다르지만 콘텐츠관리 API 아래로 |
| 10 |
- * 들어와 규약은 같다(등록은 form, 수정은 JSON, 삭제는 soft delete). |
|
| 10 |
+ * 들어와 규약은 같다(등록·수정 모두 JSON, 삭제는 soft delete). |
|
| 11 | 11 |
* |
| 12 | 12 |
* ``` |
| 13 | 13 |
* GET /api/v1/mngr/cntntns/quiz/pagination 학교·학년·난이도·검색어 |
| 14 |
- * POST /api/v1/mngr/cntntns/quiz 어노테이션 없음 → form(중첩 목록은 인덱스 표기) |
|
| 14 |
+ * POST /api/v1/mngr/cntntns/quiz @RequestBody(JSON) |
|
| 15 | 15 |
* PUT /api/v1/mngr/cntntns/quiz/{quizSn} @RequestBody(JSON)
|
| 16 | 16 |
* DELETE /api/v1/mngr/cntntns/quiz/{quizSn} soft delete(DEL_YN)
|
| 17 | 17 |
* GET /api/v1/mngr/cntntns/quiz/excel/download 엑셀 다운로드 |
... | ... | @@ -236,40 +236,17 @@ |
| 236 | 236 |
}; |
| 237 | 237 |
} |
| 238 | 238 |
|
| 239 |
-/** 등록만 form이다 — 컨트롤러가 `@RequestBody` 없이 받는다(쇼츠·만화와 같은 갈림). */ |
|
| 240 |
-function toFormParams( |
|
| 241 |
- payload: QuizWritePayload |
|
| 242 |
-): Record<string, string | number | undefined> {
|
|
| 243 |
- const params: Record<string, string | number | undefined> = {
|
|
| 244 |
- quizTitle: payload.quizTitle, |
|
| 245 |
- quizLev: payload.quizLev, |
|
| 246 |
- quizQuestion: payload.quizQuestion, |
|
| 247 |
- quizExplan: payload.quizExplan, |
|
| 248 |
- quizAnswer: payload.quizAnswer, |
|
| 249 |
- useYn: payload.useYn, |
|
| 250 |
- }; |
|
| 251 |
- |
|
| 252 |
- payload.schulGrdList.forEach((item, index) => {
|
|
| 253 |
- params[`schulGrdList[${index}].schulCd`] = item.schulCd;
|
|
| 254 |
- // undefined면 파라미터 자체가 빠져 백엔드에서 null이 된다. |
|
| 255 |
- params[`schulGrdList[${index}].grdCd`] = item.grdCd ?? undefined;
|
|
| 256 |
- }); |
|
| 257 |
- |
|
| 258 |
- return params; |
|
| 259 |
-} |
|
| 260 | 239 |
|
| 261 | 240 |
async function sendWrite( |
| 262 | 241 |
path: string, |
| 263 | 242 |
method: 'POST' | 'PUT' | 'DELETE', |
| 264 |
- body?: unknown, |
|
| 265 |
- form?: Record<string, string | number | undefined> |
|
| 243 |
+ body?: unknown |
|
| 266 | 244 |
): Promise<void> {
|
| 267 | 245 |
const accessToken = await getSessionAccessToken(); |
| 268 | 246 |
|
| 269 | 247 |
const result = await backendFetch<unknown>(path, {
|
| 270 | 248 |
method, |
| 271 | 249 |
...(body === undefined ? {} : { body }),
|
| 272 |
- ...(form === undefined ? {} : { form }),
|
|
| 273 | 250 |
accessToken: accessToken ?? undefined, |
| 274 | 251 |
cache: 'no-store', |
| 275 | 252 |
canHaveNullData: true, |
... | ... | @@ -282,7 +259,7 @@ |
| 282 | 259 |
} |
| 283 | 260 |
|
| 284 | 261 |
export async function createQuiz(values: QuizFormValues): Promise<void> {
|
| 285 |
- await sendWrite(QUIZ_PATH, 'POST', undefined, toFormParams(buildQuizPayload(values))); |
|
| 262 |
+ await sendWrite(QUIZ_PATH, 'POST', buildQuizPayload(values)); |
|
| 286 | 263 |
} |
| 287 | 264 |
|
| 288 | 265 |
export async function updateQuiz( |
--- lib/data/repositories/content-repository.ts
+++ lib/data/repositories/content-repository.ts
... | ... | @@ -17,7 +17,7 @@ |
| 17 | 17 |
* ``` |
| 18 | 18 |
* GET /api/v1/mngr/cntntns/pagination searchCntntsType 필수 |
| 19 | 19 |
* GET /api/v1/mngr/cntntns/{cntntsSn} 단건
|
| 20 |
- * POST /api/v1/mngr/cntntns 어노테이션 없음 → form(중첩 목록은 인덱스 표기) |
|
| 20 |
+ * POST /api/v1/mngr/cntntns @RequestBody(JSON) |
|
| 21 | 21 |
* PUT /api/v1/mngr/cntntns/{cntntsSn} @RequestBody(JSON)
|
| 22 | 22 |
* DELETE /api/v1/mngr/cntntns/{cntntsSn} soft delete(DEL_YN)
|
| 23 | 23 |
* ``` |
... | ... | @@ -317,34 +317,6 @@ |
| 317 | 317 |
}; |
| 318 | 318 |
} |
| 319 | 319 |
|
| 320 |
-/** |
|
| 321 |
- * 등록은 `@RequestBody`가 없어 form으로 간다. 중첩 목록은 Spring이 읽는 인덱스 표기 |
|
| 322 |
- * (`keywordList[0].cntntsKeyword`)로 편다 — JSON으로 보내면 한 칸도 바인딩되지 않는다. |
|
| 323 |
- */ |
|
| 324 |
-function toFormParams( |
|
| 325 |
- payload: ContentWritePayload & { cntntsType?: string }
|
|
| 326 |
-): Record<string, string | number | undefined> {
|
|
| 327 |
- const params: Record<string, string | number | undefined> = {
|
|
| 328 |
- cntntsType: payload.cntntsType, |
|
| 329 |
- cntntsNm: payload.cntntsNm, |
|
| 330 |
- cntntsTitle: payload.cntntsTitle, |
|
| 331 |
- atchFileId: payload.atchFileId, |
|
| 332 |
- thumbAtchFileId: payload.thumbAtchFileId, |
|
| 333 |
- useYn: payload.useYn, |
|
| 334 |
- }; |
|
| 335 |
- |
|
| 336 |
- payload.keywordList.forEach((item, index) => {
|
|
| 337 |
- params[`keywordList[${index}].cntntsKeyword`] = item.cntntsKeyword;
|
|
| 338 |
- params[`keywordList[${index}].cntntsExpln`] = item.cntntsExpln;
|
|
| 339 |
- }); |
|
| 340 |
- payload.schulGrdList.forEach((item, index) => {
|
|
| 341 |
- params[`schulGrdList[${index}].schulCd`] = item.schulCd;
|
|
| 342 |
- // undefined면 파라미터 자체가 빠져 백엔드에서 null이 된다. |
|
| 343 |
- params[`schulGrdList[${index}].grdCd`] = item.grdCd ?? undefined;
|
|
| 344 |
- }); |
|
| 345 |
- |
|
| 346 |
- return params; |
|
| 347 |
-} |
|
| 348 | 320 |
|
| 349 | 321 |
/** |
| 350 | 322 |
* 실패한 쓰기의 사유와 보낸 본문을 남긴다. 요청 자체는 테스트 환경에서 backend-fetch가 |
... | ... | @@ -354,19 +326,11 @@ |
| 354 | 326 |
method: string, |
| 355 | 327 |
path: string, |
| 356 | 328 |
body: unknown, |
| 357 |
- form: Record<string, string | number | undefined> | undefined, |
|
| 358 | 329 |
failure: { code: number; message: string }
|
| 359 | 330 |
): void {
|
| 360 | 331 |
const lines = [ |
| 361 | 332 |
`[contents] ${method} ${path} 실패 (code ${failure.code}: ${failure.message})`,
|
| 362 | 333 |
body === undefined ? null : `body(JSON):\n${JSON.stringify(body, null, 2)}`,
|
| 363 |
- form === undefined |
|
| 364 |
- ? null |
|
| 365 |
- : `form:\n${new URLSearchParams(
|
|
| 366 |
- Object.entries(form).flatMap(([key, value]) => |
|
| 367 |
- value === undefined ? [] : [[key, String(value)] as [string, string]] |
|
| 368 |
- ) |
|
| 369 |
- ).toString()}`, |
|
| 370 | 334 |
].filter(Boolean); |
| 371 | 335 |
|
| 372 | 336 |
console.error(lines.join('\n'));
|
... | ... | @@ -375,15 +339,13 @@ |
| 375 | 339 |
async function sendWrite( |
| 376 | 340 |
path: string, |
| 377 | 341 |
method: 'POST' | 'PUT' | 'DELETE', |
| 378 |
- body?: unknown, |
|
| 379 |
- form?: Record<string, string | number | undefined> |
|
| 342 |
+ body?: unknown |
|
| 380 | 343 |
): Promise<string | null> {
|
| 381 | 344 |
const accessToken = await getSessionAccessToken(); |
| 382 | 345 |
|
| 383 | 346 |
const result = await backendFetch<unknown>(path, {
|
| 384 | 347 |
method, |
| 385 | 348 |
...(body === undefined ? {} : { body }),
|
| 386 |
- ...(form === undefined ? {} : { form }),
|
|
| 387 | 349 |
accessToken: accessToken ?? undefined, |
| 388 | 350 |
cache: 'no-store', |
| 389 | 351 |
canHaveNullData: true, |
... | ... | @@ -391,7 +353,7 @@ |
| 391 | 353 |
}); |
| 392 | 354 |
|
| 393 | 355 |
if (!result.ok) {
|
| 394 |
- logWriteFailure(method, path, body, form, result); |
|
| 356 |
+ logWriteFailure(method, path, body, result); |
|
| 395 | 357 |
throw new BackendRequestError(result); |
| 396 | 358 |
} |
| 397 | 359 |
|
... | ... | @@ -403,7 +365,7 @@ |
| 403 | 365 |
payload: ContentWritePayload, |
| 404 | 366 |
cntntsType: string |
| 405 | 367 |
): Promise<string | null> {
|
| 406 |
- return sendWrite(CONTENT_BASE_PATH, 'POST', undefined, toFormParams({ ...payload, cntntsType }));
|
|
| 368 |
+ return sendWrite(CONTENT_BASE_PATH, 'POST', { ...payload, cntntsType });
|
|
| 407 | 369 |
} |
| 408 | 370 |
|
| 409 | 371 |
export async function updateContent( |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?