Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
mreduar committed Jul 21, 2024
1 parent 3c195f4 commit 41bebaa
Show file tree
Hide file tree
Showing 14 changed files with 356 additions and 12 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
fail-fast: true
matrix:
os: [ubuntu-latest, windows-latest]
php: [8.3, 8.2, 8.1]
php: [8.3, 8.2]
laravel: [11.*, 10.*]
stability: [prefer-lowest, prefer-stable]
include:
Expand Down Expand Up @@ -55,4 +55,4 @@ jobs:
run: composer show -D

- name: Execute tests
run: vendor/bin/pest --ci
run: vendor/bin/pest --ci --testsuite=Common && vendor/bin/pest --ci --testsuite=Isolated
8 changes: 5 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
],
"require": {
"php": "^8.1",
"spatie/laravel-package-tools": "^1.16",
"illuminate/contracts": "^10.0||^11.0"
"aws/aws-sdk-php": "^3.316",
"illuminate/contracts": "^10.0||^11.0",
"spatie/laravel-package-tools": "^1.16"
},
"require-dev": {
"larastan/larastan": "^2.9",
"laravel/pint": "^1.14",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.1.1||^7.10.0",
"larastan/larastan": "^2.9",
"orchestra/testbench": "^9.0.0||^8.22.0",
"pestphp/pest": "^2.34",
"pestphp/pest-plugin-arch": "^2.7",
Expand Down
7 changes: 5 additions & 2 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@
backupStaticProperties="false"
>
<testsuites>
<testsuite name="MrEduar Test Suite">
<directory>tests</directory>
<testsuite name="Common">
<directory>./tests/Common</directory>
</testsuite>
<testsuite name="Isolated">
<directory>./tests/Isolated</directory>
</testsuite>
</testsuites>
<logging>
Expand Down
6 changes: 6 additions & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

use Illuminate\Support\Facades\Route;
use MrEduar\LaravelS3Multipart\Http\Controllers\S3MultipartController;

Route::get('s3m/create-multipart-upload', [S3MultipartController::class, 'createMultipartUpload'])->name('s3m.create-multipart');
17 changes: 17 additions & 0 deletions src/Contracts/StorageMultipartUploadControllerContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace MrEduar\LaravelS3Multipart\Contracts;

use Illuminate\Http\JsonResponse;
use MrEduar\LaravelS3Multipart\Http\Requests\CompleteMultipartUploadRequest;
use MrEduar\LaravelS3Multipart\Http\Requests\CreateMultipartUploadRequest;
use MrEduar\LaravelS3Multipart\Http\Requests\SignPartRequest;

interface StorageMultipartUploadControllerContract
{
public function createMultipartUpload(CreateMultipartUploadRequest $request): JsonResponse;

public function signPartUpload(SignPartRequest $request): JsonResponse;

public function completeMultipartUpload(CompleteMultipartUploadRequest $request): JsonResponse;
}
136 changes: 136 additions & 0 deletions src/Http/Controllers/S3MultipartController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?php

namespace MrEduar\LaravelS3Multipart\Http\Controllers;

use Aws\S3\S3Client;
use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Str;
use InvalidArgumentException;
use MrEduar\LaravelS3Multipart\Contracts\StorageMultipartUploadControllerContract;
use MrEduar\LaravelS3Multipart\Http\Requests\CreateMultipartUploadRequest;
use MrEduar\LaravelS3Multipart\Http\Requests\SignPartRequest;

class S3MultipartController extends Controller implements StorageMultipartUploadControllerContract
{
/**
* Create a new multipart upload.
*/
public function createMultipartUpload(CreateMultipartUploadRequest $request): JsonResponse
{
$this->ensureEnvironmentVariablesAreAvailable($request);

$client = $this->storageClient();

$bucket = $request->input('bucket') ?: $_ENV['AWS_BUCKET'];

$uuid = (string) Str::uuid();

$key = $this->getKey($uuid);

try {
$uploader = $client->createMultipartUpload([
'Bucket' => $bucket,
'Key' => $key,
'ACL' => $request->input('visibility') ?: $this->defaultVisibility(),
'ContentType' => $request->input('content_type') ?: 'application/octet-stream',
]);

return response()->json([
'uuid' => $uuid,
'bucket' => $bucket,
'key' => $key,
'uploadId' => $uploader['UploadId'],
]);
} catch (Exception $e) {
return response()->json([
'error' => $e->getMessage(),
], 500);
}
}

/**
* Sign a part upload.
*/
public function signPartUpload(SignPartRequest $request): JsonResponse
{
return new JsonResponse([]);
}

/**
* Complete a multipart upload.
*/
public function completeMultipartUpload(Request $request): JsonResponse
{
return new JsonResponse([]);
}

/**
* Ensure the required environment variables are available.
*
* @throws \InvalidArgumentException
*/
protected function ensureEnvironmentVariablesAreAvailable(Request $request): void
{
$missing = array_diff_key(array_flip(array_filter([
$request->input('bucket') ? null : 'AWS_BUCKET',
'AWS_DEFAULT_REGION',
'AWS_ACCESS_KEY_ID',
'AWS_SECRET_ACCESS_KEY',
])), $_ENV);

if (empty($missing)) {
return;
}

throw new InvalidArgumentException(
'Unable to issue signed URL. Missing environment variables: '.implode(', ', array_keys($missing))
);
}

/**
* Get the S3 storage client instance.
*/
protected function storageClient(): S3Client
{
$config = [
'region' => config('filesystems.disks.s3.region', $_ENV['AWS_DEFAULT_REGION']),
'version' => 'latest',
'signature_version' => 'v4',
'use_path_style_endpoint' => config('filesystems.disks.s3.use_path_style_endpoint', false),
];

$config['credentials'] = array_filter([
'key' => $_ENV['AWS_ACCESS_KEY_ID'] ?? null,
'secret' => $_ENV['AWS_SECRET_ACCESS_KEY'] ?? null,
'token' => $_ENV['AWS_SESSION_TOKEN'] ?? null,
'url' => $_ENV['AWS_URL'] ?? null,
'endpoint' => $_ENV['AWS_URL'] ?? null,
]);

if (array_key_exists('AWS_URL', $_ENV) && ! is_null($_ENV['AWS_URL'])) {
$config['url'] = $_ENV['AWS_URL'];
$config['endpoint'] = $_ENV['AWS_URL'];
}

return new S3Client($config);
}

/**
* Get key for the given UUID.
*/
protected function getKey(string $uuid): string
{
return 'tmp/'.$uuid;
}

/**
* Get the default visibility for uploads.
*/
protected function defaultVisibility(): string
{
return 'private';
}
}
30 changes: 30 additions & 0 deletions src/Http/Requests/CompleteMultipartUploadRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace MrEduar\LaravelS3Multipart\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class CompleteMultipartUploadRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}

/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
}
31 changes: 31 additions & 0 deletions src/Http/Requests/CreateMultipartUploadRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace MrEduar\LaravelS3Multipart\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;

class CreateMultipartUploadRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return Gate::allows('uploadFiles', [$this->user(), $this->input('bucket')]);
}

/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
*/
public function rules(): array
{
return [
'bucket' => ['nullable', 'string'],
'visibility' => ['nullable', 'string'],
'content_type' => ['nullable', 'string'],
];
}
}
30 changes: 30 additions & 0 deletions src/Http/Requests/SignPartRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace MrEduar\LaravelS3Multipart\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class SignPartRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}

/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
//
];
}
}
7 changes: 7 additions & 0 deletions src/LaravelS3MultipartServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@
namespace MrEduar\LaravelS3Multipart;

use Illuminate\View\Compilers\BladeCompiler;
use MrEduar\LaravelS3Multipart\Http\Controllers\S3MultipartController;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;

class LaravelS3MultipartServiceProvider extends PackageServiceProvider
{
public function configurePackage(Package $package): void
{
$this->app->singleton(
Contracts\StorageMultipartUploadControllerContract::class,
S3MultipartController::class
);

if ($this->app->resolved('blade.compiler')) {
$this->registerDirective($this->app['blade.compiler']);
} else {
Expand All @@ -18,6 +24,7 @@ public function configurePackage(Package $package): void

$package
->name('laravel-s3-multipart')
->hasRoute('web')
->hasConfigFile();
}

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use MrEduar\LaravelS3Multipart\BladeFunctionGenerator;

test('render script tag', function () {
$routeFunction = file_get_contents(__DIR__.'/../../dist/function.umd.js');
$routeFunction = file_get_contents(__DIR__.'/../../../dist/function.umd.js');

expect((new BladeFunctionGenerator)->generate())->toBe(
<<<HTML
Expand Down
Loading

0 comments on commit 41bebaa

Please sign in to comment.