mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
chore: remove deprecated S3Service
This commit is contained in:
parent
b4ea2856f3
commit
0e634a33a8
7 changed files with 89 additions and 176 deletions
|
@ -1,18 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API\ObjectStorage\S3;
|
||||
namespace App\Http\Controllers\API;
|
||||
|
||||
use App\Exceptions\SongPathNotFoundException;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\API\ObjectStorage\S3\PutSongRequest;
|
||||
use App\Http\Requests\API\ObjectStorage\S3\RemoveSongRequest;
|
||||
use App\Services\S3Service;
|
||||
use App\Services\SongStorage\S3LambdaStorage;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class SongController extends Controller
|
||||
class LambdaSongController extends Controller
|
||||
{
|
||||
public function __construct(private S3Service $s3Service)
|
||||
public function __construct(private S3LambdaStorage $storage)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ class SongController extends Controller
|
|||
{
|
||||
$artist = Arr::get($request->tags, 'artist', '');
|
||||
|
||||
$song = $this->s3Service->createSongEntry(
|
||||
$song = $this->storage->createSongEntry(
|
||||
$request->bucket,
|
||||
$request->key,
|
||||
$artist,
|
||||
|
@ -39,7 +39,7 @@ class SongController extends Controller
|
|||
public function remove(RemoveSongRequest $request)
|
||||
{
|
||||
try {
|
||||
$this->s3Service->deleteSongEntry($request->bucket, $request->key);
|
||||
$this->storage->deleteSongEntry($request->bucket, $request->key);
|
||||
} catch (SongPathNotFoundException) {
|
||||
abort(Response::HTTP_NOT_FOUND);
|
||||
}
|
|
@ -40,7 +40,6 @@ use Throwable;
|
|||
* @property ?bool $liked Whether the song is liked by the current user (dynamically calculated)
|
||||
* @property ?int $play_count The number of times the song has been played by the current user (dynamically calculated)
|
||||
* @property Carbon $created_at
|
||||
* @property array<mixed> $s3_params
|
||||
* @property int $owner_id
|
||||
* @property bool $is_public
|
||||
* @property User $owner
|
||||
|
@ -188,19 +187,6 @@ class Song extends Model
|
|||
);
|
||||
}
|
||||
|
||||
protected function s3Params(): Attribute
|
||||
{
|
||||
return Attribute::get(function (): ?array {
|
||||
if (!preg_match('/^s3:\\/\\/(.*)/', $this->path, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
[$bucket, $key] = explode('/', $matches[1], 2);
|
||||
|
||||
return compact('bucket', 'key');
|
||||
});
|
||||
}
|
||||
|
||||
public static function getPathFromS3BucketAndKey(string $bucket, string $key): string
|
||||
{
|
||||
return "s3://$bucket/$key";
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Events\LibraryChanged;
|
||||
use App\Exceptions\SongPathNotFoundException;
|
||||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
||||
use App\Models\Song;
|
||||
use App\Repositories\SongRepository;
|
||||
use App\Repositories\UserRepository;
|
||||
use App\Values\SongStorageTypes;
|
||||
use Aws\S3\S3ClientInterface;
|
||||
use Illuminate\Cache\Repository as Cache;
|
||||
|
||||
class S3Service implements ObjectStorageInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ?S3ClientInterface $s3Client,
|
||||
private Cache $cache,
|
||||
private MediaMetadataService $mediaMetadataService,
|
||||
private SongRepository $songRepository,
|
||||
private UserRepository $userRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getSongPublicUrl(Song $song): string
|
||||
{
|
||||
return $this->cache->remember("OSUrl/$song->id", now()->addHour(), function () use ($song): string {
|
||||
$cmd = $this->s3Client->getCommand('GetObject', [
|
||||
'Bucket' => $song->s3_params['bucket'],
|
||||
'Key' => $song->s3_params['key'],
|
||||
]);
|
||||
|
||||
// Here we specify that the request is valid for 1 hour.
|
||||
// We'll also cache the public URL for future reuse.
|
||||
$request = $this->s3Client->createPresignedRequest($cmd, '+1 hour');
|
||||
return (string) $request->getUri();
|
||||
});
|
||||
}
|
||||
|
||||
public function createSongEntry(
|
||||
string $bucket,
|
||||
string $key,
|
||||
string $artistName,
|
||||
string $albumName,
|
||||
string $albumArtistName,
|
||||
?array $cover,
|
||||
string $title,
|
||||
float $duration,
|
||||
int $track,
|
||||
string $lyrics
|
||||
): Song {
|
||||
$user = $this->userRepository->getDefaultAdminUser();
|
||||
$path = Song::getPathFromS3BucketAndKey($bucket, $key);
|
||||
$artist = Artist::getOrCreate($artistName);
|
||||
|
||||
$albumArtist = $albumArtistName && $albumArtistName !== $artistName
|
||||
? Artist::getOrCreate($albumArtistName)
|
||||
: $artist;
|
||||
|
||||
$album = Album::getOrCreate($albumArtist, $albumName);
|
||||
|
||||
if ($cover) {
|
||||
$this->mediaMetadataService->writeAlbumCover(
|
||||
$album,
|
||||
base64_decode($cover['data'], true),
|
||||
$cover['extension']
|
||||
);
|
||||
}
|
||||
|
||||
/** @var Song $song */
|
||||
$song = Song::query()->updateOrCreate(['path' => $path], [
|
||||
'album_id' => $album->id,
|
||||
'artist_id' => $artist->id,
|
||||
'title' => $title,
|
||||
'length' => $duration,
|
||||
'track' => $track,
|
||||
'lyrics' => $lyrics,
|
||||
'mtime' => time(),
|
||||
'owner_id' => $user->id,
|
||||
'is_public' => true,
|
||||
'storage' => SongStorageTypes::S3_LAMBDA,
|
||||
]);
|
||||
|
||||
event(new LibraryChanged());
|
||||
|
||||
return $song;
|
||||
}
|
||||
|
||||
public function deleteSongEntry(string $bucket, string $key): void
|
||||
{
|
||||
$path = Song::getPathFromS3BucketAndKey($bucket, $key);
|
||||
$song = $this->songRepository->findOneByPath($path);
|
||||
|
||||
throw_unless((bool) $song, SongPathNotFoundException::create($path));
|
||||
|
||||
$song->delete();
|
||||
event(new LibraryChanged());
|
||||
}
|
||||
}
|
|
@ -2,9 +2,16 @@
|
|||
|
||||
namespace App\Services\SongStorage;
|
||||
|
||||
use App\Events\LibraryChanged;
|
||||
use App\Exceptions\MethodNotImplementedException;
|
||||
use App\Exceptions\SongPathNotFoundException;
|
||||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
||||
use App\Models\Song;
|
||||
use App\Models\User;
|
||||
use App\Repositories\SongRepository;
|
||||
use App\Repositories\UserRepository;
|
||||
use App\Services\MediaMetadataService;
|
||||
use App\Values\SongStorageTypes;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
|
||||
|
@ -14,11 +21,78 @@ use Illuminate\Http\UploadedFile;
|
|||
*/
|
||||
final class S3LambdaStorage extends S3CompatibleStorage
|
||||
{
|
||||
public function __construct( // @phpcs:ignore
|
||||
private MediaMetadataService $mediaMetadataService,
|
||||
private SongRepository $songRepository,
|
||||
private UserRepository $userRepository
|
||||
) {
|
||||
}
|
||||
|
||||
public function storeUploadedFile(UploadedFile $file, User $uploader): Song
|
||||
{
|
||||
throw new MethodNotImplementedException('Lambda storage does not support uploading.');
|
||||
}
|
||||
|
||||
public function createSongEntry(
|
||||
string $bucket,
|
||||
string $key,
|
||||
string $artistName,
|
||||
string $albumName,
|
||||
string $albumArtistName,
|
||||
?array $cover,
|
||||
string $title,
|
||||
float $duration,
|
||||
int $track,
|
||||
string $lyrics
|
||||
): Song {
|
||||
$user = $this->userRepository->getDefaultAdminUser();
|
||||
$path = Song::getPathFromS3BucketAndKey($bucket, $key);
|
||||
$artist = Artist::getOrCreate($artistName);
|
||||
|
||||
$albumArtist = $albumArtistName && $albumArtistName !== $artistName
|
||||
? Artist::getOrCreate($albumArtistName)
|
||||
: $artist;
|
||||
|
||||
$album = Album::getOrCreate($albumArtist, $albumName);
|
||||
|
||||
if ($cover) {
|
||||
$this->mediaMetadataService->writeAlbumCover(
|
||||
$album,
|
||||
base64_decode($cover['data'], true),
|
||||
$cover['extension']
|
||||
);
|
||||
}
|
||||
|
||||
/** @var Song $song */
|
||||
$song = Song::query()->updateOrCreate(['path' => $path], [
|
||||
'album_id' => $album->id,
|
||||
'artist_id' => $artist->id,
|
||||
'title' => $title,
|
||||
'length' => $duration,
|
||||
'track' => $track,
|
||||
'lyrics' => $lyrics,
|
||||
'mtime' => time(),
|
||||
'owner_id' => $user->id,
|
||||
'is_public' => true,
|
||||
'storage' => SongStorageTypes::S3_LAMBDA,
|
||||
]);
|
||||
|
||||
event(new LibraryChanged());
|
||||
|
||||
return $song;
|
||||
}
|
||||
|
||||
public function deleteSongEntry(string $bucket, string $key): void
|
||||
{
|
||||
$path = Song::getPathFromS3BucketAndKey($bucket, $key);
|
||||
$song = $this->songRepository->findOneByPath($path);
|
||||
|
||||
throw_unless((bool) $song, SongPathNotFoundException::create($path));
|
||||
|
||||
$song->delete();
|
||||
event(new LibraryChanged());
|
||||
}
|
||||
|
||||
public function supported(): bool
|
||||
{
|
||||
return SongStorageTypes::supported(SongStorageTypes::S3_LAMBDA);
|
||||
|
|
|
@ -22,9 +22,9 @@ use App\Http\Controllers\API\FetchRecentlyPlayedSongController;
|
|||
use App\Http\Controllers\API\FetchSongsForQueueController;
|
||||
use App\Http\Controllers\API\GenreController;
|
||||
use App\Http\Controllers\API\GenreSongController;
|
||||
use App\Http\Controllers\API\LambdaSongController as S3SongController;
|
||||
use App\Http\Controllers\API\LikeMultipleSongsController;
|
||||
use App\Http\Controllers\API\MovePlaylistSongsController;
|
||||
use App\Http\Controllers\API\ObjectStorage\S3\SongController as S3SongController;
|
||||
use App\Http\Controllers\API\PlaylistCollaboration\AcceptPlaylistCollaborationInviteController;
|
||||
use App\Http\Controllers\API\PlaylistCollaboration\CreatePlaylistCollaborationTokenController;
|
||||
use App\Http\Controllers\API\PlaylistCollaboration\PlaylistCollaboratorController;
|
||||
|
|
|
@ -7,14 +7,6 @@ use Tests\TestCase;
|
|||
|
||||
class SongTest extends TestCase
|
||||
{
|
||||
public function testGettingS3HostedSongs(): void
|
||||
{
|
||||
/** @var Song $song */
|
||||
$song = Song::factory()->create(['path' => 's3://foo/bar']);
|
||||
|
||||
self::assertEquals(['bucket' => 'foo', 'key' => 'bar'], $song->s3_params);
|
||||
}
|
||||
|
||||
public function testLyricsDoNotContainTimestamps(): void
|
||||
{
|
||||
/** @var Song $song */
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit\Services;
|
||||
namespace Tests\Unit\Services\SongStorage;
|
||||
|
||||
use App\Events\LibraryChanged;
|
||||
use App\Models\Song;
|
||||
use App\Repositories\SongRepository;
|
||||
use App\Repositories\UserRepository;
|
||||
use App\Services\MediaMetadataService;
|
||||
use App\Services\S3Service;
|
||||
use Aws\CommandInterface;
|
||||
use Aws\S3\S3ClientInterface;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use Illuminate\Cache\Repository as Cache;
|
||||
use App\Services\SongStorage\S3LambdaStorage;
|
||||
use Mockery;
|
||||
use Mockery\LegacyMockInterface;
|
||||
use Mockery\MockInterface;
|
||||
|
@ -19,28 +15,21 @@ use Tests\TestCase;
|
|||
|
||||
use function Tests\create_admin;
|
||||
|
||||
class S3ServiceTest extends TestCase
|
||||
class S3LambdaStorageTest extends TestCase
|
||||
{
|
||||
private S3ClientInterface|LegacyMockInterface|MockInterface $s3Client;
|
||||
private Cache|LegacyMockInterface|MockInterface $cache;
|
||||
private SongRepository|LegacyMockInterface|MockInterface $songRepository;
|
||||
private UserRepository|LegacyMockInterface|MockInterface $userRepository;
|
||||
private S3Service $s3Service;
|
||||
private S3LambdaStorage $storage;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->s3Client = Mockery::mock(S3ClientInterface::class);
|
||||
$this->cache = Mockery::mock(Cache::class);
|
||||
|
||||
$metadataService = Mockery::mock(MediaMetadataService::class);
|
||||
$this->songRepository = Mockery::mock(SongRepository::class);
|
||||
$this->userRepository = Mockery::mock(UserRepository::class);
|
||||
|
||||
$this->s3Service = new S3Service(
|
||||
$this->s3Client,
|
||||
$this->cache,
|
||||
$this->storage = new S3LambdaStorage(
|
||||
$metadataService,
|
||||
$this->songRepository,
|
||||
$this->userRepository
|
||||
|
@ -56,7 +45,7 @@ class S3ServiceTest extends TestCase
|
|||
->once()
|
||||
->andReturn($user);
|
||||
|
||||
$song = $this->s3Service->createSongEntry(
|
||||
$song = $this->storage->createSongEntry(
|
||||
bucket: 'foo',
|
||||
key: 'bar',
|
||||
artistName: 'Queen',
|
||||
|
@ -93,7 +82,7 @@ class S3ServiceTest extends TestCase
|
|||
'path' => 's3://foo/bar',
|
||||
]);
|
||||
|
||||
$this->s3Service->createSongEntry(
|
||||
$this->storage->createSongEntry(
|
||||
bucket: 'foo',
|
||||
key: 'bar',
|
||||
artistName: 'Queen',
|
||||
|
@ -134,35 +123,8 @@ class S3ServiceTest extends TestCase
|
|||
->once()
|
||||
->andReturn($song);
|
||||
|
||||
$this->s3Service->deleteSongEntry('foo', 'bar');
|
||||
$this->storage->deleteSongEntry('foo', 'bar');
|
||||
|
||||
self::assertModelMissing($song);
|
||||
}
|
||||
|
||||
public function testGetSongPublicUrl(): void
|
||||
{
|
||||
/** @var Song $song */
|
||||
$song = Song::factory()->create(['path' => 's3://foo/bar']);
|
||||
|
||||
$cmd = Mockery::mock(CommandInterface::class);
|
||||
|
||||
$this->s3Client->shouldReceive('getCommand')
|
||||
->with('GetObject', [
|
||||
'Bucket' => 'foo',
|
||||
'Key' => 'bar',
|
||||
])
|
||||
->andReturn($cmd);
|
||||
|
||||
$request = Mockery::mock(Request::class, ['getUri' => 'https://aws.com/foo.mp3']);
|
||||
|
||||
$this->s3Client->shouldReceive('createPresignedRequest')
|
||||
->with($cmd, '+1 hour')
|
||||
->andReturn($request);
|
||||
|
||||
$this->cache->shouldReceive('remember')
|
||||
->once()
|
||||
->andReturn('https://aws.com/foo.mp3');
|
||||
|
||||
self::assertSame('https://aws.com/foo.mp3', $this->s3Service->getSongPublicUrl($song));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue