백엔드 insert에 @RequestBody가 붙었는데(MngrCntntsQuizApiController) 우리는 여전히 form으로
보내고 있어 등록이 HttpMediaTypeNotSupportedException으로 실패했다. 수정과 같은 JSON으로
통일하고 form 경로를 지운다. 쇼츠·만화 insert는 아직 어노테이션이 없어 그대로 form이다.
노출 문구 필드도 실제 계약으로 맞춘다 — answerMessageList(가칭)가 아니라 descrList이고
칸이 cntntsQuizDescr 하나뿐이다. 코드가 아니라 문구 자체를 싣는다: 학습자 앱이 이 값을
그대로 말풍선에 띄운다(CmmBotChatServiceImpl). 코드를 골랐으면 그 코드명이, 기타면 직접
적은 값이 저장된다.
단건 조회가 descrList를 돌려주므로 수정 화면이 저장된 문구를 되살린다. 코드명과 맞는 것이
있으면 그 코드로, 없으면 기타 + 직접 입력으로 돌린다.
Co-Authored-By: Claude Opus 5
공통코드 QUIZ_ANSWER로 O·X 선택지를 조회하던 것을 되돌린다(f2af4f2). 정답여부는 화면
고정값이고, 코드로 받는 것은 각 줄의 「사용자에게 노출할 문구」뿐이다 — O는 QUIZ_ANSWER_O,
X는 QUIZ_ANSWER_X로 목록이 서로 다르다.
Co-Authored-By: Claude Opus 5
O와 X의 문구 목록이 서로 달라 그룹이 둘로 갈린다 — QUIZ_ANSWER_O · QUIZ_ANSWER_X.
정답 코드명을 접미로 붙여 그룹 이름을 만들므로, 정답 선택지가 늘어도 그룹만 등록하면
화면은 손대지 않는다.
기타는 코드가 아니라 직접 입력을 여는 자리라 프론트가 목록 뒤에 붙이고, 고르면 사용자가
적은 문구가 전송된다. 서버 검증도 하드코딩 표 대신 같은 그룹을 조회해 대조한다.
Co-Authored-By: Claude Opus 5
지금까지는 정답으로 고른 쪽만 문구를 정할 수 있었고(나머지 줄은 비활성) 한 벌만
나갔다. 두 줄 모두 항상 편집할 수 있게 하고 둘 다 전송한다 — 정답이 아닌 쪽에도
보여 줄 문구가 필요하다.
정답 라디오는 그대로 둔다. 어느 쪽이 정답인지는 여전히 그것이 정한다.
백엔드가 아직 이 필드를 모른다(작업 중). 지금 보내도 폼은 미지의 파라미터를, JSON은
미지의 속성을 버리므로 조용히 무시된다. 이름이 확정되면 QUIZ_ANSWER_MESSAGE_FIELD
한 곳만 고치면 된다.
Co-Authored-By: Claude Opus 5
백엔드 요청 VO에서 quizTitle·quizPoint가 사라지고 keywordList가 들어왔다.
- 키워드는 쉼표로 이은 문자열 대신 목록으로 읽고 보낸다. 항목마다 설명을 갖는데
입력 칸은 공용 키워드 필드에 이미 있었고, 읽을 때만 유실되고 있었다(사용자 확정).
- 포인트는 저장되지 않으므로 숨김 칸과 폼 값에서 뺀다(사용자 확정). 되돌릴 문자열이
없어진 keywordsFromText도 함께 지운다.
Co-Authored-By: Claude Opus 5
백엔드가 콘텐츠 키를 cntntsSn -> cntntsId로, 퀴즈 필드를 quiz* -> cntntsQuiz*로
바꿨다(퀴즈 식별자도 quizSn -> cntntsId). 우리 쪽은 응답에서 옛 이름을 찾다 던지고
있어 금융쇼츠·4컷만화·금융OX퀴즈 목록이 전부 열리지 않는 상태였다.
누적포인트 목록 검색도 searchUserId -> searchLoginId로 바뀌어 맞춘다.
라우트 폴더 이름도 함께 바꿨다 — 동적 세그먼트라 주소에는 값만 나가므로 URL은
그대로다.
Co-Authored-By: Claude Opus 5
영상 id는 조회에서 채워 hidden input에 실려 있었지만 FoxFileUpload에
defaultFiles를 넘기지 않아 칸이 늘 비어 보였다. 어떤 영상이 붙어 있는지
알 수 없어 매번 다시 올려야 하는 것처럼 보였다.
조회 응답의 fileList[].orgnlFileNm을 함께 실어 와 그 이름을 그린다.
이름이 없으면 "등록된 영상"으로 대신한다.
Co-Authored-By: Claude Opus 5
저장할 때 키워드를 쉼표로 이어 quizTitle 한 칸에 넣으므로, 수정 화면을 열면 키워드가
전부 비어 있었다. 제목을 쉼표로 갈라 줄을 채운다.
설명은 저장 경로에 자리가 없어 되살릴 수 없다(사용자 확정) — 키워드만 채워지고 설명은
빈 칸으로 열린다. 백엔드가 키워드 목록을 받게 되면 이 변환은 필요 없어진다.
Co-Authored-By: Claude Opus 5
퀴즈가 콘텐츠관리 API 아래로 들어왔다. 경로를 `/api/v1/mngr/quiz`에서
`/api/v1/mngr/cntntns/quiz`로 옮기고 쇼츠·만화와 같은 규약으로 맞춘다 — 등록은 form(중첩
목록은 인덱스 표기), 수정은 JSON, 삭제는 soft delete.
학교·학년이 생겼다. `TB_COM_QUIZ_SCHUL_GRD`에 따로 저장되고 `schulGrdList`로 오간다 —
쇼츠·만화와 같은 모양이라 매핑·전송 규칙을 그대로 쓴다(학년 없는 학교는 `grdCd`를 빼서
GRD_CD에 빈 문자열이 박히지 않게 한다). 수정 화면이 저장된 학교·학년을 불러온다.
엑셀 일괄등록은 기획 중이라 넣지 않았다 — 백엔드에도 업로드 엔드포인트가 없고
`excel/download`만 있다. 버튼과 Server Action을 걷어내고 TODO만 남긴다. 다운로드 경로는 새
API로 옮겼다.
확인(임시 프로브로 실제 요청 캡처): 등록 `POST /cntntns/quiz` form +
`schulGrdList[0].schulCd`, 수정 `PUT /cntntns/quiz/42` JSON, 삭제 `DELETE /cntntns/quiz/42`,
학년 없는 학교는 `grdCd` 파라미터 자체가 빠짐. 응답 매핑도 `schulGrdList` → 학교 코드 +
학년 배열, `useYn:'N'` → 비공개로 확인.
⚠️ 백엔드 결함 5건을 Repository 주석에 적었다. 특히 **등록 INSERT의 컬럼 11개 : 값 10개**
불일치(`USE_YN` 자리 누락)로 지금은 등록이 SQL 오류로 끝난다. 프론트는 `useYn`을 정상적으로
보내므로 값 목록만 맞추면 그대로 동작한다.
Co-Authored-By: Claude Opus 5
백엔드가 toon/video 컨트롤러를 /cntntns 하나로 합치고 CRUD를 열었다. 목록은
searchCntntsType(V·T)으로 갈리고, 단건·등록·수정·삭제가 모두 실제 API를 탄다.
등록은 @RequestBody가 없어 form이라 중첩 목록을 keywordList[0].cntntsKeyword 같은
인덱스 표기로 편다. JSON으로 보내면 한 칸도 바인딩되지 않는다.
학교·학년은 새로 생긴 schulGrdList로 오간다 — 학년마다 한 줄이다.
컷은 upload/list/{moduleId}로 한 첨부에 묶어 올린다. 백엔드가 fileSn을 1,2,3으로
채번해 주므로 컷을 가리키는 건 아이디가 아니라 순번이 됐다. 응답의 fileList로
실제 순번을 받아 미리보기 주소를 만든다.
Co-Authored-By: Claude Opus 5
학교급을 프론트 상수 표(ELEM/MIDD/HIGH/ALL + 학년 수)로 들고 있었다. 그 표를 지우고 공통코드를
조회해 쓴다 — 1:1문의 진행상태(BBS_ANS_CD), 꾸미기 아이템 카테고리(ITEM_CATE_CD)와 같은 방식이다.
학년은 학교마다 다르므로 `GET /api/v1/mngr/code/list/GRD_CD?atrbNm={학교코드}`로 학교별로
조회한다(백엔드 매퍼가 `ATRB_NM1 = #{atrbNm}`로 거른다). 상세 목록 SQL이 `ATRB_NM1`을 select하지
않아 한 번에 받아 화면에서 가를 수 없어 학교 수만큼 왕복하고, `fetchSchoolGradeCodes()`가 그
왕복을 한 곳에 모아 `cache()`로 중복을 막는다.
저장에는 `GRD_CD` 코드값을 보낸다 — 화면의 학년 칩이 코드값을 그대로 hidden 필드에 싣고,
Server Action은 저장 직전에 코드표를 다시 조회해 그 학교에 없는 학년 코드를 거른다(화면이 보낸
값을 믿지 않는 기존 규칙과 같다).
"전체연령은 학년 선택 불가"가 하드코딩에서 데이터로 바뀌었다 — 학년 코드가 하나도 없는 학교는
칩을 그리지 않고 검증도 학년을 요구하지 않는다.
확인: 학교 전환 시 학년 칩이 그 학교 것으로 갈리고 이전 선택이 비워지는 것, 다중 선택이
`GRD001,GRD003`처럼 코드값으로 실리는 것, 학년 코드가 없는 학교는 칩이 0개인 것을 임시 페이지로
확인한 뒤 지웠다.
Co-Authored-By: Claude Opus 5
콘텐츠관리는 게시판(bbs)이 아니라 별도 API다. 쇼츠·만화는 `TB_COM_CNTNTS`를 공유하고
(`/api/v1/mngr/cntntns/{video|toon}`), 퀴즈는 테이블도 경로도 따로다(`/api/v1/mngr/quiz`).
그래서 Repository도 그 경계대로 둘로 나눴다.
등록·수정은 팝업이 아니라 별도 화면이다 — 기획의 화면ID·화면경로(등록 < 금융쇼츠 < 콘텐츠관리)와
디자인 시안이 모두 전체 화면이고, 항목 수가 팝업에 담기지 않는다.
목록 화면은 디자인 시안이 없어 디자인 시스템과 기존 목록 화면의 흐름을 따랐다 — 정렬은 기획의
두 버튼 대신 다른 목록과 같은 정렬 셀렉트로, 학교급·학년·난이도는 필터 칩으로 놓았다.
정답여부는 O·X 두 줄을 모두 그리되 정답으로 고른 줄에서만 노출 문구를 정한다(기획 ③). 기획은
두 줄 다 입력칸을 그리고 아닌 쪽을 흐리게, 디자인 시안은 고른 쪽에만 컨트롤을 그렸는데 비활성으로
두면 두 시안이 같은 화면이 된다.
백엔드 미비 사항은 코드에 이슈로 남겼다. 요약:
- 등록·수정·삭제·단건조회 API가 3종 모두 없다. Repository는 다른 관리 화면과 같은 규약으로
미리 맞춰 두었고, 단건은 그 API가 생길 때까지 목록에서 찾는다.
- `cntntns` 경로 오타, `video/pagination`의 `return null`, toon 매퍼의 `BEN_WORD_NM` 복붙,
키워드 조인으로 인한 count 불일치.
- 학교급·학년·주제·전달메시지·정답 노출문구를 담을 컬럼이 없다. 코드값도 정의되지 않아
잠정 상수로 두고 TODO를 달았다.
확인: 임시 페이지로 세 폼과 두 목록을 렌더링해 항목 구성, 학교급 전환 시 학년 칩 잠금(전체연령),
학년 다중선택, 정답여부 「기타」 선택 시 직접입력 노출을 확인한 뒤 페이지를 지웠다.
Co-Authored-By: Claude Opus 5