diff --git a/app/Builders/SongBuilder.php b/app/Builders/SongBuilder.php index c45decd9..91989446 100644 --- a/app/Builders/SongBuilder.php +++ b/app/Builders/SongBuilder.php @@ -104,8 +104,9 @@ class SongBuilder extends Builder : $column; } - public function hostedOnS3(): static + public function storedOnCloud(): static { - return $this->where('path', 'LIKE', 's3://%'); + return $this->where('path', 'LIKE', 's3://%') + ->orWhere('path', 'LIKE', 'r2://%'); } } diff --git a/app/Factories/StreamerFactory.php b/app/Factories/StreamerFactory.php index ec41f005..55a7f8d0 100644 --- a/app/Factories/StreamerFactory.php +++ b/app/Factories/StreamerFactory.php @@ -3,20 +3,17 @@ namespace App\Factories; use App\Models\Song; -use App\Services\Streamers\DirectStreamerInterface; -use App\Services\Streamers\ObjectStorageStreamerInterface; +use App\Services\Streamers\LocalStreamerInterface; +use App\Services\Streamers\S3CompatibleStreamer; use App\Services\Streamers\StreamerInterface; -use App\Services\Streamers\TranscodingStreamerInterface; +use App\Services\Streamers\TranscodingStreamer; use App\Services\TranscodingService; +use App\Values\SongStorageMetadata\S3CompatibleMetadata; class StreamerFactory { - public function __construct( - private DirectStreamerInterface $directStreamer, - private TranscodingStreamerInterface $transcodingStreamer, - private ObjectStorageStreamerInterface $objectStorageStreamer, - private TranscodingService $transcodingService - ) { + public function __construct(private TranscodingService $transcodingService) + { } public function createStreamer( @@ -25,26 +22,28 @@ class StreamerFactory ?int $bitRate = null, float $startTime = 0.0 ): StreamerInterface { - if ($song->s3_params) { - $this->objectStorageStreamer->setSong($song); - - return $this->objectStorageStreamer; + if ($song->storage_metadata instanceof S3CompatibleMetadata) { + return tap( + app(S3CompatibleStreamer::class), + static fn (S3CompatibleStreamer $streamer) => $streamer->setSong($song) + ); } - if ($transcode === null && $this->transcodingService->songShouldBeTranscoded($song)) { - $transcode = true; - } + $transcode ??= $this->transcodingService->songShouldBeTranscoded($song); if ($transcode) { - $this->transcodingStreamer->setSong($song); - $this->transcodingStreamer->setBitRate($bitRate ?: config('koel.streaming.bitrate')); - $this->transcodingStreamer->setStartTime($startTime); + /** @var TranscodingStreamer $streamer */ + $streamer = app(TranscodingStreamer::class); + $streamer->setSong($song); + $streamer->setBitRate($bitRate ?: config('koel.streaming.bitrate')); + $streamer->setStartTime($startTime); - return $this->transcodingStreamer; + return $streamer; } - $this->directStreamer->setSong($song); - - return $this->directStreamer; + return tap( + app(LocalStreamerInterface::class), + static fn (LocalStreamerInterface $streamer) => $streamer->setSong($song) + ); } } diff --git a/app/Http/Controllers/API/UploadController.php b/app/Http/Controllers/API/UploadController.php index 28f2a7ae..6a836d05 100644 --- a/app/Http/Controllers/API/UploadController.php +++ b/app/Http/Controllers/API/UploadController.php @@ -12,7 +12,7 @@ use App\Http\Resources\SongResource; use App\Models\User; use App\Repositories\AlbumRepository; use App\Repositories\SongRepository; -use App\Services\UploadService; +use App\Services\SongStorage\SongStorage; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Http\Response; @@ -20,7 +20,7 @@ class UploadController extends Controller { /** @param User $user */ public function __invoke( - UploadService $uploadService, + SongStorage $storage, AlbumRepository $albumRepository, SongRepository $songRepository, UploadRequest $request, @@ -29,7 +29,7 @@ class UploadController extends Controller $this->authorize('upload', User::class); try { - $song = $songRepository->getOne($uploadService->handleUploadedFile($request->file, $user)->id, $user); + $song = $songRepository->getOne($storage->storeUploadedFile($request->file, $user)->id, $user); event(new LibraryChanged()); diff --git a/app/Listeners/DeleteNonExistingRecordsPostSync.php b/app/Listeners/DeleteNonExistingRecordsPostSync.php index d375b286..c1474d2f 100644 --- a/app/Listeners/DeleteNonExistingRecordsPostSync.php +++ b/app/Listeners/DeleteNonExistingRecordsPostSync.php @@ -18,7 +18,7 @@ class DeleteNonExistingRecordsPostSync $paths = $event->results ->valid() ->map(static fn (ScanResult $result) => $result->path) - ->merge($this->songRepository->getAllHostedOnS3()->pluck('path')) + ->merge($this->songRepository->getAllStoredOnCloud()->pluck('path')) ->toArray(); Song::deleteWhereValueNotIn($paths, 'path'); diff --git a/app/Models/Song.php b/app/Models/Song.php index 027c5742..9f86190c 100644 --- a/app/Models/Song.php +++ b/app/Models/Song.php @@ -3,6 +3,9 @@ namespace App\Models; use App\Builders\SongBuilder; +use App\Values\SongStorageMetadata\LocalMetadata; +use App\Values\SongStorageMetadata\S3CompatibleMetadata; +use App\Values\SongStorageMetadata\StorageMetadata; use Carbon\Carbon; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -37,6 +40,7 @@ use Laravel\Scout\Searchable; * @property int $owner_id * @property bool $is_public * @property User $owner + * @property-read StorageMetadata $storage_metadata * * // The following are only available for collaborative playlists * @property-read ?string $collaborator_email The email of the user who added the song to the playlist @@ -143,6 +147,19 @@ class Song extends Model return new Attribute(get: $normalizer, set: $normalizer); } + protected function storageMetadata(): Attribute + { + return new Attribute( + get: function (): StorageMetadata { + if (preg_match('/^s3:\\/\\/(.*)\\/(.*)/', $this->path, $matches)) { + return S3CompatibleMetadata::make($matches[1], $matches[2]); + } + + return LocalMetadata::make($this->path); + } + ); + } + protected function s3Params(): Attribute { return Attribute::get(function (): ?array { diff --git a/app/Providers/SongStorageServiceProvider.php b/app/Providers/SongStorageServiceProvider.php new file mode 100644 index 00000000..6a9c584c --- /dev/null +++ b/app/Providers/SongStorageServiceProvider.php @@ -0,0 +1,27 @@ +app->bind(SongStorage::class, function () { + $concrete = match (config('koel.storage_driver')) { + 's3' => S3CompatibleStorage::class, + default => LocalStorage::class, + }; + + return $this->app->make($concrete); + }); + + $this->app->when(S3CompatibleStorage::class) + ->needs('$bucket') + ->giveConfig('filesystems.disks.s3.bucket'); + } +} diff --git a/app/Providers/StreamerServiceProvider.php b/app/Providers/StreamerServiceProvider.php index 9ab1a072..d13b3664 100644 --- a/app/Providers/StreamerServiceProvider.php +++ b/app/Providers/StreamerServiceProvider.php @@ -2,12 +2,8 @@ namespace App\Providers; -use App\Services\Streamers\DirectStreamerInterface; -use App\Services\Streamers\ObjectStorageStreamerInterface; +use App\Services\Streamers\LocalStreamerInterface; use App\Services\Streamers\PhpStreamer; -use App\Services\Streamers\S3Streamer; -use App\Services\Streamers\TranscodingStreamer; -use App\Services\Streamers\TranscodingStreamerInterface; use App\Services\Streamers\XAccelRedirectStreamer; use App\Services\Streamers\XSendFileStreamer; use Illuminate\Support\ServiceProvider; @@ -16,15 +12,12 @@ class StreamerServiceProvider extends ServiceProvider { public function register(): void { - $this->app->bind(DirectStreamerInterface::class, static function (): DirectStreamerInterface { + $this->app->bind(LocalStreamerInterface::class, static function (): LocalStreamerInterface { return match (config('koel.streaming.method')) { 'x-sendfile' => new XSendFileStreamer(), 'x-accel-redirect' => new XAccelRedirectStreamer(), default => new PhpStreamer(), }; }); - - $this->app->bind(TranscodingStreamerInterface::class, TranscodingStreamer::class); - $this->app->bind(ObjectStorageStreamerInterface::class, S3Streamer::class); } } diff --git a/app/Repositories/SongRepository.php b/app/Repositories/SongRepository.php index 2e4f87ad..47fbbe08 100644 --- a/app/Repositories/SongRepository.php +++ b/app/Repositories/SongRepository.php @@ -17,15 +17,15 @@ class SongRepository extends Repository { private const DEFAULT_QUEUE_LIMIT = 500; - public function getOneByPath(string $path): ?Song + public function findOneByPath(string $path): ?Song { return Song::query()->where('path', $path)->first(); } /** @return Collection|array */ - public function getAllHostedOnS3(): Collection + public function getAllStoredOnCloud(): Collection { - return Song::query()->hostedOnS3()->get(); + return Song::query()->storedOnCloud()->get(); } /** @return Collection|array */ diff --git a/app/Services/FileScanner.php b/app/Services/FileScanner.php index 5ab3ed62..3cd45d31 100644 --- a/app/Services/FileScanner.php +++ b/app/Services/FileScanner.php @@ -42,7 +42,7 @@ class FileScanner $file = $path instanceof SplFileInfo ? $path : new SplFileInfo($path); $this->filePath = $file->getRealPath(); - $this->song = $this->songRepository->getOneByPath($this->filePath); + $this->song = $this->songRepository->findOneByPath($this->filePath); $this->fileModifiedTime = Helper::getModifiedTime($file); return $this; diff --git a/app/Services/MediaScanner.php b/app/Services/MediaScanner.php index fd8c3cd1..5df30a97 100644 --- a/app/Services/MediaScanner.php +++ b/app/Services/MediaScanner.php @@ -134,7 +134,7 @@ class MediaScanner private function handleDeletedFileRecord(string $path): void { - $song = $this->songRepository->getOneByPath($path); + $song = $this->songRepository->findOneByPath($path); if ($song) { $song->delete(); diff --git a/app/Services/S3Service.php b/app/Services/S3Service.php index e2137cda..c4f33478 100644 --- a/app/Services/S3Service.php +++ b/app/Services/S3Service.php @@ -89,7 +89,7 @@ class S3Service implements ObjectStorageInterface public function deleteSongEntry(string $bucket, string $key): void { $path = Song::getPathFromS3BucketAndKey($bucket, $key); - $song = $this->songRepository->getOneByPath($path); + $song = $this->songRepository->findOneByPath($path); throw_unless((bool) $song, SongPathNotFoundException::create($path)); diff --git a/app/Services/UploadService.php b/app/Services/SongStorage/LocalStorage.php similarity index 88% rename from app/Services/UploadService.php rename to app/Services/SongStorage/LocalStorage.php index cf6a6ae8..17ce5d1a 100644 --- a/app/Services/UploadService.php +++ b/app/Services/SongStorage/LocalStorage.php @@ -1,12 +1,13 @@ getUploadDirectory($uploader); $targetFileName = $this->getTargetFileName($file, $uploader); @@ -29,12 +30,11 @@ class UploadService $targetPathName = $uploadDirectory . $targetFileName; try { - $result = $this->scanner->setFile($targetPathName)->scan( - ScanConfiguration::make( + $result = $this->scanner->setFile($targetPathName) + ->scan(ScanConfiguration::make( owner: $uploader, makePublic: $uploader->preferences->makeUploadsPublic - ) - ); + )); } catch (Throwable $e) { File::delete($targetPathName); throw new SongUploadFailedException($e->getMessage()); diff --git a/app/Services/SongStorage/S3CompatibleStorage.php b/app/Services/SongStorage/S3CompatibleStorage.php new file mode 100644 index 00000000..66ac02f3 --- /dev/null +++ b/app/Services/SongStorage/S3CompatibleStorage.php @@ -0,0 +1,58 @@ +move($tmpDir, $file->getClientOriginalName()); + + return DB::transaction(function () use ($tmpFile, $uploader) { + $this->scanner->setFile($tmpFile) + ->scan(ScanConfiguration::make( + owner: $uploader, + makePublic: $uploader->preferences->makeUploadsPublic + )); + + $song = $this->scanner->getSong(); + $key = $this->generateStorageKey($tmpFile->getFilename(), $uploader); + + Storage::disk('s3')->put($key, $tmpFile->getContent()); + $song->update(['path' => "s3://$this->bucket/$key"]); + + File::delete($tmpFile->getRealPath()); + + return $song; + }); + } + + public function getSongPresignedUrl(Song $song): string + { + return Storage::disk('s3')->temporaryUrl($song->storage_metadata->getPath(), now()->addHour()); + } + + private function generateStorageKey(string $filename, User $uploader): string + { + return sprintf('%s__%s__%s', $uploader->id, Str::lower(Ulid::generate()), $filename); + } +} diff --git a/app/Services/SongStorage/SongStorage.php b/app/Services/SongStorage/SongStorage.php new file mode 100644 index 00000000..774e3130 --- /dev/null +++ b/app/Services/SongStorage/SongStorage.php @@ -0,0 +1,12 @@ +storage->getSongPresignedUrl($this->song)); + } +} diff --git a/app/Services/Streamers/S3Streamer.php b/app/Services/Streamers/S3Streamer.php deleted file mode 100644 index 89f92b79..00000000 --- a/app/Services/Streamers/S3Streamer.php +++ /dev/null @@ -1,28 +0,0 @@ -s3Service->getSongPublicUrl($this->song)); - } -} diff --git a/app/Services/Streamers/Streamer.php b/app/Services/Streamers/Streamer.php index f4b5600e..b13991ab 100644 --- a/app/Services/Streamers/Streamer.php +++ b/app/Services/Streamers/Streamer.php @@ -3,9 +3,9 @@ namespace App\Services\Streamers; use App\Models\Song; -use Illuminate\Support\Facades\File; +use App\Values\SongStorageMetadata\LocalMetadata; -class Streamer +class Streamer implements StreamerInterface { protected ?Song $song = null; @@ -21,12 +21,10 @@ class Streamer { $this->song = $song; - abort_unless($this->song->s3_params || File::exists($this->song->path), 404); - // Hard code the content type instead of relying on PHP's fileinfo() // or even Symfony's MIMETypeGuesser, since they appear to be wrong sometimes. - if (!$this->song->s3_params) { - $this->contentType = 'audio/' . pathinfo($this->song->path, PATHINFO_EXTENSION); + if ($this->song->storage_metadata instanceof LocalMetadata) { + $this->contentType = 'audio/' . pathinfo($this->song->storage_metadata->getPath(), PATHINFO_EXTENSION); } } } diff --git a/app/Services/Streamers/StreamerInterface.php b/app/Services/Streamers/StreamerInterface.php index 84a44eb8..25d80268 100644 --- a/app/Services/Streamers/StreamerInterface.php +++ b/app/Services/Streamers/StreamerInterface.php @@ -7,6 +7,4 @@ use App\Models\Song; interface StreamerInterface { public function setSong(Song $song): void; - - public function stream(); // @phpcs:ignore } diff --git a/app/Services/Streamers/TranscodingStreamer.php b/app/Services/Streamers/TranscodingStreamer.php index 1ecb35b2..f63aa18d 100644 --- a/app/Services/Streamers/TranscodingStreamer.php +++ b/app/Services/Streamers/TranscodingStreamer.php @@ -2,7 +2,7 @@ namespace App\Services\Streamers; -class TranscodingStreamer extends Streamer implements TranscodingStreamerInterface +class TranscodingStreamer extends Streamer implements StreamerInterface { /** * Bit rate the stream should be transcoded at. @@ -39,7 +39,7 @@ class TranscodingStreamer extends Streamer implements TranscodingStreamerInterfa ]; if ($this->startTime) { - array_unshift($args, "-ss {$this->startTime}"); + array_unshift($args, "-ss $this->startTime"); } passthru("$ffmpeg " . implode(' ', $args)); diff --git a/app/Services/Streamers/TranscodingStreamerInterface.php b/app/Services/Streamers/TranscodingStreamerInterface.php deleted file mode 100644 index c43afb48..00000000 --- a/app/Services/Streamers/TranscodingStreamerInterface.php +++ /dev/null @@ -1,10 +0,0 @@ -contentType}"); + header("Content-Type: $this->contentType"); header('Content-Disposition: inline; filename="' . basename($this->song->path) . '"'); exit; diff --git a/app/Services/Streamers/XSendFileStreamer.php b/app/Services/Streamers/XSendFileStreamer.php index 2c0e89c7..c95f6a4b 100644 --- a/app/Services/Streamers/XSendFileStreamer.php +++ b/app/Services/Streamers/XSendFileStreamer.php @@ -2,7 +2,7 @@ namespace App\Services\Streamers; -class XSendFileStreamer extends Streamer implements DirectStreamerInterface +class XSendFileStreamer extends Streamer implements LocalStreamerInterface { /** * Stream the current song using Apache's x_sendfile module. diff --git a/app/Values/SongStorageMetadata/LocalMetadata.php b/app/Values/SongStorageMetadata/LocalMetadata.php new file mode 100644 index 00000000..818e4433 --- /dev/null +++ b/app/Values/SongStorageMetadata/LocalMetadata.php @@ -0,0 +1,20 @@ +path; + } +} diff --git a/app/Values/SongStorageMetadata/R2Metadata.php b/app/Values/SongStorageMetadata/R2Metadata.php new file mode 100644 index 00000000..ff668335 --- /dev/null +++ b/app/Values/SongStorageMetadata/R2Metadata.php @@ -0,0 +1,7 @@ +key; + } +} diff --git a/app/Values/SongStorageMetadata/S3Metadata.php b/app/Values/SongStorageMetadata/S3Metadata.php new file mode 100644 index 00000000..0f5934f7 --- /dev/null +++ b/app/Values/SongStorageMetadata/S3Metadata.php @@ -0,0 +1,7 @@ +=5.5" }, "require-dev": { - "phpunit/phpunit": "^4.8.35|^5.4.3" + "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." }, "type": "library", "autoload": { @@ -117,7 +121,7 @@ } ], "description": "AWS Common Runtime for PHP", - "homepage": "http://aws.amazon.com/sdkforphp", + "homepage": "https://github.com/awslabs/aws-crt-php", "keywords": [ "amazon", "aws", @@ -126,40 +130,42 @@ ], "support": { "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.0.2" + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.4" }, - "time": "2021-09-03T22:57:30+00:00" + "time": "2023-11-08T00:42:13+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.231.15", + "version": "3.298.1", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "ba379285d24b609a997bd8b40933d3e0a3826dfb" + "reference": "7c7dd6f596e7f7ba22653a503ae76e8e11702028" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/ba379285d24b609a997bd8b40933d3e0a3826dfb", - "reference": "ba379285d24b609a997bd8b40933d3e0a3826dfb", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7c7dd6f596e7f7ba22653a503ae76e8e11702028", + "reference": "7c7dd6f596e7f7ba22653a503ae76e8e11702028", "shasum": "" }, "require": { - "aws/aws-crt-php": "^1.0.2", + "aws/aws-crt-php": "^1.2.3", "ext-json": "*", "ext-pcre": "*", "ext-simplexml": "*", "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", - "guzzlehttp/promises": "^1.4.0", - "guzzlehttp/psr7": "^1.8.5 || ^2.3", + "guzzlehttp/promises": "^1.4.0 || ^2.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", "mtdowling/jmespath.php": "^2.6", - "php": ">=5.5" + "php": ">=7.2.5", + "psr/http-message": "^1.0 || ^2.0" }, "require-dev": { "andrewsville/php-token-reflection": "^1.4", "aws/aws-php-sns-message-validator": "~1.0", "behat/behat": "~3.0", "composer/composer": "^1.10.22", + "dms/phpunit-arraysubset-asserts": "^0.4.0", "doctrine/cache": "~1.4", "ext-dom": "*", "ext-openssl": "*", @@ -167,10 +173,11 @@ "ext-sockets": "*", "nette/neon": "^2.3", "paragonie/random_compat": ">= 2", - "phpunit/phpunit": "^4.8.35 || ^5.6.3", + "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", "psr/cache": "^1.0", "psr/simple-cache": "^1.0", - "sebastian/comparator": "^1.2.3" + "sebastian/comparator": "^1.2.3 || ^4.0", + "yoast/phpunit-polyfills": "^1.0" }, "suggest": { "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", @@ -218,84 +225,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.231.15" + "source": "https://github.com/aws/aws-sdk-php/tree/3.298.1" }, - "time": "2022-07-27T18:59:36+00:00" - }, - { - "name": "aws/aws-sdk-php-laravel", - "version": "3.7.0", - "source": { - "type": "git", - "url": "https://github.com/aws/aws-sdk-php-laravel.git", - "reference": "cfae1e4e770704cf546051c0ba3d480f0031c51f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php-laravel/zipball/cfae1e4e770704cf546051c0ba3d480f0031c51f", - "reference": "cfae1e4e770704cf546051c0ba3d480f0031c51f", - "shasum": "" - }, - "require": { - "aws/aws-sdk-php": "~3.0", - "illuminate/support": "^5.1 || ^6.0 || ^7.0 || ^8.0 || ^9.0", - "php": ">=5.5.9" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0", - "vlucas/phpdotenv": "^1.0 || ^2.0 || ^3.0 || ^4.0 || ^5.0" - }, - "suggest": { - "laravel/framework": "To test the Laravel bindings", - "laravel/lumen-framework": "To test the Lumen bindings" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Aws\\Laravel\\AwsServiceProvider" - ], - "aliases": { - "AWS": "Aws\\Laravel\\AwsFacade" - } - } - }, - "autoload": { - "psr-4": { - "Aws\\Laravel\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Amazon Web Services", - "homepage": "http://aws.amazon.com" - } - ], - "description": "A simple Laravel 5/6/7/8/9 service provider for including the AWS SDK for PHP.", - "homepage": "http://aws.amazon.com/sdkforphp2", - "keywords": [ - "amazon", - "aws", - "dynamodb", - "ec2", - "laravel", - "laravel 5", - "laravel 6", - "laravel 7", - "laravel 8", - "laravel 9", - "s3", - "sdk" - ], - "support": { - "issues": "https://github.com/aws/aws-sdk-php-laravel/issues", - "source": "https://github.com/aws/aws-sdk-php-laravel/tree/3.7.0" - }, - "time": "2022-03-08T22:02:03+00:00" + "time": "2024-02-01T19:07:41+00:00" }, { "name": "brick/math", @@ -1434,22 +1366,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.4.5", + "version": "7.8.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82" + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1dd98b0564cb3f6bd16ce683cb755f94c10fbd82", - "reference": "1dd98b0564cb3f6bd16ce683cb755f94c10fbd82", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.9 || ^2.4", + "guzzlehttp/promises": "^1.5.3 || ^2.0.1", + "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -1458,10 +1390,11 @@ "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "^3.0", - "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -1471,8 +1404,9 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "7.4-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { @@ -1538,7 +1472,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.4.5" + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" }, "funding": [ { @@ -1554,38 +1488,37 @@ "type": "tidelift" } ], - "time": "2022-06-20T22:16:13+00:00" + "time": "2023-12-03T20:35:24+00:00" }, { "name": "guzzlehttp/promises", - "version": "1.5.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", - "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", "shasum": "" }, "require": { - "php": ">=5.5" + "php": "^7.2.5 || ^8.0" }, "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.5-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { - "files": [ - "src/functions_include.php" - ], "psr-4": { "GuzzleHttp\\Promise\\": "src/" } @@ -1622,7 +1555,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.1" + "source": "https://github.com/guzzle/promises/tree/2.0.2" }, "funding": [ { @@ -1638,26 +1571,26 @@ "type": "tidelift" } ], - "time": "2021-10-22T20:56:57+00:00" + "time": "2023-12-03T20:19:20+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.4.0", + "version": "2.6.2", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "13388f00956b1503577598873fffb5ae994b5737" + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/13388f00956b1503577598873fffb5ae994b5737", - "reference": "13388f00956b1503577598873fffb5ae994b5737", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", + "psr/http-message": "^1.1 || ^2.0", "ralouphie/getallheaders": "^3.0" }, "provide": { @@ -1665,17 +1598,18 @@ "psr/http-message-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", + "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.8 || ^9.3.10" + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.4-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { @@ -1737,7 +1671,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.4.0" + "source": "https://github.com/guzzle/psr7/tree/2.6.2" }, "funding": [ { @@ -1753,7 +1687,7 @@ "type": "tidelift" } ], - "time": "2022-06-20T21:43:11+00:00" + "time": "2023-12-03T20:05:35+00:00" }, { "name": "guzzlehttp/uri-template", @@ -2913,23 +2847,26 @@ }, { "name": "league/flysystem", - "version": "3.12.2", + "version": "3.23.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "f6377c709d2275ed6feaf63e44be7a7162b0e77f" + "reference": "199e1aebbe3e62bd39f4d4fc8c61ce0b3786197e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f6377c709d2275ed6feaf63e44be7a7162b0e77f", - "reference": "f6377c709d2275ed6feaf63e44be7a7162b0e77f", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/199e1aebbe3e62bd39f4d4fc8c61ce0b3786197e", + "reference": "199e1aebbe3e62bd39f4d4fc8c61ce0b3786197e", "shasum": "" }, "require": { + "league/flysystem-local": "^3.0.0", "league/mime-type-detection": "^1.0.0", "php": "^8.0.2" }, "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", "aws/aws-sdk-php": "3.209.31 || 3.210.0", "guzzlehttp/guzzle": "<7.0", "guzzlehttp/ringphp": "<1.1.1", @@ -2937,8 +2874,8 @@ "symfony/http-client": "<5.2" }, "require-dev": { - "async-aws/s3": "^1.5", - "async-aws/simple-s3": "^1.1", + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", "aws/aws-sdk-php": "^3.220.0", "composer/semver": "^3.0", "ext-fileinfo": "*", @@ -2947,9 +2884,9 @@ "friendsofphp/php-cs-fixer": "^3.5", "google/cloud-storage": "^1.23", "microsoft/azure-storage-blob": "^1.1", - "phpseclib/phpseclib": "^3.0.14", - "phpstan/phpstan": "^0.12.26", - "phpunit/phpunit": "^9.5.11", + "phpseclib/phpseclib": "^3.0.34", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", "sabre/dav": "^4.3.1" }, "type": "library", @@ -2984,7 +2921,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.12.2" + "source": "https://github.com/thephpleague/flysystem/tree/3.23.1" }, "funding": [ { @@ -2994,36 +2931,158 @@ { "url": "https://github.com/frankdejonge", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/flysystem", - "type": "tidelift" } ], - "time": "2023-01-19T12:02:19+00:00" + "time": "2024-01-26T18:42:03+00:00" }, { - "name": "league/mime-type-detection", - "version": "1.11.0", + "name": "league/flysystem-aws-s3-v3", + "version": "3.23.1", "source": { "type": "git", - "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "97728e7a0d40ec9c6147eb0f4ee4cdc6ff0a8240" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", - "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/97728e7a0d40ec9c6147eb0f4ee4cdc6ff0a8240", + "reference": "97728e7a0d40ec9c6147eb0f4ee4cdc6ff0a8240", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "^3.220.0", + "league/flysystem": "^3.10.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3V3\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "AWS S3 filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "aws", + "file", + "files", + "filesystem", + "s3", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.23.1" + }, + "funding": [ + { + "url": "https://ecologi.com/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + } + ], + "time": "2024-01-26T18:25:23+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.23.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "b884d2bf9b53bb4804a56d2df4902bb51e253f00" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/b884d2bf9b53bb4804a56d2df4902bb51e253f00", + "reference": "b884d2bf9b53bb4804a56d2df4902bb51e253f00", "shasum": "" }, "require": { "ext-fileinfo": "*", - "php": "^7.2 || ^8.0" + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem-local/issues", + "source": "https://github.com/thephpleague/flysystem-local/tree/3.23.1" + }, + "funding": [ + { + "url": "https://ecologi.com/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + } + ], + "time": "2024-01-26T18:25:23+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.15.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3" + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" }, "type": "library", "autoload": { @@ -3044,7 +3103,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0" }, "funding": [ { @@ -3056,7 +3115,7 @@ "type": "tidelift" } ], - "time": "2022-04-17T13:12:02+00:00" + "time": "2024-01-28T23:22:08+00:00" }, { "name": "lstrojny/functional-php", @@ -3383,25 +3442,25 @@ }, { "name": "mtdowling/jmespath.php", - "version": "2.6.1", + "version": "2.7.0", "source": { "type": "git", "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb" + "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9b87907a81b87bc76d19a7fb2d61e61486ee9edb", - "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/bbb69a935c2cbb0c03d7f481a238027430f6440b", + "reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b", "shasum": "" }, "require": { - "php": "^5.4 || ^7.0 || ^8.0", + "php": "^7.2.5 || ^8.0", "symfony/polyfill-mbstring": "^1.17" }, "require-dev": { - "composer/xdebug-handler": "^1.4 || ^2.0", - "phpunit/phpunit": "^4.8.36 || ^7.5.15" + "composer/xdebug-handler": "^3.0.3", + "phpunit/phpunit": "^8.5.33" }, "bin": [ "bin/jp.php" @@ -3409,7 +3468,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "2.7-dev" } }, "autoload": { @@ -3425,6 +3484,11 @@ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", @@ -3438,9 +3502,9 @@ ], "support": { "issues": "https://github.com/jmespath/jmespath.php/issues", - "source": "https://github.com/jmespath/jmespath.php/tree/2.6.1" + "source": "https://github.com/jmespath/jmespath.php/tree/2.7.0" }, - "time": "2021-06-14T00:11:39+00:00" + "time": "2023-08-25T10:54:48+00:00" }, { "name": "nesbot/carbon", @@ -4687,21 +4751,21 @@ }, { "name": "psr/http-client", - "version": "1.0.1", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -4721,7 +4785,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP clients", @@ -4733,27 +4797,27 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/master" + "source": "https://github.com/php-fig/http-client" }, - "time": "2020-06-29T06:28:15+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { "name": "psr/http-factory", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + "reference": "e616d01114759c4c489f93b099585439f795fe35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", "shasum": "" }, "require": { "php": ">=7.0.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -4773,7 +4837,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interfaces for PSR-7 HTTP message factories", @@ -4788,31 +4852,31 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" }, - "time": "2019-04-30T12:38:16+00:00" + "time": "2023-04-10T20:10:41+00:00" }, { "name": "psr/http-message", - "version": "1.0.1", + "version": "1.1", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -4841,9 +4905,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/master" + "source": "https://github.com/php-fig/http-message/tree/1.1" }, - "time": "2016-08-06T14:39:51+00:00" + "time": "2023-04-04T09:50:52+00:00" }, { "name": "psr/log", @@ -5392,25 +5456,25 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.0.2", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c" + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", - "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", "shasum": "" }, "require": { - "php": ">=8.0.2" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -5439,7 +5503,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.2" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" }, "funding": [ { @@ -5455,7 +5519,7 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:55:41+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/error-handler", @@ -6494,16 +6558,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.28.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "42292d99c55abe617799667f454222c54c60e229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", "shasum": "" }, "require": { @@ -6518,7 +6582,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.27-dev" + "dev-main": "1.28-dev" }, "thanks": { "name": "symfony/polyfill", @@ -6557,7 +6621,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" }, "funding": [ { @@ -6573,7 +6637,7 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2023-07-28T09:04:16+00:00" }, { "name": "symfony/polyfill-php72", diff --git a/config/app.php b/config/app.php index c8bc3949..a470b7f6 100644 --- a/config/app.php +++ b/config/app.php @@ -123,7 +123,6 @@ return [ Illuminate\Translation\TranslationServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, - Aws\Laravel\AwsServiceProvider::class, Jackiedo\DotenvEditor\DotenvEditorServiceProvider::class, Intervention\Image\ImageServiceProvider::class, @@ -143,6 +142,7 @@ return [ App\Providers\BroadcastServiceProvider::class, App\Providers\ITunesServiceProvider::class, App\Providers\StreamerServiceProvider::class, + App\Providers\SongStorageServiceProvider::class, App\Providers\ObjectStorageServiceProvider::class, App\Providers\MacroProvider::class, App\Providers\LicenseServiceProvider::class, diff --git a/config/aws.php b/config/aws.php deleted file mode 100644 index 2919bb5f..00000000 --- a/config/aws.php +++ /dev/null @@ -1,30 +0,0 @@ - env('AWS_ENDPOINT', 'https://s3.amazonaws.com'), - - 'credentials' => [ - 'key' => env('AWS_ACCESS_KEY_ID'), - 'secret' => env('AWS_SECRET_ACCESS_KEY'), - ], - - 'region' => env('AWS_REGION', 'us-east-1'), - 'version' => 'latest', - 'ua_append' => [ - 'L5MOD/' . AwsServiceProvider::VERSION, - ], -]; diff --git a/config/filesystems.php b/config/filesystems.php index 9f4aa101..2c441851 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -62,10 +62,14 @@ return [ 's3' => [ 'driver' => 's3', - 'key' => env('AWS_KEY'), - 'secret' => env('AWS_SECRET'), - 'region' => env('AWS_REGION'), + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_REGION', 'us-east-1'), 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), + 'endpoint' => env('AWS_ENDPOINT'), + 'use_path_style_endpoint' => false, + 'throw' => true, ], 'rackspace' => [ diff --git a/config/koel.php b/config/koel.php index ddc7dae4..4bd97af1 100644 --- a/config/koel.php +++ b/config/koel.php @@ -1,6 +1,8 @@ env('STORAGE_DRIVER', 'r2'), + 'media_path' => env('MEDIA_PATH'), // The *relative* path to the directory to store album covers and thumbnails, *with* a trailing slash. diff --git a/tests/Feature/KoelPlus/SongPlayTest.php b/tests/Feature/KoelPlus/SongPlayTest.php index 30ce19ed..60a0620e 100644 --- a/tests/Feature/KoelPlus/SongPlayTest.php +++ b/tests/Feature/KoelPlus/SongPlayTest.php @@ -3,7 +3,7 @@ namespace Tests\Feature\KoelPlus; use App\Models\Song; -use App\Services\Streamers\DirectStreamerInterface; +use App\Services\Streamers\LocalStreamerInterface; use App\Services\TokenManager; use App\Values\CompositeToken; use Mockery; @@ -24,7 +24,7 @@ class SongPlayTest extends PlusTestCase 'path' => test_path('songs/blank.mp3'), ]); - $mockStreamer = $this->mock(DirectStreamerInterface::class); + $mockStreamer = $this->mock(LocalStreamerInterface::class); $mockStreamer->shouldReceive('setSong')->with( Mockery::on(static fn (Song $retrievedSong): bool => $retrievedSong->id === $song->id) @@ -46,7 +46,7 @@ class SongPlayTest extends PlusTestCase /** @var CompositeToken $token */ $token = app(TokenManager::class)->createCompositeToken($song->owner); - $mockStreamer = $this->mock(DirectStreamerInterface::class); + $mockStreamer = $this->mock(LocalStreamerInterface::class); $mockStreamer->shouldReceive('setSong')->with( Mockery::on(static fn (Song $retrievedSong): bool => $retrievedSong->id === $song->id) diff --git a/tests/Feature/SongPlayTest.php b/tests/Feature/SongPlayTest.php index 94a4397a..7f643abf 100644 --- a/tests/Feature/SongPlayTest.php +++ b/tests/Feature/SongPlayTest.php @@ -3,7 +3,7 @@ namespace Tests\Feature; use App\Models\Song; -use App\Services\Streamers\DirectStreamerInterface; +use App\Services\Streamers\LocalStreamerInterface; use App\Services\TokenManager; use App\Values\CompositeToken; use Mockery; @@ -26,7 +26,7 @@ class SongPlayTest extends TestCase 'path' => test_path('songs/blank.mp3'), ]); - $mockStreamer = $this->mock(DirectStreamerInterface::class); + $mockStreamer = $this->mock(LocalStreamerInterface::class); $mockStreamer->shouldReceive('setSong')->with( Mockery::on(static fn (Song $retrievedSong): bool => $retrievedSong->id === $song->id) diff --git a/tests/Integration/Factories/StreamerFactoryTest.php b/tests/Integration/Factories/StreamerFactoryTest.php index fb361f13..b91c6b60 100644 --- a/tests/Integration/Factories/StreamerFactoryTest.php +++ b/tests/Integration/Factories/StreamerFactoryTest.php @@ -5,7 +5,7 @@ namespace Tests\Integration\Factories; use App\Factories\StreamerFactory; use App\Models\Song; use App\Services\Streamers\PhpStreamer; -use App\Services\Streamers\S3Streamer; +use App\Services\Streamers\S3CompatibleStreamer; use App\Services\Streamers\TranscodingStreamer; use App\Services\Streamers\XAccelRedirectStreamer; use App\Services\Streamers\XSendFileStreamer; @@ -33,7 +33,7 @@ class StreamerFactoryTest extends TestCase /** @var Song $song */ $song = Song::factory()->make(['path' => 's3://bucket/foo.mp3']); - self::assertInstanceOf(S3Streamer::class, $this->streamerFactory->createStreamer($song)); + self::assertInstanceOf(S3CompatibleStreamer::class, $this->streamerFactory->createStreamer($song)); } public function testCreateTranscodingStreamerIfSupported(): void diff --git a/tests/Integration/Repositories/SongRepositoryTest.php b/tests/Integration/Repositories/SongRepositoryTest.php index ff2abe81..2d9de188 100644 --- a/tests/Integration/Repositories/SongRepositoryTest.php +++ b/tests/Integration/Repositories/SongRepositoryTest.php @@ -21,6 +21,6 @@ class SongRepositoryTest extends TestCase { /** @var Song $song */ $song = Song::factory()->create(['path' => 'foo']); - self::assertSame($song->id, $this->songRepository->getOneByPath('foo')->id); + self::assertSame($song->id, $this->songRepository->findOneByPath('foo')->id); } } diff --git a/tests/Integration/Services/UploadServiceTest.php b/tests/Integration/Services/FileStorage/LocalStorageTest.php similarity index 64% rename from tests/Integration/Services/UploadServiceTest.php rename to tests/Integration/Services/FileStorage/LocalStorageTest.php index 5943606f..24c55841 100644 --- a/tests/Integration/Services/UploadServiceTest.php +++ b/tests/Integration/Services/FileStorage/LocalStorageTest.php @@ -1,11 +1,11 @@ service = app(UploadService::class); + $this->service = app(LocalStorage::class); } public function testHandleUploadedFileWithMediaPathNotSet(): void @@ -29,7 +29,7 @@ class UploadServiceTest extends TestCase Setting::set('media_path'); self::expectException(MediaPathNotSetException::class); - $this->service->handleUploadedFile(Mockery::mock(UploadedFile::class), create_user()); + $this->service->storeUploadedFile(Mockery::mock(UploadedFile::class), create_user()); } public function testHandleUploadedFileFails(): void @@ -37,7 +37,7 @@ class UploadServiceTest extends TestCase Setting::set('media_path', public_path('sandbox/media')); self::expectException(SongUploadFailedException::class); - $this->service->handleUploadedFile(UploadedFile::fake()->create('fake.mp3'), create_user()); + $this->service->storeUploadedFile(UploadedFile::fake()->create('fake.mp3'), create_user()); } public function testHandleUploadedFile(): void @@ -45,7 +45,7 @@ class UploadServiceTest extends TestCase Setting::set('media_path', public_path('sandbox/media')); $user = create_user(); - $song = $this->service->handleUploadedFile(UploadedFile::fromFile(test_path('songs/full.mp3')), $user); //@phpstan-ignore-line + $song = $this->service->storeUploadedFile(UploadedFile::fromFile(test_path('songs/full.mp3')), $user); //@phpstan-ignore-line self::assertSame($song->owner_id, $user->id); self::assertSame(public_path("sandbox/media/__KOEL_UPLOADS_\${$user->id}__/full.mp3"), $song->path); @@ -58,12 +58,12 @@ class UploadServiceTest extends TestCase $user->save(); Setting::set('media_path', public_path('sandbox/media')); - $song = $this->service->handleUploadedFile(UploadedFile::fromFile(test_path('songs/full.mp3')), $user); //@phpstan-ignore-line + $song = $this->service->storeUploadedFile(UploadedFile::fromFile(test_path('songs/full.mp3')), $user); //@phpstan-ignore-line self::assertTrue($song->is_public); $user->preferences->makeUploadsPublic = false; $user->save(); - $privateSongs = $this->service->handleUploadedFile(UploadedFile::fromFile(test_path('songs/full.mp3')), $user); //@phpstan-ignore-line + $privateSongs = $this->service->storeUploadedFile(UploadedFile::fromFile(test_path('songs/full.mp3')), $user); //@phpstan-ignore-line self::assertFalse($privateSongs->is_public); } } diff --git a/tests/Unit/Services/S3ServiceTest.php b/tests/Unit/Services/S3ServiceTest.php index 987dd1bc..eba235cb 100644 --- a/tests/Unit/Services/S3ServiceTest.php +++ b/tests/Unit/Services/S3ServiceTest.php @@ -129,7 +129,7 @@ class S3ServiceTest extends TestCase 'path' => 's3://foo/bar', ]); - $this->songRepository->shouldReceive('getOneByPath') + $this->songRepository->shouldReceive('findOneByPath') ->with('s3://foo/bar') ->once() ->andReturn($song);