8.6 KiB
앱 구현 프롬프트: 가로형 서브 배너
이 프롬프트는 앱 작업에 단독으로 사용할 수 있다. 현재 repository에 접근해 실제 앱 stack과 기존 화면·URL opener·API client 패턴을 먼저 확인하고 그 패턴에 맞춰 작업하라. 특정 framework를 가정하지 말고 기존 API를 변경하지 않는다.
상태: 소프트 삭제와 두 추천 API 통합의 백엔드 구현·검증 완료, 미배포. 전체 테스트·lint·빌드와 독립 리뷰를 통과했다. 제공 환경을 확인하기 전 운영 연동 또는 서버 배포가 완료됐다고 가정하지 않는다.
목표
메인 홈 추천과 메인 콘텐츠 추천 화면에 가로형 서브 배너 영역을 추가한다. 각 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로 앱의 정밀도 안전 전략 사용),placementenum,imageUrl절대 http/https URL,webUrlstring|null,androidDeepLinkstring|null,iosDeepLinkstring|null. 다른 배너 필드는 가정하지 않는다. - 공개
SubBannerDTO는 위 기본 필드 그대로이며startDate/endDate/isActive를 추가하거나 가정하지 않는다. 서버는 활성 배너 중 현재 노출 기간 안인 항목 전체를 id 오름차순으로data.subBanners에 반환하고, 결과가 없어도 배열을[]로 반환한다. 앱은 그 목록에 기존 웹 URL/현재 OS 링크 필터를 적용한다. 응답 순서와 placement는 보존한다. - 더 이상 사용하지 않을 별도 앱 요청은
GET /api/v2/sub-banners다. 앱에서 해당 endpoint를 호출하거나 대체용 두 번째 요청을 추가하지 않는다. 삭제된 경로의 구체적인 오류 status를 가정하지 않는다. - 기간 판단은 서버가 맡는다. 기기 시계로 기간을 다시 계산하거나 로컬 예약 timer를 추가하지 않는다.
- 서브 배너만을 위한 별도 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 없음.
- URI 입력을 보존하면서 대상 OS 링크 우선, OS 링크 부재 시 웹, 딥링크 실패 시 웹 fallback 한 번, 양쪽 실패 시 비치명 상태.
- 다른 OS 링크를 실행하지 않고 잘못된 scheme을 거부한다.
- 비로그인 두 GET의
data.subBanners를 소비하며 별도 배너 요청이 발생하지 않는다. 추천 API 오류와 정상 빈 배열을 구분하고, 개별 이미지·링크 오류는 다른 추천 콘텐츠를 깨뜨리지 않는다. - fixture 검증은 명시 모드에 한정되고 정상 운영 요청에 자동 적용되지 않는다.
최소 변경으로 구현하고 관련 focused test, 프로젝트 type/lint 검사, 실제 화면에서 두 영역·빈 상태·tap 동작을 확인한다. 서버나 API를 구현하지 말고, 서버 준비를 완료했다고 보고하지 않는다.