SaaS API콘텐츠 검수
PUT /ai/influence/contents/review/{reviewId}/draft
검수 라운드 일괄 임시저장 (장면 순서 반영)
검수 라운드 일괄 임시저장
검수 라운드의 모든 제출물 내용을 한 번에 임시저장합니다. 요청 items의 배열 순서가 스크립트 장면 순서로 저장됩니다.
장면 순서를 바꿀 때는 이 API를 사용해야 합니다
PUT /ai/influence/contents/{itemId}를 장면마다 여러 번 호출하는 방식으로는 순서 변경을 저장할 수 없습니다.
자리 맞바꿈은 두 장면의 번호를 동시에 바꿔야 하는 연산인데, 단건 호출로 쪼개면 호출 사이에 번호가 겹치는 구간이 생깁니다. 그 구간에서 요청이 실패하거나 사용자가 이탈하면 겹친 상태가 그대로 남습니다.
이 API는 전 항목을 한 트랜잭션에서 처리하므로 중간 상태가 외부에 보이지 않습니다.
수행하는 일 / 하지 않는 일
수행:
- 각 제출물의 내용(
filePath,editorState) 저장 - 스크립트 장면 순서를 요청 배열 순서대로 재배치 (
scriptIndex를 0부터 다시 매김)
하지 않음:
- 제출 처리 (
isSubmitted·status변경 없음) - 히스토리 저장 / 버전 증가
- 피드백 해결완료 처리
- AI 검수 트리거, 알림 발송
제출은 PUT /review/{reviewId}/submit을 사용합니다.
HTTP 요청
PUT /ai/influence/contents/review/{reviewId}/draft
Authorization: Bearer {access_token}
Content-Type: application/jsonPath Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
reviewId | long | 예 | 검수 라운드 ID |
Request Body
PUT /review/{reviewId}/submit과 동일한 스키마를 사용합니다.
{
"items": [
{
"itemId": 3,
"itemType": "SCRIPT_VIDEO",
"editorState": {"root": {"children": []}}
},
{
"itemId": 1,
"itemType": "SCRIPT_VIDEO",
"editorState": {"root": {"children": []}}
},
{
"itemId": null,
"itemType": "SCRIPT_VIDEO",
"editorState": {"root": {"children": []}}
}
]
}Request Body 스키마
| 필드명 | 타입 | 필수 | 설명 |
|---|---|---|---|
items | array | 예 | 저장할 제출물 목록. 배열 순서가 곧 장면 순서입니다. |
items[].itemId | long | 조건부 | 제출물 ID (기존 항목 수정 시 필수, 신규 시 null) |
items[].itemType | string | 조건부 | 제출물 타입 (신규 생성 시 필수) |
items[].scriptIndex | int | 아니오 | 신규 장면의 희망 번호. 최종 순서는 배열 순서로 덮어써지므로 생략해도 됩니다. |
items[].filePath | string | 아니오 | S3 파일 URL (임시저장이므로 VIDEO/PHOTO도 필수 아님) |
items[].editorState | object | 아니오 | Lexical 에디터 상태 JSON |
순서 재배치 규칙
- 요청에 담긴 스크립트가 배열 순서대로
scriptIndex0, 1, 2… 를 받습니다. - 요청에 담기지 않은 기존 장면은 그 뒤로 이어붙습니다. 결과적으로 번호는 항상
0..N-1연속이 되며, 과거에 생긴 번호 구멍도 함께 정리됩니다. - 스크립트 타입(
SCRIPT_VIDEO,SCRIPT_BLOG)에만 적용됩니다.CAPTION,HASHTAG등은scriptIndex가null입니다.
신규 장면이 기존 장면을 덮어쓰지 않습니다
itemId 없이 스크립트를 저장할 때 요청한 scriptIndex가 이미 쓰이고 있으면,
그 장면을 재사용하지 않고 뒤 번호로 새 장면을 만듭니다.
예전에는 같은 번호의 기존 장면을 재사용해 내용을 덮어썼고, 그 결과 장면이 통째로 사라질 수 있었습니다.
응답
성공 응답 (200 OK)
{
"status": 200,
"code": null,
"message": "임시저장 완료",
"data": {
"reviewId": 902,
"items": [
{
"id": 3,
"reviewId": 902,
"itemType": "SCRIPT_VIDEO",
"scriptIndex": 0,
"editorState": {"root": {"children": []}},
"status": "PENDING",
"isSubmitted": false,
"submittedAt": null,
"currentVersion": 1,
"feedbacks": []
},
{
"id": 1,
"reviewId": 902,
"itemType": "SCRIPT_VIDEO",
"scriptIndex": 1,
"editorState": {"root": {"children": []}},
"status": "PENDING",
"isSubmitted": false,
"submittedAt": null,
"currentVersion": 1,
"feedbacks": []
}
],
"resolvedFeedbackCount": 0
}
}Response Body 스키마
| 필드명 | 타입 | 설명 |
|---|---|---|
reviewId | long | 검수 라운드 ID |
items | array | 저장된 제출물 목록 (재배치된 scriptIndex 반영) |
resolvedFeedbackCount | int | 항상 0 (임시저장은 피드백을 해결하지 않음) |
에러 응답
| 상황 | 상태 코드 | 메시지 |
|---|---|---|
| 검수 라운드 없음 | 400 | 검수 라운드를 찾을 수 없습니다. |
| 제출물 없음 | 400 | 저장할 제출물이 없습니다. |
| 제출물 타입 누락 | 400 | 신규 생성 시 제출물 타입은 필수입니다. |
| itemId에 해당하는 제출물 없음 | 400 | 제출물을 찾을 수 없습니다. itemId: {id} |
세 가지 저장 경로 비교
| 항목 | PUT /{itemId} | PUT /review/{reviewId}/draft | PUT /review/{reviewId}/submit |
|---|---|---|---|
| 대상 | 단일 제출물 | 검수 라운드 전체 | 검수 라운드 전체 |
| 장면 순서 반영 | 불가 | 가능 | 가능 |
| 제출 처리 | isSubmit에 따라 | 안 함 | 무조건 제출 |
| 피드백 해결 | 안 함 | 안 함 | 자동 일괄 해결 |
| 히스토리 | 변경 시 저장 | 안 함 | 재제출 시 저장 |
| 용도 | 개별 항목 저장 | 작성 중 임시저장 | 최종 제출 |