Skip to content

feat: add S3_PREFIX to store uploads under a folder in the bucket - #500

Open
jordanmiguel wants to merge 1 commit into
useplunk:nextfrom
jordanmiguel:feat/s3-prefix
Open

jordanmiguel wants to merge 1 commit into
useplunk:nextfrom
jordanmiguel:feat/s3-prefix

Conversation

@jordanmiguel

Copy link
Copy Markdown
Contributor

Description

Uploads are always written to the bucket root as <projectId>/<timestamp>-<random>.<ext>, and on startup initializeBucket() grants public read on arn:aws:s3:::<bucket>/*. So Plunk can't share a bucket with another application: that application's objects would become publicly readable too.

This adds an optional S3_PREFIX env var:

  • Uploads go to <prefix>/<projectId>/<timestamp>-<random>.<ext>, and the returned URL is ${S3_PUBLIC_URL}/<prefix>/....
  • The public-read policy is scoped to arn:aws:s3:::<bucket>/<prefix>/*, so objects outside the prefix stay private.
  • Leading and trailing slashes are ignored (/plunk/ → plunk), and nested prefixes (tenants/plunk) work.
  • If it's unset, key layout and policy are exactly as before.

Wiring follows the MAIL_FROM_SUBDOMAIN pattern: validateEnv in constants.ts, passthrough in docker-compose.yml, the three env lists in turbo.json, and docs in apps/api/.env.example, .env.self-host.example, the self-hosting env-var wiki page, and CLAUDE.md.

Note: PutBucketPolicy still replaces the bucket's entire policy, as it does today. That behaviour is unchanged here and could be a separate follow-up.

Type of Change

  • feat: New feature (MINOR version bump)
  • fix: Bug fix (PATCH version bump)
  • feat!: Breaking change - new feature (MAJOR version bump)
  • fix!: Breaking change - bug fix (MAJOR version bump)
  • docs: Documentation update (no version bump)
  • chore: Maintenance/dependencies (no version bump)
  • refactor: Code refactoring (no version bump)
  • test: Adding tests (no version bump)
  • perf: Performance improvement (PATCH version bump)

Testing

  • New apps/api/src/services/__tests__/S3Service.test.ts: object key, public URL and policy resource, both with and without a prefix, plus slash trimming. I wrote it before the implementation; it failed on the three prefix cases and passed on the two unchanged cases.
  • yarn test:run: 52 files, 1280 tests pass (local Postgres 16 + Redis 7, same env as CI).
  • yarn build --filter=api and yarn lint --filter=api pass (no new warnings).
  • End-to-end against MinIO (pgsty/minio:RELEASE.2026-08-04T00-00-00Z) with S3_PREFIX=/plunk/:
    • The upload was stored at plunk/project-1/<ts>-<rand>.png.
    • An anonymous GET on the upload returned 200.
    • An anonymous GET on another object in the same bucket, outside the prefix, returned 403.

Checklist

  • PR title follows conventional commits format
  • Code builds successfully
  • Tests pass locally
  • Documentation updated (if needed)

Uploads always landed at the bucket root and startup granted public read on
the entire bucket, so Plunk could not share a bucket with another application
without exposing that application's objects. S3_PREFIX places uploads under a
folder and narrows the public-read policy to that folder only. Unset keeps the
current layout and policy.
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