File Uploads (Uppy + S3)
The Portal uses Uppy (client-side upload library) with a custom S3 Companion implementation for direct-to-S3 multipart file uploads.
Architecture
Browser (Uppy Dashboard) -> Portal Companion routes -> AWS S3 (presigned URLs)Uppy in the browser communicates with the Portal’s companion endpoints, which generate presigned S3 URLs. Files are uploaded directly from the browser to S3, avoiding the Portal server as a middleman.
Client-Side (Uppy)
Packages (from package.json):
@uppy/core(v5.1.1) — Core upload engine@uppy/dashboard(v5.0.3) — Upload UI widget@uppy/drag-drop(v5.0.2) — Drag and drop support@uppy/aws-s3(v5.0.2) — AWS S3 plugin for multipart uploads
The Uppy Dashboard component provides the user-facing upload interface with drag-and-drop, progress bars, and file preview.
Server-Side Companion
UppyCompanionService
File: app/Services/UppyCompanionService.php
A service class that wraps the AWS S3 SDK to handle presigned URL generation and multipart upload management.
Constructor dependencies:
S3Client— AWS S3 client instancestring $s3Bucket— Target bucket name
Default expiry: 60 minutes for presigned URLs
Methods
getPresignedUrl(string $uploadKey, string $type): array
Generates a presigned PUT URL for simple (non-multipart) uploads.
Returns: { url, fields, headers }
createMultipartUpload(string $fileName, string $type, array $metadata): array
Initiates an S3 multipart upload.
Returns: { uploadId, key }
listPartsPage(string $uploadId, int $partIndex = 0): Collection
Lists all uploaded parts for a multipart upload. Handles pagination automatically with a safety limit of 1000 iterations.
presignPartURL(string $uploadKey, string $uploadId, int $partNumber): string
Generates a presigned URL for uploading a single part of a multipart upload. Uses SHA1 checksum algorithm.
completeMultipartUpload(string $uploadKey, string $uploadId, array $parts): Result
Completes a multipart upload by assembling all parts.
abortMultipartUpload(string $uploadKey, string $uploadId): void
Aborts an in-progress multipart upload and cleans up uploaded parts.
Companion Routes
Defined in: routes/web.php (no auth middleware — accessible publicly)
| Method | Path | Controller | Purpose |
|---|---|---|---|
| POST | companion/s3/params | ParamsController@index | Get presigned URL params |
| OPTIONS | companion/s3/multipart | MultipartController@options | CORS preflight |
| POST | companion/s3/multipart | MultipartController@store | Create multipart upload |
| GET | companion/s3/multipart/{uploadId} | MultipartController@show | Get multipart upload info |
| DELETE | companion/s3/multipart/{uploadId} | MultipartController@destroy | Abort multipart upload |
| GET | companion/s3/multipart/{uploadId}/{partNumber} | SignpartController@show | Get presigned URL for part |
| POST | companion/s3/multipart/{uploadId}/complete | CompleteMultipartController@post | Complete multipart upload |
Controllers are in app/Http/Controllers/Uppy/.
S3 Buckets
File uploads target different S3 buckets depending on the use case (configured in config/filesystems.php):
| Disk | Bucket | Use Case |
|---|---|---|
public_images | housr-public-images | House photos, agent logos, event banners |
agent_documents | housr-agent-documents | Agent verification documents |
public_misc | housr-public-misc | CSV exports, miscellaneous files |
Filament File Uploads
The Filament admin panel uses Filament’s built-in FileUpload component, which handles uploads differently:
- Files are uploaded through PHP to the server first
- Then saved to S3 via the configured Flysystem disk
- Example:
AgentResourcesaves logos to thepublic_imagesdisk with random filenames - Example:
EventFormsaves banner/modal images topublic_imagesunderevents/{slug}/
This is separate from the Uppy companion flow and does not use presigned URLs.
Security Note
The companion routes at companion/s3/* do not have authentication middleware applied. They are publicly accessible. The presigned URL approach limits exposure since the URLs are time-limited (60 minutes) and scoped to specific S3 keys.