docs(ai-character): 관리자 API 계약 문서를 고정한다
This commit is contained in:
229
docs/20260724_AI캐릭터_관리자_API/api-contract.md
Normal file
229
docs/20260724_AI캐릭터_관리자_API/api-contract.md
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
# AI 캐릭터 관리자 API Contract
|
||||||
|
|
||||||
|
## 1. 문서 목적
|
||||||
|
|
||||||
|
클라이언트 개발과 서버 계약 테스트가 같은 스키마를 사용하도록 AI 캐릭터 관리자 API의 전체 request/response를
|
||||||
|
OpenAPI 3.1 JSON으로 고정한다.
|
||||||
|
|
||||||
|
- 정식 계약: `api-contract.openapi.json`
|
||||||
|
- endpoint: 23개
|
||||||
|
- 현재 route 구현: 9개
|
||||||
|
- 현재 구현 중 계약 정합화 필요: 9개
|
||||||
|
- 구현 예정: 14개
|
||||||
|
- 공통 envelope: `ApiResponse<T>`
|
||||||
|
- 인증: JWT `ROLE_ADMIN`과 현재 DB `Member.role == ADMIN` 동시 충족
|
||||||
|
|
||||||
|
`x-implementation-status`의 의미는 다음과 같다.
|
||||||
|
|
||||||
|
- `implemented-contract-alignment-required`: route는 구현되어 있지만 2026-07-28 확정 레거시 JSON 계약과 runtime DTO가
|
||||||
|
아직 달라 `P23-CONTRACT-2`, `P23-CONTRACT-3`을 완료하기 전 호출 호환을 보장하지 않는다.
|
||||||
|
- `planned`: 계약만 고정되었고 route는 아직 구현되지 않았다.
|
||||||
|
|
||||||
|
## 2. 계약 결정
|
||||||
|
|
||||||
|
1. 신규 endpoint와 `characterId` 기반 관리자 target 경계는 유지한다.
|
||||||
|
2. JSON 필드명, 타입, optional/nullable, 기본값과 성공 `data` 형태는 레거시 API를 유지한다.
|
||||||
|
3. 신규 path에 포함된 `characterId`, `contentId`, `seriesId`, `postId`, `fanTalkId`만 request body에서 중복 제거한다.
|
||||||
|
4. 레거시 mutation이 `ApiResponse.ok(null)`이면 신규 endpoint도 `data: null`을 반환한다.
|
||||||
|
5. 오디오 콘텐츠 생성은 레거시 `CreateAudioContentResponse(contentId)`를 반환한다.
|
||||||
|
6. FanTalk 답변 작성만 신규 계획의 축약 응답
|
||||||
|
`fanTalkId`, `replyId`, `creatorMemberId`, `content`, `createdAtUtc`를 사용한다.
|
||||||
|
7. FanTalk 목록은 공개 v2 `CreatorChannelFanTalkTabResponse`의 필드 형태를 유지하는 관리자 전용 endpoint로 추가한다.
|
||||||
|
8. 시리즈 연결 콘텐츠 목록과 미연결 콘텐츠 검색은 response shape가 달라 별도 endpoint로 분리한다.
|
||||||
|
|
||||||
|
## 3. endpoint와 레거시 근거
|
||||||
|
|
||||||
|
| 상태 | Method | Endpoint | request 근거 | response `data` 근거 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 정합화 필요 | GET | `/api/v2/admin/ai-characters` | `searchTerm?`, `page`, `size` | `ChatCharacterListPageResponse` / `ChatCharacterSearchListPageResponse` |
|
||||||
|
| 정합화 필요 | POST | `/api/v2/admin/ai-characters` | `ChatCharacterRegisterRequest`, 필수 `image` | `null` |
|
||||||
|
| 정합화 필요 | GET | `/api/v2/admin/ai-characters/{characterId}` | path only | `ChatCharacterDetailResponse` |
|
||||||
|
| 정합화 필요 | PUT | `/api/v2/admin/ai-characters/{characterId}` | `ChatCharacterUpdateRequest`에서 `id` 제외 | `null` |
|
||||||
|
| 정합화 필요 | GET | `/api/v2/admin/ai-characters/audio-content-themes` | body 없음 | `List<GetAudioContentThemeResponse>` |
|
||||||
|
| 정합화 필요 | GET | `/api/v2/admin/ai-characters/{characterId}/audio-contents` | `search_word?`, `page`, `size` | `GetCreatorAdminContentListResponse` |
|
||||||
|
| 정합화 필요 | POST | `/api/v2/admin/ai-characters/{characterId}/audio-contents` | `CreateAudioContentRequest`, `contentFile`, `coverImage` | `CreateAudioContentResponse` |
|
||||||
|
| 정합화 필요 | GET | `/api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}` | 필수 `timezone` | `GetAudioContentDetailResponse` |
|
||||||
|
| 정합화 필요 | PUT | `/api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}` | `UpdateCreatorAdminContentRequest`에서 `id` 제외 | `null` |
|
||||||
|
| 계획 | GET | `/api/v2/admin/ai-characters/{characterId}/series` | `page`, `size` | `GetCreatorAdminContentSeriesListResponse` |
|
||||||
|
| 계획 | POST | `/api/v2/admin/ai-characters/{characterId}/series` | `CreateSeriesRequest`, 필수 `image` | `null` |
|
||||||
|
| 계획 | PUT | `/api/v2/admin/ai-characters/{characterId}/series/orders` | `UpdateOrdersRequest` | `null` |
|
||||||
|
| 계획 | GET | `/api/v2/admin/ai-characters/{characterId}/series/{seriesId}` | path only | `GetCreatorAdminContentSeriesDetailResponse` |
|
||||||
|
| 계획 | PUT | `/api/v2/admin/ai-characters/{characterId}/series/{seriesId}` | `ModifySeriesRequest`에서 `seriesId` 제외 | `null` |
|
||||||
|
| 계획 | GET | `/api/v2/admin/ai-characters/{characterId}/series/{seriesId}/contents` | `page`, `size` | `GetCreatorAdminContentSeriesContentResponse` |
|
||||||
|
| 계획 | POST | `/api/v2/admin/ai-characters/{characterId}/series/{seriesId}/contents` | `AddingContentToTheSeriesRequest`에서 `seriesId` 제외 | `null` |
|
||||||
|
| 계획 | GET | `/api/v2/admin/ai-characters/{characterId}/series/{seriesId}/contents/search` | 필수 `search_word` | `List<SearchContentNotInSeriesResponse>` |
|
||||||
|
| 계획 | DELETE | `/api/v2/admin/ai-characters/{characterId}/series/{seriesId}/contents/{contentId}` | body 없음 | `null` |
|
||||||
|
| 계획 | GET | `/api/v2/admin/ai-characters/{characterId}/community-posts` | 필수 `timezone`, `page`, `size` | `List<GetCommunityPostListResponse>` |
|
||||||
|
| 계획 | POST | `/api/v2/admin/ai-characters/{characterId}/community-posts` | `CreateCommunityPostRequest`, `audioFile?`, `postImage?` | `null` |
|
||||||
|
| 계획 | PUT | `/api/v2/admin/ai-characters/{characterId}/community-posts/{postId}` | 두 레거시 update request에서 ID 제외 | `null` |
|
||||||
|
| 계획 | GET | `/api/v2/admin/ai-characters/{characterId}/fan-talks` | 공개 v2 `page?`, `size?` | `CreatorChannelFanTalkTabResponse` |
|
||||||
|
| 계획 | POST | `/api/v2/admin/ai-characters/{characterId}/fan-talks/{fanTalkId}/replies` | `content` | 신규 축약 응답 |
|
||||||
|
|
||||||
|
## 4. JSON Schema 해석
|
||||||
|
|
||||||
|
- 객체의 `required` 배열에 포함된 필드는 JSON key가 필수다.
|
||||||
|
- `required`에 없으면 optional이며 key를 생략할 수 있다.
|
||||||
|
- `type: ["string", "null"]`, 다른 union의 `type: "null"`은 명시적 `null`을 허용한다.
|
||||||
|
- response DTO의 nullable 생성자 필드는 key는 존재하고 값이 `null`일 수 있으므로 `required`와 nullable을 함께 사용한다.
|
||||||
|
- request의 optional nullable 필드는 key 생략과 명시적 `null`을 모두 허용한다.
|
||||||
|
- request는 `additionalProperties: false`이므로 정의되지 않은 이름은 계약 위반이다.
|
||||||
|
- 모든 DB ID는 JSON integer, OpenAPI `int64`다.
|
||||||
|
- 레거시 날짜 문자열은 원래 format을 유지한다. UTC ISO-8601이 보장되는 FanTalk `createdAtUtc`만 `date-time`이다.
|
||||||
|
- enum은 대소문자를 구분하며 Kotlin enum 이름을 그대로 사용한다.
|
||||||
|
- `Accept-Language`는 `ko`, `en`, `ja` 외 문자열도 전송할 수 있고 서버가 KO로 fallback하므로 enum으로 제한하지 않는다.
|
||||||
|
|
||||||
|
### multipart
|
||||||
|
|
||||||
|
multipart API의 `request` part는 schema상 JSON 객체다.
|
||||||
|
|
||||||
|
- part name: `request`
|
||||||
|
- part Content-Type: `application/json`
|
||||||
|
- Character 생성: `image`, `request` 필수
|
||||||
|
- Character 수정: `image` optional, `request` 필수
|
||||||
|
- AudioContent 생성: `contentFile`, `coverImage`, `request` 필수
|
||||||
|
- AudioContent 수정: `coverImage` optional, `request` 필수
|
||||||
|
- Series 생성: `image`, `request` 필수
|
||||||
|
- Series 수정: `image` optional, `request` 필수
|
||||||
|
- Community 생성: `audioFile`, `postImage` optional, `request` 필수
|
||||||
|
- Community 수정/고정: `postImage` optional, `request` 필수
|
||||||
|
|
||||||
|
## 5. domain별 전체 필드 근거
|
||||||
|
|
||||||
|
### Character
|
||||||
|
|
||||||
|
- request:
|
||||||
|
`ChatCharacterRegisterRequest`, `ChatCharacterUpdateRequest`,
|
||||||
|
`ChatCharacterRelationshipRequest`, `ChatCharacterPersonalityRequest`,
|
||||||
|
`ChatCharacterBackgroundRequest`, `ChatCharacterMemoryRequest`
|
||||||
|
- response:
|
||||||
|
`ChatCharacterListResponse`, `ChatCharacterListPageResponse`,
|
||||||
|
`ChatCharacterSearchListPageResponse`, `ChatCharacterDetailResponse`,
|
||||||
|
`RelationshipResponse`, `PersonalityResponse`, `BackgroundResponse`,
|
||||||
|
`MemoryResponse`, `OriginalWorkBriefResponse`
|
||||||
|
- 목록 response는 `totalCount`, `content`이며 `items/page/size/hasNext`로 바꾸지 않는다.
|
||||||
|
- 상세에는 `systemPrompt`, 캐릭터 속성 배열과 `originalWork`를 포함한다.
|
||||||
|
- 생성과 수정의 성공 `data`는 상세가 아니라 `null`이다.
|
||||||
|
- 수정은 `isActive=false`와 다른 optional field의 동시 입력도 레거시 request처럼 허용한다. 이 경우 레거시 service 의미대로
|
||||||
|
비활성화만 반영하고 나머지 JSON field는 적용하지 않는다.
|
||||||
|
|
||||||
|
### AudioContent
|
||||||
|
|
||||||
|
- request:
|
||||||
|
`CreateAudioContentRequest`, `UpdateCreatorAdminContentRequest`
|
||||||
|
- response:
|
||||||
|
`GetAudioContentThemeResponse`, `GetCreatorAdminContentListResponse`,
|
||||||
|
`GetCreatorAdminContentListItem`, `CreateAudioContentResponse`,
|
||||||
|
`GetAudioContentDetailResponse`, `OtherContentResponse`,
|
||||||
|
`AudioContentCreator`, `ContentBuyer`,
|
||||||
|
`GetAudioContentCommentListItem`, `TranslatedContent`
|
||||||
|
- `detail`, `releaseDate`, `contentFile`, `id/theme/image`,
|
||||||
|
`audioContentId`, `contentUrl`, `tags`를 레거시 이름 그대로 사용한다.
|
||||||
|
- 현재 v2 전용 `description`, `releaseDateUtc`, `audioSignedUrl`, `status`,
|
||||||
|
`seriesIds`, `themeName`, `imageUrl` 별칭은 정식 계약에 포함하지 않는다.
|
||||||
|
|
||||||
|
### Series
|
||||||
|
|
||||||
|
- request:
|
||||||
|
`CreateSeriesRequest`, `ModifySeriesRequest`,
|
||||||
|
`AddingContentToTheSeriesRequest`, `RemoveContentToTheSeriesRequest`,
|
||||||
|
`UpdateOrdersRequest`
|
||||||
|
- response:
|
||||||
|
`GetCreatorAdminContentSeriesListResponse`,
|
||||||
|
`GetCreatorAdminContentSeriesDetailResponse`,
|
||||||
|
`GetCreatorAdminContentSeriesContentResponse`,
|
||||||
|
`SearchContentNotInSeriesResponse`
|
||||||
|
- `publishedDaysOfWeek` enum은 `SUN..SAT`, `RANDOM`이며 state는
|
||||||
|
`PROCEEDING`, `SUSPEND`, `COMPLETE`다.
|
||||||
|
- 상세의 `publishedDaysOfWeek`, `genre`, `keywords`, `state`는 레거시처럼 문자열이다.
|
||||||
|
- 상세 `state`의 현재 관찰 값은 `연재중`, `휴재중`, `완결`이지만 DTO 타입이 `String`이므로 enum으로 제한하지 않는다.
|
||||||
|
- 연결 request는 `contentIdList`, 순서 request는 `ids`다.
|
||||||
|
|
||||||
|
### Community
|
||||||
|
|
||||||
|
- request:
|
||||||
|
`CreateCommunityPostRequest`, `ModifyCommunityPostRequest`,
|
||||||
|
`UpdateCommunityPostFixedRequest`
|
||||||
|
- response:
|
||||||
|
`GetCommunityPostListResponse`, `GetCommunityPostCommentListItem`
|
||||||
|
- 목록 `data`는 pagination wrapper가 아니라 직접 배열이다.
|
||||||
|
- 생성 part 이름은 `postImage`, `audioFile`이다.
|
||||||
|
- 수정 endpoint는 기존 본문 수정과 고정/해제를 합치므로 ID를 제외한
|
||||||
|
`content`, `isCommentAvailable`, `isAdult`, `isActive`, `isFixed`를 받는다.
|
||||||
|
- 레거시에 없는 수정 `price`, `audioFile`은 포함하지 않는다.
|
||||||
|
|
||||||
|
### FanTalk
|
||||||
|
|
||||||
|
- 목록 response:
|
||||||
|
`CreatorChannelFanTalkTabResponse`, `CreatorChannelFanTalkResponse`,
|
||||||
|
`CreatorChannelFanTalkReplyResponse`
|
||||||
|
- 공개 v2 endpoint를 직접 사용하지 않는다. 공개 v2는 `creatorId`, viewer 인증과 block filter, 다른 CORS 경계를 사용하기
|
||||||
|
때문이다.
|
||||||
|
- 신규 관리자 목록은 `characterId`로 creator를 해석하고 공개 v2 response field만 유지한다.
|
||||||
|
- 답변 request는 path로 이동한 `creatorId`, `parentId`를 제외하고 `content`만 받는다.
|
||||||
|
- 답변 response는 사용자 승인 예외인 신규 축약 형태다.
|
||||||
|
|
||||||
|
## 6. 공통 응답과 오류
|
||||||
|
|
||||||
|
일반 조회 성공:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": null,
|
||||||
|
"data": {},
|
||||||
|
"errorProperty": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
레거시 mutation 성공:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": null,
|
||||||
|
"data": null,
|
||||||
|
"errorProperty": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
오류:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "현지화된 오류 메시지",
|
||||||
|
"data": null,
|
||||||
|
"errorProperty": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
주요 status는 400, 401, 403, 404, 405, 406, 415, 500이다. 405는 `Allow`, 415는 `Accept` header를 유지한다.
|
||||||
|
Spring CORS 계층이 차단한 미허용 Origin의 403 body는 이 envelope 계약 대상이 아니다.
|
||||||
|
|
||||||
|
## 7. 기계 검증과 클라이언트 생성
|
||||||
|
|
||||||
|
```bash
|
||||||
|
jq empty docs/20260724_AI캐릭터_관리자_API/api-contract.openapi.json
|
||||||
|
|
||||||
|
npx --yes @redocly/cli lint --skip-rule info-license \
|
||||||
|
docs/20260724_AI캐릭터_관리자_API/api-contract.openapi.json
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
tsc --noEmit --target ES2020 --module commonjs --lib ES2020,DOM \
|
||||||
|
/tmp/ai-character-admin-typescript-client/index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
`info-license`만 제외하는 이유는 저장소 라이선스 값을 추측해 계약에 추가하지 않기 위해서다.
|
||||||
|
생성 클라이언트에는 23개 operation이 모두 포함된다. `implemented-contract-alignment-required` operation은 해당 runtime
|
||||||
|
정합화 Gate가 끝나기 전 production 호출 대상으로 간주하지 않는다.
|
||||||
|
|
||||||
|
OpenAPI의 `/` server URL은 현재 host를 의미하지만 OpenAPI Generator 7.24.0의 `typescript-fetch` runtime 기본값은
|
||||||
|
`http://localhost`다. 실제 클라이언트는 배포 환경의 API origin을 `new Configuration({ basePath: "..." })`로 반드시
|
||||||
|
지정한다.
|
||||||
1260
docs/20260724_AI캐릭터_관리자_API/api-contract.openapi.json
Normal file
1260
docs/20260724_AI캐릭터_관리자_API/api-contract.openapi.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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.
|
> **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 컴포넌트를 테스트로 고정해 선택적으로 재사용한다.
|
**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 |
|
| 작성일 | 2026-07-24 |
|
||||||
| 요구사항 기준 | `docs/20260724_AI캐릭터_관리자_API/prd.md` |
|
| 요구사항 기준 | `docs/20260724_AI캐릭터_관리자_API/prd.md` |
|
||||||
| API 기준 | 이 문서의 `Endpoint Contract Summary` |
|
| API 기준 | `docs/20260724_AI캐릭터_관리자_API/api-contract.openapi.json` |
|
||||||
| 현재 Phase | Phase 3 6차 리뷰 완료 |
|
| 현재 Phase | Phase 2·3 레거시 계약 정합화 |
|
||||||
| 현재 활성 Goal | `P4-T1` 대기 |
|
| 현재 활성 Goal | `P23-CONTRACT-2` 대기 |
|
||||||
|
|
||||||
## 현재 상태
|
## 현재 상태
|
||||||
|
|
||||||
| Phase | 상태 | 기존 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 |
|
| Phase | 상태 | 기존 완료 Task | 활성/다음 Goal | 차단 또는 남은 조건 |
|
||||||
|---:|---|---:|---|---|
|
|---:|---|---:|---|---|
|
||||||
| 1 | 완료 | 7/7 | 완료 | 없음 |
|
| 1 | 완료 | 7/7 | 완료 | 없음 |
|
||||||
| 2 | 완료 | 11/11 | 완료 | 없음 |
|
| 2 | 후속 보완 대기 | 11/11 | `P23-CONTRACT-2` | 레거시 JSON 계약과 현재 v2 구현 정합화 |
|
||||||
| 3 | 완료 | 15/15 | 완료 | 없음 |
|
| 3 | 후속 보완 대기 | 15/15 | `P23-CONTRACT-3` | `P23-CONTRACT-2` |
|
||||||
| 4 | 대기 | 0/6 | `P4-T1` | `P3-R5-GATE`, 사용자 진행 지시 |
|
| 4 | 대기 | 0/6 | `P4-T1` | `P23-CONTRACT-GATE` |
|
||||||
| 5 | 대기 | 0/6 | `P5-T1` | `P4-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 완료 |
|
| 7 | 대기 | 0/2 | `P7-T1` | Phase 1~6 Gate 완료 |
|
||||||
|
|
||||||
- Phase는 결과와 의존성을 묶는 문서 단위다. `create_goal`에는 Task 또는 Phase Gate 하나만 등록한다.
|
- Phase는 결과와 의존성을 묶는 문서 단위다. `create_goal`에는 Task 또는 Phase Gate 하나만 등록한다.
|
||||||
@@ -56,6 +57,15 @@
|
|||||||
415 `Accept` 표준 header를 유지한다.
|
415 `Accept` 표준 header를 유지한다.
|
||||||
- 문서 작성 규칙: `docs/agent-guides/작업절차.md`, `docs/agent-guides/문서유지보수.md`, `docs/agent-guides/테스트스타일.md`
|
- 문서 작성 규칙: `docs/agent-guides/작업절차.md`, `docs/agent-guides/문서유지보수.md`, `docs/agent-guides/테스트스타일.md`
|
||||||
- 기존 AI 캐릭터 연결 문서: `docs/20260611_AI캐릭터_크리에이터기능_최소연결/{prd.md,plan-task.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
|
## Endpoint Contract Summary
|
||||||
|
|
||||||
@@ -67,7 +77,37 @@
|
|||||||
|
|
||||||
`characterId`는 target resource endpoint의 외부 대상 식별자다. 캐릭터 목록/검색은 선택된 target이 없어 `characterId`를 받지 않고, 캐릭터 생성은 새 `ChatCharacter`를 만드는 endpoint라 path `characterId`를 받지 않는다.
|
`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
|
```json
|
||||||
{
|
{
|
||||||
@@ -652,6 +692,8 @@ Response `data`:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 구현 Phase
|
||||||
|
|
||||||
### Phase 1: 공통 ADMIN 인증과 AI 캐릭터 target resolver 기반
|
### Phase 1: 공통 ADMIN 인증과 AI 캐릭터 target resolver 기반
|
||||||
|
|
||||||
#### 공통 Task 실행 규칙
|
#### 공통 Task 실행 규칙
|
||||||
@@ -956,11 +998,14 @@ AI 캐릭터 목록/검색/상세/생성/수정/비활성화를 신규 ADMIN v2
|
|||||||
- 기존 `ChatCharacterService`, `ChatCharacterCreatorMemberService`, image/S3/event 관련 컴포넌트 동작을 특성화해야 한다.
|
- 기존 `ChatCharacterService`, `ChatCharacterCreatorMemberService`, image/S3/event 관련 컴포넌트 동작을 특성화해야 한다.
|
||||||
|
|
||||||
#### API endpoint와 request/response contract
|
#### API endpoint와 request/response contract
|
||||||
- `GET /api/v2/admin/ai-characters?search=&page=&size=` -> `AiCharacterAdminListResponse(totalCount, items, page, size, hasNext)`
|
- 정식 schema는 `api-contract.openapi.json`의 Character operation을 따른다.
|
||||||
- `GET /api/v2/admin/ai-characters/{characterId}` -> `AiCharacterAdminDetailResponse`
|
- `GET /api/v2/admin/ai-characters?searchTerm=&page=&size=` ->
|
||||||
- `POST /api/v2/admin/ai-characters` multipart `image?`, `request: CreateAiCharacterAdminRequest` -> detail
|
`ChatCharacterListPageResponse(totalCount, content)` 또는 동일 필드의 검색 response.
|
||||||
- `PUT /api/v2/admin/ai-characters/{characterId}` multipart `image?`, `request: UpdateAiCharacterAdminRequest` -> detail
|
- `GET /api/v2/admin/ai-characters/{characterId}` -> nested 필드 전체를 포함한 `ChatCharacterDetailResponse`.
|
||||||
- `UpdateAiCharacterAdminRequest.isActive=false`는 soft delete 의미다.
|
- `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, repository, service 변경
|
||||||
- Entity: 변경 없음.
|
- Entity: 변경 없음.
|
||||||
@@ -1397,29 +1442,21 @@ git diff --check
|
|||||||
#### API endpoint와 request/response contract
|
#### API endpoint와 request/response contract
|
||||||
- `GET /api/v2/admin/ai-characters/audio-content-themes`
|
- `GET /api/v2/admin/ai-characters/audio-content-themes`
|
||||||
- Request: query/body 없음.
|
- Request: query/body 없음.
|
||||||
- Response `data`:
|
- Response: `List<GetAudioContentThemeResponse(id, theme, image)>`.
|
||||||
|
- `GET /api/v2/admin/ai-characters/{characterId}/audio-contents?search_word=&page=&size=` ->
|
||||||
```json
|
`GetCreatorAdminContentListResponse` 전체 필드.
|
||||||
[
|
- `GET /api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}?timezone=Asia/Seoul` ->
|
||||||
{
|
`GetAudioContentDetailResponse` 전체 nested 필드.
|
||||||
"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}`
|
|
||||||
- `POST /api/v2/admin/ai-characters/{characterId}/audio-contents`
|
- `POST /api/v2/admin/ai-characters/{characterId}/audio-contents`
|
||||||
- multipart `coverImage`, `audioFile`, `request` JSON string part를 사용한다.
|
- multipart 필수 `contentFile`, `coverImage`, `request: CreateAudioContentRequest`.
|
||||||
- `request` JSON은 `title`, `description`, `tags`, `price`, `purchaseOption`, `limited`, `isAdult`, `isActive`, `themeId`, `releaseDateUtc?`, `seriesIds`, `isGeneratePreview`, `isOnlyRental`, `isPointAvailable`, `isCommentAvailable`, `isFullDetailVisible`, `previewStartTime`, `previewEndTime`, `languageCode`를 포함한다.
|
- Response: `CreateAudioContentResponse(contentId)`.
|
||||||
- legacy `CreateAudioContentRequest`의 `detail`은 v2 `description`, `releaseDate`는 UTC ISO-8601 `releaseDateUtc`로 받으며 facade에서 기존 pipeline 입력으로 변환한다.
|
|
||||||
- `PUT /api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}`
|
- `PUT /api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}`
|
||||||
- `audioFile` 교체는 기존 creator/admin 수정 pipeline에 없는 동작이므로 Phase 3 범위에서는 제공하지 않는다. 오디오 파일 교체가 필요하면 별도 upload/processing parity 설계 후 추가한다.
|
- multipart optional `coverImage`, 필수 `request: UpdateCreatorAdminContentRequest`에서 `id` 제외.
|
||||||
- response item은 현 v2 목록 계약을 유지한다. response detail에는 기존 `GetAudioContentDetailResponse`의 필드 전체를 포함하고, `description`, `audioSignedUrl`, `releaseDateUtc`, `seriesIds`, `createdAtUtc`, `updatedAtUtc` 같은 v2 관리자 필드도 유지한다. 구매·좋아요·핀·추천·댓글 목록처럼 viewer 상태가 필요한 legacy 상세 필드는 관리자 상세에서 안전한 기본값을 반환한다.
|
- 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, repository, service 변경
|
||||||
- Entity: 변경 없음.
|
- 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
|
### Phase 4: 시리즈 관리 vertical slice
|
||||||
|
|
||||||
#### 목표
|
#### 목표
|
||||||
@@ -1979,11 +2145,15 @@ git diff --check
|
|||||||
- 기존 creator series 생성/수정/soft delete, 콘텐츠 연결/해제, 조회/검색, 순서 변경 behavior를 통과하는 특성화 테스트로 먼저 고정해야 한다.
|
- 기존 creator series 생성/수정/soft delete, 콘텐츠 연결/해제, 조회/검색, 순서 변경 behavior를 통과하는 특성화 테스트로 먼저 고정해야 한다.
|
||||||
|
|
||||||
#### API endpoint와 request/response contract
|
#### API endpoint와 request/response contract
|
||||||
|
- 정식 전체 schema는 `api-contract.openapi.json`의 Series operation 9개를 따른다.
|
||||||
- `GET/POST/PUT /api/v2/admin/ai-characters/{characterId}/series...`
|
- `GET/POST/PUT /api/v2/admin/ai-characters/{characterId}/series...`
|
||||||
- `GET /series/{seriesId}/contents` query: `search?`, `page`, `size`
|
- `GET /series/{seriesId}/contents` query: `page`, `size`; response:
|
||||||
- `POST /series/{seriesId}/contents` request: `AddAiCharacterAdminSeriesContentsRequest(contentIds: List<Long>)`
|
`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}`
|
- `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, repository, service 변경
|
||||||
- Entity: 변경 없음.
|
- Entity: 변경 없음.
|
||||||
@@ -2028,7 +2198,7 @@ git diff --check
|
|||||||
|
|
||||||
**Goal 실행 `P4-T1`:** 기존 creator series의 CRUD·연결·검색·순서 동작을 신규 v2 구현의 비교 기준으로 고정한다.
|
**Goal 실행 `P4-T1`:** 기존 creator series의 CRUD·연결·검색·순서 동작을 신규 v2 구현의 비교 기준으로 고정한다.
|
||||||
|
|
||||||
- **시작 조건:** 최신 Phase 3 후속 Gate인 `P3-R5-GATE` 완료와 사용자 진행 지시.
|
- **시작 조건:** Phase 2·3 runtime 계약 정합화의 `P23-CONTRACT-GATE` 완료와 사용자 진행 지시.
|
||||||
- **완료 증거:** production 변경 전 특성화 테스트 통과, 관찰된 오류·side-effect 정책과 Progress 기록.
|
- **완료 증거:** production 변경 전 특성화 테스트 통과, 관찰된 오류·side-effect 정책과 Progress 기록.
|
||||||
- **범위 밖:** 신규 v2 series production code 구현.
|
- **범위 밖:** 신규 v2 series production code 구현.
|
||||||
|
|
||||||
@@ -2044,10 +2214,10 @@ git diff --check
|
|||||||
|
|
||||||
- [ ] **Task 4.2: 시리즈 목록·상세 조회 구현**
|
- [ ] **Task 4.2: 시리즈 목록·상세 조회 구현**
|
||||||
|
|
||||||
**Goal 실행 `P4-T2`:** target owner의 활성 시리즈 목록·상세를 pagination 계약으로 제공한다.
|
**Goal 실행 `P4-T2`:** target owner의 활성 시리즈 목록·상세를 레거시 response 계약으로 제공한다.
|
||||||
|
|
||||||
- **시작 조건:** `P4-T1` 완료와 Phase 4 오류 계약의 계획 반영.
|
- **시작 조건:** `P4-T1` 완료와 Phase 4 오류 계약의 계획 반영.
|
||||||
- **완료 증거:** 목록·상세·inactive·cross-owner·pagination RED/GREEN과 Progress 기록.
|
- **완료 증거:** 목록·상세 전체 필드, inactive·cross-owner·pagination RED/GREEN과 Progress 기록.
|
||||||
- **범위 밖:** 시리즈 mutation, 콘텐츠 연결, 순서 변경.
|
- **범위 밖:** 시리즈 mutation, 콘텐츠 연결, 순서 변경.
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
@@ -2060,7 +2230,8 @@ git diff --check
|
|||||||
|
|
||||||
- [ ] 미구현 목록·상세, inactive 제외, cross-owner 거부와 pagination 경계 실패 test를 작성한다.
|
- [ ] 미구현 목록·상세, inactive 제외, cross-owner 거부와 pagination 경계 실패 test를 작성한다.
|
||||||
- [ ] owner-scoped 목록·상세 최소 구현으로 focused 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에 기록한다.
|
- [ ] focused test와 `ktlintCheck` 결과를 Progress에 기록한다.
|
||||||
|
|
||||||
- [ ] **Task 4.3: 시리즈 생성·수정·soft delete 구현**
|
- [ ] **Task 4.3: 시리즈 생성·수정·soft delete 구현**
|
||||||
@@ -2088,7 +2259,8 @@ git diff --check
|
|||||||
**Goal 실행 `P4-T4`:** 동일 owner의 시리즈와 콘텐츠만 검색·연결·해제할 수 있도록 한다.
|
**Goal 실행 `P4-T4`:** 동일 owner의 시리즈와 콘텐츠만 검색·연결·해제할 수 있도록 한다.
|
||||||
|
|
||||||
- **시작 조건:** `P4-T2`, `P4-T3`와 Phase 3 owner query 계약 완료.
|
- **시작 조건:** `P4-T2`, `P4-T3`와 Phase 3 owner query 계약 완료.
|
||||||
- **완료 증거:** 검색·pagination·전체 ID 사전 검증·원자적 연결/해제 RED/GREEN과 Progress 기록.
|
- **완료 증거:** 연결 목록·미연결 검색의 분리된 응답, pagination·전체 ID 사전 검증·원자적 연결/해제 RED/GREEN과
|
||||||
|
Progress 기록.
|
||||||
- **범위 밖:** 콘텐츠 자체 수정, 시리즈 순서 변경.
|
- **범위 밖:** 콘텐츠 자체 수정, 시리즈 순서 변경.
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
@@ -2098,9 +2270,10 @@ git diff --check
|
|||||||
- Modify: `src/main/kotlin/kr/co/vividnext/sodalive/v2/api/admin/aicharacter/series/AiCharacterAdminSeriesRepository.kt`
|
- 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`
|
- 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 전에 검증하는 최소 구현을 통과시킨다.
|
- [ ] 모든 series/content ID를 mutation 전에 검증하는 최소 구현을 통과시킨다.
|
||||||
- [ ] 일부 연결 성공이 남지 않는 원자성과 pagination 경계를 검증한다.
|
- [ ] `contentIdList` 필드와 mutation `data: null`, 일부 연결 성공이 남지 않는 원자성과 pagination 경계를 검증한다.
|
||||||
- [ ] focused/Phase 3 owner query 회귀와 `ktlintCheck` 결과를 Progress에 기록한다.
|
- [ ] focused/Phase 3 owner query 회귀와 `ktlintCheck` 결과를 Progress에 기록한다.
|
||||||
|
|
||||||
- [ ] **Task 4.5: owner-scoped 시리즈 순서 변경 구현**
|
- [ ] **Task 4.5: owner-scoped 시리즈 순서 변경 구현**
|
||||||
@@ -2168,10 +2341,14 @@ git diff --check
|
|||||||
- 기존 community write behavior 특성화 테스트.
|
- 기존 community write behavior 특성화 테스트.
|
||||||
|
|
||||||
#### API endpoint와 request/response contract
|
#### API endpoint와 request/response contract
|
||||||
- `GET /api/v2/admin/ai-characters/{characterId}/community-posts`
|
- 정식 전체 schema는 `api-contract.openapi.json`의 Community operation 3개를 따른다.
|
||||||
- `POST /api/v2/admin/ai-characters/{characterId}/community-posts`
|
- `GET /api/v2/admin/ai-characters/{characterId}/community-posts?timezone=&page=&size=` ->
|
||||||
- `PUT /api/v2/admin/ai-characters/{characterId}/community-posts/{postId}`
|
`List<GetCommunityPostListResponse>`.
|
||||||
- update request는 `isFixed`, `isActive`, 본문/이미지/오디오/가격 필드를 포함한다.
|
- `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, repository, service 변경
|
||||||
- Entity: 변경 없음.
|
- 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 캐릭터 일반 사용자 활동.
|
- 제외: FanTalk 원글 작성, nested reply, 구매/댓글형 기능, AI 캐릭터 일반 사용자 활동.
|
||||||
|
|
||||||
#### 선행 Phase 및 의존성
|
#### 선행 Phase 및 의존성
|
||||||
@@ -2354,14 +2533,19 @@ git diff --check
|
|||||||
- 기존 FanTalk 저장 엔티티와 응답 DTO 의미 특성화.
|
- 기존 FanTalk 저장 엔티티와 응답 DTO 의미 특성화.
|
||||||
|
|
||||||
#### API endpoint와 request/response contract
|
#### 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`
|
- `POST /api/v2/admin/ai-characters/{characterId}/fan-talks/{fanTalkId}/replies`
|
||||||
- Request: `CreateAiCharacterAdminFanTalkReplyRequest(content: String)`
|
- Request: `CreateAiCharacterAdminFanTalkReplyRequest(content: String)`
|
||||||
- Response: `AiCharacterAdminFanTalkReplyResponse(fanTalkId, replyId, creatorMemberId, content, createdAtUtc)`
|
- Response: `AiCharacterAdminFanTalkReplyResponse(fanTalkId, replyId, creatorMemberId, content, createdAtUtc)`
|
||||||
|
|
||||||
#### entity, repository, service 변경
|
#### entity, repository, service 변경
|
||||||
- Entity: 변경 없음.
|
- Entity: 변경 없음.
|
||||||
- Repository: `CreatorCheers` 또는 FanTalk repository에 root/active/creator owner 조회 adapter 추가 가능.
|
- Repository: 관리자 목록용 owner-scoped root/reply 조회와 root/active/creator owner 검증 adapter를 추가한다.
|
||||||
- Service: 신규 FanTalk reply application service 구현. target 검증 후 기존 저장/언어 감지 로직을 필요한 만큼 재사용한다.
|
- Service: 관리자 목록은 공개 v2 DTO 형태로 조립하되 viewer/block 필터를 적용하지 않는다. reply는 target 검증 후 기존
|
||||||
|
저장/언어 감지 로직을 필요한 만큼 재사용한다.
|
||||||
|
|
||||||
#### DB migration
|
#### DB migration
|
||||||
- 없음.
|
- 없음.
|
||||||
@@ -2376,23 +2560,24 @@ git diff --check
|
|||||||
|
|
||||||
#### acceptance criteria
|
#### acceptance criteria
|
||||||
- target AI character는 자신의 활성 root FanTalk에만 답변할 수 있다.
|
- target AI character는 자신의 활성 root FanTalk에만 답변할 수 있다.
|
||||||
|
- 관리자는 공개 v2와 동일한 필드 형태로 target AI character의 root FanTalk와 creator reply를 조회할 수 있다.
|
||||||
- cross-character, nested parent, inactive/missing FanTalk는 4xx이며 reply 저장과 이벤트 발행이 없다.
|
- cross-character, nested parent, inactive/missing FanTalk는 4xx이며 reply 저장과 이벤트 발행이 없다.
|
||||||
- 저장된 답변의 writer/creator는 해석된 creatorMember와 일관된다.
|
- 저장된 답변의 writer/creator는 해석된 creatorMember와 일관된다.
|
||||||
|
|
||||||
#### targeted test
|
#### targeted test
|
||||||
- Characterization: `LegacyFanTalkReplyCharacterizationTest`.
|
- Characterization: `LegacyFanTalkReplyCharacterizationTest`.
|
||||||
- V2 RED/GREEN: `AiCharacterAdminFanTalkReplyServiceTest`.
|
- V2 RED/GREEN: `AiCharacterAdminFanTalkQueryTest`, `AiCharacterAdminFanTalkReplyServiceTest`.
|
||||||
- Run: `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.fantalk.*'`
|
- Run: `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.fantalk.*'`
|
||||||
|
|
||||||
#### 전체 회귀 테스트 영향
|
#### 전체 회귀 테스트 영향
|
||||||
- 기존 FanTalk 조회/작성 관련 테스트가 통과해야 한다.
|
- 기존 FanTalk 조회/작성 관련 테스트가 통과해야 한다.
|
||||||
|
|
||||||
#### rollback 전략
|
#### rollback 전략
|
||||||
- 신규 FanTalk reply v2 admin route/facade를 제거한다.
|
- 신규 FanTalk 목록·reply v2 admin route/facade를 제거한다.
|
||||||
- 신규 DDL이 없으므로 schema rollback은 없다.
|
- 신규 DDL이 없으므로 schema rollback은 없다.
|
||||||
|
|
||||||
#### 권장 commit 경계
|
#### 권장 commit 경계
|
||||||
- `feat: add ai character admin fan talk reply slice`
|
- `feat: add ai character admin fan talk slice`
|
||||||
|
|
||||||
- [ ] **Task 6.1: 기존 FanTalk reply 의미 특성화 baseline 고정**
|
- [ ] **Task 6.1: 기존 FanTalk reply 의미 특성화 baseline 고정**
|
||||||
|
|
||||||
@@ -2412,11 +2597,32 @@ git diff --check
|
|||||||
- [ ] production code 변경 없이 기존 구현 대상 특성화 테스트 통과를 확인한다.
|
- [ ] production code 변경 없이 기존 구현 대상 특성화 테스트 통과를 확인한다.
|
||||||
- [ ] fixture만 정리하고 test와 `ktlintCheck` 결과를 Progress에 기록한다.
|
- [ ] 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 오류 계약의 계획 반영.
|
- **시작 조건:** `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 기록.
|
- **완료 증거:** 정상 저장·언어 감지·DTO·writer/creator RED/GREEN과 Progress 기록.
|
||||||
- **범위 밖:** FanTalk 원글, nested reply, 일반 사용자 대리 작성.
|
- **범위 밖:** FanTalk 원글, nested reply, 일반 사용자 대리 작성.
|
||||||
|
|
||||||
@@ -2433,11 +2639,11 @@ git diff --check
|
|||||||
- [ ] writer/creator가 resolver의 creatorMember와 일치하고 principal impersonation이 없음을 검증한다.
|
- [ ] writer/creator가 resolver의 creatorMember와 일치하고 principal impersonation이 없음을 검증한다.
|
||||||
- [ ] focused/legacy characterization test와 `ktlintCheck` 결과를 Progress에 기록한다.
|
- [ ] 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 기록.
|
- **완료 증거:** 네 거부 분기의 정확한 오류 계약, DB/event 0건과 Progress 기록.
|
||||||
- **범위 밖:** 새로운 중복 답변 차단 정책.
|
- **범위 밖:** 새로운 중복 답변 차단 정책.
|
||||||
|
|
||||||
@@ -2452,11 +2658,11 @@ git diff --check
|
|||||||
- [ ] 각 실패의 정확한 status/message key/KO·EN·JA와 reply insert/event 0건을 검증한다.
|
- [ ] 각 실패의 정확한 status/message key/KO·EN·JA와 reply insert/event 0건을 검증한다.
|
||||||
- [ ] focused test와 `ktlintCheck` 결과를 Progress에 기록한다.
|
- [ ] 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 기록.
|
- **완료 증거:** 권한 매트릭스, request binding/domain 오류, legacy 회귀와 Progress 기록.
|
||||||
- **범위 밖:** Phase 7 외 전체 기능 수정.
|
- **범위 밖:** Phase 7 외 전체 기능 수정.
|
||||||
|
|
||||||
@@ -2472,11 +2678,11 @@ git diff --check
|
|||||||
|
|
||||||
#### Phase 6 Gate
|
#### 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-GATE` 완료:** 시작 조건과 완료 증거를 모두 충족하고 현재 상태표와 Progress를 갱신한다.
|
||||||
|
|
||||||
- **시작 조건:** `P6-T1`~`P6-T4` 완료.
|
- **시작 조건:** `P6-T1`~`P6-T5` 완료.
|
||||||
- **완료 증거:** `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.fantalk.*'`와
|
- **완료 증거:** `./gradlew test --tests 'kr.co.vividnext.sodalive.v2.api.admin.aicharacter.fantalk.*'`와
|
||||||
`./gradlew ktlintCheck` 성공, Progress 기록.
|
`./gradlew ktlintCheck` 성공, Progress 기록.
|
||||||
- **범위 밖:** 실패와 무관한 신규 기능.
|
- **범위 밖:** 실패와 무관한 신규 기능.
|
||||||
@@ -2496,7 +2702,7 @@ git diff --check
|
|||||||
- Phase 1~6 완료.
|
- Phase 1~6 완료.
|
||||||
|
|
||||||
#### API endpoint와 request/response contract
|
#### API endpoint와 request/response contract
|
||||||
- Endpoint Contract Summary의 모든 endpoint가 구현되어야 한다.
|
- `api-contract.openapi.json`의 23개 operation이 모두 구현되어야 한다.
|
||||||
- 모든 신규 endpoint가 JWT ADMIN + 현재 DB ADMIN 이중 인가와 공통 오류 envelope/i18n을 공유해야 한다.
|
- 모든 신규 endpoint가 JWT ADMIN + 현재 DB ADMIN 이중 인가와 공통 오류 envelope/i18n을 공유해야 한다.
|
||||||
- legacy/public endpoint URI와 성공·오류 status/body/message diff가 없어야 한다.
|
- 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에서 수정·증거 보강 |
|
| 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` 문서 계약 동기화 |
|
| 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에서 수정·증거 보강 |
|
| 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로 되돌림 |
|
| 15 | `P23-CONTRACT-1` → `P23-CONTRACT-2` → `P23-CONTRACT-3` → `P23-CONTRACT-GATE` | `P3-R5-GATE`, 사용자 계약 확정 | 아니요 | 문서 또는 runtime 불일치 소유 Goal에서 수정 |
|
||||||
| 16 | `P5-T1`~`P5-T6` → `P5-GATE` | `P4-GATE` | 아니요 | 실패 소유 Task로 되돌림 |
|
| 16 | `P4-T1`~`P4-T6` → `P4-GATE` | `P23-CONTRACT-GATE` | 아니요 | 실패 소유 Task로 되돌림 |
|
||||||
| 17 | `P6-T1`~`P6-T4` → `P6-GATE` | `P5-GATE` | 아니요 | 실패 소유 Task로 되돌림 |
|
| 17 | `P5-T1`~`P5-T6` → `P5-GATE` | `P4-GATE` | 아니요 | 실패 소유 Task로 되돌림 |
|
||||||
| 18 | `P7-T1` → `P7-T2` → `P7-GATE` | Phase 1~6 최신 Gate | 아니요 | 실패 소유 Phase에 회귀 수정 Goal 추가 |
|
| 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`이지만 사용자 진행 지시 전까지 시작하지 않는다.
|
- 남은 항목: 없음. 다음 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
|
## Decision Log
|
||||||
|
|
||||||
| 날짜 | ID | 상태 | 결정 | 근거 | 영향 Goal/문서 |
|
| 날짜 | 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-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-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-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을 단언했다. |
|
| `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차 보완 재점검 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): `./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 판정에 맞춰 `처리 완료`로 동기화했다.
|
- Phase 2·3 6차 보완 재점검 판정(2026-07-28): `REV-018`~`REV-020`의 production/test 보완과 Gate 완료 증거가 일치했고 추가 production finding은 확정되지 않았다. 하단 종합 표에서만 미처리로 남은 `REV-001`~`REV-009`를 각 소유 Gate·Progress 판정에 맞춰 `처리 완료`로 동기화했다.
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
- 모든 신규 target endpoint는 외부 대상 식별자로 `characterId`를 받고, 서버가 `ChatCharacter.creatorMember`를 내부 행위자로 해석한다. 단, 캐릭터 목록/검색은 아직 선택된 target이 없어 `characterId`를 받지 않고, 캐릭터 생성은 새 `ChatCharacter`를 만드는 endpoint라 path `characterId`를 받지 않는다.
|
- 모든 신규 target endpoint는 외부 대상 식별자로 `characterId`를 받고, 서버가 `ChatCharacter.creatorMember`를 내부 행위자로 해석한다. 단, 캐릭터 목록/검색은 아직 선택된 target이 없어 `characterId`를 받지 않고, 캐릭터 생성은 새 `ChatCharacter`를 만드는 endpoint라 path `characterId`를 받지 않는다.
|
||||||
- target 해석 시 `ChatCharacter` 존재, `creatorMember` 존재, `creatorMember.role == CREATOR`, `creatorMember.memberKind == AI_CHARACTER`를 모두 검증한다.
|
- target 해석 시 `ChatCharacter` 존재, `creatorMember` 존재, `creatorMember.role == CREATOR`, `creatorMember.memberKind == AI_CHARACTER`를 모두 검증한다.
|
||||||
- 검증 실패 시 4xx로 거부하고 DB, S3, 외부 캐릭터 API, 이벤트 발행 등 후속 부작용을 만들지 않는다.
|
- 검증 실패 시 4xx로 거부하고 DB, S3, 외부 캐릭터 API, 이벤트 발행 등 후속 부작용을 만들지 않는다.
|
||||||
- 캐릭터, 오디오 콘텐츠, 시리즈, 커뮤니티 게시글, FanTalk 답변, 오디오 signed URL을 신규 관리자 API에서 관리한다.
|
- 캐릭터, 오디오 콘텐츠, 시리즈, 커뮤니티 게시글, FanTalk 목록·답변, 오디오 signed URL을 신규 관리자 API에서 관리한다.
|
||||||
- 기존 legacy/public endpoint의 URI, 성공·오류 HTTP status, response body, message/i18n을 포함한 외부 계약은 변경하지 않는다.
|
- 기존 legacy/public endpoint의 URI, 성공·오류 HTTP status, response body, message/i18n을 포함한 외부 계약은 변경하지 않는다.
|
||||||
단, 캐릭터 관리자 frontend가 기존 관리자 인증을 재사용할 수 있도록 `/admin/member/login`, `/member/logout`의 CORS 허용
|
단, 캐릭터 관리자 frontend가 기존 관리자 인증을 재사용할 수 있도록 `/admin/member/login`, `/member/logout`의 CORS 허용
|
||||||
Origin만 path-specific으로 확장한다.
|
Origin만 path-specific으로 확장한다.
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
- 운영자는 선택한 AI 캐릭터 소유 오디오 콘텐츠를 조회, 생성, 수정, soft delete하고 관리자 화면에서 재생 가능한 signed URL을 받고 싶다.
|
- 운영자는 선택한 AI 캐릭터 소유 오디오 콘텐츠를 조회, 생성, 수정, soft delete하고 관리자 화면에서 재생 가능한 signed URL을 받고 싶다.
|
||||||
- 운영자는 선택한 AI 캐릭터 소유 시리즈를 조회, 생성, 수정, soft delete하고 콘텐츠 연결/해제/순서를 관리하고 싶다.
|
- 운영자는 선택한 AI 캐릭터 소유 시리즈를 조회, 생성, 수정, soft delete하고 콘텐츠 연결/해제/순서를 관리하고 싶다.
|
||||||
- 운영자는 선택한 AI 캐릭터 소유 커뮤니티 게시글을 작성, 수정, 고정/해제, soft delete하고 싶다.
|
- 운영자는 선택한 AI 캐릭터 소유 커뮤니티 게시글을 작성, 수정, 고정/해제, soft delete하고 싶다.
|
||||||
- 운영자는 선택한 AI 캐릭터가 자신의 활성 root FanTalk에 creator reply를 작성하게 하고 싶다.
|
- 운영자는 선택한 AI 캐릭터의 FanTalk 목록과 기존 creator reply를 확인한 뒤 활성 root FanTalk에 답변하고 싶다.
|
||||||
- 서버는 다른 AI 캐릭터나 HUMAN creator의 resource ID가 전달되면 변경 없이 4xx로 거부해야 한다.
|
- 서버는 다른 AI 캐릭터나 HUMAN creator의 resource ID가 전달되면 변경 없이 4xx로 거부해야 한다.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -95,7 +95,8 @@
|
|||||||
|
|
||||||
#### Requirements
|
#### Requirements
|
||||||
- 캐릭터 소유 콘텐츠 목록/검색/상세 조회, 생성, 수정, 기존 삭제 동작에 따른 soft delete를 제공한다.
|
- 캐릭터 소유 콘텐츠 목록/검색/상세 조회, 생성, 수정, 기존 삭제 동작에 따른 soft delete를 제공한다.
|
||||||
- 콘텐츠 생성 화면에서 사용할 활성 콘텐츠 테마(카테고리) 목록 조회를 제공한다. 기존 크리에이터 관리자 콘텐츠 등록 화면의 테마 조회와 같은 기능이며, 신규 v2 응답 필드는 `themeId`, `themeName`, `imageUrl`로 명확히 구분한다.
|
- 콘텐츠 생성 화면에서 사용할 활성 콘텐츠 테마(카테고리) 목록 조회를 제공한다. 기존 크리에이터 관리자 콘텐츠 등록
|
||||||
|
화면의 `GetAudioContentThemeResponse`와 같은 `id`, `theme`, `image` 필드명을 유지한다.
|
||||||
- 기존 크리에이터 콘텐츠 관리의 검증, 파일 처리, content upload/processing pipeline, 가격, 공개/예약, 번역/알림 등 business behavior parity를 유지한다.
|
- 기존 크리에이터 콘텐츠 관리의 검증, 파일 처리, content upload/processing pipeline, 가격, 공개/예약, 번역/알림 등 business behavior parity를 유지한다.
|
||||||
- 관리자 화면 재생용 signed URL을 콘텐츠 목록/상세 응답에 제공한다.
|
- 관리자 화면 재생용 signed URL을 콘텐츠 목록/상세 응답에 제공한다.
|
||||||
- signed URL은 공통 `AudioContentCloudFront`를 재사용하고 기존 크리에이터 관리자와 같은 만료 정책을 따른다.
|
- signed URL은 공통 `AudioContentCloudFront`를 재사용하고 기존 크리에이터 관리자와 같은 만료 정책을 따른다.
|
||||||
@@ -135,6 +136,9 @@
|
|||||||
### Feature F. FanTalk 답변
|
### Feature F. FanTalk 답변
|
||||||
|
|
||||||
#### Requirements
|
#### Requirements
|
||||||
|
- 선택한 AI 캐릭터의 FanTalk 목록과 각 root 글의 creator reply 목록을 관리자 전용 endpoint로 조회한다.
|
||||||
|
- 목록 응답 필드와 page 정책은 공개 v2 `CreatorChannelFanTalkTabResponse`를 유지하되, 공개 v2 endpoint를 직접 재사용하지
|
||||||
|
않고 `characterId` target 해석과 관리자 ownership 정책을 적용한다.
|
||||||
- 선택한 AI 캐릭터가 자신의 활성 root FanTalk에 creator reply를 작성한다.
|
- 선택한 AI 캐릭터가 자신의 활성 root FanTalk에 creator reply를 작성한다.
|
||||||
- 요청은 `characterId`와 대상 root `fanTalkId`를 포함한다.
|
- 요청은 `characterId`와 대상 root `fanTalkId`를 포함한다.
|
||||||
- 대상 FanTalk가 존재하고 활성 상태이며, 대상 creator가 해석된 `creatorMember`와 일치하는 root 글인지 검증한다.
|
- 대상 FanTalk가 존재하고 활성 상태이며, 대상 creator가 해석된 `creatorMember`와 일치하는 root 글인지 검증한다.
|
||||||
@@ -181,12 +185,23 @@
|
|||||||
- 이후 phase의 domain/client/server 오류는 각 task에서 정확한 HTTP status와 KO/EN/JA message key를 먼저 정의하고 같은
|
- 이후 phase의 domain/client/server 오류는 각 task에서 정확한 HTTP status와 KO/EN/JA message key를 먼저 정의하고 같은
|
||||||
envelope를 적용한다.
|
envelope를 적용한다.
|
||||||
- 신규 prefix 전용 오류 처리는 legacy/public endpoint의 기존 성공·오류 응답에 적용하지 않는다.
|
- 신규 prefix 전용 오류 처리는 legacy/public endpoint의 기존 성공·오류 응답에 적용하지 않는다.
|
||||||
- page 기반 조회는 기존 v2 탭 API 관례를 따라 `page` 기본값 0, `size` 기본값 20, 최소 20, 최대 50 보정을 기본안으로 하며, 경계값 보정은 구현 task와 테스트에 포함한다.
|
- request/response의 기계 검증 가능한 단일 기준은 같은 디렉터리의 `api-contract.openapi.json`이다. 설명과 레거시 근거는
|
||||||
|
`api-contract.md`에 기록한다.
|
||||||
|
- 신규 endpoint는 레거시 API의 request/response 필드명, 타입, optional/nullable, 기본값과 성공 `data` 형태를 그대로
|
||||||
|
이관한다. `characterId`, `contentId`, `seriesId`, `postId`, `fanTalkId`처럼 신규 path로 이동한 ID만 request body에서
|
||||||
|
중복 제거한다.
|
||||||
|
- 캐릭터 수정은 레거시 `ChatCharacterUpdateRequest`처럼 `isActive=false`와 다른 optional field의 동시 입력을 허용하며,
|
||||||
|
이 경우 레거시 service 의미대로 비활성화만 반영한다.
|
||||||
|
- 목록 endpoint의 query와 page 동작은 각 레거시 API를 따른다. FanTalk 관리자 목록만 공개 v2 탭의 `page` 기본값 0,
|
||||||
|
`size` 기본값 20, 최소 20, 최대 50 보정을 따른다.
|
||||||
- multipart 생성/수정은 기존 admin/creator-admin 관례대로 파일 part와 `request` JSON string part를 사용한다.
|
- multipart 생성/수정은 기존 admin/creator-admin 관례대로 파일 part와 `request` JSON string part를 사용한다.
|
||||||
- `GET /api/v2/admin/ai-characters/audio-content-themes`는 request body 없이 활성 콘텐츠 테마 목록을 반환한다. 성공 응답 `data`는 `[{ "themeId": 11, "themeName": "ASMR", "imageUrl": "https://cdn.example.com/audio-content-theme/asmr.png" }]` 형태이며, 기존 내부/legacy DTO의 `id`, `theme`, `image` 필드명을 외부 계약으로 노출하지 않는다.
|
- 시리즈 연결 콘텐츠 목록과 미연결 콘텐츠 검색은 응답 형태가 다르므로 각각
|
||||||
- 오디오 콘텐츠 생성 `request` JSON에는 콘텐츠 테마 선택값인 `themeId`와 기존 `CreateAudioContentRequest`의 생성 필드 전체를 포함한다. v2는 `detail` 대신 `description`, `releaseDate` 대신 UTC ISO-8601 `releaseDateUtc`를 외부 계약으로 사용하고 legacy pipeline 호출 시 변환한다.
|
`GET .../series/{seriesId}/contents`와 `GET .../series/{seriesId}/contents/search?search_word=...`로 분리한다.
|
||||||
- 오디오 콘텐츠 목록 응답은 현 v2 관리자 목록 계약을 유지한다. 상세 응답은 기존 `GetAudioContentDetailResponse`의 필드 전체를 v2 상세 DTO에 포함하되, 구매·좋아요·핀·추천·댓글 목록처럼 viewer 상태가 필요한 필드는 관리자 상세에서 안전한 기본값을 반환한다.
|
- 레거시 mutation이 `ApiResponse.ok(null)`을 반환하면 신규 endpoint도 `data: null`을 반환한다. 오디오 콘텐츠 생성은
|
||||||
- request/response DTO는 신규 v2 AI character admin API 전용 DTO로 두고 legacy/public DTO를 외부 계약으로 재노출하지 않는다.
|
레거시 `CreateAudioContentResponse(contentId)`를 유지한다. FanTalk 답변 작성만 신규 계획의 축약 응답
|
||||||
|
`fanTalkId`, `replyId`, `creatorMemberId`, `content`, `createdAtUtc`를 사용한다.
|
||||||
|
- request/response 구현 DTO는 신규 v2 AI character admin API 전용으로 둘 수 있지만, JSON 외부 계약은
|
||||||
|
`api-contract.openapi.json`의 레거시 필드명과 형태를 유지한다.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -248,6 +263,8 @@
|
|||||||
- 콘텐츠 생성/수정/soft delete와 signed URL 응답은 기존 creator/admin behavior parity를 유지한다.
|
- 콘텐츠 생성/수정/soft delete와 signed URL 응답은 기존 creator/admin behavior parity를 유지한다.
|
||||||
- 시리즈 CRUD, 콘텐츠 연결/해제/순서 변경은 target character 소유 범위를 벗어나지 않는다.
|
- 시리즈 CRUD, 콘텐츠 연결/해제/순서 변경은 target character 소유 범위를 벗어나지 않는다.
|
||||||
- 커뮤니티 생성/수정/고정/해제/soft delete는 target creatorMember 소유 게시글에만 적용된다.
|
- 커뮤니티 생성/수정/고정/해제/soft delete는 target creatorMember 소유 게시글에만 적용된다.
|
||||||
|
- FanTalk 관리자 목록은 target AI character의 root 글과 creator reply를 공개 v2 응답 필드명으로 반환하며, 공개 v2의
|
||||||
|
viewer/block 필터나 creator 관리자 CORS 경계에 의존하지 않는다.
|
||||||
- FanTalk 답변은 target AI character 자신의 활성 root FanTalk에만 저장된다.
|
- FanTalk 답변은 target AI character 자신의 활성 root FanTalk에만 저장된다.
|
||||||
- 기존 legacy/public endpoint 테스트가 통과하고, 두 공유 인증 경로의 CORS 허용 Origin 확장 외 성공·오류
|
- 기존 legacy/public endpoint 테스트가 통과하고, 두 공유 인증 경로의 CORS 허용 Origin 확장 외 성공·오류
|
||||||
status/body/message를 포함한 request/response contract가 변경되지 않는다.
|
status/body/message를 포함한 request/response contract가 변경되지 않는다.
|
||||||
|
|||||||
Reference in New Issue
Block a user