docs(subbanner): 관리자 언어 설정 증분 프롬프트를 추가한다

This commit is contained in:
2026-11-03 01:42:05 +09:00
parent 8e1e22a7db
commit 6bcc900e5c
2 changed files with 101 additions and 0 deletions
@@ -0,0 +1,83 @@
# 기존 관리자 서브 배너: 언어 선택만 추가하는 구현 프롬프트
## 목표와 범위
기존 관리자 프런트엔드의 서브 배너 목록·생성·수정·삭제 CRUD는 이미 구현되어 있다.
이 프롬프트만으로 기존 생성·수정 폼에 언어 선택을 추가하라. CRUD를 다시 만들거나 화면을 재설계하지 않는다.
특정 프레임워크를 가정하지 말고 작업 저장소의 기존 컴포넌트, 스타일, 폼 상태, 인증, API client, multipart 전송 방식을 재사용한다.
기존 화면·DTO·요청 직렬화·오류 처리·테스트의 영향 지점을 먼저 확인하고 최소 변경한다.
언어 필터·검색·페이지네이션, 새 의존성, feature flag, 호환성 fallback을 추가하지 않는다.
백엔드·DDL·배포는 범위 밖이며 운영 환경의 조회·변경 등 실서비스 작업은 하지 않는다.
## 연동 전 확인과 기존 API
백엔드 언어 계약은 구현·검증됐지만 운영 배포 완료를 뜻하지 않는다.
연동 전에 승인된 비운영 배포 대상의 실제 관리자 목록 응답에서 각 항목의 `lang` 반환을 확인한다.
GET 응답의 `data` 배열 항목에 `lang`이 없으면 KO로 간주하지 말고 계약 미충족으로 보고하여 연동을 중단한다. POST/PUT 응답의 언어 위치는 `data.lang`이다. 환경 자동 전환이나 mock fallback도 금지한다.
- `GET /admin/sub-banners?placement={placement}`: 기존 목록 조회. `placement`는 필수이며 `HOME_RECOMMENDATION` 또는 `CONTENT_RECOMMENDATION`이다.
- `POST /admin/sub-banners`: 기존 생성. `PUT /admin/sub-banners/{id}`: 기존 수정. PATCH나 언어 전용 경로를 만들지 않는다.
- 기존 DELETE, placement별 목록, 서버 id 오름차순, 저장 후 갱신 흐름은 그대로 둔다. 목록은 기간·언어와 무관한 삭제되지 않은 전체 항목이다.
- 기존 `Authorization: Bearer <token>`과 ADMIN 인증 흐름을 유지한다. 비관리자 응답이 반드시 403이라고 가정하지 않는다.
- 성공은 HTTP 200의 `ApiResponse`: `success`, `message`, `data`, `errorProperty`. GET의 `data`는 배열, POST/PUT은 저장된 전체 객체다.
## 데이터와 언어 선택
기존 관리자 DTO의 8필드 `id`, `placement`, `imageUrl`, `webUrl`, `androidDeepLink`, `iosDeepLink`, `startDate`, `endDate`를 보존하고 필수 `lang`을 더해 9필드로 확장한다.
기존 필드 타입은 유지한다: `id`는 정밀도를 보존할 정수 식별자, `placement`는 위 enum, `imageUrl`은 URL 문자열, 세 링크와 두 날짜는 각각 문자열 또는 null이다.
요청·응답·폼의 언어 타입은 저장소 언어에 맞는 enum 또는 리터럴 유니온 `KO | JA | EN`으로 제한하며 nullable로 만들지 않는다.
1. 기존 생성·수정 폼에 연결된 label `언어`를 가진 접근 가능한 select를 추가한다. 키보드 조작과 기존 포커스·오류 표시 패턴을 유지한다.
2. 선택지는 값/표시명을 `KO`/`한국어`, `JA`/`일본어`, `EN`/`영어`로 제공한다. 신규 생성 폼의 기본값만 KO다.
3. 수정 폼은 기존 목록 객체의 저장된 `lang`으로 채운다. JA/EN 행을 KO로 초기화하거나 비동기 로딩 중 기본값으로 덮어쓰지 않는다. 별도 상세 GET은 없다.
4. 생성·수정 시 현재 선택을 대문자 코드로 반드시 직렬화한다. 이미지·기간만 수정할 때도 저장된 언어 선택을 유지한다.
5. 필요하면 기존 목록 행 스타일에 언어 표시만 추가한다. 목록 구조·정렬·조회 조건은 바꾸지 않는다.
## 요청 직렬화: 기존 multipart에 lang만 추가
POST/PUT의 전체 요청은 기존대로 `multipart/form-data`다.
선택한 `lang`은 `Content-Type: application/json`인 **`request` JSON 파트 내부**에 넣는다.
별도 form field, header, query로 보내거나 요청 전체를 JSON으로 바꾸지 않는다.
`image` 파일 파트는 생성 시 필수, 수정 시 선택이며 생략하면 기존 이미지가 유지된다.
`placement`, `webUrl`, `androidDeepLink`, `iosDeepLink` 네 JSON 필드는 생성·수정 모두 반드시 보낸다.
수정의 네 필드는 교체 값이며 링크 null은 삭제다. 언어만 바꿀 때도 현재 placement·세 링크·이미지·기간을 보존한다.
기존 링크 유효성 검사와 원문 보존을 유지하며 언어 변경을 이유로 주소를 비우거나 변형하지 않는다.
아래는 `request` 파트 내용의 예시다. POST/PUT 모두 이 JSON을 multipart로 감싸고, POST에는 `image`도 포함한다.
PUT에서는 실제 폼의 기존 필드·기간 값을 사용한다. 이 예시의 주소나 null로 덮어쓰지 않는다.
```json
{
"placement": "HOME_RECOMMENDATION",
"webUrl": "https://example.test/promo",
"androidDeepLink": "sampleapp://promo",
"iosDeepLink": null,
"startDate": "2026-11-01 09:30",
"endDate": null,
"lang": "JA"
}
```
날짜 의미와 기존 직렬화는 변경하지 않는다. KST(`Asia/Seoul`) `yyyy-MM-dd HH:mm`이며 UTC ISO/브라우저 시간대로 변환하지 않는다.
생성의 날짜 생략/null은 무제한, 수정의 생략은 해당 경계 보존, null은 해당 경계 해제다. 시작 분 `:00`부터 종료 분 `:59`까지 포함한다.
언어만 수정할 때 기존 날짜를 의도치 않게 null로 바꾸지 않는다. 기존 날짜 검증과 개별 경계 해제 동작도 유지한다.
## 언어 오류와 운영자 안내
서버는 `lang` 생성 생략 시 KO, 수정 생략 시 기존 언어를 보존한다. 이는 서버 호환 규칙이며 새 폼은 선택값을 명시적으로 보낸다.
서버는 대소문자 무시·앞뒤 trim을 허용하지만 폼은 정규 코드만 보낸다. 명시적 null, 비문자열(숫자·boolean·배열·객체), blank, 미지원 코드는 HTTP 400이다.
언어를 지우는 null 선택지나 전송은 없다. 언어 오류 후에도 선택값·다른 필드·선택 파일·기존 이미지·날짜 상태를 지우지 않는다.
기존 HTTP 200 `success:false` 실패 처리를 유지하고 HTTP 상태와 `success`를 함께 확인한다. 400은 표준 Spring Boot body일 수 있어 `ApiResponse`로 단정하지 않는다.
앱 공개 노출은 서버가 resolve한 국가 기준 JP→JA, 그 외→KO다. 서버 회원 국가 매핑이 우선하며 일반 접속 국가 헤더 누락은 KR이다. `Accept-Language`나 관리자 브라우저 언어 기준이 아니다.
EN은 저장·관리자 목록 조회가 가능하지만 현재 앱 공개 국가 매핑에서는 노출되지 않는다. 이 안내를 폼에 눈에 보이게 표시하되 EN 선택·저장을 막지 않는다.
배너 소재 언어와 연결 이벤트 언어는 운영자가 수동으로 일치시킨다. 기존 KO 값은 자동 분류의 결과가 아니다. URL 언어 추론·파싱, 자동 번역은 하지 않는다.
## 완료 기준과 구현자 제출물
1. 신규 기본 KO와 JA/EN 선택 각각을 확인하고, POST/PUT의 `request` JSON 안에 정규 `lang`과 필수 네 필드가 포함되는 focused test를 추가한다.
2. 저장·목록 재조회·편집 재진입에서 KO/JA/EN이 유지되고, JA/EN 행이 해당 언어로 미리 선택되는지 확인한다. 이미지·날짜만 수정해도 언어가 보존되어야 한다.
3. null·비문자열·blank·미지원 언어의 400 거부와 오류 표시를 확인한다. HTTP 200 `success:false` 및 Boot 오류 body에서도 입력·이미지·날짜가 보존되는지 테스트한다.
4. 기존 목록·생성·수정·삭제와 이미지 필수/생략, 링크·기간 동작을 회귀 검증한다. 언어만 수정해도 다른 값이 바뀌지 않는지 확인한다.
5. 실제 UI에서 label과 키보드 선택·저장, EN 경고, 저장 후 재편집을 수동 QA한다. 배포 대상의 실제 `lang` 응답 확인과 누락 시 연동 중단도 기록한다.
구현자는 변경 파일 목록, focused test 실행 명령·결과, 실제 UI/manual QA 수행 내용·결과와 미검증 사항을 제출한다. 이 문서 작성 자체를 UI 구현·검증 완료로 보고하지 않는다.
@@ -645,3 +645,21 @@ export TMPDIR=/tmp/soda-banner-impl.CfAkhD NODE_COMPILE_CACHE=/tmp/soda-banner-i
- Native MySQL·운영 DB에는 실행하지 않았으며, H2 결과를 MySQL 실행 증거로 표현하지 않는다. 앱 소스·CREATE SQL 변경이 없어 Gradle 테스트/빌드/린트는 반복하지 않았다. 임시 driver는 결과 기록 후 정리한다. - Native MySQL·운영 DB에는 실행하지 않았으며, H2 결과를 MySQL 실행 증거로 표현하지 않는다. 앱 소스·CREATE SQL 변경이 없어 Gradle 테스트/빌드/린트는 반복하지 않았다. 임시 driver는 결과 기록 후 정리한다.
- 독립 scoped 리뷰 `ses_e630f286affevZ6Rc77rxuL3Dn`: APPROVE, 문서화된 1회 실행 전제 아래 데이터 손실 회귀·차단 사항 없음. 기존 Phase 7의 동적 SQL 검토 기록은 당시 이력이며 현재 계약은 DEC-013/P8이다. - 독립 scoped 리뷰 `ses_e630f286affevZ6Rc77rxuL3Dn`: APPROVE, 문서화된 1회 실행 전제 아래 데이터 손실 회귀·차단 사항 없음. 기존 Phase 7의 동적 SQL 검토 기록은 당시 이력이며 현재 계약은 DEC-013/P8이다.
- Teardown: Java 검증 프로세스 잔존 없음 확인, 소유 `/tmp/soda-banner-sql.CIhdCg`와 `/tmp/soda-sql-review.439u9H`의 내용·실경로를 확인한 뒤 삭제하고 부재를 확인했다. 임시 notepad도 삭제했다. 공유 H2/Gradle 캐시는 보존했다. - Teardown: Java 검증 프로세스 잔존 없음 확인, 소유 `/tmp/soda-banner-sql.CIhdCg`와 `/tmp/soda-sql-review.439u9H`의 내용·실경로를 확인한 뒤 삭제하고 부재를 확인했다. 임시 notepad도 삭제했다. 공유 H2/Gradle 캐시는 보존했다.
## 관리자 언어 증분 프롬프트 (`DOC-LANG-T1`) — 2026-11-03
- Objective: 언어 변경 전 관리자 CRUD가 이미 적용된 프로젝트에서 언어 선택만 추가할 수 있는 독립 프롬프트를 제공한다.
- 기준: 사용자 후속 요청, 기존 `SB-013` 및 관리자 API 계약. 새로운 API/제품 정책을 정의하지 않는다.
- Files: 신규 `docs/20261029_가로형서브배너/admin-language-prompt.md`, 이 계획의 검증 기록.
- 범위 밖: 관리자 전체 재구현, 실제 UI/서버/SQL 변경·운영 연동·배포.
- TDD 예외: 문서 전용. 생성 multipart의 lang, 수정 기존 언어 보존, 유효 enum/오류·노출 정책을 현재 코드/계약과 대조하고 `git diff --check`로 검증한다. Gradle·브라우저 QA는 실제 UI 구현 작업에서 수행한다.
- [x] 신규 증분 프롬프트를 작성하고 현재 계약과 대조했다.
- [x] 문서 내용 대조·공백 검증과 소유 임시 자원 정리를 완료했다. 문서 변경만 단일 커밋으로 기록한다.
### DOC-LANG-T1 검증 기록
- 기존 관리자 CRUD는 사용자 진술을 기준으로 이미 적용된 것으로 두고 전체 재구현 대신 언어 필드 추가만 지시했다. 새로운 framework/UI 디자인이나 backend 계약은 만들지 않았다.
- 새 `admin-language-prompt.md` 전체를 부모가 읽고 기존 `admin-prompt.md`, `api-contract.md`, `AdminSubBannerController`, `SubBannerService`, request/response DTO와 대조했다. S1 생성 KO/JA/EN 및 multipart `request.lang`, S2 수정 저장값 보존·invalid 처리, S3 기존 CRUD/기간/이미지/링크 보존과 EN 비노출 안내를 확인했다.
- GET `data` 배열 항목의 lang 및 POST/PUT `data.lang` 위치를 명확히 했다. 언어 누락 응답을 KO로 추정하거나 미확인 배포 상태를 성공으로 간주하지 않도록 명시했다.
- 문서 전용으로 Gradle·LSP·브라우저·실제 관리자 요청은 실행하지 않았다. 실제 UI 구현자에게 focused test와 실제 화면 QA 증거 제출을 요구했다.
- Teardown: 소유 `/tmp/soda-admin-prompt.McfWCQ`, `/tmp/soda-admin-prompt-worker.jDSFlG`가 비어 있음을 확인하고 제거 후 부재를 확인했다. 임시 notepad도 삭제했다. 실행 서버·브라우저·QA 프로세스를 생성하지 않았다. 기존 관리자 전체 프롬프트와 backend 코드는 변경하지 않았다.