# 수정 요청 변경 필드 전송 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`