1. API 목록

2. GET: 제출 파일 다운로드 대상 목록 조회

선택한 '제출물 종류 및 분과' 조합들의 다운로드 대상 목록(제출 팀 수, 예상 용량)을 반환합니다.

Table 1. /contests/{contestId}/submissions/downloads
Parameter Description

contestId

대회 ID

Table 2. Query Parameters
Parameter Description

submissionTypeId

제출물 종류 ID (선택)

trackId

분과 ID (선택)

Table 3. HTTP Request Headers
Name Description

Authorization

Bearer {accessToken} (관리자)

HTTP Request
GET /contests/1/submissions/downloads?submissionTypeId=3&trackId=2 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: 221

{
  "targets" : [ {
    "submissionTypeId" : 3,
    "submissionTypeName" : "최종 발표 자료",
    "trackId" : 2,
    "trackName" : "AI/데이터",
    "submittedTeamCount" : 6,
    "estimatedSize" : 314572800
  } ]
}
Table 4. Response Body’s Fields
Path Type Description

targets

Array

다운로드 대상 목록

targets[].submissionTypeId

Number

제출물 종류 ID

targets[].submissionTypeName

String

제출물 종류명

targets[].trackId

Number

분과 ID

targets[].trackName

String

분과명

targets[].submittedTeamCount

Number

제출 팀 수

targets[].estimatedSize

Number

예상 용량 (byte)

2.1. ⚠️ 실패 케이스

❌ Case 1: 존재하지 않는 대회
GET /contests/999/submissions/downloads 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" : "존재하지 않는 대회입니다."
}

3. POST: 제출 파일 일괄 다운로드 (zip)

선택한 조합들에 속한 모든 팀의 제출 파일을 하나의 zip으로 묶어 내려줍니다. 팀별 폴더 구조를 유지합니다.

Table 5. /contests/{contestId}/submissions/downloads
Parameter Description

contestId

대회 ID

Table 6. HTTP Request Headers
Name Description

Authorization

Bearer {accessToken} (관리자)

HTTP Request
POST /contests/1/submissions/downloads HTTP/1.1
Content-Type: application/json;charset=UTF-8
Authorization: Bearer admin.access.token
Content-Length: 126
Host: localhost:8080

{
  "targets" : [ {
    "submissionTypeId" : 3,
    "trackId" : 2
  }, {
    "submissionTypeId" : 3,
    "trackId" : 5
  } ]
}
Table 7. Request Body’s Fields
Path Type Description

targets

Array

다운로드 대상 조합

targets[].submissionTypeId

Number

제출물 종류 ID

targets[].trackId

Number

분과 ID (선택, 생략 시 전체 분과)

HTTP Response
HTTP/1.1 200 OK
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Content-Type: application/zip;charset=UTF-8
Content-Disposition: attachment; filename="=?UTF-8?Q?2026-PNUops=5F20260605.zip?="; filename*=UTF-8''2026-PNUops_20260605.zip
Content-Length: 10

zip-binary
Table 8. Response Headers
Name Description

Content-Type

application/zip

Content-Disposition

attachment; filename="…​zip"

3.1. ⚠️ 실패 케이스

❌ Case 1: 빈 배열 (다운로드 대상 없음)
POST /contests/1/submissions/downloads HTTP/1.1
Content-Type: application/json;charset=UTF-8
Authorization: Bearer admin.access.token
Content-Length: 21
Host: localhost:8080

{
  "targets" : [ ]
}
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: 89

{
  "message" : "[targets] [] : 다운로드 대상은 비어 있을 수 없습니다."
}
❌ Case 2: 대상에 해당하는 제출 없음
POST /contests/1/submissions/downloads HTTP/1.1
Content-Type: application/json;charset=UTF-8
Authorization: Bearer admin.access.token
Content-Length: 73
Host: localhost:8080

{
  "targets" : [ {
    "submissionTypeId" : 3,
    "trackId" : 2
  } ]
}
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: 62

{
  "message" : "다운로드할 제출물이 없습니다."
}

4. GET: 제출 파일 개별 다운로드

제출물 상세에서 파일을 하나씩 내려받습니다.

Table 9. /contests/{contestId}/submissions/{submissionId}/files/{fileId}
Parameter Description

contestId

대회 ID

submissionId

제출 ID

fileId

파일 ID

Table 10. HTTP Request Headers
Name Description

Authorization

Bearer {accessToken} (관리자)

HTTP Request
GET /contests/1/submissions/12/files/101 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/pdf;charset=UTF-8
Content-Disposition: attachment; filename="=?UTF-8?Q?=EB=B0=9C=ED=91=9C=EC=9E=90=EB=A3=8C.pdf?="; filename*=UTF-8''%EB%B0%9C%ED%91%9C%EC%9E%90%EB%A3%8C.pdf
Content-Length: 10
Accept-Ranges: bytes

pdf-binary
Table 11. Response Headers
Name Description

Content-Type

파일 MIME 타입

Content-Disposition

attachment; filename="발표자료.pdf"

Content-Length

파일 크기(byte)

4.1. ⚠️ 실패 케이스

❌ Case 1: 존재하지 않는 파일
GET /contests/1/submissions/12/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" : "파일을 찾을 수 없습니다"
}
❌ Case 2: 존재하지 않는 제출물
GET /contests/1/submissions/999/files/101 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 3: 물리적 파일 없음
GET /contests/1/submissions/12/files/101 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: 65

{
  "message" : "물리적 파일이 존재하지 않습니다"
}