chore: some cleanups

This commit is contained in:
Phan An 2024-09-15 15:33:59 +02:00
parent 2240fc0b80
commit e3bbf4ab4b
10 changed files with 60 additions and 70 deletions

View file

@ -14,7 +14,7 @@ class ScrobbleController extends Controller
/** @param User $user */ /** @param User $user */
public function __invoke(ScrobbleRequest $request, Song $song, Authenticatable $user) public function __invoke(ScrobbleRequest $request, Song $song, Authenticatable $user)
{ {
if (!$song->artist->is_unknown && $user->connectedToLastfm()) { if (!$song->artist->is_unknown && $user->connected_to_lastfm) {
ScrobbleJob::dispatch($user, $song, $request->timestamp); ScrobbleJob::dispatch($user, $song, $request->timestamp);
} }

View file

@ -4,6 +4,7 @@ namespace App\Models;
use App\Casts\SmartPlaylistRulesCast; use App\Casts\SmartPlaylistRulesCast;
use App\Facades\License as LicenseFacade; use App\Facades\License as LicenseFacade;
use App\Models\Song as Playable;
use App\Values\SmartPlaylistRuleGroupCollection; use App\Values\SmartPlaylistRuleGroupCollection;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
@ -16,7 +17,6 @@ use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Laravel\Scout\Searchable; use Laravel\Scout\Searchable;
use LogicException;
/** /**
* @property string $id * @property string $id
@ -24,8 +24,7 @@ use LogicException;
* @property bool $is_smart * @property bool $is_smart
* @property int $user_id * @property int $user_id
* @property User $user * @property User $user
* @property Collection<array-key, Song> $songs * @property Collection<array-key, Playable> $playables
* @property array<string> $song_ids
* @property ?SmartPlaylistRuleGroupCollection $rule_groups * @property ?SmartPlaylistRuleGroupCollection $rule_groups
* @property ?SmartPlaylistRuleGroupCollection $rules * @property ?SmartPlaylistRuleGroupCollection $rules
* @property Carbon $created_at * @property Carbon $created_at
@ -61,9 +60,9 @@ class Playlist extends Model
}); });
} }
public function songs(): BelongsToMany public function playables(): BelongsToMany
{ {
return $this->belongsToMany(Song::class) return $this->belongsToMany(Playable::class)
->withTimestamps() ->withTimestamps()
->withPivot('position') ->withPivot('position')
->orderByPivot('position'); ->orderByPivot('position');
@ -100,13 +99,6 @@ class Playlist extends Model
return Attribute::get(fn () => $this->rules); return Attribute::get(fn () => $this->rules);
} }
public function songIds(): Attribute
{
throw_if($this->is_smart, new LogicException('Smart playlist contents are generated dynamically.'));
return Attribute::get(fn () => $this->songs->pluck('id')->all());
}
protected function cover(): Attribute protected function cover(): Attribute
{ {
return Attribute::get(static fn (?string $value): ?string => playlist_cover_url($value)); return Attribute::get(static fn (?string $value): ?string => playlist_cover_url($value));
@ -156,39 +148,39 @@ class Playlist extends Model
} }
/** /**
* @param Collection|array<array-key, Song>|Song|array<string> $songs * @param Collection|array<array-key, Playable>|Playable|array<string> $playables
*/ */
public function addPlayables(Collection|Song|array $songs, ?User $collaborator = null): void public function addPlayables(Collection|Playable|array $playables, ?User $collaborator = null): void
{ {
$collaborator ??= $this->user; $collaborator ??= $this->user;
$maxPosition = $this->songs()->getQuery()->max('position') ?? 0; $maxPosition = $this->playables()->getQuery()->max('position') ?? 0;
if (!is_array($songs)) { if (!is_array($playables)) {
$songs = Collection::wrap($songs)->pluck('id')->all(); $playables = Collection::wrap($playables)->pluck('id')->all();
} }
$data = []; $data = [];
foreach ($songs as $song) { foreach ($playables as $playable) {
$data[$song] = [ $data[$playable] = [
'position' => ++$maxPosition, 'position' => ++$maxPosition,
'user_id' => $collaborator->id, 'user_id' => $collaborator->id,
]; ];
} }
$this->songs()->attach($data); $this->playables()->attach($data);
} }
/** /**
* @param Collection<array-key, Song>|Song|array<string> $songs * @param Collection<array-key, Playable>|Playable|array<string> $playables
*/ */
public function removePlayables(Collection|Song|array $songs): void public function removePlayables(Collection|Playable|array $playables): void
{ {
if (!is_array($songs)) { if (!is_array($playables)) {
$songs = Collection::wrap($songs)->pluck('id')->all(); $playables = Collection::wrap($playables)->pluck('id')->all();
} }
$this->songs()->detach($songs); $this->playables()->detach($playables);
} }
protected function isCollaborative(): Attribute protected function isCollaborative(): Attribute

View file

@ -20,27 +20,28 @@ use Laravel\Sanctum\HasApiTokens;
use Laravel\Sanctum\PersonalAccessToken; use Laravel\Sanctum\PersonalAccessToken;
/** /**
* @property UserPreferences $preferences
* @property int $id
* @property bool $is_admin
* @property string $name
* @property string $email
* @property string $password
* @property-read bool $has_custom_avatar
* @property-read string $avatar
* @property Collection<array-key, Playlist> $playlists
* @property Collection<array-key, PlaylistFolder> $playlist_folders
* @property PersonalAccessToken $currentAccessToken
* @property ?Carbon $invitation_accepted_at * @property ?Carbon $invitation_accepted_at
* @property ?Carbon $invited_at
* @property ?User $invitedBy * @property ?User $invitedBy
* @property ?string $invitation_token * @property ?string $invitation_token
* @property ?Carbon $invited_at
* @property-read bool $is_prospect
* @property Collection<array-key, Playlist> $collaboratedPlaylists * @property Collection<array-key, Playlist> $collaboratedPlaylists
* @property ?string $sso_provider * @property Collection<array-key, Playlist> $playlists
* @property ?string $sso_id * @property Collection<array-key, PlaylistFolder> $playlist_folders
* @property bool $is_sso
* @property Collection<array-key, Podcast> $podcasts * @property Collection<array-key, Podcast> $podcasts
* @property PersonalAccessToken $currentAccessToken
* @property UserPreferences $preferences
* @property bool $is_admin
* @property int $id
* @property string $email
* @property string $name
* @property string $password
* @property-read ?string $sso_id
* @property-read ?string $sso_provider
* @property-read bool $connected_to_lastfm Whether the user is connected to Last.fm
* @property-read bool $has_custom_avatar
* @property-read bool $is_prospect
* @property-read bool $is_sso
* @property-read string $avatar
*/ */
class User extends Authenticatable class User extends Authenticatable
{ {
@ -126,11 +127,8 @@ class User extends Authenticatable
return Attribute::get(fn (): bool => License::isPlus() && $this->sso_provider); return Attribute::get(fn (): bool => License::isPlus() && $this->sso_provider);
} }
/** protected function connectedToLastfm(): Attribute
* Determine if the user is connected to Last.fm.
*/
public function connectedToLastfm(): bool
{ {
return (bool) $this->preferences->lastFmSessionKey; return Attribute::get(fn (): bool => (bool) $this->preferences->lastFmSessionKey);
} }
} }

View file

@ -65,7 +65,7 @@ class PlaylistCollaborationService
DB::transaction(static function () use ($playlist, $user): void { DB::transaction(static function () use ($playlist, $user): void {
$playlist->collaborators()->detach($user); $playlist->collaborators()->detach($user);
$playlist->songs()->wherePivot('user_id', $user->id)->detach(); $playlist->playables()->wherePivot('user_id', $user->id)->detach();
}); });
} }
} }

View file

@ -95,7 +95,7 @@ class PlaylistService
$playables = Collection::wrap($playables); $playables = Collection::wrap($playables);
$playlist->addPlayables( $playlist->addPlayables(
$playables->filter(static fn ($song): bool => !$playlist->songs->contains($song)), $playables->filter(static fn ($song): bool => !$playlist->playables->contains($song)),
$user $user
); );
@ -119,7 +119,7 @@ class PlaylistService
public function makePlaylistContentPublic(Playlist $playlist): void public function makePlaylistContentPublic(Playlist $playlist): void
{ {
$playlist->songs()->where('is_public', false)->update(['is_public' => true]); $playlist->playables()->where('is_public', false)->update(['is_public' => true]);
} }
public function movePlayablesInPlaylist(Playlist $playlist, array $movingIds, string $target, string $type): void public function movePlayablesInPlaylist(Playlist $playlist, array $movingIds, string $target, string $type): void
@ -128,11 +128,11 @@ class PlaylistService
throw_if($playlist->is_smart, OperationNotApplicableForSmartPlaylistException::class); throw_if($playlist->is_smart, OperationNotApplicableForSmartPlaylistException::class);
DB::transaction(static function () use ($playlist, $movingIds, $target, $type): void { DB::transaction(static function () use ($playlist, $movingIds, $target, $type): void {
$targetPosition = $playlist->songs()->wherePivot('song_id', $target)->value('position'); $targetPosition = $playlist->playables()->wherePivot('song_id', $target)->value('position');
$insertPosition = $type === 'before' ? $targetPosition : $targetPosition + 1; $insertPosition = $type === 'before' ? $targetPosition : $targetPosition + 1;
// create a "gap" for the moving songs by incrementing the position of the songs after the target // create a "gap" for the moving songs by incrementing the position of the songs after the target
$playlist->songs() $playlist->playables()
->newPivotQuery() ->newPivotQuery()
->where('position', $type === 'before' ? '>=' : '>', $targetPosition) ->where('position', $type === 'before' ? '>=' : '>', $targetPosition)
->whereNotIn('song_id', $movingIds) ->whereNotIn('song_id', $movingIds)
@ -144,7 +144,7 @@ class PlaylistService
$values[$id] = ['position' => $insertPosition++]; $values[$id] = ['position' => $insertPosition++];
} }
$playlist->songs()->syncWithoutDetaching($values); $playlist->playables()->syncWithoutDetaching($values);
}); });
} }
} }

View file

@ -58,7 +58,7 @@ class PlaylistSongTest extends PlusTestCase
->assertSuccessful(); ->assertSuccessful();
$playlist->refresh(); $playlist->refresh();
$songs->each(static fn (Song $song) => self::assertTrue($playlist->songs->contains($song))); $songs->each(static fn (Song $song) => self::assertTrue($playlist->playables->contains($song)));
} }
public function testCollaboratorCanRemoveSongs(): void public function testCollaboratorCanRemoveSongs(): void
@ -74,6 +74,6 @@ class PlaylistSongTest extends PlusTestCase
->assertSuccessful(); ->assertSuccessful();
$playlist->refresh(); $playlist->refresh();
$songs->each(static fn (Song $song) => self::assertFalse($playlist->songs->contains($song))); $songs->each(static fn (Song $song) => self::assertFalse($playlist->playables->contains($song)));
} }
} }

View file

@ -68,7 +68,7 @@ class PlaylistSongTest extends TestCase
$this->postAs("api/playlists/$playlist->id/songs", ['songs' => $songs->pluck('id')->all()], $playlist->user) $this->postAs("api/playlists/$playlist->id/songs", ['songs' => $songs->pluck('id')->all()], $playlist->user)
->assertSuccessful(); ->assertSuccessful();
self::assertEqualsCanonicalizing($songs->pluck('id')->all(), $playlist->songs->pluck('id')->all()); self::assertEqualsCanonicalizing($songs->pluck('id')->all(), $playlist->playables->pluck('id')->all());
} }
public function testRemoveSongsFromPlaylist(): void public function testRemoveSongsFromPlaylist(): void
@ -83,7 +83,7 @@ class PlaylistSongTest extends TestCase
$playlist->addPlayables($toRemainSongs->merge($toBeRemovedSongs)); $playlist->addPlayables($toRemainSongs->merge($toBeRemovedSongs));
self::assertCount(7, $playlist->songs); self::assertCount(7, $playlist->playables);
$this->deleteAs( $this->deleteAs(
"api/playlists/$playlist->id/songs", "api/playlists/$playlist->id/songs",
@ -94,7 +94,7 @@ class PlaylistSongTest extends TestCase
$playlist->refresh(); $playlist->refresh();
self::assertEqualsCanonicalizing($toRemainSongs->pluck('id')->all(), $playlist->songs->pluck('id')->all()); self::assertEqualsCanonicalizing($toRemainSongs->pluck('id')->all(), $playlist->playables->pluck('id')->all());
} }
public function testNonOwnerCannotModifyPlaylist(): void public function testNonOwnerCannotModifyPlaylist(): void

View file

@ -43,7 +43,7 @@ class PlaylistTest extends TestCase
self::assertSame('Foo Bar', $playlist->name); self::assertSame('Foo Bar', $playlist->name);
self::assertTrue($playlist->ownedBy($user)); self::assertTrue($playlist->ownedBy($user));
self::assertNull($playlist->getFolder()); self::assertNull($playlist->getFolder());
self::assertEqualsCanonicalizing($songs->pluck('id')->all(), $playlist->songs->pluck('id')->all()); self::assertEqualsCanonicalizing($songs->pluck('id')->all(), $playlist->playables->pluck('id')->all());
} }
public function testCreatingSmartPlaylist(): void public function testCreatingSmartPlaylist(): void

View file

@ -75,7 +75,7 @@ class SongPlayTest extends TestCase
->shouldReceive('stream') ->shouldReceive('stream')
->once(); ->once();
$this->get("play/$song->id/1/128?t=$token->audioToken") $this->get("play/$song->id/1?t=$token->audioToken")
->assertOk(); ->assertOk();
} }
} }

View file

@ -50,7 +50,7 @@ class PlaylistServiceTest extends TestCase
self::assertSame('foo', $playlist->name); self::assertSame('foo', $playlist->name);
self::assertTrue($user->is($playlist->user)); self::assertTrue($user->is($playlist->user));
self::assertFalse($playlist->is_smart); self::assertFalse($playlist->is_smart);
self::assertEqualsCanonicalizing($playlist->songs->pluck('id')->all(), $songs->pluck('id')->all()); self::assertEqualsCanonicalizing($playlist->playables->pluck('id')->all(), $songs->pluck('id')->all());
} }
public function testCreateSmartPlaylist(): void public function testCreateSmartPlaylist(): void
@ -201,9 +201,9 @@ class PlaylistServiceTest extends TestCase
$playlist->refresh(); $playlist->refresh();
self::assertCount(2, $addedSongs); self::assertCount(2, $addedSongs);
self::assertCount(5, $playlist->songs); self::assertCount(5, $playlist->playables);
self::assertEqualsCanonicalizing($addedSongs->pluck('id')->all(), $songs->pluck('id')->all()); self::assertEqualsCanonicalizing($addedSongs->pluck('id')->all(), $songs->pluck('id')->all());
$songs->each(static fn (Song $song) => self::assertTrue($playlist->songs->contains($song))); $songs->each(static fn (Song $song) => self::assertTrue($playlist->playables->contains($song)));
} }
public function testAddEpisodesToPlaylist(): void public function testAddEpisodesToPlaylist(): void
@ -222,7 +222,7 @@ class PlaylistServiceTest extends TestCase
$playlist->refresh(); $playlist->refresh();
self::assertCount(2, $addedEpisodes); self::assertCount(2, $addedEpisodes);
self::assertCount(5, $playlist->songs); self::assertCount(5, $playlist->playables);
self::assertEqualsCanonicalizing($addedEpisodes->pluck('id')->all(), $episodes->pluck('id')->all()); self::assertEqualsCanonicalizing($addedEpisodes->pluck('id')->all(), $episodes->pluck('id')->all());
} }
@ -238,7 +238,7 @@ class PlaylistServiceTest extends TestCase
$playlist->refresh(); $playlist->refresh();
self::assertCount(4, $addedEpisodes); self::assertCount(4, $addedEpisodes);
self::assertCount(7, $playlist->songs); self::assertCount(7, $playlist->playables);
self::assertEqualsCanonicalizing($addedEpisodes->pluck('id')->all(), $playables->pluck('id')->all()); self::assertEqualsCanonicalizing($addedEpisodes->pluck('id')->all(), $playables->pluck('id')->all());
} }
@ -268,7 +268,7 @@ class PlaylistServiceTest extends TestCase
$this->service->makePlaylistContentPublic($playlist); $this->service->makePlaylistContentPublic($playlist);
$playlist->songs->each(static fn (Song $song) => self::assertTrue($song->is_public)); $playlist->playables->each(static fn (Song $song) => self::assertTrue($song->is_public));
} }
public function testMoveSongsInPlaylist(): void public function testMoveSongsInPlaylist(): void
@ -282,17 +282,17 @@ class PlaylistServiceTest extends TestCase
$playlist->addPlayables($songs); $playlist->addPlayables($songs);
$this->service->movePlayablesInPlaylist($playlist, [$ids[2], $ids[3]], $ids[0], 'after'); $this->service->movePlayablesInPlaylist($playlist, [$ids[2], $ids[3]], $ids[0], 'after');
self::assertSame([$ids[0], $ids[2], $ids[3], $ids[1]], $playlist->refresh()->songs->pluck('id')->all()); self::assertSame([$ids[0], $ids[2], $ids[3], $ids[1]], $playlist->refresh()->playables->pluck('id')->all());
$this->service->movePlayablesInPlaylist($playlist, [$ids[0]], $ids[3], 'before'); $this->service->movePlayablesInPlaylist($playlist, [$ids[0]], $ids[3], 'before');
self::assertSame([$ids[2], $ids[0], $ids[3], $ids[1]], $playlist->refresh()->songs->pluck('id')->all()); self::assertSame([$ids[2], $ids[0], $ids[3], $ids[1]], $playlist->refresh()->playables->pluck('id')->all());
// move to the first position // move to the first position
$this->service->movePlayablesInPlaylist($playlist, [$ids[0], $ids[1]], $ids[2], 'before'); $this->service->movePlayablesInPlaylist($playlist, [$ids[0], $ids[1]], $ids[2], 'before');
self::assertSame([$ids[0], $ids[1], $ids[2], $ids[3]], $playlist->refresh()->songs->pluck('id')->all()); self::assertSame([$ids[0], $ids[1], $ids[2], $ids[3]], $playlist->refresh()->playables->pluck('id')->all());
// move to the last position // move to the last position
$this->service->movePlayablesInPlaylist($playlist, [$ids[0], $ids[1]], $ids[3], 'after'); $this->service->movePlayablesInPlaylist($playlist, [$ids[0], $ids[1]], $ids[3], 'after');
self::assertSame([$ids[2], $ids[3], $ids[0], $ids[1]], $playlist->refresh()->songs->pluck('id')->all()); self::assertSame([$ids[2], $ids[3], $ids[0], $ids[1]], $playlist->refresh()->playables->pluck('id')->all());
} }
} }