POST /ai/admin/proxy-submission/applications/{applicationId}/review
관리자가 크리에이터 대신 1·2차 검수를 제출
검수 대리제출 (관리자용)
크리에이터가 직접 제출하지 못하는 상황에서 관리자가 1·2차 검수 제출을 대신 수행합니다. 처리 결과는 크리에이터가 웹앱에서 직접 제출한 것과 동일합니다.
매직링크와의 차이
제출 링크(매직링크)는 1회용 링크를 발급해 크리에이터가 그 링크를 열어야 제출됩니다. 크리에이터가 링크 자체를 열지 못하는 경우(화면 진입 불가, 링크 미수신 등)에는 쓸 수 없습니다. 이 API는 링크 없이 관리자가 바로 제출합니다.
처리 순서
applicationId로 신청 조회reviewId가 그 신청의 검수 라운드인지 검증 (다른 신청 제출 방지)- VIDEO 항목은 트랜스코딩 트리거 (웹앱·매직링크와 동일한 호출)
- 기존 일괄 제출(batchSubmit) 호출 → 저장 · 제출 · 히스토리 · 피드백 해결
- 광고주 "제출했습니다" 알림 발행
3번과 5번은 이 API가 직접 수행합니다. 크리에이터 제출 API(PUT /ai/influence/contents/review/{reviewId}/submit)를 관리자 토큰으로 그대로 호출하면 두 가지가 빠집니다. 광고주 알림은 원래 크리에이터 웹앱이 브라우저에서 발송하는 구조라, 서버 경로로 제출하면 발화하지 않습니다.
웹앱 경로는 변경되지 않았으므로 중복 발송은 발생하지 않습니다. 한 건의 제출은 웹앱·매직링크·대리제출 중 한 경로만 탑니다.
HTTP 요청
POST /ai/admin/proxy-submission/applications/{applicationId}/review
Authorization: Bearer {access_token}
Content-Type: application/jsonPath Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
applicationId | long | 예 | 대상 신청(CampaignApplication) ID |
Request Body
{
"reviewId": 1090,
"items": [
{
"itemType": "VIDEO",
"filePath": "https://storage.googleapis.com/glowb-input/campaign/content/admin/20652/...mp4"
},
{
"itemType": "CAPTION",
"editorState": "{\"root\":{\"children\":[]}}"
},
{
"itemType": "HASHTAG",
"editorState": "{\"root\":{\"children\":[]}}"
}
]
}| 필드명 | 타입 | 필수 | 설명 |
|---|---|---|---|
reviewId | long | 예 | 제출할 검수 라운드 ID. 해당 신청의 라운드여야 합니다 |
items | array | 예 | 제출물 목록 (크리에이터 일괄 제출과 동일 구조) |
items[].itemId | long | 조건부 | 제출물 ID (기존 항목 수정 시 필수, 신규는 생략) |
items[].itemType | string | 조건부 | 제출물 타입 (신규 시 필수). SCRIPT_VIDEO, SCRIPT_BLOG, VIDEO, PHOTO, CAPTION, HASHTAG |
items[].scriptIndex | integer | 아니오 | 장면 번호(0부터). SCRIPT_VIDEO/SCRIPT_BLOG 다중 장면용. 생략 시 서버가 자동 부여. 그 외 타입은 무시 |
items[].filePath | string | 조건부 | GCS URL. VIDEO/PHOTO는 필수이며, 비어 있으면 에러 |
items[].editorState | string | 아니오 | 에디터 상태 JSON을 문자열로 직렬화(stringify) 한 값 |
items[].comment | string | 아니오 | 크리에이터 코멘트 (Lexical 에디터 JSON 문자열). 생략하면 재제출 시 이전 차수 코멘트는 비워집니다 (새 차수는 빈칸에서 시작) |
editorState는 반드시 문자열입니다. 객체째 보내면 Cannot deserialize String from Object 로 실패합니다.
영상이 포함된 제출
VIDEO/PHOTO 항목의 filePath는 업로드 URL 발급으로 받은 publicUrl을 그대로 넣습니다. 업로드를 먼저 끝낸 뒤 이 API를 호출합니다.
응답
성공 응답 (200 OK) — 크리에이터 일괄 제출과 동일한 BatchResponse
{
"status": 200,
"code": null,
"message": "제출 완료",
"data": {
"reviewId": 1090,
"items": [ { "id": 3879, "itemType": "VIDEO", "isSubmitted": true } ],
"resolvedFeedbackCount": 0
}
}에러 응답
이 서버는 에러도 HTTP 200으로 내려보내고, 실제 상태는 응답 본문의 status/code 에 담습니다. 클라이언트는 HTTP 상태가 아니라 본문을 보고 분기해야 합니다. (인증·인가 실패만 예외적으로 실제 401/403)
| 상황 | 본문 status | 메시지 |
|---|---|---|
| 신청 없음 | 404 | 신청을 찾을 수 없습니다. |
reviewId 누락 | 404 | reviewId 는 필수입니다. |
| 검수 라운드 없음 | 404 | 검수 라운드를 찾을 수 없습니다. |
| 다른 신청의 라운드 | 404 | 해당 신청의 검수 라운드가 아닙니다. |
| 제출물 없음 | 404 | 제출할 제출물이 없습니다. |
| VIDEO/PHOTO 파일 없음 | 404 | 영상/사진 파일을 업로드해주세요. |
| 인증 실패 | HTTP 401 | 토큰이 만료되었습니다. |
| ADMIN 권한 아님 | HTTP 403 | 권한이 없는 요청 입니다. |
감사 기록
제출 주체를 담는 컬럼이 없어 DB만 보면 크리에이터 본인 제출과 구분되지 않습니다. 누가 대신 제출했는지는 애플리케이션 로그로만 남습니다.
[AdminProxySubmission] 검수 대리제출 완료. adminId=..., applicationId=..., reviewId=..., influenceNo=...