docs(ai-character): 관리자 API 계약 문서를 고정한다
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** `ADMIN`이 AI 캐릭터용 Member로 로그인하지 않고 `characterId` 기준으로 캐릭터, 콘텐츠, 시리즈, 커뮤니티, FanTalk 답변을 안전하게 대리 관리하는 신규 v2 관리자 API를 구현한다.
|
||||
**Goal:** `ADMIN`이 AI 캐릭터용 Member로 로그인하지 않고 `characterId` 기준으로 캐릭터, 콘텐츠, 시리즈, 커뮤니티,
|
||||
FanTalk 목록·답변을 안전하게 대리 관리하는 신규 v2 관리자 API를 구현한다.
|
||||
|
||||
**Architecture:** 신규 외부 경계는 `/api/v2/admin/ai-characters` 하위 controller/facade/application에 둔다. 공통 target resolver가 `characterId -> ChatCharacter.creatorMember`를 해석하고 `CREATOR + AI_CHARACTER` 불변식과 ownership을 먼저 검증한 뒤, 각 domain vertical slice가 기존 entity/repository/S3/CloudFront/event 컴포넌트를 테스트로 고정해 선택적으로 재사용한다.
|
||||
|
||||
@@ -13,20 +14,20 @@
|
||||
| 상태 | 구현 중 |
|
||||
| 작성일 | 2026-07-24 |
|
||||
| 요구사항 기준 | `docs/20260724_AI캐릭터_관리자_API/prd.md` |
|
||||
| API 기준 | 이 문서의 `Endpoint Contract Summary` |
|
||||
| 현재 Phase | Phase 3 6차 리뷰 완료 |
|
||||
| 현재 활성 Goal | `P4-T1` 대기 |
|
||||
| API 기준 | `docs/20260724_AI캐릭터_관리자_API/api-contract.openapi.json` |
|
||||
| 현재 Phase | Phase 2·3 레거시 계약 정합화 |
|
||||
| 현재 활성 Goal | `P23-CONTRACT-2` 대기 |
|
||||
|
||||
## 현재 상태
|
||||
|
||||
| Phase | 상태 | 기존 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 |
|
||||
|---:|---|---:|---|---|
|
||||
| 1 | 완료 | 7/7 | 완료 | 없음 |
|
||||
| 2 | 완료 | 11/11 | 완료 | 없음 |
|
||||
| 3 | 완료 | 15/15 | 완료 | 없음 |
|
||||
| 4 | 대기 | 0/6 | `P4-T1` | `P3-R5-GATE`, 사용자 진행 지시 |
|
||||
| 2 | 후속 보완 대기 | 11/11 | `P23-CONTRACT-2` | 레거시 JSON 계약과 현재 v2 구현 정합화 |
|
||||
| 3 | 후속 보완 대기 | 15/15 | `P23-CONTRACT-3` | `P23-CONTRACT-2` |
|
||||
| 4 | 대기 | 0/6 | `P4-T1` | `P23-CONTRACT-GATE` |
|
||||
| 5 | 대기 | 0/6 | `P5-T1` | `P4-GATE` |
|
||||
| 6 | 대기 | 0/4 | `P6-T1` | `P5-GATE` |
|
||||
| 6 | 대기 | 0/5 | `P6-T1` | `P5-GATE` |
|
||||
| 7 | 대기 | 0/2 | `P7-T1` | Phase 1~6 Gate 완료 |
|
||||
|
||||
- Phase는 결과와 의존성을 묶는 문서 단위다. `create_goal`에는 Task 또는 Phase Gate 하나만 등록한다.
|
||||
@@ -56,6 +57,15 @@
|
||||
415 `Accept` 표준 header를 유지한다.
|
||||
- 문서 작성 규칙: `docs/agent-guides/작업절차.md`, `docs/agent-guides/문서유지보수.md`, `docs/agent-guides/테스트스타일.md`
|
||||
- 기존 AI 캐릭터 연결 문서: `docs/20260611_AI캐릭터_크리에이터기능_최소연결/{prd.md,plan-task.md}`
|
||||
- 2026-07-28 후속 확정 정책: 신규 endpoint의 JSON request/response는 레거시 필드명·타입·optional/nullable·기본값과
|
||||
성공 `data` 형태를 그대로 유지한다. path로 이동한 ID만 body에서 제거한다. FanTalk 답변 작성만 계획의 축약 응답을
|
||||
유지하고, FanTalk 목록은 공개 v2 `CreatorChannelFanTalkTabResponse` 필드 형태의 관리자 전용 endpoint로 제공한다.
|
||||
따라서 `DEC-P2-T5-001`의 캐릭터 `isActive=false` 단독 입력 제한은 계약 차원에서 폐기하고, 레거시처럼 다른 optional
|
||||
field와 동시 입력을 허용하되 비활성화만 반영한다.
|
||||
- 기계 검증 가능한 API 계약 원본:
|
||||
`docs/20260724_AI캐릭터_관리자_API/api-contract.openapi.json`
|
||||
- 계약 근거와 예외 설명:
|
||||
`docs/20260724_AI캐릭터_관리자_API/api-contract.md`
|
||||
|
||||
## Endpoint Contract Summary
|
||||
|
||||
@@ -67,7 +77,37 @@
|
||||
|
||||
`characterId`는 target resource endpoint의 외부 대상 식별자다. 캐릭터 목록/검색은 선택된 target이 없어 `characterId`를 받지 않고, 캐릭터 생성은 새 `ChatCharacter`를 만드는 endpoint라 path `characterId`를 받지 않는다.
|
||||
|
||||
모든 목록/검색 endpoint는 `page` 기본값 0, `size` 기본값 20, 최소 20, 최대 50 보정을 적용하고 경계값 테스트를 둔다.
|
||||
목록/검색 endpoint의 query와 pagination은 `api-contract.openapi.json`에 명시한 각 레거시 API를 따른다. FanTalk 관리자
|
||||
목록만 공개 v2 query policy의 `page` 기본값 0, `size` 기본값 20, 최소 20, 최대 50 보정을 적용한다.
|
||||
|
||||
정식 endpoint, request/response schema, 타입, required/optional/nullable, 기본값과 multipart encoding은
|
||||
`api-contract.openapi.json`만 기준으로 사용한다. 사람이 읽는 레거시 근거와 생성 방법은 `api-contract.md`를 참고한다.
|
||||
|
||||
| Domain | operation | 구현 상태와 소유 Goal |
|
||||
|---|---:|---|
|
||||
| Character | 4 | runtime 정합화 필요, `P23-CONTRACT-2` |
|
||||
| AudioContent | 5 | runtime 정합화 필요, `P23-CONTRACT-3` |
|
||||
| Series | 9 | 구현 예정, Phase 4 |
|
||||
| Community | 3 | 구현 예정, Phase 5 |
|
||||
| FanTalk | 2 | 구현 예정, Phase 6 |
|
||||
|
||||
- 신규 path에 포함된 `characterId`, `contentId`, `seriesId`, `postId`, `fanTalkId`만 레거시 request body에서 제거한다.
|
||||
- 그 밖의 JSON 필드명·타입·optional/nullable·기본값과 성공 `data` 형태는 레거시 API를 유지한다.
|
||||
- 레거시 mutation의 성공 `data`는 `null`이고 오디오 콘텐츠 생성만 `CreateAudioContentResponse(contentId)`를 반환한다.
|
||||
- FanTalk 답변 작성만 승인된 축약 응답 `fanTalkId`, `replyId`, `creatorMemberId`, `content`, `createdAtUtc`를 사용한다.
|
||||
- 캐릭터 수정의 `isActive=false`는 다른 optional JSON field와 함께 받을 수 있으며 레거시 의미대로 비활성화만 반영한다.
|
||||
- multipart의 `request` part는 `application/json`이고 각 파일 part의 이름과 required 여부는 OpenAPI encoding을 따른다.
|
||||
- 공통 오류는 400/401/403/404/405/406/415/500과 `ApiResponse.error`를 사용한다. 405의 `Allow`, 415의 `Accept`,
|
||||
미지원 `Accept-Language`의 KO fallback과 Spring CORS 정책 거부 403 예외를 유지한다.
|
||||
- Character·AudioContent 9개 operation은 `P23-CONTRACT-GATE` 완료 전 production 호출 호환을 보장하지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 과거 구현 계약 이력 (비규범)
|
||||
|
||||
아래 축약 예시는 2026-07-28 레거시 계약 확정 전 Phase 2·3 구현과 계획 변경 이력을 보존하기 위한 자료다.
|
||||
클라이언트 개발, 신규 구현, 테스트의 계약으로 사용하지 않으며 위 Endpoint Contract Summary와
|
||||
`api-contract.openapi.json`이 항상 우선한다.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -652,6 +692,8 @@ Response `data`:
|
||||
|
||||
---
|
||||
|
||||
## 구현 Phase
|
||||
|
||||
### Phase 1: 공통 ADMIN 인증과 AI 캐릭터 target resolver 기반
|
||||
|
||||
#### 공통 Task 실행 규칙
|
||||
@@ -956,11 +998,14 @@ AI 캐릭터 목록/검색/상세/생성/수정/비활성화를 신규 ADMIN v2
|
||||
- 기존 `ChatCharacterService`, `ChatCharacterCreatorMemberService`, image/S3/event 관련 컴포넌트 동작을 특성화해야 한다.
|
||||
|
||||
#### API endpoint와 request/response contract
|
||||
- `GET /api/v2/admin/ai-characters?search=&page=&size=` -> `AiCharacterAdminListResponse(totalCount, items, page, size, hasNext)`
|
||||
- `GET /api/v2/admin/ai-characters/{characterId}` -> `AiCharacterAdminDetailResponse`
|
||||
- `POST /api/v2/admin/ai-characters` multipart `image?`, `request: CreateAiCharacterAdminRequest` -> detail
|
||||
- `PUT /api/v2/admin/ai-characters/{characterId}` multipart `image?`, `request: UpdateAiCharacterAdminRequest` -> detail
|
||||
- `UpdateAiCharacterAdminRequest.isActive=false`는 soft delete 의미다.
|
||||
- 정식 schema는 `api-contract.openapi.json`의 Character operation을 따른다.
|
||||
- `GET /api/v2/admin/ai-characters?searchTerm=&page=&size=` ->
|
||||
`ChatCharacterListPageResponse(totalCount, content)` 또는 동일 필드의 검색 response.
|
||||
- `GET /api/v2/admin/ai-characters/{characterId}` -> nested 필드 전체를 포함한 `ChatCharacterDetailResponse`.
|
||||
- `POST /api/v2/admin/ai-characters` multipart 필수 `image`, 필수 `request: ChatCharacterRegisterRequest` -> `data: null`.
|
||||
- `PUT /api/v2/admin/ai-characters/{characterId}` multipart optional `image`, 필수
|
||||
`request: ChatCharacterUpdateRequest`에서 `id` 제외 -> `data: null`.
|
||||
- update request의 `isActive=false`는 레거시 soft delete 의미다.
|
||||
|
||||
#### entity, repository, service 변경
|
||||
- Entity: 변경 없음.
|
||||
@@ -1397,29 +1442,21 @@ git diff --check
|
||||
#### API endpoint와 request/response contract
|
||||
- `GET /api/v2/admin/ai-characters/audio-content-themes`
|
||||
- Request: query/body 없음.
|
||||
- Response `data`:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"themeId": 11,
|
||||
"themeName": "ASMR",
|
||||
"imageUrl": "https://cdn.example.com/audio-content-theme/asmr.png"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
- 기존 크리에이터 관리자 콘텐츠 등록 화면의 콘텐츠 테마(카테고리) 조회와 같은 기능이다.
|
||||
- 기존 내부/legacy DTO의 `id`, `theme`, `image` 필드명은 frontend 계약으로 노출하지 않고, 신규 v2 DTO의 `themeId`, `themeName`, `imageUrl`만 사용한다.
|
||||
- `GET /api/v2/admin/ai-characters/{characterId}/audio-contents`
|
||||
- `GET /api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}`
|
||||
- Response: `List<GetAudioContentThemeResponse(id, theme, image)>`.
|
||||
- `GET /api/v2/admin/ai-characters/{characterId}/audio-contents?search_word=&page=&size=` ->
|
||||
`GetCreatorAdminContentListResponse` 전체 필드.
|
||||
- `GET /api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}?timezone=Asia/Seoul` ->
|
||||
`GetAudioContentDetailResponse` 전체 nested 필드.
|
||||
- `POST /api/v2/admin/ai-characters/{characterId}/audio-contents`
|
||||
- multipart `coverImage`, `audioFile`, `request` JSON string part를 사용한다.
|
||||
- `request` JSON은 `title`, `description`, `tags`, `price`, `purchaseOption`, `limited`, `isAdult`, `isActive`, `themeId`, `releaseDateUtc?`, `seriesIds`, `isGeneratePreview`, `isOnlyRental`, `isPointAvailable`, `isCommentAvailable`, `isFullDetailVisible`, `previewStartTime`, `previewEndTime`, `languageCode`를 포함한다.
|
||||
- legacy `CreateAudioContentRequest`의 `detail`은 v2 `description`, `releaseDate`는 UTC ISO-8601 `releaseDateUtc`로 받으며 facade에서 기존 pipeline 입력으로 변환한다.
|
||||
- multipart 필수 `contentFile`, `coverImage`, `request: CreateAudioContentRequest`.
|
||||
- Response: `CreateAudioContentResponse(contentId)`.
|
||||
- `PUT /api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}`
|
||||
- `audioFile` 교체는 기존 creator/admin 수정 pipeline에 없는 동작이므로 Phase 3 범위에서는 제공하지 않는다. 오디오 파일 교체가 필요하면 별도 upload/processing parity 설계 후 추가한다.
|
||||
- response item은 현 v2 목록 계약을 유지한다. response detail에는 기존 `GetAudioContentDetailResponse`의 필드 전체를 포함하고, `description`, `audioSignedUrl`, `releaseDateUtc`, `seriesIds`, `createdAtUtc`, `updatedAtUtc` 같은 v2 관리자 필드도 유지한다. 구매·좋아요·핀·추천·댓글 목록처럼 viewer 상태가 필요한 legacy 상세 필드는 관리자 상세에서 안전한 기본값을 반환한다.
|
||||
- multipart optional `coverImage`, 필수 `request: UpdateCreatorAdminContentRequest`에서 `id` 제외.
|
||||
- Response: `data: null`.
|
||||
- 수정 `audioFile` 교체는 레거시 creator admin 수정 pipeline에 없어 제공하지 않는다.
|
||||
- 정식 전체 schema와 optional/nullable은 `api-contract.openapi.json`의 AudioContent operation을 따른다. 현재 구현의
|
||||
`description`, `releaseDateUtc`, `audioSignedUrl`, `status`, `seriesIds`, `themeName`, `imageUrl` 별칭은
|
||||
`P23-CONTRACT-3`에서 레거시 계약으로 정합화한다.
|
||||
|
||||
#### entity, repository, service 변경
|
||||
- Entity: 변경 없음.
|
||||
@@ -1964,6 +2001,135 @@ git diff --check
|
||||
|
||||
---
|
||||
|
||||
### Phase 2·3 후속: 레거시 JSON 계약 정합화
|
||||
|
||||
기존 Phase 2·3 완료 이력은 보존한다. 2026-07-28 확정된 레거시 필드명·전체 payload 이관 정책에 따라 문서 계약을 먼저
|
||||
고정하고, 현재 구현된 캐릭터·오디오 콘텐츠 v2 DTO와 endpoint를 별도 후속 Goal에서 정합화한다.
|
||||
|
||||
- [x] **Task 3.16: 전체 API OpenAPI 계약 고정**
|
||||
|
||||
**Goal 실행 `P23-CONTRACT-1`:** 23개 endpoint의 request/response를 레거시 DTO 전체 필드와 직접 대조해 OpenAPI 3.1
|
||||
JSON과 설명 문서로 고정한다.
|
||||
|
||||
- **시작 조건:** 사용자 확정 정책과 시리즈 미연결 콘텐츠 검색 endpoint 분리 결정.
|
||||
- **완료 증거:** JSON 문법·OpenAPI lint/validate·TypeScript client 생성 및 `tsc --noEmit` 성공, 23개 operation과 누락 `$ref` 0건,
|
||||
`./gradlew tasks --all` 성공 및 검증 기록.
|
||||
- **범위 밖:** production DTO/controller/test 수정, legacy/public endpoint 변경.
|
||||
- **TDD 예외 사유:** 실행 코드를 변경하지 않는 계약 문서 작성 Task다.
|
||||
- **대체 검증 방법:** 레거시 Kotlin DTO의 생성자 필드와 OpenAPI schema를 대조하고 두 validator와 client generator로
|
||||
기계 검증한다.
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `docs/20260724_AI캐릭터_관리자_API/prd.md`
|
||||
- Modify: `docs/20260724_AI캐릭터_관리자_API/plan-task.md`
|
||||
- Create: `docs/20260724_AI캐릭터_관리자_API/api-contract.openapi.json`
|
||||
- Create: `docs/20260724_AI캐릭터_관리자_API/api-contract.md`
|
||||
|
||||
- [x] 캐릭터, 테마·오디오 콘텐츠, 시리즈·연결 콘텐츠 검색, 커뮤니티, FanTalk의 레거시 DTO 전체 필드를 schema로
|
||||
옮긴다.
|
||||
- [x] path로 이동한 ID만 body에서 제거하고 나머지 query/body/response 필드명은 레거시와 동일하게 유지한다.
|
||||
- [x] 레거시 mutation의 `data: null`, 오디오 생성의 `data.contentId`, FanTalk 답변 축약 응답 예외를 operation별로
|
||||
고정한다.
|
||||
- [x] FanTalk 관리자 목록과 시리즈 미연결 콘텐츠 검색을 별도 operation으로 포함해 총 23개 endpoint를 검증한다.
|
||||
- [x] OpenAPI lint/validate, TypeScript Fetch client 생성과 `tsc --noEmit`을 실행하고 결과를 기록한다.
|
||||
|
||||
- [ ] **Task 3.17: Phase 2 캐릭터 runtime 계약 정합화**
|
||||
|
||||
**Goal 실행 `P23-CONTRACT-2`:** 현재 구현된 캐릭터 4개 endpoint를
|
||||
`api-contract.openapi.json`의 레거시 필드명·전체 request/response·mutation 응답에 맞춘다.
|
||||
|
||||
- **시작 조건:** `P23-CONTRACT-1` 완료.
|
||||
- **완료 증거:** 4개 actual endpoint의 exact JSON field/required/nullable/multipart/`data: null` RED/GREEN,
|
||||
Phase 2 focused·legacy 회귀와 Progress 기록.
|
||||
- **범위 안:** `isActive=false`와 다른 optional JSON field의 동시 입력 허용 및 나머지 JSON field 미반영이라는 레거시
|
||||
request 의미 복구.
|
||||
- **범위 밖:** 외부 API·ownership·soft delete persistence 결과 변경, 신규 character business behavior.
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterController.kt`
|
||||
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterDto.kt`
|
||||
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterFacade.kt`
|
||||
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterMapper.kt`
|
||||
- Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterControllerTest.kt`
|
||||
- Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/AiCharacterAdminCharacterControllerMutationTest.kt`
|
||||
- Confirm: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/character/LegacyChatCharacterAdminCharacterizationTest.kt`
|
||||
|
||||
- [ ] **RED:** 목록 `content`, 상세 전체 nested 필드, 생성 전체 request와 필수 `image`, 수정 optional 필드,
|
||||
`isActive=false`와 다른 optional JSON field의 동시 입력·미반영, 생성·수정 `data: null` exact JSON 테스트를 작성해
|
||||
현재 v2 축약/변환 DTO와의 불일치를 확인한다.
|
||||
- [ ] **GREEN:** business pipeline을 바꾸지 않는 최소 DTO/controller/facade/mapper 변경으로 OpenAPI 계약을 통과시킨다.
|
||||
- [ ] **REFACTOR:** Phase 2 actual endpoint와 legacy characterization, 공통 오류 계약을 회귀하고 결과를 기록한다.
|
||||
|
||||
```bash
|
||||
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.*' \
|
||||
--tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminAuthorizationTest \
|
||||
--tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest
|
||||
./gradlew ktlintCheck
|
||||
```
|
||||
|
||||
- [ ] **Task 3.18: Phase 3 오디오 콘텐츠 runtime 계약 정합화**
|
||||
|
||||
**Goal 실행 `P23-CONTRACT-3`:** 현재 구현된 테마·오디오 콘텐츠 5개 endpoint를
|
||||
`api-contract.openapi.json`의 레거시 필드명·전체 request/response·성공 응답에 맞춘다.
|
||||
|
||||
- **시작 조건:** `P23-CONTRACT-2` 완료.
|
||||
- **완료 증거:** 5개 actual endpoint의 exact query/multipart/JSON schema RED/GREEN, Phase 3 focused·legacy 회귀와
|
||||
Progress 기록.
|
||||
- **범위 밖:** upload/processing pipeline, signed URL 정책, series 연결 behavior 변경.
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentController.kt`
|
||||
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentDto.kt`
|
||||
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentFacade.kt`
|
||||
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentMapper.kt`
|
||||
- Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentControllerTest.kt`
|
||||
- Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentCreateTest.kt`
|
||||
- Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentQueryTest.kt`
|
||||
- Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentThemeControllerTest.kt`
|
||||
- Modify: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentUpdateTest.kt`
|
||||
- Confirm: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/AiCharacterAdminAudioContentOwnershipTest.kt`
|
||||
- Confirm: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/content/LegacyCreatorAdminAudioContentCharacterizationTest.kt`
|
||||
|
||||
- [ ] **RED:** 테마 `id/theme/image`, 목록 전체 item, 상세 전체 nested DTO, 생성 `contentFile`과
|
||||
`CreateAudioContentRequest`, 수정 `UpdateCreatorAdminContentRequest` 및 각 성공 `data` 형태를 exact JSON으로 고정한다.
|
||||
- [ ] **GREEN:** 기존 business pipeline과 signed URL 계산을 유지하는 최소 DTO/controller/facade/mapper adapter를 구현한다.
|
||||
- [ ] **REFACTOR:** Phase 3 actual endpoint·legacy characterization·ownership/error 계약을 회귀하고 결과를 기록한다.
|
||||
|
||||
```bash
|
||||
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.*' \
|
||||
--tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminAuthorizationTest \
|
||||
--tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest
|
||||
./gradlew ktlintCheck
|
||||
```
|
||||
|
||||
#### Phase 2·3 계약 정합화 Gate
|
||||
|
||||
**Goal 실행 `P23-CONTRACT-GATE`:** 문서 계약과 구현된 9개 endpoint의 runtime 응답이 일치하고 Phase 4가 같은 계약을
|
||||
소비할 수 있는지 판정한다.
|
||||
|
||||
- [ ] **`P23-CONTRACT-GATE` 완료:** `P23-CONTRACT-1`~`P23-CONTRACT-3` 완료 후 character/content focused·legacy
|
||||
회귀, OpenAPI validate/client 생성과 `ktlintCheck`를 fresh 실행한다.
|
||||
- **범위 밖:** Gate에서 직접 production code 수정, Phase 4 이후 기능 구현.
|
||||
|
||||
```bash
|
||||
./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.*' \
|
||||
--tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.*' \
|
||||
--tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminAuthorizationTest \
|
||||
--tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.AiCharacterAdminErrorContractTest
|
||||
./gradlew ktlintCheck
|
||||
npx --yes @openapitools/openapi-generator-cli validate \
|
||||
-i docs/20260724_AI캐릭터_관리자_API/api-contract.openapi.json
|
||||
npx --yes @openapitools/openapi-generator-cli generate \
|
||||
-i docs/20260724_AI캐릭터_관리자_API/api-contract.openapi.json \
|
||||
-g typescript-fetch \
|
||||
-o /tmp/ai-character-admin-typescript-client
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: 시리즈 관리 vertical slice
|
||||
|
||||
#### 목표
|
||||
@@ -1979,11 +2145,15 @@ git diff --check
|
||||
- 기존 creator series 생성/수정/soft delete, 콘텐츠 연결/해제, 조회/검색, 순서 변경 behavior를 통과하는 특성화 테스트로 먼저 고정해야 한다.
|
||||
|
||||
#### API endpoint와 request/response contract
|
||||
- 정식 전체 schema는 `api-contract.openapi.json`의 Series operation 9개를 따른다.
|
||||
- `GET/POST/PUT /api/v2/admin/ai-characters/{characterId}/series...`
|
||||
- `GET /series/{seriesId}/contents` query: `search?`, `page`, `size`
|
||||
- `POST /series/{seriesId}/contents` request: `AddAiCharacterAdminSeriesContentsRequest(contentIds: List<Long>)`
|
||||
- `GET /series/{seriesId}/contents` query: `page`, `size`; response:
|
||||
`GetCreatorAdminContentSeriesContentResponse(totalCount, items)`
|
||||
- `GET /series/{seriesId}/contents/search` query: 필수 `search_word`; response:
|
||||
`List<SearchContentNotInSeriesResponse>`
|
||||
- `POST /series/{seriesId}/contents` request: `AddingContentToTheSeriesRequest(contentIdList: List<Long>)`
|
||||
- `DELETE /series/{seriesId}/contents/{contentId}`
|
||||
- `PUT /series/orders` request: `UpdateAiCharacterAdminSeriesOrdersRequest(seriesIds: List<Long>)`
|
||||
- `PUT /series/orders` request: `UpdateOrdersRequest(ids: List<Long>)`
|
||||
|
||||
#### entity, repository, service 변경
|
||||
- Entity: 변경 없음.
|
||||
@@ -2028,7 +2198,7 @@ git diff --check
|
||||
|
||||
**Goal 실행 `P4-T1`:** 기존 creator series의 CRUD·연결·검색·순서 동작을 신규 v2 구현의 비교 기준으로 고정한다.
|
||||
|
||||
- **시작 조건:** 최신 Phase 3 후속 Gate인 `P3-R5-GATE` 완료와 사용자 진행 지시.
|
||||
- **시작 조건:** Phase 2·3 runtime 계약 정합화의 `P23-CONTRACT-GATE` 완료와 사용자 진행 지시.
|
||||
- **완료 증거:** production 변경 전 특성화 테스트 통과, 관찰된 오류·side-effect 정책과 Progress 기록.
|
||||
- **범위 밖:** 신규 v2 series production code 구현.
|
||||
|
||||
@@ -2044,10 +2214,10 @@ git diff --check
|
||||
|
||||
- [ ] **Task 4.2: 시리즈 목록·상세 조회 구현**
|
||||
|
||||
**Goal 실행 `P4-T2`:** target owner의 활성 시리즈 목록·상세를 pagination 계약으로 제공한다.
|
||||
**Goal 실행 `P4-T2`:** target owner의 활성 시리즈 목록·상세를 레거시 response 계약으로 제공한다.
|
||||
|
||||
- **시작 조건:** `P4-T1` 완료와 Phase 4 오류 계약의 계획 반영.
|
||||
- **완료 증거:** 목록·상세·inactive·cross-owner·pagination RED/GREEN과 Progress 기록.
|
||||
- **완료 증거:** 목록·상세 전체 필드, inactive·cross-owner·pagination RED/GREEN과 Progress 기록.
|
||||
- **범위 밖:** 시리즈 mutation, 콘텐츠 연결, 순서 변경.
|
||||
|
||||
**Files:**
|
||||
@@ -2060,7 +2230,8 @@ git diff --check
|
||||
|
||||
- [ ] 미구현 목록·상세, inactive 제외, cross-owner 거부와 pagination 경계 실패 test를 작성한다.
|
||||
- [ ] owner-scoped 목록·상세 최소 구현으로 focused test를 통과시킨다.
|
||||
- [ ] `page` 기본 0, `size` 기본·최소 20·최대 50과 legacy DTO 비노출을 검증한다.
|
||||
- [ ] 목록 `totalCount/items`와 item 전체 필드, 상세의 문자열 `publishedDaysOfWeek/genre/keywords/state`를
|
||||
`api-contract.openapi.json`과 exact JSON으로 검증한다.
|
||||
- [ ] focused test와 `ktlintCheck` 결과를 Progress에 기록한다.
|
||||
|
||||
- [ ] **Task 4.3: 시리즈 생성·수정·soft delete 구현**
|
||||
@@ -2088,7 +2259,8 @@ git diff --check
|
||||
**Goal 실행 `P4-T4`:** 동일 owner의 시리즈와 콘텐츠만 검색·연결·해제할 수 있도록 한다.
|
||||
|
||||
- **시작 조건:** `P4-T2`, `P4-T3`와 Phase 3 owner query 계약 완료.
|
||||
- **완료 증거:** 검색·pagination·전체 ID 사전 검증·원자적 연결/해제 RED/GREEN과 Progress 기록.
|
||||
- **완료 증거:** 연결 목록·미연결 검색의 분리된 응답, pagination·전체 ID 사전 검증·원자적 연결/해제 RED/GREEN과
|
||||
Progress 기록.
|
||||
- **범위 밖:** 콘텐츠 자체 수정, 시리즈 순서 변경.
|
||||
|
||||
**Files:**
|
||||
@@ -2098,9 +2270,10 @@ git diff --check
|
||||
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesRepository.kt`
|
||||
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesContentTest.kt`
|
||||
|
||||
- [ ] 콘텐츠 조회·검색·연결·해제와 cross-owner ID 실패 test를 작성한다.
|
||||
- [ ] `GET .../contents`와 `GET .../contents/search?search_word=...`의 별도 응답, 연결·해제와 cross-owner ID 실패
|
||||
test를 작성한다.
|
||||
- [ ] 모든 series/content ID를 mutation 전에 검증하는 최소 구현을 통과시킨다.
|
||||
- [ ] 일부 연결 성공이 남지 않는 원자성과 pagination 경계를 검증한다.
|
||||
- [ ] `contentIdList` 필드와 mutation `data: null`, 일부 연결 성공이 남지 않는 원자성과 pagination 경계를 검증한다.
|
||||
- [ ] focused/Phase 3 owner query 회귀와 `ktlintCheck` 결과를 Progress에 기록한다.
|
||||
|
||||
- [ ] **Task 4.5: owner-scoped 시리즈 순서 변경 구현**
|
||||
@@ -2168,10 +2341,14 @@ git diff --check
|
||||
- 기존 community write behavior 특성화 테스트.
|
||||
|
||||
#### API endpoint와 request/response contract
|
||||
- `GET /api/v2/admin/ai-characters/{characterId}/community-posts`
|
||||
- `POST /api/v2/admin/ai-characters/{characterId}/community-posts`
|
||||
- `PUT /api/v2/admin/ai-characters/{characterId}/community-posts/{postId}`
|
||||
- update request는 `isFixed`, `isActive`, 본문/이미지/오디오/가격 필드를 포함한다.
|
||||
- 정식 전체 schema는 `api-contract.openapi.json`의 Community operation 3개를 따른다.
|
||||
- `GET /api/v2/admin/ai-characters/{characterId}/community-posts?timezone=&page=&size=` ->
|
||||
`List<GetCommunityPostListResponse>`.
|
||||
- `POST /api/v2/admin/ai-characters/{characterId}/community-posts`는 optional `audioFile`, optional `postImage`, 필수
|
||||
`request: CreateCommunityPostRequest`를 받고 `data: null`을 반환한다.
|
||||
- `PUT /api/v2/admin/ai-characters/{characterId}/community-posts/{postId}`는 optional `postImage`, 필수 request를 받는다.
|
||||
- update request는 두 레거시 update DTO에서 ID를 제외한 `content`, `isCommentAvailable`, `isAdult`, `isActive`,
|
||||
`isFixed`만 포함하고 `data: null`을 반환한다. 수정 `audioFile`, `price`는 레거시 계약에 없어 포함하지 않는다.
|
||||
|
||||
#### entity, repository, service 변경
|
||||
- Entity: 변경 없음.
|
||||
@@ -2340,13 +2517,15 @@ git diff --check
|
||||
|
||||
---
|
||||
|
||||
### Phase 6: FanTalk 답변 vertical slice
|
||||
### Phase 6: FanTalk 목록·답변 vertical slice
|
||||
|
||||
#### 목표
|
||||
선택한 AI 캐릭터가 자신의 활성 root FanTalk에만 creator reply를 작성하는 v2 관리자 API를 제공한다.
|
||||
선택한 AI 캐릭터의 FanTalk 목록을 관리자 전용 경계에서 조회하고 자신의 활성 root FanTalk에만 creator reply를 작성하는
|
||||
v2 관리자 API를 제공한다.
|
||||
|
||||
#### 범위와 비범위
|
||||
- 포함: root FanTalk 존재/활성/owner 검증, creator reply 저장, 언어 감지와 기존 응답 의미 parity.
|
||||
- 포함: 관리자용 root FanTalk/creator reply 목록, root FanTalk 존재/활성/owner 검증, creator reply 저장, 언어 감지와
|
||||
기존 응답 의미 parity.
|
||||
- 제외: FanTalk 원글 작성, nested reply, 구매/댓글형 기능, AI 캐릭터 일반 사용자 활동.
|
||||
|
||||
#### 선행 Phase 및 의존성
|
||||
@@ -2354,14 +2533,19 @@ git diff --check
|
||||
- 기존 FanTalk 저장 엔티티와 응답 DTO 의미 특성화.
|
||||
|
||||
#### API endpoint와 request/response contract
|
||||
- 정식 전체 schema는 `api-contract.openapi.json`의 FanTalk operation 2개를 따른다.
|
||||
- `GET /api/v2/admin/ai-characters/{characterId}/fan-talks?page=0&size=20`
|
||||
- Response: 공개 v2 `CreatorChannelFanTalkTabResponse`와 동일한
|
||||
`fanTalkCount`, `fanTalks`, `page`, `size`, `hasNext` 및 nested root/reply 필드
|
||||
- `POST /api/v2/admin/ai-characters/{characterId}/fan-talks/{fanTalkId}/replies`
|
||||
- Request: `CreateAiCharacterAdminFanTalkReplyRequest(content: String)`
|
||||
- Response: `AiCharacterAdminFanTalkReplyResponse(fanTalkId, replyId, creatorMemberId, content, createdAtUtc)`
|
||||
|
||||
#### entity, repository, service 변경
|
||||
- Entity: 변경 없음.
|
||||
- Repository: `CreatorCheers` 또는 FanTalk repository에 root/active/creator owner 조회 adapter 추가 가능.
|
||||
- Service: 신규 FanTalk reply application service 구현. target 검증 후 기존 저장/언어 감지 로직을 필요한 만큼 재사용한다.
|
||||
- Repository: 관리자 목록용 owner-scoped root/reply 조회와 root/active/creator owner 검증 adapter를 추가한다.
|
||||
- Service: 관리자 목록은 공개 v2 DTO 형태로 조립하되 viewer/block 필터를 적용하지 않는다. reply는 target 검증 후 기존
|
||||
저장/언어 감지 로직을 필요한 만큼 재사용한다.
|
||||
|
||||
#### DB migration
|
||||
- 없음.
|
||||
@@ -2376,23 +2560,24 @@ git diff --check
|
||||
|
||||
#### acceptance criteria
|
||||
- target AI character는 자신의 활성 root FanTalk에만 답변할 수 있다.
|
||||
- 관리자는 공개 v2와 동일한 필드 형태로 target AI character의 root FanTalk와 creator reply를 조회할 수 있다.
|
||||
- cross-character, nested parent, inactive/missing FanTalk는 4xx이며 reply 저장과 이벤트 발행이 없다.
|
||||
- 저장된 답변의 writer/creator는 해석된 creatorMember와 일관된다.
|
||||
|
||||
#### targeted test
|
||||
- Characterization: `LegacyFanTalkReplyCharacterizationTest`.
|
||||
- V2 RED/GREEN: `AiCharacterAdminFanTalkReplyServiceTest`.
|
||||
- V2 RED/GREEN: `AiCharacterAdminFanTalkQueryTest`, `AiCharacterAdminFanTalkReplyServiceTest`.
|
||||
- Run: `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.fantalk.*'`
|
||||
|
||||
#### 전체 회귀 테스트 영향
|
||||
- 기존 FanTalk 조회/작성 관련 테스트가 통과해야 한다.
|
||||
|
||||
#### rollback 전략
|
||||
- 신규 FanTalk reply v2 admin route/facade를 제거한다.
|
||||
- 신규 FanTalk 목록·reply v2 admin route/facade를 제거한다.
|
||||
- 신규 DDL이 없으므로 schema rollback은 없다.
|
||||
|
||||
#### 권장 commit 경계
|
||||
- `feat: add ai character admin fan talk reply slice`
|
||||
- `feat: add ai character admin fan talk slice`
|
||||
|
||||
- [ ] **Task 6.1: 기존 FanTalk reply 의미 특성화 baseline 고정**
|
||||
|
||||
@@ -2412,11 +2597,32 @@ git diff --check
|
||||
- [ ] production code 변경 없이 기존 구현 대상 특성화 테스트 통과를 확인한다.
|
||||
- [ ] fixture만 정리하고 test와 `ktlintCheck` 결과를 Progress에 기록한다.
|
||||
|
||||
- [ ] **Task 6.2: FanTalk root reply 저장 구현**
|
||||
- [ ] **Task 6.2: FanTalk 관리자 목록 조회 구현**
|
||||
|
||||
**Goal 실행 `P6-T2`:** 선택한 AI 캐릭터의 활성 root FanTalk에 creator reply를 저장하고 전용 응답을 반환한다.
|
||||
**Goal 실행 `P6-T2`:** 선택한 AI 캐릭터의 root FanTalk와 creator reply를 공개 v2 응답 필드 형태로 조회한다.
|
||||
|
||||
- **시작 조건:** `P6-T1` 완료와 Phase 6 오류 계약의 계획 반영.
|
||||
- **완료 증거:** owner-scoped 목록·nested reply·pagination exact JSON RED/GREEN과 Progress 기록.
|
||||
- **범위 밖:** 공개 v2 endpoint 변경, viewer/block 필터 재사용, FanTalk 원글 작성.
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkController.kt`
|
||||
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkDto.kt`
|
||||
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkFacade.kt`
|
||||
- Create: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkRepository.kt`
|
||||
- Test: `src/test/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/fantalk/AiCharacterAdminFanTalkQueryTest.kt`
|
||||
|
||||
- [ ] **RED:** `fanTalkCount/fanTalks/page/size/hasNext`와 root/reply 전체 필드, target owner, pagination 실패
|
||||
test를 작성한다.
|
||||
- [ ] **GREEN:** 공개 v2 DTO 필드 형태를 유지하면서 관리자 target 정책으로 조회하는 최소 구현을 통과시킨다.
|
||||
- [ ] **REFACTOR:** 공개 v2 endpoint 계약 불변, viewer/block 필터 비적용과 ADMIN 인가를 회귀하고 기록한다.
|
||||
|
||||
- [ ] **Task 6.3: FanTalk root reply 저장 구현**
|
||||
|
||||
**Goal 실행 `P6-T3`:** 선택한 AI 캐릭터의 활성 root FanTalk에 creator reply를 저장하고 전용 응답을 반환한다.
|
||||
|
||||
- **시작 조건:** `P6-T1`, `P6-T2` 완료와 Phase 6 오류 계약의 계획 반영.
|
||||
- **완료 증거:** 정상 저장·언어 감지·DTO·writer/creator RED/GREEN과 Progress 기록.
|
||||
- **범위 밖:** FanTalk 원글, nested reply, 일반 사용자 대리 작성.
|
||||
|
||||
@@ -2433,11 +2639,11 @@ git diff --check
|
||||
- [ ] writer/creator가 resolver의 creatorMember와 일치하고 principal impersonation이 없음을 검증한다.
|
||||
- [ ] focused/legacy characterization test와 `ktlintCheck` 결과를 Progress에 기록한다.
|
||||
|
||||
- [ ] **Task 6.3: FanTalk target·root·ownership 거부 구현**
|
||||
- [ ] **Task 6.4: FanTalk target·root·ownership 거부 구현**
|
||||
|
||||
**Goal 실행 `P6-T3`:** cross-character, nested, inactive, missing FanTalk를 저장·이벤트 없이 거부한다.
|
||||
**Goal 실행 `P6-T4`:** cross-character, nested, inactive, missing FanTalk를 저장·이벤트 없이 거부한다.
|
||||
|
||||
- **시작 조건:** `P6-T1`, `P6-T2` 완료.
|
||||
- **시작 조건:** `P6-T1`~`P6-T3` 완료.
|
||||
- **완료 증거:** 네 거부 분기의 정확한 오류 계약, DB/event 0건과 Progress 기록.
|
||||
- **범위 밖:** 새로운 중복 답변 차단 정책.
|
||||
|
||||
@@ -2452,11 +2658,11 @@ git diff --check
|
||||
- [ ] 각 실패의 정확한 status/message key/KO·EN·JA와 reply insert/event 0건을 검증한다.
|
||||
- [ ] focused test와 `ktlintCheck` 결과를 Progress에 기록한다.
|
||||
|
||||
- [ ] **Task 6.4: Phase 6 보안·오류·회귀 검증**
|
||||
- [ ] **Task 6.5: Phase 6 보안·오류·회귀 검증**
|
||||
|
||||
**Goal 실행 `P6-T4`:** FanTalk reply endpoint의 ADMIN·오류 계약과 기존 FanTalk 회귀를 고정한다.
|
||||
**Goal 실행 `P6-T5`:** FanTalk 목록·reply endpoint의 ADMIN·오류 계약과 기존 FanTalk 회귀를 고정한다.
|
||||
|
||||
- **시작 조건:** `P6-T2`, `P6-T3` 완료.
|
||||
- **시작 조건:** `P6-T2`~`P6-T4` 완료.
|
||||
- **완료 증거:** 권한 매트릭스, request binding/domain 오류, legacy 회귀와 Progress 기록.
|
||||
- **범위 밖:** Phase 7 외 전체 기능 수정.
|
||||
|
||||
@@ -2472,11 +2678,11 @@ git diff --check
|
||||
|
||||
#### Phase 6 Gate
|
||||
|
||||
**Goal 실행 `P6-GATE`:** Phase 6 FanTalk reply의 root·ownership·저장·회귀 품질을 최종 판정한다.
|
||||
**Goal 실행 `P6-GATE`:** Phase 6 FanTalk 목록·reply의 조회·root·ownership·저장·회귀 품질을 최종 판정한다.
|
||||
|
||||
- [ ] **`P6-GATE` 완료:** 시작 조건과 완료 증거를 모두 충족하고 현재 상태표와 Progress를 갱신한다.
|
||||
|
||||
- **시작 조건:** `P6-T1`~`P6-T4` 완료.
|
||||
- **시작 조건:** `P6-T1`~`P6-T5` 완료.
|
||||
- **완료 증거:** `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.fantalk.*'`와
|
||||
`./gradlew ktlintCheck` 성공, Progress 기록.
|
||||
- **범위 밖:** 실패와 무관한 신규 기능.
|
||||
@@ -2496,7 +2702,7 @@ git diff --check
|
||||
- Phase 1~6 완료.
|
||||
|
||||
#### API endpoint와 request/response contract
|
||||
- Endpoint Contract Summary의 모든 endpoint가 구현되어야 한다.
|
||||
- `api-contract.openapi.json`의 23개 operation이 모두 구현되어야 한다.
|
||||
- 모든 신규 endpoint가 JWT ADMIN + 현재 DB ADMIN 이중 인가와 공통 오류 envelope/i18n을 공유해야 한다.
|
||||
- legacy/public endpoint URI와 성공·오류 status/body/message diff가 없어야 한다.
|
||||
|
||||
@@ -2614,10 +2820,11 @@ git diff --check
|
||||
| 12 | `P3-R5` → `P3-R6` → `P3-R4-GATE` | `P2-R4-GATE`, 5차 Phase 3 review | 아니요 | `REV-016`~`REV-017` 소유 Task에서 수정·증거 보강 |
|
||||
| 13 | `P2-R5` → `P2-R5-GATE` | 6차 Phase 2 review | 아니요 | `REV-018` 문서 계약 동기화 |
|
||||
| 14 | `P3-R7` → `P3-R8` → `P3-R5-GATE` | `P2-R5-GATE`, 6차 Phase 3 review | 아니요 | `REV-019`~`REV-020` 소유 Task에서 수정·증거 보강 |
|
||||
| 15 | `P4-T1`~`P4-T6` → `P4-GATE` | `P3-R5-GATE`, 사용자 진행 지시 | 아니요 | 실패 소유 Task로 되돌림 |
|
||||
| 16 | `P5-T1`~`P5-T6` → `P5-GATE` | `P4-GATE` | 아니요 | 실패 소유 Task로 되돌림 |
|
||||
| 17 | `P6-T1`~`P6-T4` → `P6-GATE` | `P5-GATE` | 아니요 | 실패 소유 Task로 되돌림 |
|
||||
| 18 | `P7-T1` → `P7-T2` → `P7-GATE` | Phase 1~6 최신 Gate | 아니요 | 실패 소유 Phase에 회귀 수정 Goal 추가 |
|
||||
| 15 | `P23-CONTRACT-1` → `P23-CONTRACT-2` → `P23-CONTRACT-3` → `P23-CONTRACT-GATE` | `P3-R5-GATE`, 사용자 계약 확정 | 아니요 | 문서 또는 runtime 불일치 소유 Goal에서 수정 |
|
||||
| 16 | `P4-T1`~`P4-T6` → `P4-GATE` | `P23-CONTRACT-GATE` | 아니요 | 실패 소유 Task로 되돌림 |
|
||||
| 17 | `P5-T1`~`P5-T6` → `P5-GATE` | `P4-GATE` | 아니요 | 실패 소유 Task로 되돌림 |
|
||||
| 18 | `P6-T1`~`P6-T5` → `P6-GATE` | `P5-GATE` | 아니요 | 실패 소유 Task로 되돌림 |
|
||||
| 19 | `P7-T1` → `P7-T2` → `P7-GATE` | Phase 1~6 최신 Gate | 아니요 | 실패 소유 Phase에 회귀 수정 Goal 추가 |
|
||||
|
||||
## 변경 금지·중단 규칙
|
||||
|
||||
@@ -2842,6 +3049,38 @@ git diff --check
|
||||
불일치를 확인해 `처리 완료`로 동기화했다.
|
||||
- 남은 항목: 없음. 다음 Goal은 `P4-T1`이지만 사용자 진행 지시 전까지 시작하지 않는다.
|
||||
|
||||
### `P23-CONTRACT-1` 완료 — 2026-07-28
|
||||
|
||||
- 상태: 완료
|
||||
- 무엇을: 캐릭터, 테마·오디오 콘텐츠, 시리즈, 커뮤니티, FanTalk의 신규 관리자 API 23개 request/response를
|
||||
OpenAPI 3.1 JSON과 설명 문서로 고정했다.
|
||||
- 왜: 신규 path와 관리자 target만 적용하고 클라이언트 JSON 계약은 레거시 필드명·타입·optional/nullable·기본값과 성공
|
||||
`data` 형태를 그대로 이관해야 하기 때문이다.
|
||||
- 어떻게: 레거시 Kotlin DTO·controller·service와 schema를 대조하고 path ID만 body에서 제거했다. FanTalk 답변 축약 응답,
|
||||
관리자 FanTalk 목록, 분리된 시리즈 미연결 콘텐츠 검색만 확정 예외로 반영했다. 독립 리뷰에서 확인한
|
||||
`Accept-Language` fallback, 캐릭터 비활성화 혼합 입력, 시리즈 상세 문자열 `state`, Phase 4 선행 Gate 문제를 교정했다.
|
||||
- 결과: JSON parse, 내부 `$ref` 누락 0건, 고유 operation 23개와 상태 9/14를 확인했다. Redocly lint와 OpenAPI Generator
|
||||
validate가 통과했고 `typescript-fetch` 생성 후 TypeScript 5.9.3 `tsc --noEmit`도 성공했다.
|
||||
`./gradlew tasks --all`은 `BUILD SUCCESSFUL in 898ms`였으며 독립 최종 리뷰는 Critical 0, Important 0이었다.
|
||||
- 남은 항목: 현재 구현된 캐릭터 4개 endpoint의 runtime DTO를 맞추는 `P23-CONTRACT-2`. production code는 변경하지 않았다.
|
||||
|
||||
### `P23-CONTRACT-2`~`P7-GATE` 실행 계획 보완 — 2026-07-28
|
||||
|
||||
- 상태: 구현 시작 준비 완료
|
||||
- 무엇을: `P23-CONTRACT-2`·`P23-CONTRACT-3`의 실제 facade와 전용 query/theme/ownership/legacy test 범위를 보강하고,
|
||||
Task별 focused 명령을 추가했다. Phase 4~6은 각 OpenAPI tag를 정식 schema로, Phase 7은 OpenAPI 23개 operation을
|
||||
최종 구현 기준으로 명시했다.
|
||||
- 왜: 확정 계약이 있어도 실제 request parsing·response 조립을 담당하는 facade와 전용 테스트가 계획에서 누락되면
|
||||
구현 중 범위가 다시 흔들릴 수 있기 때문이다.
|
||||
- 어떻게: OpenAPI operation 23개와 Character 4, AudioContent 5, Series 9, Community 3, FanTalk 2 분류를 Phase/Goal과
|
||||
대조하고 기존 source/test 파일 존재, Goal ID 중복, 선행 Gate와 PRD Open Questions를 확인했다.
|
||||
- 결과: 과거 축약 예시는 비규범 이력으로 분리했고 현재 Endpoint Contract Summary는 OpenAPI와 같은 9/14 상태 및 예외만
|
||||
제공한다. Task 3.17부터 `P7-GATE`까지 시작 조건·파일·RED/GREEN/REFACTOR·검증 명령이 연결됐으며
|
||||
`./gradlew tasks --all`은 `BUILD SUCCESSFUL in 922ms`였다.
|
||||
- 제외: 생성 TypeScript의 `tsc` 실행은 클라이언트 build/CI 책임으로 두고 server runtime `P23-CONTRACT-GATE`에는 추가하지
|
||||
않았다. `P23-CONTRACT-1`의 일회성 생성·컴파일 검증 기록은 유지한다.
|
||||
- 다음 행동: production 변경 없이 문서 보완만 완료했다. 구현 시작 Goal은 `P23-CONTRACT-2`다.
|
||||
|
||||
## Decision Log
|
||||
|
||||
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 |
|
||||
@@ -2856,6 +3095,10 @@ git diff --check
|
||||
| 2026-07-28 | `DEC-REVIEW-005` | 확정 | 5차 보완과 완료 이력은 보존하고, 추가로 확인한 문서 계약·empty-file 경계·detached publisher 증거 문제를 `REV-018`~`REV-020`과 새 후속 Task/Gate로 추적한다. | fresh focused 216건과 lint는 통과했지만 Endpoint Contract Summary, `MultipartFile.isEmpty` 처리와 실제 service publisher field를 코드·test 단위로 대조해 세 문제가 재현됐다. | `P2-R5`~`P3-R5-GATE`, 두 6차 review |
|
||||
| 2026-07-28 | `DEC-P3-R7-001` | 확정 | 생성의 빈 `coverImage`·`audioFile`은 400 `common.error.invalid_request`로 거부한다. 수정의 빈 `coverImage`는 생략으로 정규화하고, 수정 `audioFile`은 미지원이므로 part가 존재하면 크기와 관계없이 400으로 거부한다. | non-null binding은 part 누락만 차단하며 빈 파일은 0-byte upload와 cover 교체를 유발할 수 있다. optional cover의 빈 part는 일반 multipart client의 생략 표현으로 안전하게 처리할 수 있지만 미지원 audio part는 존재 자체가 계약 위반이다. | `P3-R7`, `REV-019`, Endpoint Contract Summary |
|
||||
| 2026-07-28 | `DEC-REVIEW-006` | 확정 | Phase 2·3 6차 보완의 코드와 완료 이력은 유지하고 새 production Goal은 추가하지 않는다. 종결 Gate가 있는 `REV-001`~`REV-009`의 종합 표 상태만 `처리 완료`로 동기화한다. | fresh focused 130건이 모두 통과했고 6차 보완 범위의 추가 production 결함은 재현되지 않았지만, 하단 종합 표 상태가 각 Gate·Progress의 처리 완료 판정과 모순됐다. | `P2-GATE`, `P2-R2-GATE`, `P3-GATE`, 하단 발견된 문제 표 |
|
||||
| 2026-07-28 | `DEC-API-CONTRACT-001` | 확정 | 신규 관리자 endpoint는 레거시 request/response의 필드명·타입·optional/nullable·기본값·성공 `data` 형태를 유지하고 path로 이동한 ID만 body에서 제거한다. FanTalk 답변은 축약 응답을 유지한다. | 신규 endpoint의 목적이 로그인 불가능한 AI 캐릭터를 관리자 경계로 대리 관리하는 것이며, 클라이언트 계약까지 임의로 재설계하는 범위가 아니기 때문이다. | PRD, `api-contract.openapi.json`, `P23-CONTRACT-1`~`P23-CONTRACT-GATE` |
|
||||
| 2026-07-28 | `DEC-API-CONTRACT-002` | 확정 | FanTalk 목록은 공개 v2 field 형태를 유지하는 관리자 전용 endpoint로 추가하고, 시리즈 연결 목록과 미연결 검색은 응답 형태가 달라 별도 endpoint로 분리한다. | 공개 v2 직접 호출은 `creatorId`, viewer/block filter와 CORS 경계가 관리자 요구와 다르고, 시리즈 두 legacy API의 응답은 wrapper와 direct array로 서로 다르다. | Phase 4, Phase 6, `api-contract.openapi.json` |
|
||||
| 2026-07-28 | `DEC-API-CONTRACT-003` | 확정 | `DEC-P2-T5-001`의 캐릭터 `isActive=false` 단독 입력 제한을 최신 JSON 계약에서 폐기한다. `ChatCharacterUpdateRequest`처럼 다른 optional field와 동시 입력을 허용하고, `isActive=false`이면 레거시 service와 같이 비활성화만 반영한다. | 사용자 확정 원칙은 path ID만 제거하고 레거시 request를 그대로 이관하는 것이다. 레거시 controller는 혼합 request를 받으며 service는 비활성화 분기에서 나머지 JSON field를 적용하지 않는다. | `api-contract.openapi.json`, `P23-CONTRACT-2` |
|
||||
| 2026-07-28 | `DEC-API-CONTRACT-004` | 확정 | 생성된 TypeScript의 `tsc` 실행은 클라이언트 build/CI 책임으로 두고 server runtime `P23-CONTRACT-GATE` 완료 조건에는 추가하지 않는다. | 서버 Gate는 실제 HTTP runtime과 OpenAPI validate/client 생성 가능성을 판정하며, 언어별 client compile은 소비 클라이언트의 toolchain에서 검증해야 한다. 계약 작성 Task의 일회성 TypeScript 생성·컴파일 검증은 이미 완료됐다. | `P23-CONTRACT-GATE`, `api-contract.md` |
|
||||
|
||||
## 발견된 문제
|
||||
|
||||
@@ -2883,6 +3126,20 @@ git diff --check
|
||||
| `REV-020` | Low | 처리 완료 | Phase 3 ownership/domain test의 event no-interaction mock이 실제 `AudioContentService`·`CreatorAdminContentService` publisher field에 연결되지 않았다. | `P3-R8`, `P3-R5-GATE` | 실제 두 service proxy target의 publisher를 mock으로 교체·복원하고 identity/no-interaction을 단언했다. |
|
||||
|
||||
## 검증 기록
|
||||
- 실행 계획 동기화 검증(2026-07-28): OpenAPI 23개 고유 operation과 Character 4, AudioContent 5, Series 9,
|
||||
Community 3, FanTalk 2 분류를 현재 Endpoint Contract Summary 및 `P23-CONTRACT-2`~Phase 7과 대조했다. Goal ID 중복은
|
||||
없고 PRD Open Questions는 `없음`이며 Task 3.17·3.18에 추가한 production/test/characterization 파일의 존재를 확인했다.
|
||||
- 실행 계획 명령 유효성(2026-07-28): `./gradlew tasks --all` 실행 결과 `test`, `ktlintCheck`, `tasks`가 존재했고
|
||||
`BUILD SUCCESSFUL in 922ms`였다. `git diff --check`, `git diff --cached --check`는 출력이 없었다.
|
||||
- `P23-CONTRACT-1` OpenAPI 검증(2026-07-28): `jq empty`, 23개 고유 operation과
|
||||
`implemented-contract-alignment-required` 9개/`planned` 14개 assertion, 내부 `$ref` 해석을 실행해 모두 성공했다.
|
||||
`npx --yes @redocly/cli lint --skip-rule info-license ...`는 `valid`, OpenAPI Generator `validate`는
|
||||
`No validation issues detected`였다.
|
||||
- `P23-CONTRACT-1` 클라이언트 검증(2026-07-28): OpenAPI Generator 7.24.0 `typescript-fetch` 생성을 완료하고,
|
||||
생성된 23개 Raw operation(Character 4, AudioContent 5, Series 9, Community 3, FanTalk 2)을 확인했다.
|
||||
`tsc --noEmit --target ES2020 --module commonjs --lib ES2020,DOM .../index.ts`는 TypeScript 5.9.3에서 성공했다.
|
||||
- `P23-CONTRACT-1` 문서·리뷰 Gate(2026-07-28): `./gradlew tasks --all`은 `BUILD SUCCESSFUL in 898ms`였고,
|
||||
독립 레거시 DTO/controller/service 대조 및 생성물 재검증 결과 Critical 0, Important 0이었다.
|
||||
- Phase 2·3 6차 보완 재점검 focused 검증(2026-07-28): `./gradlew test --rerun-tasks --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.character.AiCharacterAdminCharacterControllerMutationTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentCreateTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentUpdateTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentControllerTest --tests kr.co.vividnext.sodalive.v2.api.admin.aicharacter.content.AiCharacterAdminAudioContentOwnershipTest` 실행 결과 `BUILD SUCCESSFUL in 4m 19s`였다. 다섯 XML 합계 130건의 failure/error/skipped는 모두 0이었다.
|
||||
- Phase 2·3 6차 보완 재점검 문서 명령 유효성(2026-07-28): `./gradlew tasks --all` 실행 결과 `test`, `ktlintCheck`, `tasks`가 존재했고 `BUILD SUCCESSFUL in 858ms`였다.
|
||||
- Phase 2·3 6차 보완 재점검 판정(2026-07-28): `REV-018`~`REV-020`의 production/test 보완과 Gate 완료 증거가 일치했고 추가 production finding은 확정되지 않았다. 하단 종합 표에서만 미처리로 남은 `REV-001`~`REV-009`를 각 소유 Gate·Progress 판정에 맞춰 `처리 완료`로 동기화했다.
|
||||
|
||||
Reference in New Issue
Block a user