Last active
February 16, 2024 03:13
-
-
Save mxmtsk/78f92f5e87ff198ddcbc372c66c83611 to your computer and use it in GitHub Desktop.
Laravel controller for handling AWS S3 Multipart uploads with Uppy.io uploader. Replicates the API of the node server so you only have to specify `companionUrl` in the Uppy Configuration without specific the custom methods.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Http\Controllers; | |
use Exception; | |
use Illuminate\Http\Request; | |
use Illuminate\Support\Facades\File; | |
use Illuminate\Support\Facades\Storage; | |
use Illuminate\Support\Facades\Config; | |
class MultipartController extends Controller | |
{ | |
public function createMultipartUpload(Request $request) | |
{ | |
$input = $request->input(); | |
if (!$input["filename"] || !is_string($input["filename"])) { | |
return response()->json(["error" => 's3: filename returned from `getKey` must be a string'], 500); | |
} | |
if (!$input["type"] || !is_string($input["type"])) { | |
return response()->json(["error" => 's3: content type must be a string'], 400); | |
} | |
$originalFilename = $input['filename']; | |
$filename = md5(uniqid()) . "-" . uniqid() . "." . File::extension($originalFilename); | |
$disk = Storage::disk('s3'); | |
$client = $disk->getDriver()->getAdapter()->getClient(); | |
try { | |
$uploader = $client->createMultipartUpload([ | |
'Bucket' => Config::get('filesystems.disks.s3.bucket'), | |
'Key' => $filename, | |
'Metadata' => $input["metadata"], | |
'ACL' => 'public-read', | |
'Expires' => 3000, | |
'ContenType' => $input["type"], | |
]); | |
return response()->json([ | |
"key" => $filename, | |
"uploadId" => $uploader["UploadId"], | |
]); | |
} catch (Exception $e) { | |
return response()->json(["error" => 's3: something went wrong'], 500); | |
} | |
} | |
public function getUploadedParts(Request $request) | |
{ | |
$uploadId = $request->route('uploadId'); | |
$key = $request->input('key'); | |
if (!$key || !is_string($key)) { | |
return response()->json(["error" => 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"'], 400); | |
} | |
$disk = Storage::disk('s3'); | |
$client = $disk->getDriver()->getAdapter()->getClient(); | |
$parts = []; | |
$listPartsPage = function ($startAt) use ($key, $uploadId, $client, &$parts, &$listPartsPage) { | |
$listParts = $client->listParts([ | |
'Bucket' => Config::get('filesystems.disks.s3.bucket'), | |
'Key' => $key, | |
'UploadId' => $uploadId, | |
'PartNumberMarker' => $startAt, | |
]); | |
if ($listParts["IsTruncated"]) { | |
$listPartsPage($listParts["NextPartNumberMarker"]); | |
} | |
$parts = array_merge($listParts["Parts"], $parts); | |
}; | |
$listPartsPage(0); | |
return response()->json(["parts" => $parts]); | |
} | |
public function signPartUpload(Request $request) | |
{ | |
$uploadId = $request->route('uploadId'); | |
$partNumber = $request->route('partNumber'); | |
$key = $request->input('key'); | |
if (!$key || !is_string($key)) { | |
return response()->json(["error" => 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"'], 400); | |
} | |
$disk = Storage::disk('s3'); | |
$client = $disk->getDriver()->getAdapter()->getClient(); | |
try { | |
$cmd = $client->getCommand('UploadPart', [ | |
'Bucket' => Config::get('filesystems.disks.s3.bucket'), | |
'Key' => $key, | |
'UploadId' => $uploadId, | |
'PartNumber' => $partNumber, | |
'ACL' => 'public-read', | |
//'Body' => '', | |
'Expires' => 3000, | |
//'ContentType' => 'application/octet-stream', | |
]); | |
$request = $client->createPresignedRequest($cmd, '+20 minutes'); | |
// Get the actual presigned-url | |
$presignedUrl = (string) $request->getUri(); | |
return response()->json([ | |
"url" => $presignedUrl, | |
]); | |
} catch (Exception $e) { | |
return response()->json(["error" => 's3: something went wrong'], 500); | |
} | |
} | |
public function completeMultipartUpload(Request $request) | |
{ | |
$uploadId = $request->route('uploadId'); | |
$key = $request->input('key'); | |
$parts = $request->input('parts'); | |
if (!$key || !is_string($key)) { | |
return response()->json(["error" => 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"'], 400); | |
} | |
if (!$parts || !is_array($parts)) { | |
return response()->json(["error" => 's3: `parts` must be an array of {ETag, PartNumber} objects.'], 400); | |
} | |
$disk = Storage::disk('s3'); | |
$client = $disk->getDriver()->getAdapter()->getClient(); | |
try { | |
$completeUpload = $client->completeMultipartUpload([ | |
'Bucket' => Config::get('filesystems.disks.s3.bucket'), | |
'Key' => $key, | |
'UploadId' => $uploadId, | |
'MultipartUpload' => [ | |
'Parts' => $parts | |
], | |
]); | |
return response()->json([ | |
"location" => $completeUpload["Location"], | |
]); | |
} catch (Exception $e) { | |
return response()->json(["error" => 's3: something went wrong'], 500); | |
} | |
} | |
public function abortMultipartUpload(Request $request) | |
{ | |
$uploadId = $request->route('uploadId'); | |
$key = $request->input('key'); | |
if (!$key || !is_string($key)) { | |
return response()->json(["error" => 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"'], 400); | |
} | |
$disk = Storage::disk('s3'); | |
$client = $disk->getDriver()->getAdapter()->getClient(); | |
try { | |
$client->abortMultipartUpload([ | |
'Bucket' => Config::get('filesystems.disks.s3.bucket'), | |
'Key' => $key, | |
'UploadId' => $uploadId, | |
]); | |
return response()->json([]); | |
} catch (Exception $e) { | |
return response()->json(["error" => 's3: something went wrong'], 500); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// Routes | |
Route::post('/s3/multipart', 'MultipartController@createMultipartUpload'); | |
Route::get('/s3/multipart/{uploadId}', 'MultipartController@getUploadedParts'); | |
Route::get('/s3/multipart/{uploadId}/{partNumber}', 'MultipartController@signPartUpload'); | |
Route::post('/s3/multipart/{uploadId}/complete', 'MultipartController@completeMultipartUpload'); | |
Route::delete('/s3/multipart/{uploadId}', 'MultipartController@abortMultipartUpload'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Great! It would be helpful if you could make a blog or upload an example application on a repo.
I am looking for a similar solution. So need the AWS bucket config as well.
Getting 400 bad request : https://prnt.sc/Syz_qwimb70x
Though I have fixed the cors config and tested from another package. But I want to use this controller. Could you please help me on this