Files

10 KiB

앱 구현 프롬프트: 가로형 서브 배너

이 프롬프트는 앱 작업에 단독으로 사용할 수 있다. 현재 repository에 접근해 실제 앱 stack과 기존 화면·URL opener·API client 패턴을 먼저 확인하고 그 패턴에 맞춰 작업하라. 특정 framework를 가정하지 말고 기존 API를 변경하지 않는다.

상태: 소프트 삭제와 두 추천 API 통합의 백엔드 구현·검증 완료, 미배포. 전체 테스트·lint·빌드와 독립 리뷰를 통과했다. 제공 환경을 확인하기 전 운영 연동 또는 서버 배포가 완료됐다고 가정하지 않는다.

2026-11-03 국가별 언어 후속 계약의 전체 2,928개 테스트·영향 HTTP 재검증·lint·빌드·독립 리뷰를 완료했다. 서버 배포 전 신규 환경은 create-sub-banner.sql, 기존 테이블은 20261102_add_sub_banner_lang.sql로 lang 컬럼을 준비해야 한다. 운영 적용은 미실행이며 앱 작업에서 migration이나 운영 DB 변경을 실행하지 않는다.

목표

메인 홈 추천과 메인 콘텐츠 추천 화면에 가로형 서브 배너 영역을 추가한다. 각 placement 목록에 여러 배너가 있을 수 있다. 아래 API와 데이터 계약은 구현된 계약이다. 화면 배치, 표현, 링크 우선순위, fetch 실패 시 UI 처리는 작성자 추천이며 기존 앱 패턴을 따른다.

API 계약

추천 API 통합 계약:

  • 홈: GET /api/v2/home/recommendations, 익명 조회 가능, data.subBanners의 placement는 HOME_RECOMMENDATION으로 고정.
  • 콘텐츠 추천: GET /api/v2/audio/recommendations, 익명 조회 가능, data.subBanners의 placement는 CONTENT_RECOMMENDATION으로 고정.
  • 비로그인 사용자는 Authorization 헤더 없이 두 GET을 조회할 수 있다. 로그인 화면으로 차단하지 않는다. 로그인 사용자는 기존 클라이언트 헤더를 그대로 유지하며, 별도 서브 배너 요청을 추가하지 않는다.
  • 응답은 기존 추천 data 객체에 subBanners: SubBanner[]를 추가한 ApiResponse다. 기존 추천 필드와 banners를 보존하며 data를 배너 배열로 바꾸지 않는다. 서브 배너용 placement query는 보내지 않는다.
  • 성공은 HTTP 200이며 envelope는 success, message, data, errorProperty 네 필드다. 클라이언트는 HTTP status와 success를 모두 확인한다. HTTP 200의 success:false도 실패다. 다른 HTTP 오류도 성공으로 처리하지 않는다.
  • 배너 객체: id 정수(JSON number, 불투명 ID로 앱의 정밀도 안전 전략 사용), placement enum, imageUrl 절대 http/https URL, webUrl string|null, androidDeepLink string|null, iosDeepLink string|null. 다른 배너 필드는 가정하지 않는다.
  • 공개 SubBanner DTO는 위 기본 필드 그대로이며 startDate/endDate/isActive를 추가하거나 가정하지 않는다. 서버는 활성 배너 중 현재 노출 기간 안인 항목 전체를 id 오름차순으로 data.subBanners에 반환하고, 결과가 없어도 배열을 []로 반환한다. 앱은 그 목록에 기존 웹 URL/현재 OS 링크 필터를 적용한다. 응답 순서와 placement는 보존한다.
  • 두 추천의 data.subBanners는 국가별 언어 조건도 서버에서 적용한다. 기존 회원 국가 resolver의 강제 매핑이 우선하며 일반 회원·익명은 CloudFront-Viewer-Country를 사용하고 누락 시 KR이다. JP→JA, 그 외(KR·US 포함)→KO다. Accept-Language/LangContext 또는 기기 언어로 대체하지 않고 별도 language query나 클라이언트 언어 필터를 추가하지 않는다. 공개 DTO는 정확히 위 6필드이고 lang은 없다. EN은 관리자 저장·조회 가능하지만 현재 공개 노출되지 않는다.
  • 더 이상 사용하지 않을 별도 앱 요청은 GET /api/v2/sub-banners다. 앱에서 해당 endpoint를 호출하거나 대체용 두 번째 요청을 추가하지 않는다. 삭제된 경로의 구체적인 오류 status를 가정하지 않는다.
  • 기간 판단은 서버가 맡는다. 기기 시계로 기간을 다시 계산하거나 로컬 예약 timer를 추가하지 않는다.
  • 관리자 언어 계약은 생성 생략 KO·수정 생략 유지이며 null·비문자열·blank·unknown은 업로드/쓰기 전 400이다. 관리자 목록은 모든 언어다. 기존 KO backfill은 자동 언어 분류가 아니므로 운영 담당자가 JA 소재와 연결 이벤트 언어를 수동 검토한다. 앱은 이벤트 링크를 파싱해 언어를 추정하지 않고 외부 URL/딥링크 원문을 유지한다.
  • 서브 배너만을 위한 별도 fetch는 없다. 추천 API의 통신 실패는 기존 추천 화면의 오류 정책으로 처리하며 정상 subBanners:[]로 바꾸지 않는다. 개별 배너의 이미지 로딩·링크 실행 실패는 기존 추천 콘텐츠의 표시와 분리한다.

홈 추천 성공 응답 예시 (기존 추천 필드는 예시 데이터로 유지, 서브 배너 두 항목):

{
  "success": true,
  "message": null,
  "data": {
    "lives": [],
    "banners": [],
    "recentlyActiveCreators": [],
    "recentDebutCreators": [],
    "firstAudioContents": [],
    "aiCharacters": [],
    "genreCreators": [],
    "cheerCreators": [],
    "popularCommunityPosts": [],
    "subBanners": [
    {"id": 101, "placement": "HOME_RECOMMENDATION", "imageUrl": "https://cdn.example.test/one.jpg", "webUrl": "https://example.test/one", "androidDeepLink": "sampleapp://one", "iosDeepLink": null},
    {"id": 102, "placement": "HOME_RECOMMENDATION", "imageUrl": "https://cdn.example.test/two.jpg", "webUrl": null, "androidDeepLink": null, "iosDeepLink": "sampleapp://two"}
    ]
  },
  "errorProperty": null
}

빈 목록 응답:

{"success":true,"message":null,"data":{"lives":[],"banners":[],"recentlyActiveCreators":[],"recentDebutCreators":[],"firstAudioContents":[],"aiCharacters":[],"genreCreators":[],"cheerCreators":[],"popularCommunityPosts":[],"subBanners":[]},"errorProperty":null}

콘텐츠 추천도 기존 data 구조를 유지한다. 빈 데이터 예시는 다음과 같으며, 배너가 있으면 subBanners에 CONTENT_RECOMMENDATION 항목이 들어간다.

{"success":true,"message":null,"data":{"banners":[],"subBanners":[],"originalSeries":[],"latestAudios":[],"newAndHotAudios":[],"freeAudios":[],"pointAudios":[],"mostCommentedAudios":[],"recommendedAudios":[]},"errorProperty":null}

표시 규칙

  • 서버 응답의 id 오름차순은 유지한다. 아래 UI 선택은 작성자 추천이다: 모든 표시 대상 항목을 수직으로 나열하고 새 slider/autoplay는 추가하지 않는다.
  • placement별 화면 내 정확한 위치는 기존 화면을 살펴 정한다. main banner 뒤에 배치하는 것은 작성자 추천이며 확정된 API 동작이 아니다.
  • 작성자 추천: 이미지 원본 비율에 맞춰 표시하고, 필터 결과가 없을 때 영역과 여백을 숨긴다. 기존 화면 레이아웃에 맞게 적용하며 이를 API 계약으로 간주하지 않는다.
  • 표시 조건은 대상 OS 딥링크 또는 웹 URL 보유다. 대상 OS 딥링크가 있어도 앱 설치/launch 가능 여부로 사전 필터링하지 않는 것은 작성자 추천이다.
  • 기간 필터가 적용된 서버 결과에 기존 OS별 링크 필터를 그대로 적용한다. 공개 날짜 필드를 읽거나 클라이언트에서 기간을 판단하지 않는다.

링크 처리와 보안

작성자 추천: 탭 시 대상 OS 딥링크가 있으면 우선 기존 플랫폼 URL opener로 열고, 없으면 웹 URL을 연다. 딥링크 시도 실패 시 웹 URL이 있으면 한 번 대체 시도할 수 있다. 다른 OS 딥링크를 대체 수단으로 쓰지 않는다. 구체적인 우선순위와 실패 피드백은 기존 앱 링크 처리 패턴을 따른다.

웹 URL은 절대 http/https URL이며 host가 필요하다. 딥링크는 절대 URI이고 javascript, data, file, content scheme은 대소문자와 무관하게 거부된다. 클라이언트에서도 임의 입력을 실행하거나 문자열 연결/eval하지 않는다. URI 원문은 재인코딩·정규화하지 않는다. 네트워크 응답은 외부 입력으로 검증한다.

링크 존재 조합 확인표 (작성자 추천)

있음은 해당 주소가 nonblank임을 뜻한다. 아래 탭 동작은 작성자 추천이며 UI 의무 계약이 아니다.

web Android iOS 유효 표시 대상 Android 탭 iOS 탭
없음 없음 없음 없음 표시 안 함 표시 안 함
있음 없음 없음 양쪽 웹 웹
없음 있음 없음 Android 딥링크 표시 안 함
없음 없음 있음 iOS 표시 안 함 딥링크
있음 있음 없음 양쪽 딥링크 우선, 실패 시 웹 웹
있음 없음 있음 양쪽 웹 딥링크 우선, 실패 시 웹
없음 있음 있음 양쪽 Android 딥링크 iOS 딥링크
있음 있음 있음 양쪽 딥링크 우선, 실패 시 웹 딥링크 우선, 실패 시 웹

수용 및 검증

실제 앱 구조에 맞는 테스트를 추가한다. 최소 검증 범위:

  • 모든 항목 표시, 응답 순서 유지, 홈/콘텐츠 placement 격리, 빈 목록에서 gap 없음.
  • JP와 KR·US·국가 누락 및 강제 매핑에서 서버가 선택한 목록을 그대로 소비하고 공개 6필드에 lang을 요구하지 않는지 확인한다.
  • URI 입력을 보존하면서 대상 OS 링크 우선, OS 링크 부재 시 웹, 딥링크 실패 시 웹 fallback 한 번, 양쪽 실패 시 비치명 상태.
  • 다른 OS 링크를 실행하지 않고 잘못된 scheme을 거부한다.
  • 비로그인 두 GET의 data.subBanners를 소비하며 별도 배너 요청이 발생하지 않는다. 추천 API 오류와 정상 빈 배열을 구분하고, 개별 이미지·링크 오류는 다른 추천 콘텐츠를 깨뜨리지 않는다.
  • fixture 검증은 명시 모드에 한정되고 정상 운영 요청에 자동 적용되지 않는다.

최소 변경으로 구현하고 관련 focused test, 프로젝트 type/lint 검사, 실제 화면에서 두 영역·빈 상태·tap 동작을 확인한다. 서버나 API를 구현하지 말고, 서버 준비를 완료했다고 보고하지 않는다.