24 KiB
24 KiB
가로형 서브 배너 백엔드 요구사항
문서 정보
| 항목 | 내용 |
|---|---|
| 문서 상태 | Phase 7 언어 필터 후속 구현·전체 검증·독립 리뷰 완료, 미배포 |
| 작성일 | 2026-10-29 |
| 최종 수정일 | 2026-11-03 |
| 대상 기능 | 가로형 서브 배너 백엔드 및 영속성 |
| 관련 API 계약 | api-contract.md, 구현·실제 HTTP 검증 기준 |
| 구현 계획 | plan-task.md |
요구사항 상태와 현재 승인 범위
- 기존
USER REQUIREMENTS는 사용자가 확정한 제품 요구이며, 아래USER REQUIREMENTS와DEC-001,DEC-005에 보존한다. - 초기 API·저장 모델 제안은
DEC-009의 사용자 구현 승인에 따라 구현했으며 실제 검증한 계약은api-contract.md에 기록한다. 앱 레이아웃·링크 우선순위 등 UI 추천은 백엔드 구현 완료와 구분한다. - 현재 승인 범위는 문서대로 백엔드 코드와 DDL 파일을 생성하고 검증하는 것이다. 운영 DB 적용·배포·앱/관리자 UI 구현은 제외한다. 앞선 문서-only 승인 이력은 Decision Log에 보존한다.
- API 및 저장 모델은 이 문서와 API 계약을 기준으로 구현하되 완료·검증·배포 상태는 구분한다. 소비자 프롬프트는 실제 구현 계약과 다른 설명이 남지 않도록 완료 시 상태를 맞춘다.
USER REQUIREMENTS
- 가로형 서브 배너 기능이 필요하다.
- 관리자는 배너를 생성, 조회, 수정, 삭제할 수 있어야 한다.
- 웹 URL, Android 딥링크, iOS 딥링크는 각각 선택적으로 등록하며 적어도 하나가 있어야 한다. 정확히 하나만 허용하는 XOR 조건은 아니다. 세 값이 모두 없는 조합은 거부하고 나머지 7개 존재 조합은 허용한다.
- 노출 위치는 메인 홈 추천과 메인 콘텐츠 추천이며, 각 위치에 여러 배너를 조회할 수 있어야 한다.
- 앱은 서버가 반환한 배너 중 웹 URL 또는 현재 대상 OS 링크가 있는 항목을 표시한다.
- 관리자는 기존 콘텐츠 배너 예약 기간과 같은 선택적 시작·종료 기간을 설정할 수 있다.
- 삭제는
is_active를 이용한 소프트 삭제로 수행한다. 생성 기본값은 true이며 삭제 시 false로 변경해 행과 기존 데이터를 보존한다. - 앱 조회는 기존 홈 추천·콘텐츠 추천 API의
data.subBanners배열로 제공하며 두 GET 모두 비로그인 접근을 유지한다. 별도 앱 조회/api/v2/sub-banners는 제거한다. 앱 프롬프트도 이 계약으로 갱신한다.
초기 제안과 구현 결정
- 한 레코드에 한 placement를 저장하고 같은 소재를 두 곳에 노출하려면 각각 등록한다.
DEC-003의 초기 제안을 구현에 적용했으며 최초 사용자 발화로 소급하지 않는다. - 주소는 trim 후 빈 문자열을 null로 정규화하고 URI 원문을 보존한다.
DEC-002의 입력 처리 제안을 구현했다. - 독립 테이블, 서버 생성 이미지 key 및 이미지 검증을 유지한다. 초기 DB row 물리 삭제는
DEC-010에 따라is_active=false소프트 삭제로 변경한다. 이미지의 기존 S3 object 자동 삭제는 제공하지 않는다.
목표와 비목표
목표
- G1: 독립 테이블과 백엔드 도메인으로 두 placement의 배너 레코드를 격리하고, 생성·조회·수정·삭제 후 DB 재조회에서도 데이터가 보존된다. (
SB-001~SB-003,SB-010) - G2: 관리자 API는 ADMIN만 사용하고 이미지 multipart 포함 CRUD를 제공하며, payload 검증이 업로드와 영속화보다 먼저 이뤄진다. (
SB-004~SB-007,SB-011) - G3: 공개 GET은 placement와 유효 기간으로 필터링하고 전체 목록을 id 오름차순으로 반환하며, 날짜 없는 공개 DTO와 공통 응답 envelope를 유지한다. (
SB-008~SB-009) - G4: 배포 DDL은 적용·롤백 영향을 명시하며 기존 콘텐츠 배너 테이블과 데이터를 변경하지 않는다. (
SB-010) - G5 (2026-11-02 승인 후속): 서브 배너는 기존
Lang으로 저장되고 두 추천 API는 회원 접속 국가에 맞는 언어만 반환하며, 관리자는 모든 언어 항목을 조회·관리할 수 있다. (SB-013)
포함 범위
- Kotlin/Spring 도메인, application service, persistence adapter와 DTO/controller.
- 공개 단일 GET 및 관리자 CRUD endpoint, 보안 matcher, 이미지 업로드 연동.
- 신규 테이블 생성용 SQL artifact와 테스트, 시간 정밀도 및 접근 제어 검증.
제외 범위
- 앱 및 관리자 실제 UI 구현, 기존 대형 콘텐츠 배너 변경, 예약 스케줄러/job, 순서 편집, 별도 활성 토글·삭제 복구 UI/API, 분석.
- 새 라이브러리, 공통 예외 처리기 전면 변경, 배포 실행, 운영 DB 직접 변경.
- 기존 콘텐츠 배너 테이블 migration. 새 서브 배너 전용 테이블만 제안한다.
- idempotency key, pagination, 자동 재시도 또는 요청 간 exactly-once 보장.
대상 사용자와 권한
| 주체 | 허용 동작 | 거부 조건 |
|---|---|---|
| 익명 앱 클라이언트 | 지정 placement의 공개 GET | 다른 HTTP method 및 인접 경로는 기존 인증 정책 적용 |
ADMIN |
모든 관리자 CRUD | 인증되지 않았거나 ADMIN이 아닌 요청은 변경 전 거부 |
기능 요구사항
아래 요구사항은 DEC-009의 구현 승인에 따라 완료했으며 각 행을 plan-task.md의 실행 Goal·검증 증거에 연결한다. 상태 열의 초기 제안 표기는 요구사항 출처이며 현재 미구현을 뜻하지 않는다. 최종 wire 계약은 api-contract.md를 따른다.
| ID | 상태 | 요구사항 | 수용 기준 | 계약 / Goal |
|---|---|---|---|---|
SB-001 |
작성자 영속성 제안 | 서브 배너는 독립적인 테이블에 저장한다. placement는 HOME_RECOMMENDATION 또는 CONTENT_RECOMMENDATION 하나다. |
신규 레코드가 제안 SQL의 독립 테이블에서 저장/조회되고, 기존 콘텐츠 배너 테이블에는 schema/data 변경이 없다. | §1, P2-T1 |
SB-002 |
API 형태 제안, 최소 주소 조건 확정 | 응답 객체는 id, placement, imageUrl, 세 nullable 링크를 가진다. 내부 저장 모델은 CDN URL 대신 object key인 imagePath를 보관한다. | 8개 존재 조합 중 모두 null인 경우는 거부하고 나머지 7개 조합은 허용한다. 관리자와 공개 DTO에만 imagePath를 절대 imageUrl로 조립한다. | §1, P2-T1, P3-T1 |
SB-003 |
CRUD 확정, 응답·정렬 제안 | 관리자는 placement별 활성 목록, 생성, 수정, 삭제를 수행한다. | GET은 삭제되지 않은 행을 기간과 무관하게 id 오름차순으로 반환한다. 생성/수정은 저장된 객체, 삭제는 data:null로 응답한다. |
§3, P3-T1, P5-T1 |
SB-004 |
최소 주소 조건 확정, 정규화·URI 안전 규칙 제안 | 세 링크는 각각 nullable이며 trim 후 최소 하나가 nonblank여야 한다. | 모든 8개 존재 조합을 검사하며 모두 null/blank인 경우만 거부한다. 공백뿐인 값은 null 처리한다. nonblank 값은 trim 외 원문 byte/문자열을 재인코딩하거나 정규화하지 않는다. web URL은 absolute http/https, 딥링크는 유효한 URI이며 javascript, data, file, content scheme은 대소문자와 무관하게 거부한다. 별도의 임의 scheme allowlist는 추가하지 않는다. |
§1, P3-T1 |
SB-005 |
작성자 안전 제안 | 이미지가 필수인 생성과 선택적 이미지 교체 수정을 지원한다. | MIME/type 검증 및 모든 request 검증이 S3 업로드와 DB 변경 전에 끝난다. 업로드 실패 시 기존 이미지 값은 바뀌지 않는다. DB rollback이 이미 완료된 S3 업로드를 되돌린다고 가정하지 않는다. | §3, P3-T1 |
SB-006 |
사용자 확정 | 선택적 startDate, endDate를 독립적으로 설정·조회·수정·해제한다. |
KST yyyy-MM-dd HH:mm만 받고 응답한다. 생성 누락/null은 unbounded, 수정 누락은 유지, 수정 null은 해당 경계 해제다. 병합 후 날짜 역전은 저장하지 않는다. |
§1, §3, P2-T1, P3-T1 |
SB-007 |
확정 요구, 상태 매핑은 기존 정책 확인 필요 | 관리자 GET/POST/PUT/DELETE는 ADMIN 권한이어야 한다. | 익명 및 non-ADMIN이 CRUD를 시도해도 데이터가 변하지 않는다. 테스트는 실제 공통 접근 거부 handler의 HTTP/envelope 동작을 확인하고 임의로 403을 가정하지 않는다. | §3, P3-T1, P4-T1 |
SB-008 |
사용자 후속 확정 | 두 추천 API가 각 화면 placement의 활성·기간 내 전체 서브 배너를 함께 반환한다. | 비로그인 GET /api/v2/home/recommendations는 HOME_RECOMMENDATION, GET /api/v2/audio/recommendations는 CONTENT_RECOMMENDATION의 data.subBanners를 반환한다. id 오름차순·빈 배열·기간 경계·무절단 규칙은 유지하고 별도 앱 조회 endpoint를 제거한다. |
§2, P6-T1 |
SB-009 |
사용자 후속 확정 | 기존 추천 응답에 subBanners만 추가하고 다른 필드는 보존한다. |
각 배열 항목은 기존 6필드 {id, placement, imageUrl, webUrl, androidDeepLink, iosDeepLink}다. 기존 banners와 추천 콘텐츠를 교체하지 않는다. 날짜·isActive는 노출하지 않으며 OS 필터는 앱 책임이다. |
§1, §2, P6-T1 |
SB-010 |
작성자 데이터 안전 제안 | 신규 테이블 DDL과 적용 전후 안전성을 제공한다. | docs/20261029_가로형서브배너/create-sub-banner.sql에 모든 column/table COMMENT, nullable UTC TIMESTAMP 기간 컬럼, 필요 시 기존 audit timestamp 관례를 포함한다. 계획에 적용 조건과 rollback 절차를 적고 자동 live DDL은 하지 않는다. |
§1, P2-T1 |
SB-011 |
작성자 API/오류 제안 | 성공 envelope와 계약된 응답 shape를 일관되게 유지한다. | 성공은 ApiResponse<T> 4필드, HTTP 200, 관리자 빈 목록 [], 관리자 POST/PUT 객체, DELETE null, 공개 날짜 미포함. 잘못된 입력/없음 상태의 status는 API 계약 검토 후 기존 handler를 활용하거나 endpoint 경계에서 처리한다. |
api-contract.md §1, §5, P3-T1, P4-T1 |
SB-012 |
사용자 후속 확정 | 관리자 삭제는 is_active=false 소프트 삭제다. |
DB 행·이미지 key·주소·기간·생성 시각을 보존한다. 관리자/공개 목록과 id 조회에서 비활성 행을 제외하며 삭제된 id 수정·재삭제는 404다. 응답에 isActive를 추가하지 않고 S3는 보존한다. 복구/활성 토글/cleanup은 범위 밖이다. | §3, P5-T1 |
SB-013 |
사용자 후속 확정, 2026-11-02 | 서브 배너 언어는 기존 Lang의 KO, EN, JA 중 하나로 저장한다. 관리자 생성에서 생략하면 KO, 수정에서 생략하면 기존 값을 유지하고 응답에는 lang을 포함한다. 공개 추천은 기존 회원 국가 판정 JP→JA, 그 외→KO를 적용한다. |
명시적 null·비문자열·잘못된 코드는 HTTP 400이며 업로드/저장 전 거부된다. 관리자 목록은 언어 미필터 전체 조회다. public repository에는 필수 Lang 조건을 전달하고 public SubBanner DTO는 변경하지 않는다. 기존 행은 KO로 backfill하며 JA 분류 및 연결 이벤트와의 언어 정합성은 운영 담당자가 수동 확인한다. |
§3, P7-T1, P7-GATE |
API 및 데이터 계약
- 앱 조회:
GET /api/v2/home/recommendations,GET /api/v2/audio/recommendations의data.subBanners배열. 비로그인 허용, 화면별 placement 고정, 페이지네이션 없음, id 오름차순. 별도/api/v2/sub-banners는 제거한다. - 관리자:
GET/POST /admin/sub-banners,PUT/DELETE /admin/sub-banners/{id}, ADMIN 전용. GET은 필수 placement query. - 성공은 HTTP 200과 공통
ApiResponse{success,message,data,errorProperty}. POST/PUT multipart에는image와 JSONrequest; POST image 필수, PUT image 선택. PUT의 기존 네 필드는 필수 replacement이고 날짜만 선택적 보존/해제다. - 두 추천 응답의
subBanners항목은 기본 6필드, 관리자 DTO는 날짜 문자열 두 필드를 더한다. 추천 응답의 기존banners등 모든 기존 필드는 유지한다. - 관리자 목록은 삭제되지 않은 활성 행의 모든 기간 상태를 반환한다. 공개 조회는 활성 행에 현재 기간 필터를 추가하며 앱에서 로컬 시간으로 기간 판정하지 않는다.
- 입력 검증과 없는 id는
ResponseStatusException을 통해 400/404 Boot 오류 body를 반환한다. 익명은 401, 유효한 비관리자 및 multipart 바인딩 오류는 기존 handler의 200success:false다. 공통 예외 정책을 변경하지 않았다.
시간·검증·저장 규칙
- 입력 시각은 엄격한 KST
yyyy-MM-dd HH:mm분 정밀도만 허용한다. 초, offset,Z, 공백/빈 문자열, raw scalar의 문자열 외 타입, 잘못된 달력 날짜 및 병합 후 start > end는 저장/업로드 전에 거부한다. - 시작 분은
:00, 종료 분은:59까지 포함한다. 동일 분 경계는 유효하다. null 경계는 독립적으로 unbounded다. - 저장 경계는 KST 입력에서 한 번 정규화한 UTC 초 단위 값이며, 그 값을 영속화와 관리자 응답 변환에 공통 사용한다. DB flush/clear/reload 후 시작
:00·종료:59와 관리자 날짜 문자열을 정확히 왕복 비교한다. 공개 조회의 현재 시각은 한 번만 얻어 초 단위로 절삭한다. 이 시각 seam에 나노초0,123456789,999999999및 날짜 경계를 넣어 종료 분 마지막 초와 다음 초의 판정을 확인한다. 분 형식 API에 소수초 입력을 허용하는 것은 아니다. - 링크 입력은 JSON 원문 유형 확인 후 trim 처리한다. nonblank URI는 trim 이후 원문을 보존한다. 기존 Tika 기반
validateImage(file, gifAllowed = true)를 호출해 이미지 내용을 검사하고 GIF를 허용한다. 빈 파일은 거부한다. 신규 dependency나 임의 추가 크기 제한은 없다. - 현재
application.ymlmultipart 제한은 파일 및 요청 각각 1024MB다. 이를 새 제품 상한으로 해석하거나 몰래 변경하지 않는다. S3Uploader.upload(InputStream, bucket, filePath, metadata?)반환은 object key다.imageUrl생성 시 기존 CDN 변환 관례를 사용하며 object key를 URL로 오인해 응답하지 않는다. 저장 실패 뒤 이미 업로드된 S3 객체는 자동 삭제/rollback을 보장하지 않으며 cleanup job은 범위 밖이다.- 삭제는
is_active=false만 반영하고 DB 행과 S3 object를 보존한다. 관리자 목록은 활성 행의 모든 기간 상태를, 공개 목록은 활성 행 중 기간 내 항목만 반환한다.
근거와 제안 경계
현행 동작 근거:
- 추천 배너 구조:
src/main/kotlin/kr/co/vividnext/sodalive/v2/api/common/dto/RecommendationBannerResponse.kt,.../v2/api/home/adapter/in/web/HomeRecommendationController.kt,.../v2/api/content/recommendation/adapter/in/web/AudioRecommendationController.kt. - 기존 관리자 배너:
src/main/kotlin/kr/co/vividnext/sodalive/admin/content/banner/AdminContentBannerController.kt,AdminContentBannerService.kt,GetAdminContentBannerResponse.kt. - 예약 기간 규칙:
docs/20261027_콘텐츠배너예약기간/prd.md§6-8,src/main/kotlin/kr/co/vividnext/sodalive/content/main/banner/AudioContentBannerVisibility.kt,docs/20261027_콘텐츠배너예약기간/alter-content-banner-reservation.sql. - 경량 헥사고날 패키지와 권한:
docs/agent-guides/코드스타일.md,src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt. - 응답/예외 사실:
src/main/kotlin/kr/co/vividnext/sodalive/common/ApiResponse.kt,SodaExceptionHandler.kt. - 이미지 업로드/검증:
src/main/kotlin/kr/co/vividnext/sodalive/aws/s3/S3Uploader.kt,src/main/kotlin/kr/co/vividnext/sodalive/utils/ImageValidation.kt. - 테스트 관례:
src/test/kotlin/kr/co/vividnext/sodalive/admin/content/banner/AdminContentBannerControllerIntegrationTest.kt,AdminContentBannerServiceTest.kt,src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/dto/recommendation/HomeRecommendationResponseTest.kt,src/test/kotlin/kr/co/vividnext/sodalive/v2/api/content/recommendation/adapter/in/web/AudioRecommendationEndToEndTest.kt.
신규 v2.subbanner 경로와 SQL은 구현되어 있다. 기존 v2.creatorname 패턴을 따른 단일 독립 기능 모듈이며 불필요한 API composition 모듈이나 utility wrapper를 추가하지 않았다.
수용 기준
SB-001~SB-012각각을 구현 Goal·테스트·Gate 증거로 연결했다.- 신규 테이블, 기간 및 기본 CRUD는 기존 콘텐츠 배너 schema/data를 변경하지 않는다.
- 관리자 기간 경계를 실제 DB flush/clear/reload와 HTTP 날짜 왕복으로 정확히 검증했다.
- 익명 public GET만 permitAll이며 인접 경로·다른 method·관리자 권한 보호를 실제 HTTP로 검증했다.
- payload 검증 실패의 이미지 upload/DB mutation 0회와 실제 오류 응답을 검증했다.
- 전체 2,676 tests, lint, bootJar 및 독립 최종 리뷰 통과. 운영 DB 적용·배포는 별도이며 완료로 표시하지 않는다.
- 후속 P5·P6는 전체 2,682 tests(실패·무시 0), lint·bootJar와 독립 리뷰를 통과했다. 비로그인 두 추천 응답의 subBanners와 standalone handler 제거, 소프트 삭제 행 보존 및 동시 비복원을 실제 HTTP/DB로 검증했다. 앞선 2,676건은 초기 구현 이력이다.
- 후속
SB-013: 기존Lang필드, 관리자 생성 KO 기본값·수정 생략 유지·잘못된 값의 부작용 전 HTTP 400, 관리자 전체 언어 목록, 두 공개 추천 API의 회원 국가별 언어 필터와 public DTO 불변을 검증했다. (P7-T1,P7-GATE; 2026-11-03 전체 2,928개 및 영향 HTTP 재검증 완료)
Decision Log
| 날짜 | ID | 상태 | 결정 | 근거 |
|---|---|---|---|---|
| 2026-10-29 | DEC-001 | 사용자 확정 | 가로형 서브 배너, 관리자 CRUD, 세 nullable 주소 중 최소 하나, 두 추천 placement, 각 목록 다중 항목, 웹 URL 또는 대상 OS 딥링크가 있는 앱 항목 표시 | 사용자 확인 |
| 2026-10-29 | DEC-002 | 작성자 제안 | trim 후 blank를 null로 만들고 nonblank URI를 변형하지 않음 | 입력 안전성 제안, 승인되지 않음 |
| 2026-10-29 | DEC-003 | 작성자 제안 | API 경로·모델·정렬 순서·단일 placement 레코드 정책 | 일관된 최소 계약 제안, 승인 및 서버 제공 대기 |
| 2026-10-29 | DEC-004 | 작성자 제안 | 대상 OS 딥링크 우선 및 실패 시 웹 URL fallback | 앱 이동 정책 제안, 승인되지 않음 |
| 2026-10-29 | DEC-005 | 사용자 확정 | 관리자에서 콘텐츠 배너와 같은 선택적 노출 기간을 설정한다. KST 분 형식 및 경계 의미는 기존 콘텐츠 배너 예약 동작을 따른다. | 사용자 확인 및 기존 콘텐츠 배너 근거 |
| 2026-10-29 | DEC-006 | 사용자 승인, 범위 제한 | 이번 승인으로 기존 디렉터리의 PRD와 plan-task를 백엔드 요구사항 및 구현 계획으로 완성한다. 이번 변경은 문서 전용이며 제안 API 전체 승인이나 실제 구현 승인이 아니다. | 사용자 요청의 현재 작업 승인 범위 |
| 2026-10-29 | DEC-007 | 작성자 구현 권고, 검토 필요 | 독립 v2.subbanner 모듈과 제안된 신규 테이블을 사용한다. 이미 업로드된 S3 객체의 DB rollback 후 자동 정리는 보장하지 않으며 삭제 정책은 첫 구현 Task에서 확인한다. |
현행 코드 패턴과 S3/DB transaction 한계. 사용자 제품 결정으로 간주하지 않음 |
| 2026-10-29 | DEC-008 | 사용자 승인 | 문서 전용 작업의 검증은 문서 대조와 공백 검사로 한정하고 Gradle·테스트·빌드는 실행하지 않는다. 향후 구현용 명령을 문서에 적는 것만으로 실행 검증을 요구하지 않는다. | 사용자 승인 및 docs/agent-guides/문서유지보수.md의 검증 지침 수정 |
| 2026-10-29 | DEC-009 | 사용자 구현 승인 | 문서대로 백엔드 코드와 DDL 파일을 구현한다. 운영 DB 적용·배포는 승인 범위에 포함하지 않는다. 기존 기간·독립 API 모델을 사용하며 검증 시 H2·테스트 JWT·S3 대체 객체로 외부 부작용을 격리한다. | 후속 사용자 요청 및 ulw 실행 |
| 2026-10-29 | DEC-010 | 사용자 후속 확정·정정 | is_active BOOLEAN NOT NULL DEFAULT TRUE를 추가하고 DELETE는 false 갱신으로 변경한다. 삭제된 행은 모든 소비자 조회에서 제외하며 응답·권한·기간·S3 정책은 유지한다. 앞선 하드 삭제 완료 기록은 당시 이력으로 보존한다. |
"is_active를 두고 소프트 삭제로 하자" |
| 2026-10-29 | DEC-011 | 사용자 후속 확정·정정 | 별도 앱 GET을 제거하고 두 추천 GET에 data.subBanners를 추가한다. 두 GET 모두 비로그인 접근을 유지하고 앱 프롬프트도 함께 변경한다. 관리자 CRUD·소프트 삭제·기간·응답 항목은 유지한다. |
두 추천 API 통합 및 앱 프롬프트 수정 요청 |
| 2026-11-02 | DEC-012 | 사용자 후속 확정 | 서브 배너에 기존 Lang enum 필드를 추가한다. 관리자 생성 누락은 KO, 수정 누락은 유지, 명시적 null·비문자열·invalid는 HTTP 400이며 upload/write 전 검증한다. 관리자 목록은 언어 전체 조회, 공개 두 추천은 회원 국가 기준 JP→JA/그 외→KO로 필터링한다. public DTO는 유지한다. |
승인된 최소 이벤트/서브 배너 언어 후속 수정 |
후속 사용자 요구사항: 관리자 노출 기간
- 관리자는 가로형 서브 배너의 선택적
startDate,endDate를 설정·조회·수정·해제할 수 있다. - 두 경계는 독립적 nullable 값이다. 둘 다 null이면 기간 제한이 없고, 시작만 있으면 시작 시각부터, 종료만 있으면 종료 경계까지 노출한다. 같은 분의 시작/종료도 유효하다.
- 관리자 입력과 응답은
Asia/Seoul의yyyy-MM-dd HH:mm문자열이며 분 정밀도다. 시작 분은:00부터 포함하고 종료 분은:59까지 포함한다. 초,Z, offset은 받지 않는다. - 공개 조회는 현재 기간 안에 있는 항목 전체를 반환한다. 앱은 그 결과에 기존 웹 URL/현재 OS 딥링크 필터를 적용하며 앱 로컬 시계로 기간을 계산하지 않는다.
- 공개 DTO에는 날짜·활성 필드를 추가하지 않는다. 관리자 목록은 삭제되지 않은 활성 행의 미래·현재·만료·무제한 상태를 모두 포함한다.
- 기존 근거는
docs/20261027_콘텐츠배너예약기간/prd.md§6-8,AdminContentBannerService.kt및GetAdminContentBannerResponse.kt다. 신규 계약의 승인이나 구현을 뜻하지 않는다.
후속 사용자 요구사항: 서브 배너 언어 — 2026-11-02
SubBannerdomain, JPA entity/storage, admin request와 admin response에 기존Lang의KO,EN,JA를 추가한다. 새 enum은 만들지 않는다.- 관리자 create에서
lang을 생략하면 KO로 저장한다. update에서 생략하면 기존 언어를 보존한다. 명시적 null, JSON 비문자열 또는 유효하지 않은 코드는 HTTP 400으로 거부하고 S3 upload와 DB write 이전에 검증한다. - 관리자 목록은 기존 placement 조건 외 언어 필터를 추가하지 않으며 응답마다
lang을 반환한다. publicSubBannerResponse에는lang을 노출하지 않는다. - 공개 조회 service는 member를 받아 기존
MemberContentPreferenceService.resolveCountryCode(member)로 JP→JA, 그 외→KO를 선택하고 이를 필수 Lang으로 repository에 전달한다. 두 추천 facade는 기존 member를 service에 전달한다. - 기존 테이블 CREATE SQL에는
lang VARCHAR(10) NOT NULL DEFAULT 'KO'를 포함한다. 별도 신규 migration SQL은 기존 테이블에 언어 컬럼을 안전하게 추가하고 null 값을 KO로 backfill한 뒤 NOT NULL DEFAULT KO를 적용한다. - KO backfill은 기존 행의 언어를 자동 추론하지 않는다. 운영 적용 전에 배너 소재를 검토해 JA 배너와 연결 이벤트의 언어를 운영 담당자가 수동 분류·정합성 확인해야 한다. 운영 DB에 DDL을 실행하지 않는다.
- 제외: 앱/관리자 UI, event link parsing, event detail/adult 정책 변경, 새 dependency, public DTO 변경, 운영 DB·DDL 실행.