Skip to content

feat(web): multi-file batch upload endpoint (POST /shrink-batch)#195

Open
seonghobae wants to merge 1 commit into
mainfrom
feat/web-batch-upload
Open

feat(web): multi-file batch upload endpoint (POST /shrink-batch)#195
seonghobae wants to merge 1 commit into
mainfrom
feat/web-batch-upload

Conversation

@seonghobae

Copy link
Copy Markdown
Contributor

Summary

The web app's /shrink accepts exactly one file, so users who batch-shrink folders with the CLI had no web equivalent. This PR adds POST /shrink-batch: upload multiple media files, get back a single zip with every shrunk output plus a results.json manifest.

What's included

  • POST /shrink-batch — accepts list[UploadFile] (up to MAX_BATCH_FILES = 20) and target_bytes, converts each file with the existing media_shrinker.convert_file engine, and returns one ZIP_STORED archive.
  • results.json manifest inside the zip — per-file index, filename, status (ok/error), output_name, output_bytes, and error message.
  • Per-file failure isolation — a failing conversion is recorded in the manifest; the rest of the batch continues.
  • Web UI — additive "Shrink Multiple Files" form (multiple file input) posting to the new endpoint; existing single-file form untouched.
  • /shrink is untouched — handler code and all its existing tests are unmodified and passing.

Validation & security

  • 400 for zero files, more than 20 files, or target_bytes <= 0.
  • Per-file audio/* / video/* content-type allowlist (rejected files recorded in the manifest, not converted).
  • Per-file MAX_UPLOAD_BYTES cap using the same 1 MB chunked save pattern as /shrink; whole request still bounded by the existing request-size middleware.
  • Outputs are only added to the zip if they resolve inside the request's temp workspace (path-escape guard).
  • Isolated input_N/ / output_N/ dirs per upload — duplicate filenames cannot collide; zip entries are index-prefixed.
  • Temp workspace cleaned up after the response via BackgroundTasks.

Tests (13 new, FastAPI TestClient with mocked convert_file)

  • Two files → zip contains both outputs + manifest with statuses/sizes
  • One file fails → other still in zip, manifest records the failure, no internal paths leaked
  • Zero files / 21 files / target_bytes=0 → 400
  • Disallowed content type → per-file manifest error, batch continues
  • No-output result, oversized upload, workspace-creation failure, zip-write failure, outside-workspace output guard, unsafe-filename fallback, UI form presence

Gates

  • coverage report --include=saas_web.py100% (182 stmts, 0 miss)
  • interrogate -c pyproject.toml saas_web.py100%
  • unittest discover -s tests → 126 tests, no new failures (baseline exactly 5 macOS os.listxattr errors)

🤖 Generated with Claude Code

The web UI could only shrink one file per request while the CLI handles
whole folders. This adds a batch endpoint plus a matching upload form:

- POST /shrink-batch accepts up to MAX_BATCH_FILES (20) uploads via
  FastAPI list[UploadFile], converts each with media_shrinker.convert_file,
  and returns one ZIP_STORED archive containing every successful output
  plus a results.json manifest (per-file status, output name, output
  bytes, error message).
- Per-file failures are recorded in the manifest and never abort the
  rest of the batch.
- Validation: 400 for zero files, more than 20 files, or target_bytes <= 0;
  per-file audio/*|video/* content-type allowlist; per-file
  MAX_UPLOAD_BYTES cap with the same 1 MB chunked save pattern as /shrink;
  outputs are only served from inside the request's temp workspace.
- Each upload gets isolated input_N/output_N dirs so duplicate filenames
  cannot collide; zip entries are prefixed with the upload index.
- Temp workspace is cleaned up after the response via background task.
- Existing /shrink endpoint and its tests are untouched.

Gates: 126 tests (baseline 5 macOS os.listxattr errors only),
coverage 100% on saas_web.py, interrogate 100%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CJRVbDrp1vGYkJgNHMGPpG
@seonghobae seonghobae enabled auto-merge (squash) July 6, 2026 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant