feat: 퀴즈 키워드를 목록으로 보낸다 — 이름은 한 곳에 모아 둔다
백엔드가 quizTitle을 없애고 키워드 목록을 받도록 바뀌는 중인데 필드 이름이 아직 미정이라, QUIZ_KEYWORD_FIELD 상수 하나에 목록·키워드·설명 이름을 모았다. 확정되면 그 세 값만 고치면 등록(form 인덱스 표기)과 수정(JSON 본문 키)이 함께 따라간다. 지금 값은 콘텐츠 API의 이름을 빌린 잠정치라 백엔드가 무시하거나 실패한다(사용자 확정). quizTitle은 아직 함께 보낸다 — 수정 SQL이 조건 없이 덮어써서 지금 빼면 기존 키워드가 지워진다. 백엔드에서 컬럼이 사라지면 그 줄만 지우면 된다. Co-Authored-By: Claude Opus 5
@657d07702b74ca22728de9ebe8c8d14f48ada759
--- lib/data/repositories/content-quiz-repository.ts
+++ lib/data/repositories/content-quiz-repository.ts
... | ... | @@ -209,22 +209,45 @@ |
| 209 | 209 |
return items.find((item) => item.quizSn === quizSn) ?? null; |
| 210 | 210 |
} |
| 211 | 211 |
|
| 212 |
+/** |
|
| 213 |
+ * ⚠️ 백엔드가 키워드 목록을 받도록 바뀌는 중이다(`quizTitle`은 없어질 예정). **이름이 아직 |
|
| 214 |
+ * 확정되지 않아** 여기 한 곳에 모아 둔다 — 확정되면 이 세 값만 고치면 요청·전송이 모두 따라간다. |
|
| 215 |
+ * |
|
| 216 |
+ * 지금 값은 콘텐츠 API(`/api/v1/mngr/cntntns`)의 이름을 그대로 빌린 잠정치다. 백엔드가 아직 |
|
| 217 |
+ * 이 필드를 모르므로 **보내도 무시되거나 실패한다**(사용자 확정 — 이름만 나중에 바꾸기로 함). |
|
| 218 |
+ */ |
|
| 219 |
+const QUIZ_KEYWORD_FIELD = {
|
|
| 220 |
+ /** 목록 자체의 이름. form에서는 `keywordList[0].…` 형태가 된다. */ |
|
| 221 |
+ list: 'keywordList', |
|
| 222 |
+ /** 항목의 키워드 필드. */ |
|
| 223 |
+ keyword: 'cntntsKeyword', |
|
| 224 |
+ /** 항목의 설명 필드. */ |
|
| 225 |
+ description: 'cntntsExpln', |
|
| 226 |
+} as const; |
|
| 227 |
+ |
|
| 228 |
+type QuizKeywordPayload = Record<string, string>; |
|
| 229 |
+ |
|
| 212 | 230 |
type QuizWritePayload = {
|
| 213 | 231 |
/** 값이 있을 때만 실어 보낸다 — 비어 있는 채로 보내면 백엔드가 NULL로 덮어쓴다. */ |
| 214 | 232 |
quizPoint?: string; |
| 233 |
+ /** |
|
| 234 |
+ * TODO(백엔드): 키워드 목록으로 대체되면 이 필드를 지운다. 그전까지 남겨 두는 이유는 수정 |
|
| 235 |
+ * SQL이 `QUIZ_TITLE = #{quizTitle}`로 조건 없이 덮어써서, 빼는 순간 기존 키워드가 지워지기
|
|
| 236 |
+ * 때문이다. |
|
| 237 |
+ */ |
|
| 215 | 238 |
quizTitle: string; |
| 216 | 239 |
quizLev: string; |
| 217 | 240 |
quizQuestion: string; |
| 218 | 241 |
quizExplan: string; |
| 219 | 242 |
quizAnswer: string; |
| 220 | 243 |
useYn: string; |
| 244 |
+ keywordList: QuizKeywordPayload[]; |
|
| 221 | 245 |
schulGrdList: Array<{ schulCd: string; grdCd: string | null }>;
|
| 222 | 246 |
}; |
| 223 | 247 |
|
| 224 | 248 |
/** |
| 225 | 249 |
* 이슈: 정답 노출 문구(기획 "기본 3종 + 기타")를 담을 필드가 백엔드에 없다. 화면은 값을 받지만 |
| 226 | 250 |
* 저장되지 않는다 — 컬럼이 생기면 여기에 실어 보낸다. |
| 227 |
- * 이슈: 키워드가 별도 테이블이 아니라 `quizTitle` 한 칸이라 쉼표로 이어 붙인다. |
|
| 228 | 251 |
*/ |
| 229 | 252 |
function buildQuizPayload(values: QuizFormValues): QuizWritePayload {
|
| 230 | 253 |
return {
|
... | ... | @@ -235,6 +258,10 @@ |
| 235 | 258 |
quizExplan: values.explanation, |
| 236 | 259 |
quizAnswer: values.answer, |
| 237 | 260 |
useYn: values.isVisible ? 'Y' : 'N', |
| 261 |
+ keywordList: values.keywords.map((item) => ({
|
|
| 262 |
+ [QUIZ_KEYWORD_FIELD.keyword]: item.keyword, |
|
| 263 |
+ [QUIZ_KEYWORD_FIELD.description]: item.description, |
|
| 264 |
+ })), |
|
| 238 | 265 |
schulGrdList: toSchoolGradePayload(values.schoolCode, values.grades), |
| 239 | 266 |
}; |
| 240 | 267 |
} |
... | ... | @@ -259,6 +286,13 @@ |
| 259 | 286 |
useYn: payload.useYn, |
| 260 | 287 |
}; |
| 261 | 288 |
|
| 289 |
+ // 중첩 목록은 Spring이 읽는 인덱스 표기로 편다 — JSON으로 보내면 한 칸도 바인딩되지 않는다. |
|
| 290 |
+ payload.keywordList.forEach((item, index) => {
|
|
| 291 |
+ for (const [field, value] of Object.entries(item)) {
|
|
| 292 |
+ params[`${QUIZ_KEYWORD_FIELD.list}[${index}].${field}`] = value;
|
|
| 293 |
+ } |
|
| 294 |
+ }); |
|
| 295 |
+ |
|
| 262 | 296 |
payload.schulGrdList.forEach((item, index) => {
|
| 263 | 297 |
params[`schulGrdList[${index}].schulCd`] = item.schulCd;
|
| 264 | 298 |
// undefined면 파라미터 자체가 빠져 백엔드에서 null이 된다. |
... | ... | @@ -266,6 +300,15 @@ |
| 266 | 300 |
}); |
| 267 | 301 |
|
| 268 | 302 |
return params; |
| 303 |
+} |
|
| 304 |
+ |
|
| 305 |
+/** |
|
| 306 |
+ * 수정은 JSON이라 목록 이름이 본문의 키가 된다 — 상수를 실제로 따르도록 여기서 바꿔 끼운다. |
|
| 307 |
+ * (form 경로는 `toFormParams`가 같은 상수로 인덱스 표기를 만든다.) |
|
| 308 |
+ */ |
|
| 309 |
+function toJsonBody(payload: QuizWritePayload): Record<string, unknown> {
|
|
| 310 |
+ const { keywordList, ...rest } = payload;
|
|
| 311 |
+ return { ...rest, [QUIZ_KEYWORD_FIELD.list]: keywordList };
|
|
| 269 | 312 |
} |
| 270 | 313 |
|
| 271 | 314 |
async function sendWrite( |
... | ... | @@ -302,7 +345,7 @@ |
| 302 | 345 |
await sendWrite( |
| 303 | 346 |
`${QUIZ_PATH}/${encodeURIComponent(quizSn)}`,
|
| 304 | 347 |
'PUT', |
| 305 |
- buildQuizPayload(values) |
|
| 348 |
+ toJsonBody(buildQuizPayload(values)) |
|
| 306 | 349 |
); |
| 307 | 350 |
} |
| 308 | 351 |
|
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?