feat(plus): support S3 compatible storages

This commit is contained in:
Phan An 2024-02-04 21:31:01 +01:00
parent 28af8c0122
commit b723f3d7c9
43 changed files with 563 additions and 379 deletions

View file

@ -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://%');
}
}

View file

@ -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)
);
}
}

View file

@ -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());

View file

@ -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');

View file

@ -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 {

View file

@ -0,0 +1,27 @@
<?php
namespace App\Providers;
use App\Services\SongStorage\LocalStorage;
use App\Services\SongStorage\S3CompatibleStorage;
use App\Services\SongStorage\SongStorage;
use Illuminate\Support\ServiceProvider;
class SongStorageServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->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');
}
}

View file

@ -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);
}
}

View file

@ -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<Song> */
public function getAllHostedOnS3(): Collection
public function getAllStoredOnCloud(): Collection
{
return Song::query()->hostedOnS3()->get();
return Song::query()->storedOnCloud()->get();
}
/** @return Collection|array<array-key, Song> */

View file

@ -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;

View file

@ -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();

View file

@ -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));

View file

@ -1,12 +1,13 @@
<?php
namespace App\Services;
namespace App\Services\SongStorage;
use App\Exceptions\MediaPathNotSetException;
use App\Exceptions\SongUploadFailedException;
use App\Models\Setting;
use App\Models\Song;
use App\Models\User;
use App\Services\FileScanner;
use App\Values\ScanConfiguration;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\File;
@ -14,13 +15,13 @@ use Throwable;
use function Functional\memoize;
class UploadService
class LocalStorage implements SongStorage
{
public function __construct(private FileScanner $scanner)
{
}
public function handleUploadedFile(UploadedFile $file, User $uploader): Song
public function storeUploadedFile(UploadedFile $file, User $uploader): Song
{
$uploadDirectory = $this->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());

View file

@ -0,0 +1,58 @@
<?php
namespace App\Services\SongStorage;
use App\Models\Song;
use App\Models\User;
use App\Services\FileScanner;
use App\Values\ScanConfiguration;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Symfony\Component\Uid\Ulid;
class S3CompatibleStorage implements SongStorage
{
public function __construct(private FileScanner $scanner, private string $bucket)
{
}
public function storeUploadedFile(UploadedFile $file, User $uploader): Song
{
// can't scan the uploaded file directly, as it may cause some misbehavior
// instead, we copy the file to the tmp directory and scan it from there
$tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . Str::uuid();
File::makeDirectory($tmpDir);
$tmpFile = $file->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);
}
}

View file

@ -0,0 +1,12 @@
<?php
namespace App\Services\SongStorage;
use App\Models\Song;
use App\Models\User;
use Illuminate\Http\UploadedFile;
interface SongStorage
{
public function storeUploadedFile(UploadedFile $file, User $uploader): Song;
}

View file

@ -1,7 +0,0 @@
<?php
namespace App\Services\Streamers;
interface DirectStreamerInterface extends StreamerInterface
{
}

View file

@ -0,0 +1,7 @@
<?php
namespace App\Services\Streamers;
interface LocalStreamerInterface extends StreamerInterface
{
}

View file

@ -1,7 +0,0 @@
<?php
namespace App\Services\Streamers;
interface ObjectStorageStreamerInterface extends StreamerInterface
{
}

View file

@ -14,7 +14,7 @@ use Symfony\Component\HttpFoundation\Response;
use function DaveRandom\Resume\get_request_header;
class PhpStreamer extends Streamer implements DirectStreamerInterface
class PhpStreamer extends Streamer implements LocalStreamerInterface
{
public function stream(): void
{

View file

@ -0,0 +1,24 @@
<?php
namespace App\Services\Streamers;
use App\Services\SongStorage\S3CompatibleStorage;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Redirector;
class S3CompatibleStreamer extends Streamer
{
public function __construct(private S3CompatibleStorage $storage)
{
parent::__construct();
}
/**
* Stream the current song from the Object Storable server.
* Actually, we just redirect the request to the object's presigned URL.
*/
public function stream(): Redirector|RedirectResponse
{
return redirect($this->storage->getSongPresignedUrl($this->song));
}
}

View file

@ -1,28 +0,0 @@
<?php
namespace App\Services\Streamers;
use App\Services\S3Service;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Redirector;
class S3Streamer extends Streamer implements ObjectStorageStreamerInterface
{
public function __construct(private S3Service $s3Service)
{
parent::__construct();
}
/**
* Stream the current song through S3.
* Actually, we just redirect the request to the S3 object's location.
*
* @return Redirector|RedirectResponse
*
*/
public function stream() // @phpcs:ignore
{
// Get and redirect to the actual presigned-url
return redirect($this->s3Service->getSongPublicUrl($this->song));
}
}

View file

@ -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);
}
}
}

View file

@ -7,6 +7,4 @@ use App\Models\Song;
interface StreamerInterface
{
public function setSong(Song $song): void;
public function stream(); // @phpcs:ignore
}

View file

@ -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));

View file

@ -1,10 +0,0 @@
<?php
namespace App\Services\Streamers;
interface TranscodingStreamerInterface extends StreamerInterface
{
public function setBitRate(int $bitRate): void;
public function setStartTime(float $startTime): void;
}

View file

@ -4,7 +4,7 @@ namespace App\Services\Streamers;
use App\Models\Setting;
class XAccelRedirectStreamer extends Streamer implements DirectStreamerInterface
class XAccelRedirectStreamer extends Streamer implements LocalStreamerInterface
{
/**
* Stream the current song using nginx's X-Accel-Redirect.
@ -18,7 +18,7 @@ class XAccelRedirectStreamer extends Streamer implements DirectStreamerInterface
// See nginx.conf.example.
header('X-Media-Root: ' . Setting::get('media_path'));
header("X-Accel-Redirect: /media/$relativePath");
header("Content-Type: {$this->contentType}");
header("Content-Type: $this->contentType");
header('Content-Disposition: inline; filename="' . basename($this->song->path) . '"');
exit;

View file

@ -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.

View file

@ -0,0 +1,20 @@
<?php
namespace App\Values\SongStorageMetadata;
final class LocalMetadata implements StorageMetadata
{
private function __construct(public string $path)
{
}
public static function make(string $path): self
{
return new self($path);
}
public function getPath(): string
{
return $this->path;
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace App\Values\SongStorageMetadata;
final class R2Metadata extends S3CompatibleMetadata
{
}

View file

@ -0,0 +1,20 @@
<?php
namespace App\Values\SongStorageMetadata;
class S3CompatibleMetadata implements StorageMetadata
{
private function __construct(public string $bucket, public string $key)
{
}
public static function make(string $bucket, string $key): self
{
return new static($bucket, $key);
}
public function getPath(): string
{
return $this->key;
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace App\Values\SongStorageMetadata;
final class S3Metadata extends S3CompatibleMetadata
{
}

View file

@ -0,0 +1,8 @@
<?php
namespace App\Values\SongStorageMetadata;
interface StorageMetadata
{
public function getPath(): string;
}

View file

@ -13,7 +13,6 @@
"laravel/framework": "^9.0",
"james-heinrich/getid3": "^1.9",
"guzzlehttp/guzzle": "^7.0.1",
"aws/aws-sdk-php-laravel": "^3.1",
"pusher/pusher-php-server": "^4.0",
"predis/predis": "~1.0",
"jackiedo/dotenv-editor": "^2.0",
@ -36,7 +35,8 @@
"nunomaduro/collision": "^6.2",
"jwilsson/spotify-web-api-php": "^5.2",
"meilisearch/meilisearch-php": "^0.24.0",
"http-interop/http-factory-guzzle": "^1.2"
"http-interop/http-factory-guzzle": "^1.2",
"league/flysystem-aws-s3-v3": "^3.0"
},
"require-dev": {
"mockery/mockery": "~1.0",

478
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "310243edd4bc8d4f2184ff38520a52dd",
"content-hash": "6a64a8f7d4da75827254d63d4197bd51",
"packages": [
{
"name": "algolia/algoliasearch-client-php",
@ -82,23 +82,27 @@
},
{
"name": "aws/aws-crt-php",
"version": "v1.0.2",
"version": "v1.2.4",
"source": {
"type": "git",
"url": "https://github.com/awslabs/aws-crt-php.git",
"reference": "3942776a8c99209908ee0b287746263725685732"
"reference": "eb0c6e4e142224a10b08f49ebf87f32611d162b2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/3942776a8c99209908ee0b287746263725685732",
"reference": "3942776a8c99209908ee0b287746263725685732",
"url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/eb0c6e4e142224a10b08f49ebf87f32611d162b2",
"reference": "eb0c6e4e142224a10b08f49ebf87f32611d162b2",
"shasum": ""
},
"require": {
"php": ">=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",

View file

@ -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,

View file

@ -1,30 +0,0 @@
<?php
use Aws\Laravel\AwsServiceProvider;
return [
/*
|--------------------------------------------------------------------------
| AWS SDK Configuration
|--------------------------------------------------------------------------
|
| The configuration options set in this file will be passed directly to the
| `Aws\Sdk` object, from which all client objects are created. The minimum
| required options are declared here, but the full set of possible options
| are documented at:
| http://docs.aws.amazon.com/aws-sdk-php/v3/guide/guide/configuration.html
|
*/
'endpoint' => 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,
],
];

View file

@ -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' => [

View file

@ -1,6 +1,8 @@
<?php
return [
'storage_driver' => 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.

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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);
}
}

View file

@ -1,11 +1,11 @@
<?php
namespace Tests\Integration\Services;
namespace Tests\Integration\Services\FileStorage;
use App\Exceptions\MediaPathNotSetException;
use App\Exceptions\SongUploadFailedException;
use App\Models\Setting;
use App\Services\UploadService;
use App\Services\SongStorage\LocalStorage;
use Illuminate\Http\UploadedFile;
use Mockery;
use Tests\TestCase;
@ -13,15 +13,15 @@ use Tests\TestCase;
use function Tests\create_user;
use function Tests\test_path;
class UploadServiceTest extends TestCase
class LocalStorageTest extends TestCase
{
private UploadService $service;
private LocalStorage $service;
public function setUp(): void
{
parent::setUp();
$this->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);
}
}

View file

@ -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);