기존 single-shot POST /upload는 그대로 유지하고, 5GB+ 안정성을 위한
chunk upload 5-endpoint를 추가했다.
- POST /upload/init — mint-token jti consume + 세션 디렉토리 생성
- PUT /upload/{sid}/chunk?offset=N — offset 매칭 후 .part 파일 append
· 불일치 시 409 + X-Current-Offset 헤더로 재개 지점 통보
- GET /upload/{sid}/status — 현재 written / expected_size 조회
- POST /upload/{sid}/complete — atomic rename + Supabase INSERT
- DELETE /upload/{sid} — 세션 중단 + 부분파일 정리
auth.py: verify_upload_token_no_consume() 추가 — chunk/complete/abort/status
는 동일 mint-token을 재사용해야 하므로 jti consume 없이 시그니처+만료만 검증.
models.py: InitUploadResponse, ChunkUploadResponse 추가.
세션 state: PACK_BASE_DIR/.uploads/{jti}/meta.json + data.part (파일시스템
영속, 단일 컨테이너 가정).
chunk 크기 상한: PACK_CHUNK_MAX_SIZE env (기본 64MB).
tests: chunk upload 시나리오 8종 — full-flow / offset mismatch / status /
abort / wrong token / incomplete complete / filename collision / host path
저장. 전체 37 테스트 pass.
CLAUDE.md: packs-lab API 표에 chunk 5-endpoint + 사용 패턴 보강.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
67 lines
1.6 KiB
Python
67 lines
1.6 KiB
Python
"""Pydantic schemas for packs API."""
|
|
from datetime import datetime
|
|
from typing import Literal
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
PackTier = Literal["starter", "pro", "master"]
|
|
|
|
|
|
class SignLinkRequest(BaseModel):
|
|
"""Vercel → backend: 사용자 다운로드 링크 발급 요청."""
|
|
file_path: str = Field(..., description="NAS 절대 경로 — pack_files.file_path 그대로")
|
|
expires_in_seconds: int = Field(default=14400, description="공유 링크 만료 (기본 4시간)")
|
|
|
|
|
|
class SignLinkResponse(BaseModel):
|
|
url: str
|
|
expires_at: datetime
|
|
|
|
|
|
class UploadResponse(BaseModel):
|
|
file_id: str # uuid
|
|
file_path: str
|
|
filename: str
|
|
size_bytes: int
|
|
min_tier: PackTier
|
|
label: str
|
|
uploaded_at: datetime
|
|
|
|
|
|
class PackFileItem(BaseModel):
|
|
id: str
|
|
min_tier: PackTier
|
|
label: str
|
|
file_path: str
|
|
filename: str
|
|
size_bytes: int
|
|
sort_order: int
|
|
uploaded_at: datetime
|
|
|
|
|
|
class MintTokenRequest(BaseModel):
|
|
"""Vercel → backend: admin upload 토큰 발급 요청."""
|
|
tier: PackTier
|
|
label: str = Field(..., max_length=200)
|
|
filename: str = Field(..., max_length=255)
|
|
size_bytes: int = Field(..., gt=0, le=5 * 1024 * 1024 * 1024)
|
|
|
|
|
|
class MintTokenResponse(BaseModel):
|
|
token: str
|
|
expires_at: datetime
|
|
jti: str
|
|
|
|
|
|
class InitUploadResponse(BaseModel):
|
|
"""chunked upload 세션 초기화 응답. session_id는 mint-token의 jti와 동일."""
|
|
session_id: str
|
|
chunk_max_size: int
|
|
expected_size: int
|
|
expires_at: datetime
|
|
|
|
|
|
class ChunkUploadResponse(BaseModel):
|
|
written: int
|
|
expected_size: int
|