File Storage¶
[!abstract] Summary Images are compressed client-side and uploaded directly to Cloudflare R2 via presigned URLs. No server-side processing.
Overview¶
Unicorn Trails uses Cloudflare R2 (S3-compatible object storage) for:
- Marker photos (multiple per marker, up to configurable limit)
- Profile pictures (one per user, replaced on upload)
File Categories¶
| Category | Scope | Max Count |
|---|---|---|
ProfilePicture | Per user | 1 (replaces existing) |
MarkerImage | Per marker | Configurable (default 5) |
Storage Paths¶
Upload Flow¶
sequenceDiagram
participant C as Client
participant A as API
participant R2 as Cloudflare R2
C->>C: Validate file (type, size)
C->>C: Compress image (browser)
C->>C: Validate output size
C->>A: Request upload URL
A->>A: Create File record (Pending)
A-->>C: Presigned URL + FileId
C->>R2: PUT (direct upload)
R2-->>C: 200 OK
C->>A: Confirm upload
A->>R2: Verify file exists
A->>R2: Get file size
A->>A: Validate size ≤ limit
A->>A: Delete old file (profile picture only)
A->>A: Mark File as Completed
A-->>C: 200 OK Client-Side Processing¶
Input Validation¶
- File type in allowed MIME types (configurable)
- File size within pre-compression limit (configurable)
Compression Settings¶
Uses browser-image-compression — see Compression Settings for the current defaults. Web Worker with fallback to main thread.
Output Validation¶
- Compressed size ≤ backend limit (configurable, default 10 MB)
- Valid image MIME type
File Lifecycle¶
| Status | Description |
|---|---|
Pending | Upload URL generated, awaiting upload |
Processing | Reserved for future use |
Completed | File uploaded and confirmed |
Failed | Reserved for future use |
The IsAvailable property returns true when status is Completed AND storage key exists.
Validation Summary¶
| Stage | Check | Action on Fail |
|---|---|---|
| Frontend select | MIME type allowed | Skip file, show toast |
| Frontend select | Size ≤ pre-compression limit | Skip file, show toast |
| Frontend compress | Output ≤ backend limit | Show error toast |
| Backend get URL | Image count < max per marker | Return error |
| Backend confirm | File exists in R2 | Return error |
| Backend confirm | Size ≤ backend limit | Delete from R2, return error |
Cleanup¶
The CleanupJob handles orphaned files:
- Files with
Pendingstatus older than upload URL expiration + 5 min buffer - Deletes from R2 storage and removes database records
For deleted markers:
- Marker images are deleted from R2 before marker is permanently removed
- Only removes marker if all file deletions succeed
Related¶
- Background Jobs — Cleanup job details
- Hooks Pattern — useR2Upload hook
- Integrations — Cloudflare R2
- Environment Variables — Size limits, MIME types
- Data Model — StoredFile entity