잠정값으로 두었던 `CODE_GROUP.pointEvent`를 `COM_EVENT_CD`로 확정한다(사용자 확인).
같은 김에 표기도 다른 코드 항목들과 맞춘다 — 목록의 「이벤트/구분」과 수정 팝업의 읽기 전용
칸이 코드값 대신 상세코드명을 보여준다(1:1문의 진행상태·FAQ 유형과 같은 방식). 표에 없는
코드는 지어내지 않고 코드 그대로 노출한다.
**보이는 것과 저장되는 것을 갈랐다.** 화면은 코드명이고 hidden 필드와 전송 payload는 코드값
그대로다 — 수정 시 `EVENT_CD`가 코드명으로 덮이면 안 된다.
확인: 코드명이 다른 표(EVENT-3 → "과정 이수")로 목록이 「과정 이수」를 그리고, 표에 없는
ZZZ-9는 코드 그대로 나오며, 수정 팝업의 읽기 전용 칸은 「과정 이수」인데 hidden은 EVENT-3다.
Co-Authored-By: Claude Opus 5
앞선 커밋에서 "기획상 이벤트는 읽기 전용이라 등록이 없다"고 적었는데 틀렸다. 목록 기획(④)에
「+ 신규 등록」이 있고 등록 팝업 시안(PTS10100002_p)도 따로 있다. "읽기 전용"은 수정 팝업의
이벤트 칸에만 해당한다.
등록 이벤트 = 셀렉트(기획 ① "등록된 코드중에서 선택")
수정 이벤트 = 읽기 전용 입력 — 지금까지와 같다
팝업 하나가 두 모드를 겸한다. 제목·저장 버튼·완료 토스트가 갈리고, 이벤트 칸만 셀렉트/읽기
전용으로 바뀐다. 검증도 `isCreate`로 갈린다 — 등록은 대상 id가 없고(백엔드 채번) 이벤트를
골라야 하며, 수정은 그 반대다. 등록은 저장 직전에 코드표를 다시 조회해 화면이 보낸 이벤트
코드를 대조한다.
Repository에 `createPointStandard`(form POST)를 되살렸다. 등록은 form, 수정은 JSON으로
갈리는 규약은 콘텐츠·퀴즈와 같다.
「사이트」 열을 뺐다 — 기획 목록의 열은 번호·이벤트/구분·포인트·제한포인트·중복여부·설명·
사용여부·수정일시·관리뿐이고 사이트가 없다. 백엔드에도 담을 컬럼이 없어 늘 비어 있던 열이다.
확인: 「신규 등록」을 누르면 제목 「등록」, 항목이 이벤트*·포인트*·제한포인트*·중복 여부·
이벤트 설명*·사용여부, 저장 버튼 「등록」, 이벤트가 셀렉트(EVENT-1)로 뜨고 hidden id는 빈 값.
TODO: 이벤트 공통코드 그룹ID를 받지 못해 `CODE_GROUP.pointEvent`를 잠정값('EVENT_CD')으로
두었다. 실제 값이 확인되면 그 한 줄만 바꾼다.
Co-Authored-By: Claude Opus 5
메모리 mock 저장소를 지우고 `TB_COM_EVENT_POINT`를 보는 실 API로 바꾼다.
GET /api/v1/mngr/point/pagination
PUT /api/v1/mngr/point/{eventPtSn} @RequestBody(JSON)
DELETE /api/v1/mngr/point/{eventPtSn} soft delete
`POST`(등록)는 붙이지 않았다 — 기획상 이벤트는 읽기 전용이라 화면에 신규 등록이 없다.
이벤트 코드를 hidden으로 실어 보낸다. 화면에서는 읽기 전용이지만 백엔드 UPDATE가
`EVENT_CD = #{eventCd}`로 덮어써서, 안 보내면 수정할 때마다 이벤트 코드가 지워진다
(퀴즈 포인트와 같은 종류의 함정이다).
목록 검색은 백엔드에 조건이 없어(`MngrPointPolicySearchVo`가 비어 있다) 검색어가 있을 때만
전체를 받아 여기서 거른다. 검색어가 없으면 백엔드 페이징을 그대로 쓴다.
확인(임시 프로브): 목록 응답이 도메인 타입으로 정확히 매핑되고, 수정은
`PUT /point/3` JSON에 `eventCd`가 함께 실리며, 삭제는 `DELETE /point/3`으로 나간다.
⚠️ 백엔드 결함 5건을 Repository 주석에 적었다. 지금은 목록만 부분 동작하고 수정·삭제는
100% 실패한다 — `MngrPointPolicyServiceImpl.selectOne`이 `return null;` 스텁이라 컨트롤러가
늘 "포인트 정책 조회에 실패했습니다"로 끊는다. 목록도 `MngrPointPolicyVo`에 `eventPtSn` 말고
필드가 없어 이벤트·포인트·설명이 전부 버려진다.
Co-Authored-By: Claude Opus 5
백엔드가 quizTitle을 없애고 키워드 목록을 받도록 바뀌는 중인데 필드 이름이 아직
미정이라, QUIZ_KEYWORD_FIELD 상수 하나에 목록·키워드·설명 이름을 모았다. 확정되면
그 세 값만 고치면 등록(form 인덱스 표기)과 수정(JSON 본문 키)이 함께 따라간다.
지금 값은 콘텐츠 API의 이름을 빌린 잠정치라 백엔드가 무시하거나 실패한다(사용자 확정).
quizTitle은 아직 함께 보낸다 — 수정 SQL이 조건 없이 덮어써서 지금 빼면 기존 키워드가
지워진다. 백엔드에서 컬럼이 사라지면 그 줄만 지우면 된다.
Co-Authored-By: Claude Opus 5
기획에 다시 들어갔다. a96f729를 그대로 되돌린다 — 폼 칸, 값 타입, 검증, 액션의 폼
읽기, 목록의 키워드 열, 검색 대상, 그리고 quizTitle을 키워드로 만드는 저장까지
원래대로다. 보존용으로 넣었던 hidden title은 함께 빠진다.
This reverts commit a96f729.
Co-Authored-By: Claude Opus 5
기획 MCH_001 목록 / MCH_002_p 등록 / MCH_003_p 수정. 관리자용 가맹점 API가 없어
(mngr 도메인에 컨트롤러가 없고 common/qrauth의 결제 조회용 VO는 5개 필드뿐)
Repository를 mock으로 둔다 — 화면·검증·액션은 실제 구조다.
목록은 업종·사용여부 필터와 가맹점명/사업자번호/주소 검색, 최근등록순 정렬,
엑셀다운로드(CSV)까지. 가맹점코드는 저장 시 서버가 만들고 수정에서는 읽기 전용이다.
시안에 있으나 정할 것이 남은 두 가지는 자리를 두고 표시해 둔다 — 주소 검색은 연동할
서비스가 없어 직접 입력으로 받고, 엑셀등록은 양식·검증 규칙이 없어 비활성이다.
Co-Authored-By: Claude Opus 5
기획에서 빠진 항목이다. 폼 칸, 값 타입, 검증, 액션의 폼 읽기, 목록의 키워드 열,
검색 대상까지 함께 걷어낸다. 쇼츠·만화의 키워드는 그대로다.
QUIZ_TITLE은 지우지 않는다 — 키워드를 담던 칸이지만 수정 SQL이
`QUIZ_TITLE = #{quizTitle}`로 무조건 덮어써서 안 보내면 값이 지워진다. 조회해 온
값을 hidden으로 실어 그대로 돌려보낸다(quizPoint와 같은 처리).
Co-Authored-By: Claude Opus 5
기획에서 「메뉴 선택」이 빠졌다(사용자 확정). 화면만 감추지 않고 네 층에서 모두 걷어낸다 —
남겨 두면 저장되지도 조회되지도 않는 값이 계속 도메인에 떠다닌다.
화면 등록·수정 팝업의 메뉴 칩 묶음과 그 상태·prop
검증 `AdminMemberEditableValues.menuCodes`와 허용 코드 필터
액션 `readMenuCodes` (hidden 필드 수집)
도메인 `AdminMember.menuCodes` · `ADMIN_MENU_OPTIONS` mock 카탈로그 ·
`formatAdminMenuLabels`
응답 Repository가 채우던 `menuCodes: []`
애초에 백엔드에 관리자별 메뉴 권한이 없어 저장도 조회도 되지 않던 항목이라, 지우면서 없어지는
기능은 없다.
`/admins/menus`(메뉴관리 화면)는 그대로 둔다 — 관리자마다의 접근 권한이 아니라 메뉴 자체를
관리하는 별개 화면이다.
확인: 등록 팝업에 남은 항목이 이름·ID·비밀번호·휴대전화 번호·이메일·역할 선택뿐이고,
「메뉴 선택」 문구와 칩이 하나도 없으며 hidden 필드도 phoneNumber·email·roleCode만 남는다.
Co-Authored-By: Claude Opus 5
백엔드 UPDATE가 `QUIZ_POINT = #{quizPoint}`로 무조건 덮어쓰는데 화면이 그 값을 보내지 않아,
퀴즈를 한 번 수정하면 포인트가 NULL이 됐다. 기획에 입력 칸이 없는 값이라 화면에는 두지 않고
hidden으로 실어 그대로 돌려보낸다.
값이 없을 때는 키 자체를 빼서 보낸다 — 빈 문자열을 보내면 INT 컬럼에 ''가 들어간다.
확인(임시 프로브): 수정은 `quizPoint:"10"`이 실리고, 등록은 그 키가 빠진다.
Co-Authored-By: Claude Opus 5
나가는 요청만 찍고 받은 것은 남기지 않았다. 브라우저 네트워크 탭에는 이 호출이 뜨지 않아(BFF)
서버 콘솔이 유일한 창인데, 절반만 보이니 "무엇을 보냈고 무엇을 받았는지"를 맞춰 볼 수 없었다.
세 진입점(`backendFetch`·`backendFetchStream`·`backendFetchUpload`) 모두 응답을 남긴다.
요청과 응답에 같은 일련번호를 붙인다(`#1 →` / `#1 ←`) — 동시 호출이 섞여도 쌍을 찾을 수 있고,
소요 시간도 함께 적는다. 네트워크·타임아웃으로 응답이 아예 없을 때도 그 사실을 남긴다.
**본문을 한 번만 읽도록 구조를 바꿨다.** `backendFetch`가 분기마다 `response.json()`·`.text()`를
따로 부르고 있어서, 로그를 붙이려면 스트림을 두 번 읽어야 했다. 응답 직후 글자로 한 번 읽고
로그와 파싱이 그것을 함께 쓴다 — 두 번 읽는 실수를 구조적으로 막는다.
파일 스트림 응답만 예외다. 본문을 읽으면 호출부가 흘려보낼 것이 사라지므로 형식·길이만 남긴다.
안전장치 둘:
- 키 이름이 token·password·secret·authorization이면 값을 `***`로 가린다. 로그인 응답의
accessToken이 콘솔·로그 파일에 남으면 그 자체가 유출이다.
- 본문이 4,000자를 넘으면 잘라내고 잘렸다고 적는다.
운영에서는 요청·응답 모두 남기지 않는다(기존 규칙 유지).
확인(실제 dev 백엔드 호출): `#1 → GET …/code/list/COM_SCHUL_CD` / `#1 ← 401 (46ms)` + 봉투
본문이 짝지어 찍히고, 보낸 본문의 `accessToken`이 `***`로 나온다.
Co-Authored-By: Claude Opus 5
"등록은 잘 된다"는 것이 문제였다. 영상 업로드가 413으로 실패해도 화면은 오류만 띄우고 저장
버튼은 그대로 눌렸다. 영상은 필수 항목이 아니라 검증도 통과하고, `videoFileId`가 빈 값인 채로
저장돼 **영상 없는 콘텐츠가 조용히 만들어졌다.** 사용자에게는 정상 등록으로 보인다.
고른 영상이 끝내 올라가지 않으면 그 사실을 `videoUploadFailed`로 실어 보내고 Server Action이
저장을 막는다 — 업로드 중(`videoUploading`)을 막던 것과 같은 방식이다. 파일을 지우면 플래그와
오류가 함께 풀려 영상 없이 저장하려는 의도는 그대로 통한다.
413 메시지 자체는 정확하다(재실측: 2MB·6MB 모두 nginx 413). 인프라 상향 전까지는 이 경로로
영상을 올릴 수 없고, 그 사실이 저장 시점에 드러나는 편이 낫다.
확인: 100MB 초과 선택 → 오류 + `videoUploadFailed=1` + `videoFileId` 빈 값,
삭제 → 플래그·오류 모두 해제.
Co-Authored-By: Claude Opus 5