make_post_sell/docs/tickets/mps-8.md
russell@unturf.com 32a19a339c docs: add MPS-6 through MPS-9 tickets, architecture diagram, update JS docs
- MPS-6: referrer analytics (domain, query, trend line charts)
- MPS-7: sandbox mode creative filter system
- MPS-8: user S3 bucket + artifact storage
- MPS-9: shop S3 mirror bucket
- architecture.md: system diagram, request flow, data pipeline, S3 layout
- JAVASCRIPT.md: add sandbox.js, signals.js, MediaPipe SDK entries
- sandbox-mode.md: mark S3 upload as implemented
- mps-2.md: document referrer_domain + referrer_query columns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 06:19:56 -05:00

3.6 KiB

MPS-8: User S3 Bucket + Artifact Storage

Problem

Sandbox mode (MPS-7) lets visitors export filtered images and videos, but exports download to the user's local device. Creators want a way to automatically upload sandbox artifacts to their own cloud storage — a personal S3 bucket where filtered content accumulates without manual file management.

Solution

User-level S3 credentials

Add S3-compatible storage credentials to the User model (not Shop — a user may own multiple shops but uses one storage bucket):

Column Type Example
s3_endpoint Unicode(256) "https://nyc3.digitaloceanspaces.com"
s3_region Unicode(64) "nyc3"
s3_bucket Unicode(128) "my-sandbox-exports"
s3_access_key Unicode(128) "DO00..."
s3_secret_key Unicode(128) "wJalr..."

Property has_s3_bucket returns True when endpoint, bucket, access_key, and secret_key are all non-empty.

Storage settings form

New section on user settings page (/u/settings) with an "Artifact Storage" form that POSTs to /u/settings/storage. Fields: endpoint URL, region (optional), bucket name, access key, secret key (password field).

Presigned upload endpoint

POST /u/sandbox/upload — accepts filename and content_type parameters, generates a presigned POST using the user's stored S3 credentials. Returns {presigned: {url, fields}, key}. The browser uploads the blob directly to the user's bucket — the file never touches the MPS server.

S3 key format: sandbox/{user_id}/{timestamp}-{filename}

Upload limit: 50MB per file (enforced via presigned POST conditions).

Toolbar integration

When a user has has_s3_bucket = True, the sandbox toolbar in base.j2 renders with data-has-bucket="1". The sandbox.js upload-to-bucket button appears only when this attribute is present. The upload flow:

  1. User exports image/video (canvas toBlob)
  2. User clicks "Upload to Bucket"
  3. JS POSTs to /u/sandbox/upload with filename + content_type
  4. Server returns presigned POST URL + fields
  5. JS uploads blob directly to user's S3 bucket via FormData POST

Supported services

Any S3-compatible endpoint: DigitalOcean Spaces, AWS S3, MinIO, Backblaze B2, Cloudflare R2.

Security

Credentials stored as plain Unicode columns (same pattern as Stripe/PayPal/Adyen keys on Shop model). Secret key field uses type="password" in the form. Credentials are only used server-side to generate presigned URLs — they are never exposed to the browser.

Shop-Level S3 Mirror (MPS-9)

A separate but related feature: shops can configure their own S3 bucket as a mirror of the MPS main bucket. Every file uploaded to a shop (product files, thumbnails, shop assets) is automatically copied to the shop's mirror bucket in a background thread. See MPS-9.

Files Changed

File Change
models/user.py S3 credential columns + has_s3_bucket property
views/user.py Storage settings POST handler + S3 fields in settings dict
views/user_sandbox.py Presigned upload endpoint
templates/user_settings.j2 Artifact Storage form
templates/base.j2 data-has-bucket attribute on sandbox toolbar
static/js/sandbox.js Upload-to-bucket button + presigned POST flow
routes.py user_storage_settings, user_sandbox_upload routes
scripts/alembic/versions/f898ba460612_*.py Migration: user S3 columns
tests/test_functional.py S3 bucket save/clear, presigned upload, toolbar attribute tests

Depends On

MPS-7 (sandbox mode — toolbar must exist to add upload button)