docs(event): 일반 이벤트 상세 계약과 클라이언트 연동을 정의한다
This commit is contained in:
@@ -0,0 +1,75 @@
|
|||||||
|
# 일반용 이벤트 상세 API 클라이언트 구현 프롬프트
|
||||||
|
|
||||||
|
아래 프롬프트는 이번에 구현한 서버 계약이다. 운영 배포 완료를 의미하지 않는다.
|
||||||
|
|
||||||
|
```text
|
||||||
|
기존 앱의 네트워크 계층과 화면 패턴을 사용해 일반 사용자용 이벤트 상세 조회를 연결해 주세요.
|
||||||
|
관리자 상세 API를 만들거나 기존 이벤트 목록/팝업 API를 변경하지 마세요.
|
||||||
|
|
||||||
|
[요청]
|
||||||
|
GET {기존 API_BASE_URL}/api/v2/events/{id}
|
||||||
|
- id: 기존 이벤트 목록/배너의 이벤트 id. 양수 64비트 정수이며 필수입니다.
|
||||||
|
- 요청 본문과 query parameter는 없습니다.
|
||||||
|
- 비로그인 호출이 가능합니다. 비로그인에는 Authorization 헤더를 보내지 마세요.
|
||||||
|
- 로그인 시 기존 Authorization: Bearer {accessToken}을 사용하세요.
|
||||||
|
- 오류 메시지 언어는 기존 Accept-Language 정책을 따릅니다.
|
||||||
|
- 이벤트 언어는 Accept-Language가 아니라 서버의 국가 판정에 따릅니다.
|
||||||
|
CloudFront-Viewer-Country 및 기존 서버 보정 정책을 사용하므로 국가를 임의로 바꾸지 마세요.
|
||||||
|
|
||||||
|
[성공: HTTP 200, application/json]
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": null,
|
||||||
|
"data": {
|
||||||
|
"id": 123,
|
||||||
|
"thumbnailImageUrl": "https://cdn.example.com/event/thumbnail.png",
|
||||||
|
"detailImageUrl": "https://cdn.example.com/event/detail.png",
|
||||||
|
"popupImageUrl": null,
|
||||||
|
"link": "https://example.com/promotion"
|
||||||
|
},
|
||||||
|
"errorProperty": null
|
||||||
|
}
|
||||||
|
- data는 배열이 아닌 이벤트 단일 객체입니다.
|
||||||
|
- id: 정수, thumbnailImageUrl: 문자열.
|
||||||
|
- detailImageUrl, popupImageUrl, link: 문자열 또는 null.
|
||||||
|
- title, content, startDate, endDate, lang, isAdult, isPopup, totalCount는 반환하지 않습니다.
|
||||||
|
- 이미지 URL은 서버가 구성한 값을 사용하고 CDN 주소를 다시 붙이지 마세요.
|
||||||
|
- link는 이미지 URL과 별개인 이동 목적지이며 저장값 그대로 반환합니다.
|
||||||
|
- 상세 이미지가 null이어도 link가 있는 정상 이벤트일 수 있습니다.
|
||||||
|
- 이미지와 link가 모두 있을 때의 우선순위나 자동 이동은 API가 결정하지 않습니다.
|
||||||
|
기존 앱의 표시/이동 정책을 유지하고, 임의로 자동 리다이렉트를 추가하지 마세요.
|
||||||
|
- 서버는 link의 URL 형식이나 허용 스킴을 검증하지 않습니다.
|
||||||
|
링크 열기 시 기존 앱의 안전한 외부 URL 처리 정책을 적용하세요.
|
||||||
|
|
||||||
|
[조회 불가: HTTP 200]
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "잘못된 요청입니다.",
|
||||||
|
"data": null,
|
||||||
|
"errorProperty": null
|
||||||
|
}
|
||||||
|
- 미존재 ID, 0/음수 ID, 비활성, 시작 전/종료 후, 언어 불일치, 성인 조건 불일치가 동일한 응답입니다.
|
||||||
|
- 숫자가 아니거나 Long 범위를 넘는 id는 HTTP 200 실패 응답과 기존 공통 오류 메시지를 반환합니다.
|
||||||
|
- HTTP 200만으로 성공 판정하지 말고 success=true와 유효한 data를 확인하세요.
|
||||||
|
- message는 지역화되므로 문구로 오류 유형을 분기하지 마세요.
|
||||||
|
- 서버 내부 messageKey는 응답에 없으며 미존재와 비노출 사유를 구별할 수 없습니다.
|
||||||
|
- 이 API의 조회 불가가 HTTP 404라고 가정하지 마세요.
|
||||||
|
- 네트워크 오류와 비정상 HTTP 상태/인증 오류는 기존 앱의 공통 처리 정책을 유지하세요.
|
||||||
|
모든 오류 응답이 위 JSON 형태라고 가정하지 마세요.
|
||||||
|
|
||||||
|
[서버 노출 정책: 기존 GET /event와 동일]
|
||||||
|
- 활성이고 시작 시각 <= 현재 시각 <= 종료 시각인 이벤트만 조회됩니다.
|
||||||
|
- 서버 판정 국가가 JP이면 JA 이벤트, 그 외에는 KO 이벤트만 조회됩니다.
|
||||||
|
- 비로그인/성인 미인증 회원: isAdult=null 또는 false 이벤트.
|
||||||
|
- 성인 인증 회원: isAdult=null 또는 true 이벤트. 기존 정책상 false 이벤트는 제외됩니다.
|
||||||
|
- 관리자 계정: 성인 조건만 생략하며 활성/기간/언어 조건은 유지됩니다.
|
||||||
|
- 위 정책은 서버가 적용합니다. 클라이언트가 목록과 다른 우회 조회를 구현하지 마세요.
|
||||||
|
- 로그인 상태/국가가 바뀌면 이전 조회 결과를 무조건 재사용하지 마세요.
|
||||||
|
|
||||||
|
[클라이언트 수용 기준]
|
||||||
|
- 이벤트 id로 상세 API를 호출해 loading/success/unavailable/network-error를 구분합니다.
|
||||||
|
- nullable 이미지/link 및 링크만 있는 이벤트를 정상 처리합니다.
|
||||||
|
- HTTP 200 success=false에서도 기존 데이터를 성공 화면으로 표시하지 않습니다.
|
||||||
|
- 로그인 여부에 따른 헤더와 실제 서버 결과를 사용하고, 응답에 없는 제목/본문/날짜를 만들어 내지 않습니다.
|
||||||
|
- 기존 목록 GET /event, 팝업 GET /event/popup, 관리자 API는 그대로 유지합니다.
|
||||||
|
```
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# 일반용 이벤트 상세 조회 PRD
|
||||||
|
|
||||||
|
## 문서 정보
|
||||||
|
- 작성일: 2026-10-28
|
||||||
|
- 상태: 구현 및 검증 완료 (운영 배포 제외)
|
||||||
|
- 결정권자: 사용자; 구현 담당: Sisyphus
|
||||||
|
- 구현 계획: [plan-task.md](plan-task.md)
|
||||||
|
- 클라이언트 전달 계약: [api-client-prompt.md](api-client-prompt.md)
|
||||||
|
- 최종 리뷰: [reviews/event-detail-review.md](reviews/event-detail-review.md), APPROVE
|
||||||
|
|
||||||
|
## 목표와 범위
|
||||||
|
이벤트 ID만으로 일반용 이벤트 정보를 조회할 수 있도록 `GET /api/v2/events/{id}`를 추가한다.
|
||||||
|
기존 목록과 동일한 노출 조건으로 상세 이미지와 외부 이동 링크를 반환한다.
|
||||||
|
관리자 상세 API, 기존 `/event` 계약 변경, 프론트엔드, DB 변경, 신규 의존성은 제외한다.
|
||||||
|
|
||||||
|
## 확정 요구사항
|
||||||
|
| ID | 요구사항 | 수용 기준 |
|
||||||
|
|---|---|---|
|
||||||
|
| EVT-001 | 비로그인 가능한 v2 단건 조회 | 정상 ID 요청은 HTTP 200, `success=true`, 단일 EventItem 반환 |
|
||||||
|
| EVT-002 | 기존 목록과 동일한 노출 정책 | 활성, 시작/종료 시각 포함 범위, 국가별 언어, 성인 인증 조건 일치 |
|
||||||
|
| EVT-003 | 기존 응답 필드 재사용 | id, thumbnailImageUrl, detailImageUrl, popupImageUrl, link 보존 |
|
||||||
|
| EVT-004 | 미존재와 비노출 동일 처리 | HTTP 200, `success=false`, `data=null`, 기존 invalid_request 메시지 |
|
||||||
|
| EVT-005 | 인접 API 보안 유지 | 익명 쓰기·하위 추가 경로·기존 팝업·관리자 요청은 계속 보호 |
|
||||||
|
|
||||||
|
## API 계약
|
||||||
|
- Method/path: `GET /api/v2/events/{id}`. ID는 양수 Long.
|
||||||
|
- 인증: 선택적 기존 JWT. 인증이 없으면 비성인 기준. 잘못된 JWT 처리는 기존 보안 정책 유지.
|
||||||
|
- 성공: `ApiResponse.ok(EventItem)`. 응답 필드는 기존 목록 항목과 동일하며 제목/기간 필드를 추가하지 않는다.
|
||||||
|
- 이미지: `https://`로 시작하면 그대로, 나머지 문자열은 기존 CloudFront 호스트와 `/`를 앞에 붙인다. nullable 이미지는 null 유지.
|
||||||
|
- `link`: 저장값 그대로 반환하고 서버에서 이동하거나 URL을 요청하지 않는다.
|
||||||
|
- 양수가 아닌 ID, 미존재, 비노출: `SodaException(messageKey = "common.error.invalid_request")`로 기존 HTTP 200 실패 envelope 사용.
|
||||||
|
- 숫자가 아니거나 Long 범위를 넘는 ID: 기존 Spring 바인딩과 공통 오류 처리 유지. 새 parser/handler 없음.
|
||||||
|
|
||||||
|
## 노출 정책
|
||||||
|
- `isActive == true`, `startDate <= now <= endDate`.
|
||||||
|
- `MemberContentPreferenceService.resolveCountryCode(member)` 결과가 JP면 JA, 그 외 KO.
|
||||||
|
- 관리자 역할은 성인 조건만 생략하며 활성/기간/언어는 생략하지 않는다.
|
||||||
|
- 비로그인 및 인증 기록 없는 일반 회원: 이벤트 `isAdult`가 null 또는 false.
|
||||||
|
- 인증 기록 있는 일반 회원: 이벤트 `isAdult`가 null 또는 true. 기존 목록처럼 false 이벤트는 제외한다.
|
||||||
|
- ID로 단건 조회하며 전체 목록을 가져와 검색하지 않는다.
|
||||||
|
|
||||||
|
## 검증 시나리오
|
||||||
|
| 시나리오 | 이진 성공 조건 | 실제 검증 표면 |
|
||||||
|
|---|---|---|
|
||||||
|
| S1 정상 | 익명 KO/JP, null/HTTPS/상대 이미지, 외부 link가 기존 항목과 동일 | RANDOM_PORT + TestRestTemplate 실제 HTTP 및 H2 |
|
||||||
|
| S2 경계/권한 | 미존재·비활성·예정·종료·언어 불일치·성인 제한에서 데이터 미노출; 일반/인증/관리자 정책 일치 | 실제 HTTP + 인증 MockMvc + 실제 DB |
|
||||||
|
| S3 회귀 | 기존 목록 응답 유지, 익명 팝업/관리자/POST·PUT·DELETE·추가 하위 경로 거부 | 실제 HTTP, 기존 event/admin 통합 테스트 |
|
||||||
|
|
||||||
|
## Decision Log
|
||||||
|
- 사용자 확정: 관리자 상세 불필요, 일반 상세만 추가; v2 경로 `/api/v2/events/{id}` 승인; 나머지 기존 정책 적용.
|
||||||
|
- 구현 선택: 기존 EventItem/엔티티/repository 재사용, v2 controller와 read-only service만 추가. 단건 로드 후 정책 확인으로 불필요한 port/DTO 계층을 만들지 않는다.
|
||||||
|
- 구현 선택: 미조회 오류는 기존 이벤트 수정/삭제와 공통 예외 관례를 따른다. 별도 HTTP 404 계약을 만들지 않는다.
|
||||||
|
- 사용자의 ulw 지시에 따라 실제 HTTP 증거, 전체 테스트/빌드와 독립 리뷰를 수행한다. 런타임/도구 제한은 숨기지 않는다.
|
||||||
|
- 후속 사용자 요청: 최종 결과에 클라이언트 개발자가 사용할 API 계약을 프롬프트 형태로 제공한다. 구현된 서버 계약만 명시하고 새로운 화면/서버 기능을 추가하지 않는다.
|
||||||
Reference in New Issue
Block a user