Files

214 lines
7.0 KiB
Markdown

# 수정 요청 변경 필드 전송 API Contract
## 1. 공통 계약
이 문서는 기존 [AI 캐릭터 관리자 OpenAPI](../20260725_AI캐릭터관리자웹/api-contract.openapi.json)의 endpoint·DTO를 바꾸지 않고, 프론트엔드가 수정 request를 구성하는 규칙을 구체화한다. 이 문서와 정식 OpenAPI가 충돌하면 정식 OpenAPI의 field type·nullable·response·error 계약을 우선하고 이 문서는 payload 선택 규칙만 소유한다.
### 1.1 유지되는 항목
- 기존 `PUT` method와 path를 유지한다.
- 기존 bearer 인증, `Accept-Language`, 성공 envelope, 오류 status·key를 유지한다.
- 캐릭터·오디오 콘텐츠·커뮤니티 게시글·시리즈는 `multipart/form-data`를 유지한다.
- multipart JSON part 이름은 `request`, MIME은 `application/json`이다.
- FanTalk 답글은 `application/json` body를 유지한다.
### 1.2 변경 판정
1. 상세·목록 응답으로 form을 초기화할 때 수정 기준값을 보존한다.
2. 현재 form 값과 기준값에 같은 기존 직렬화 규칙을 적용한다.
3. 문자열 trim, 빈 optional 값의 `null` 변환, 배열·객체 배열 변환 후 필드별 값을 비교한다.
4. 값이 다른 field만 request object에 포함한다.
5. 사용자가 값을 바꾼 뒤 기준값으로 되돌리면 해당 field를 생략한다.
비교 대상은 각 기능의 update DTO field다. route ID, 조회 전용 field, 서버 계산값과 수정 화면에 없는 field를 request에 복사하지 않는다.
### 1.3 생략, `null`, falsy 값
| 표현 | 의미 | 예시 |
|---|---|---|
| key 생략 | 미변경 | `{ "title": "새 제목" }`에는 `detail` 변경 없음 |
| `null` | 해당 DTO가 허용하는 기존 값 삭제 | `{ "writer": null }` |
| `false` | boolean 값을 false로 변경 | `{ "isAdult": false }` |
| `0` | 숫자 값을 0으로 변경 | `{ "price": 0 }` |
| 배열·객체 배열 | 해당 필드 전체의 새 값 | `{ "publishedDaysOfWeek": ["RANDOM"] }` |
`false`, `0`, 빈값 삭제용 `null`은 falsy 값이라는 이유로 생략하지 않는다.
### 1.4 변경 없음
- 변경 field와 교체 file이 모두 0개면 저장 control은 native `disabled` 상태다.
- disabled 상태에서는 API helper를 호출하지 않는다.
- 빈 JSON 또는 빈 multipart mutation을 전송하지 않는다.
### 1.5 파일 part
| 상태 | 파일 part | `request` JSON |
|---|---|---|
| 텍스트 field만 변경 | 생략 | 변경 field만 포함 |
| 파일과 field 변경 | 새 파일 1개 | 변경 field만 포함 |
| 파일만 변경 | 새 파일 1개 | `{}` |
| 변경 없음 | 요청 자체 없음 | 요청 자체 없음 |
기존 파일을 선택하지 않으면 서버의 현재 파일을 유지한다. 파일 삭제 기능은 이 계약에 추가하지 않는다.
## 2. 기능별 계약
### 2.1 AI 캐릭터
`PUT /api/v2/admin/ai-characters/{characterId}`
- Content-Type: `multipart/form-data`
- optional file part: `image`
- JSON part: `request`
- 비교 가능 field: `name`, `systemPrompt`, `description`, `age`, `gender`, `mbti`, `speechPattern`, `speechStyle`, `appearance`, `originalTitle`, `originalLink`, `originalWorkId`, `characterType`, `tags`, `hobbies`, `values`, `goals`, `relationships`, `personalities`, `backgrounds`, `memories`
- 금지 field: `region`, 일반 수정의 `isActive`
- 기존 예외: 원작 미선택·선택 해제의 `originalWorkId`는 현재 serializer 계약대로 key를 생략하며, 원작 연결 해제 기능은 추가하지 않는다.
이름만 변경:
```json
{
"name": "루나 수정"
}
```
태그를 모두 삭제:
```json
{
"tags": null
}
```
### 2.2 오디오 콘텐츠
`PUT /api/v2/admin/ai-characters/{characterId}/audio-contents/{contentId}`
- Content-Type: `multipart/form-data`
- optional file part: `coverImage`
- JSON part: `request`
- 수정 화면 비교 field: `title`, `detail`, `tags`, `price`
- 일반 수정 금지 field: `isActive`
- 화면에 없는 `isAdult`, `isPointAvailable`, `isCommentAvailable`은 상세 응답에서 복사하지 않는다.
상세 설명만 변경:
```json
{
"detail": "수정한 상세 설명"
}
```
가격만 무료로 변경:
```json
{
"price": 0
}
```
### 2.3 커뮤니티 게시글
`PUT /api/v2/admin/ai-characters/{characterId}/community-posts/{postId}`
- Content-Type: `multipart/form-data`
- optional file part: `postImage`
- JSON part: `request`
- 수정 저장 비교 field: `content`, `isCommentAvailable`, `isAdult`
- 수정 저장 금지 field: `isFixed`, `isActive`
- `isFixed` 전환과 soft delete는 기존 전용 action payload를 유지한다.
내용만 변경:
```json
{
"content": "수정한 게시글 내용"
}
```
댓글 허용만 끄기:
```json
{
"isCommentAvailable": false
}
```
### 2.4 시리즈
`PUT /api/v2/admin/ai-characters/{characterId}/series/{seriesId}`
- Content-Type: `multipart/form-data`
- optional file part: `image`
- JSON part: `request`
- 비교 field: `title`, `introduction`, `publishedDaysOfWeek`, `genreId`, `isAdult`, `state`, `writer`, `studio`
- 일반 수정 금지 field: `isActive`, create-only `keyword`
제목만 변경:
```json
{
"title": "달빛 상담 시리즈 수정"
}
```
작가를 삭제:
```json
{
"writer": null
}
```
### 2.5 FanTalk 답글
`PUT /api/v2/admin/ai-characters/{characterId}/fan-talks/{fanTalkId}/replies/{replyId}`
- Content-Type: `application/json`
- 비교 field: `content`
- 일반 답글 수정 금지 field: `isActive`
답글 변경:
```json
{
"content": "수정한 답글입니다."
}
```
`content`가 기존 답글과 같으면 수정 button은 disabled이고 request를 보내지 않는다.
## 3. 응답과 오류
응답과 오류 계약은 변경하지 않는다.
| 기능 | 성공 data |
|---|---|
| 캐릭터·오디오 콘텐츠·커뮤니티 게시글·시리즈 | 기존 `null` success data |
| FanTalk 답글 | 기존 `FanTalkListItem` update response |
- validation, 401, 403, 404, 406, 415, 500 처리는 정식 OpenAPI와 기존 공통 API client를 따른다.
- 부분 request 도입을 이유로 새로운 status, `errorProperty` 또는 자동 retry를 만들지 않는다.
## 4. Contract 검증 예시
| 시나리오 | 필수 assertion |
|---|---|
| title만 변경 | request key가 `title` 하나다. |
| detail만 변경 | request key가 `detail` 하나다. |
| boolean을 false로 변경 | 해당 key와 `false`가 존재한다. |
| nullable field 삭제 | 해당 key와 `null`이 존재한다. |
| 변경 후 원복 | 저장 disabled, mutation 0건이다. |
| 파일만 변경 | 파일 part 1개, request `{}`다. |
| 파일 미변경 | 파일 part가 없다. |
## 5. 범위 밖 mutation 회귀 계약
다음 기존 요청은 payload 최적화 대상이 아니며 현재 contract를 유지한다.
- 캐릭터·오디오 콘텐츠·시리즈 비활성화: `{ "isActive": false }`
- 커뮤니티 게시글 고정 전환: `{ "isFixed": boolean }`
- 커뮤니티 게시글 soft delete: 기존 `{ "isActive": false, "isFixed": false }`
- 시리즈 순서 변경: `{ "ids": number[] }`
- FanTalk 원글 삭제: body 없는 `DELETE`