We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Jump to file
- ∟ mix.exs
- ∟ .gitignore
- ∟ assets/js/app.js
- ∟ assets/js/uploaders/index.js
- ∟ assets/js/uploaders/live-uploader.js
- ∟ config/config.exs
- ∟ config/dev.exs
- ∟ config/prod.exs
- ∟ config/runtime.exs
- ∟ config/test.exs
- ∟ lib/my_app/cloud.ex
- ∟ lib/my_app/storage.ex
- ∟ lib/my_app/storage/object.ex
- ∟ lib/my_app/storage/object_uploaded_by_user.ex
- ∟ lib/my_app_web/controllers/storage_controller.ex
- ∟ lib/my_app_web/live/storage_live.ex
- ∟ lib/my_app_web/live/storage_live/components.ex
- ∟ lib/my_app_web/router.ex
- ∟ priv/repo/migrations/0001_create_storage_objects.exs
- ∟ test/my_app/storage/object_test.exs
- ∟ test/my_app/storage/object_uploaded_by_user_test.exs
- ∟ test/my_app/storage_test.exs
- ∟ test/my_app_web/controllers/storage_controller_test.exs
Install deps
Install the following dependencies to get started. You can find their documentation on HexDocs.
- Buckets
- hexdocs.pm/buckets
- Size
- hexdocs.pm/size
|
|
|
|
|
|
|
|
| - |
|
Adding uploaders in JS
The uploaders module is imported and passed to the
LiveSocket
configuration. This lets LiveView hand each upload to the uploader named in the Cloud configuration.
|
|
|
|
|
|
|
|
| + |
|
|
|
|
|
|
|
| + |
|
|
|
|
|
|
Defining uploaders
A new file exports the LiveUploader as the default uploader for the application.
|
|
| + |
|
Live uploader
The uploader sends each file with a
PUT
to the signed URL that
MyApp.Cloud.live_upload/1
returns. The script manages the file upload lifecycle, providing progress updates through the
entry.progress()
callback and handling errors appropriately.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Cloud configuration
Settings shared by every environment: the Volume adapter, a path prefix inside the bucket, the endpoint whose secret key signs upload and download URLs, and the uploader name that matches the JavaScript uploader.
In development, files are stored under
tmp/buckets_volume
.
Tests use their own directory, so test uploads stay out of development data.
In production, files are stored under
/data/storage
, where you should mount a persistent volume so files survive deploys. This is set at compile time, not in
runtime.exs
, because the router builds the Volume routes from the bucket path when it compiles.
The base URL for signed URLs comes from
PHX_HOST
.
|
|
|
|
|
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| + |
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
|
Cloud
This is your cloud module, which you'll use to manage objects. Think of this module similarly to your
MyApp.Repo
module, but for files. It uses the Buckets library with the configuration from your app's config.
This feature stores files with the Volume adapter in every environment, so it has no reliance on a cloud provider. The rest of the app only talks to
MyApp.Cloud
, so moving to a cloud provider later is a configuration change. Buckets supports these storage strategies:
- Local filesystem
- Buckets.Adapters.Volume
- Google Cloud Storage
- Buckets.Adapters.GCS
- Amazon S3
- Buckets.Adapters.S3
- Cloudflare R2, DigitalOcean Spaces, and Tigris
- Buckets.Adapters.S3 with the provider option
The Volume adapter suits a single server with a persistent disk. Your app receives every upload and serves every download, and its signed URLs don't expire. Move to a cloud adapter when you run more than one server, want uploads to go straight from the browser to the bucket, or need expiring URLs.
For each adapter's configuration, credentials, and setup like CORS rules for direct uploads, see the Buckets documentation on GitHub .
|
|
| + |
|
| + |
|
| + |
|
Storage context
The Storage context encapsulates all interactions with storage objects. It provides functions for:
The soft-delete pattern allows for a grace period before permanent deletion, useful for implementing trash/recovery features.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Object schema
The Object schema stores metadata about uploaded files. Fields include:
The module provides query helpers for filtering present (non-deleted) and deleted objects, with optional duration thresholds for cleanup operations.
The
insert_assocs_multi/3
function allows atomically inserting related records (like the uploader relationship) as part of the same transaction.
Helper functions include
image?/1
for checking content type,
signed_url/1
for generating access URLs, and
remote_object/1
for reconstructing the Buckets object from database fields.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Object uploaded by user schema
This join table links objects with the users who uploaded them. It uses a composite key of
object_id
and
uploaded_by_user_id
.
The
uploaded_by_user_query/2
function joins objects with this table to filter by uploader, ensuring users can only access their own uploads.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Storage controller
The StorageController handles secure file downloads. The
download/2
action verifies a signature parameter before allowing access to the file.
The signature is generated using
deterministic_hash/1
, which creates an HMAC using the application's secret key base. This prevents unauthorized access to files by ID alone.
The
download_path/1
function generates signed download URLs for use in templates.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Storage LiveView
The StorageLive module provides an interface for uploading and managing files. It configures uploads with:
The
presign_upload/2
function gets a signed upload URL from the Cloud module.
The
handle_progress/3
callback consumes completed uploads, creating object records and updating the UI stream.
Users can view their uploads in a table showing filename, content type, preview (for images), size, and timestamp. Actions include delete (soft-delete) and download.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Storage components
Reusable components for the storage interface:
These components can be reused by other features that need file upload functionality.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Router updates
Routes for file storage are added:
The
buckets_volume
macro mounts
GET
and
PUT
routes at
/__buckets__
for downloading and uploading files stored by the Volume adapter. They're mounted in every environment, since production stores files on disk too, and both reject requests without a valid signature. If you switch to a cloud adapter, remove them.
|
|
|
|
|
|
|
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| + |
|
| + |
|
|
|
|
|
|
|
|
|
| + |
|
| + |
|
|
|
|
|
|
Migration
Creates tables for storing file metadata:
Both tables use UUID primary keys and appropriate foreign key constraints with cascade deletes.
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
|
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
| + |
|
Install with Claude Code
Have Claude implement this feature in your project, along with anything it requires. Requires Claude Code to be installed.
With the elixir-saas skill
With the Claude Code skill installed, run this inside Claude Code:
/elixir-saas storage
Without the skill
Write instructions to implement this feature to your project directory in a LLM-friendly format, then have Claude take care of the rest:
curl "https://elixir-saas.com/llms/p/storage.md?v=1.8.3&f=impl" > storage.md;curl "https://elixir-saas.com/llms/p/storage.md?v=1.8.3&f=test" > storage_tests.md;claude "Implement the feature that is documented in: storage.md, then add the tests documented in: storage_tests.md." --allowedTools "Write Edit Bash(mix:*) Bash(mkdir:*)";