1. API 목록

2. POST: 제출물 피드백 작성 (Upsert)

외부멘토가 특정 제출물에 본인 피드백을 작성하여 저장합니다. 본인 피드백이 없으면 생성, 있으면 수정합니다. 첨부파일은 추가(files) / 삭제(removeFileIds)를 함께 처리합니다. (multipart/form-data)

Table 1. /contests/{contestId}/submissions/{submissionId}/feedbacks
Parameter Description

contestId

대회 ID

submissionId

제출물 ID

Table 2. HTTP Request Headers
Name Description

Authorization

Bearer {accessToken}

HTTP Request
PUT /contests/1/submissions/12/feedbacks HTTP/1.1
Content-Type: multipart/form-data;charset=UTF-8; boundary=6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm
Authorization: Bearer mentor.access.token
Host: localhost:8080

--6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm
Content-Disposition: form-data; name=files; filename=피드백.pdf
Content-Type: application/pdf

feedback-content
--6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm--
Table 3. Request Parts
Part Description

files

신규 추가 첨부파일 (선택, 다중 업로드 허용)

HTTP Response
HTTP/1.1 200 OK
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers

2.1. ⚠️ 실패 케이스

❌ Case 1: 존재하지 않는 제출물
PUT /contests/1/submissions/999/feedbacks HTTP/1.1
Content-Type: multipart/form-data;charset=UTF-8
Authorization: Bearer mentor.access.token
Host: localhost:8080
HTTP/1.1 404 Not Found
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 59

{
  "message" : "존재하지 않는 제출물입니다."
}
❌ Case 2: 본문이 비어있음
PUT /contests/1/submissions/12/feedbacks HTTP/1.1
Content-Type: multipart/form-data;charset=UTF-8
Authorization: Bearer mentor.access.token
Host: localhost:8080
HTTP/1.1 400 Bad Request
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 73

{
  "message" : "[description]  : 피드백 본문은 필수입니다."
}
❌ Case 3: 존재하지 않는 대회
PUT /contests/999/submissions/12/feedbacks HTTP/1.1
Content-Type: multipart/form-data;charset=UTF-8
Authorization: Bearer mentor.access.token
Host: localhost:8080
HTTP/1.1 404 Not Found
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 56

{
  "message" : "존재하지 않는 대회입니다."
}
❌ Case 4: 빈 파일 첨부
PUT /contests/1/submissions/12/feedbacks HTTP/1.1
Content-Type: multipart/form-data;charset=UTF-8; boundary=6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm
Authorization: Bearer mentor.access.token
Host: localhost:8080

--6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm
Content-Disposition: form-data; name=files; filename=빈파일.pdf
Content-Type: application/pdf


--6o2knFse3p53ty9dmcQvWAIx1zInP11uCfbm--
HTTP/1.1 400 Bad Request
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 85

{
  "message" : "업로드할 파일이 비어 있거나 존재하지 않습니다"
}
❌ Case 5: 삭제할 첨부파일을 찾을 수 없음
PUT /contests/1/submissions/12/feedbacks HTTP/1.1
Content-Type: multipart/form-data;charset=UTF-8
Authorization: Bearer mentor.access.token
Host: localhost:8080
HTTP/1.1 404 Not Found
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 53

{
  "message" : "파일을 찾을 수 없습니다"
}

3. GET: 내 피드백 단건 조회

외부멘토가 특정 제출물에 본인이 작성한 피드백 단건을 조회합니다. (작성 폼 프리필용)

Table 4. /contests/{contestId}/submissions/{submissionId}/feedbacks/me
Parameter Description

contestId

대회 ID

submissionId

제출물 ID

Table 5. HTTP Request Headers
Name Description

Authorization

Bearer {accessToken}

HTTP Request
GET /contests/1/submissions/12/feedbacks/me HTTP/1.1
Authorization: Bearer mentor.access.token
Host: localhost:8080
HTTP Response
HTTP/1.1 200 OK
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 299

{
  "feedbackId" : 50,
  "description" : "발표 흐름이 좋네요. 데모 영상 길이만 조금 줄여보세요.",
  "createdAt" : "2026-06-02T11:00:00",
  "updatedAt" : "2026-06-02T11:00:00",
  "files" : [ {
    "fileId" : 201,
    "fileName" : "피드백.pdf",
    "fileSize" : 524288
  } ]
}
Table 6. Response Body’s Fields
Path Type Description

feedbackId

Number

피드백 ID

description

String

피드백 본문

createdAt

String

작성 시각 (ISO-8601)

updatedAt

String

마지막 수정 시각 (ISO-8601)

files

Array

첨부파일 목록

files[].fileId

Number

첨부파일 ID

files[].fileName

String

첨부파일명

files[].fileSize

Number

첨부파일 용량 (byte)

3.1. ⚠️ 실패 케이스

❌ Case 1: 작성한 피드백 없음
GET /contests/1/submissions/12/feedbacks/me HTTP/1.1
Authorization: Bearer mentor.access.token
Host: localhost:8080
HTTP/1.1 404 Not Found
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 59

{
  "message" : "존재하지 않는 피드백입니다."
}
❌ Case 2: 존재하지 않는 대회
GET /contests/999/submissions/12/feedbacks/me HTTP/1.1
Authorization: Bearer mentor.access.token
Host: localhost:8080
HTTP/1.1 404 Not Found
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 56

{
  "message" : "존재하지 않는 대회입니다."
}
❌ Case 3: 존재하지 않는 제출물
GET /contests/1/submissions/999/feedbacks/me HTTP/1.1
Authorization: Bearer mentor.access.token
Host: localhost:8080
HTTP/1.1 404 Not Found
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 59

{
  "message" : "존재하지 않는 제출물입니다."
}

4. GET: 제출물 피드백 목록 조회 (관리자)

관리자 제출 자료 관리 페이지에서 특정 제출물에 달린 모든 멘토의 피드백 목록을 조회합니다.

Table 7. /contests/{contestId}/submissions/{submissionId}/feedbacks
Parameter Description

contestId

대회 ID

submissionId

제출물 ID

Table 8. HTTP Request Headers
Name Description

Authorization

Bearer {accessToken}

HTTP Request
GET /contests/1/submissions/12/feedbacks HTTP/1.1
Authorization: Bearer admin.access.token
Host: localhost:8080
HTTP Response
HTTP/1.1 200 OK
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 351

[ {
  "feedbackId" : 50,
  "memberId" : 5,
  "memberName" : "이지민",
  "description" : "발표 흐름이 좋네요. 데모 영상 길이만 조금 줄여보세요.",
  "createdAt" : "2026-06-02T11:00:00",
  "updatedAt" : "2026-06-02T11:00:00",
  "files" : [ {
    "fileId" : 201,
    "fileName" : "피드백.pdf",
    "fileSize" : 524288
  } ]
} ]
Table 9. Response Body’s Fields
Path Type Description

[]

Array

피드백 목록

[].feedbackId

Number

피드백 ID

[].memberId

Number

작성자 ID

[].memberName

String

작성자 이름

[].description

String

피드백 본문

[].createdAt

String

작성 시각 (ISO-8601)

[].updatedAt

String

마지막 수정 시각 (ISO-8601)

[].files

Array

첨부파일 목록

[].files[].fileId

Number

첨부파일 ID

[].files[].fileName

String

첨부파일명

[].files[].fileSize

Number

첨부파일 용량 (byte)

4.1. ⚠️ 실패 케이스

❌ Case 1: 존재하지 않는 대회
GET /contests/999/submissions/12/feedbacks HTTP/1.1
Authorization: Bearer admin.access.token
Host: localhost:8080
HTTP/1.1 404 Not Found
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 56

{
  "message" : "존재하지 않는 대회입니다."
}
❌ Case 2: 존재하지 않는 제출물
GET /contests/1/submissions/999/feedbacks HTTP/1.1
Authorization: Bearer admin.access.token
Host: localhost:8080
HTTP/1.1 404 Not Found
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 59

{
  "message" : "존재하지 않는 제출물입니다."
}

5. GET: 피드백 첨부파일 다운로드 (관리자)

관리자 페이지에서 특정 피드백에 첨부된 파일을 개별 다운로드합니다. `fileId`가 `feedbackId`에, `feedbackId`가 `submissionId`에 속하는지 계층 검증합니다.

Table 10. /contests/{contestId}/submissions/{submissionId}/feedbacks/{feedbackId}/files/{fileId}
Parameter Description

contestId

대회 ID

submissionId

제출물 ID

feedbackId

피드백 ID

fileId

첨부파일 ID

Table 11. HTTP Request Headers
Name Description

Authorization

Bearer {accessToken}

HTTP Request
GET /contests/1/submissions/12/feedbacks/50/files/201 HTTP/1.1
Authorization: Bearer admin.access.token
Host: localhost:8080
HTTP Response
HTTP/1.1 200 OK
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Disposition: attachment; filename="=?UTF-8?Q?=ED=94=BC=EB=93=9C=EB=B0=B1.pdf?="; filename*=UTF-8''%ED%94%BC%EB%93%9C%EB%B0%B1.pdf
Content-Type: application/pdf;charset=UTF-8
Accept-Ranges: bytes
Content-Length: 13

feedback-file

5.1. ⚠️ 실패 케이스

❌ Case 1: 존재하지 않는 피드백
GET /contests/1/submissions/12/feedbacks/999/files/201 HTTP/1.1
Authorization: Bearer admin.access.token
Host: localhost:8080
HTTP/1.1 404 Not Found
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 59

{
  "message" : "존재하지 않는 피드백입니다."
}
❌ Case 2: 피드백에 속하지 않은 파일
GET /contests/1/submissions/12/feedbacks/50/files/999 HTTP/1.1
Authorization: Bearer admin.access.token
Host: localhost:8080
HTTP/1.1 404 Not Found
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/json;charset=UTF-8
Content-Length: 53

{
  "message" : "파일을 찾을 수 없습니다"
}