mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
refactor: use custom query builders instead of scopes
This commit is contained in:
parent
7704bef3ac
commit
9d9dc0b397
52 changed files with 373 additions and 351 deletions
33
app/Builders/AlbumBuilder.php
Normal file
33
app/Builders/AlbumBuilder.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Builders;
|
||||||
|
|
||||||
|
use App\Models\Album;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Query\JoinClause;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class AlbumBuilder extends Builder
|
||||||
|
{
|
||||||
|
public function isStandard(): static
|
||||||
|
{
|
||||||
|
return $this->whereNot('albums.id', Album::UNKNOWN_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withMeta(User $user): static
|
||||||
|
{
|
||||||
|
return $this->with('artist')
|
||||||
|
->leftJoin('songs', 'albums.id', '=', 'songs.album_id')
|
||||||
|
->leftJoin('interactions', static function (JoinClause $join) use ($user): void {
|
||||||
|
$join->on('songs.id', '=', 'interactions.song_id')->where('interactions.user_id', $user->id);
|
||||||
|
})
|
||||||
|
->groupBy('albums.id')
|
||||||
|
->select(
|
||||||
|
'albums.*',
|
||||||
|
DB::raw('CAST(SUM(interactions.play_count) AS UNSIGNED) AS play_count')
|
||||||
|
)
|
||||||
|
->withCount('songs AS song_count')
|
||||||
|
->withSum('songs AS length', 'length');
|
||||||
|
}
|
||||||
|
}
|
33
app/Builders/ArtistBuilder.php
Normal file
33
app/Builders/ArtistBuilder.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Builders;
|
||||||
|
|
||||||
|
use App\Models\Artist;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Query\JoinClause;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class ArtistBuilder extends Builder
|
||||||
|
{
|
||||||
|
public function isStandard(): static
|
||||||
|
{
|
||||||
|
return $this->whereNotIn('artists.id', [Artist::UNKNOWN_ID, Artist::VARIOUS_ID]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withMeta(User $user): static
|
||||||
|
{
|
||||||
|
return $this->leftJoin('songs', 'artists.id', '=', 'songs.artist_id')
|
||||||
|
->leftJoin('interactions', static function (JoinClause $join) use ($user): void {
|
||||||
|
$join->on('interactions.song_id', '=', 'songs.id')->where('interactions.user_id', $user->id);
|
||||||
|
})
|
||||||
|
->groupBy('artists.id')
|
||||||
|
->select([
|
||||||
|
'artists.*',
|
||||||
|
DB::raw('CAST(SUM(interactions.play_count) AS UNSIGNED) AS play_count'),
|
||||||
|
DB::raw('COUNT(DISTINCT songs.album_id) AS album_count'),
|
||||||
|
])
|
||||||
|
->withCount('songs AS song_count')
|
||||||
|
->withSum('songs AS length', 'length');
|
||||||
|
}
|
||||||
|
}
|
41
app/Builders/SongBuilder.php
Normal file
41
app/Builders/SongBuilder.php
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Builders;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Query\JoinClause;
|
||||||
|
|
||||||
|
class SongBuilder extends Builder
|
||||||
|
{
|
||||||
|
public function inDirectory(string $path): static
|
||||||
|
{
|
||||||
|
// Make sure the path ends with a directory separator.
|
||||||
|
$path = rtrim(trim($path), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
||||||
|
|
||||||
|
return $this->where('path', 'LIKE', "$path%");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withMeta(User $user): static
|
||||||
|
{
|
||||||
|
return $this
|
||||||
|
->with('artist', 'album', 'album.artist')
|
||||||
|
->leftJoin('interactions', static function (JoinClause $join) use ($user): void {
|
||||||
|
$join->on('interactions.song_id', '=', 'songs.id')->where('interactions.user_id', $user->id);
|
||||||
|
})
|
||||||
|
->join('albums', 'songs.album_id', '=', 'albums.id')
|
||||||
|
->join('artists', 'songs.artist_id', '=', 'artists.id')
|
||||||
|
->select(
|
||||||
|
'songs.*',
|
||||||
|
'albums.name',
|
||||||
|
'artists.name',
|
||||||
|
'interactions.liked',
|
||||||
|
'interactions.play_count'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hostedOnS3(): static
|
||||||
|
{
|
||||||
|
return $this->where('path', 'LIKE', 's3://%');
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,9 @@ class ChangePasswordCommand extends Command
|
||||||
$email = $this->argument('email');
|
$email = $this->argument('email');
|
||||||
|
|
||||||
/** @var User|null $user */
|
/** @var User|null $user */
|
||||||
$user = $email ? User::where('email', $email)->first() : User::where('is_admin', true)->first();
|
$user = $email
|
||||||
|
? User::query()->where('email', $email)->first()
|
||||||
|
: User::query()->where('is_admin', true)->first();
|
||||||
|
|
||||||
if (!$user) {
|
if (!$user) {
|
||||||
$this->error('The user account cannot be found.');
|
$this->error('The user account cannot be found.');
|
||||||
|
|
|
@ -204,7 +204,7 @@ class InitCommand extends Command
|
||||||
private function setUpAdminAccount(): void
|
private function setUpAdminAccount(): void
|
||||||
{
|
{
|
||||||
$this->components->task('Creating default admin account', function (): void {
|
$this->components->task('Creating default admin account', function (): void {
|
||||||
User::create([
|
User::query()->create([
|
||||||
'name' => self::DEFAULT_ADMIN_NAME,
|
'name' => self::DEFAULT_ADMIN_NAME,
|
||||||
'email' => self::DEFAULT_ADMIN_EMAIL,
|
'email' => self::DEFAULT_ADMIN_EMAIL,
|
||||||
'password' => $this->hash->make(self::DEFAULT_ADMIN_PASSWORD),
|
'password' => $this->hash->make(self::DEFAULT_ADMIN_PASSWORD),
|
||||||
|
@ -217,7 +217,7 @@ class InitCommand extends Command
|
||||||
|
|
||||||
private function maybeSeedDatabase(): void
|
private function maybeSeedDatabase(): void
|
||||||
{
|
{
|
||||||
if (!User::count()) {
|
if (!User::query()->count()) {
|
||||||
$this->setUpAdminAccount();
|
$this->setUpAdminAccount();
|
||||||
|
|
||||||
$this->components->task('Seeding data', function (): void {
|
$this->components->task('Seeding data', function (): void {
|
||||||
|
|
|
@ -12,22 +12,17 @@ use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
class AlbumController extends Controller
|
class AlbumController extends Controller
|
||||||
{
|
{
|
||||||
/** @param User $user */
|
/** @param User $user */
|
||||||
public function __construct(private AlbumRepository $albumRepository, private ?Authenticatable $user)
|
public function __construct(private AlbumRepository $repository, private ?Authenticatable $user)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$pagination = Album::withMeta($this->user)
|
return AlbumResource::collection($this->repository->paginate($this->user));
|
||||||
->isStandard()
|
|
||||||
->orderBy('albums.name')
|
|
||||||
->simplePaginate(21);
|
|
||||||
|
|
||||||
return AlbumResource::collection($pagination);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show(Album $album)
|
public function show(Album $album)
|
||||||
{
|
{
|
||||||
return AlbumResource::make($this->albumRepository->getOne($album->id, $this->user));
|
return AlbumResource::make($this->repository->getOne($album->id, $this->user));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,22 +12,17 @@ use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
class ArtistController extends Controller
|
class ArtistController extends Controller
|
||||||
{
|
{
|
||||||
/** @param User $user */
|
/** @param User $user */
|
||||||
public function __construct(private ArtistRepository $artistRepository, private ?Authenticatable $user)
|
public function __construct(private ArtistRepository $repository, private ?Authenticatable $user)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$pagination = Artist::withMeta($this->user)
|
return ArtistResource::collection($this->repository->paginate($this->user));
|
||||||
->isStandard()
|
|
||||||
->orderBy('artists.name')
|
|
||||||
->simplePaginate(21);
|
|
||||||
|
|
||||||
return ArtistResource::collection($pagination);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show(Artist $artist)
|
public function show(Artist $artist)
|
||||||
{
|
{
|
||||||
return ArtistResource::make($this->artistRepository->getOne($artist->id, $this->user));
|
return ArtistResource::make($this->repository->getOne($artist->id, $this->user));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,15 @@
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Builders\AlbumBuilder;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Contracts\Database\Query\Builder as BuilderContract;
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Query\JoinClause;
|
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Laravel\Scout\Searchable;
|
use Laravel\Scout\Searchable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,14 +31,6 @@ use Laravel\Scout\Searchable;
|
||||||
* @property float|string $length Total length of the album in seconds (dynamically calculated)
|
* @property float|string $length Total length of the album in seconds (dynamically calculated)
|
||||||
* @property int|string $play_count Total number of times the album's songs have been played (dynamically calculated)
|
* @property int|string $play_count Total number of times the album's songs have been played (dynamically calculated)
|
||||||
* @property int|string $song_count Total number of songs on the album (dynamically calculated)
|
* @property int|string $song_count Total number of songs on the album (dynamically calculated)
|
||||||
*
|
|
||||||
* @method static self firstOrCreate(array $where, array $params = [])
|
|
||||||
* @method static self|null find(int $id)
|
|
||||||
* @method static Builder where(...$params)
|
|
||||||
* @method static self first()
|
|
||||||
* @method static Builder whereArtistIdAndName(int $id, string $name)
|
|
||||||
* @method static orderBy(...$params)
|
|
||||||
* @method static Builder latest()
|
|
||||||
*/
|
*/
|
||||||
class Album extends Model
|
class Album extends Model
|
||||||
{
|
{
|
||||||
|
@ -59,13 +48,23 @@ class Album extends Model
|
||||||
/** @deprecated */
|
/** @deprecated */
|
||||||
protected $appends = ['is_compilation'];
|
protected $appends = ['is_compilation'];
|
||||||
|
|
||||||
|
public static function query(): AlbumBuilder
|
||||||
|
{
|
||||||
|
return parent::query();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newEloquentBuilder($query): AlbumBuilder
|
||||||
|
{
|
||||||
|
return new AlbumBuilder($query);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an album using some provided information.
|
* Get an album using some provided information.
|
||||||
* If such is not found, a new album will be created using the information.
|
* If such is not found, a new album will be created using the information.
|
||||||
*/
|
*/
|
||||||
public static function getOrCreate(Artist $artist, ?string $name = null): self
|
public static function getOrCreate(Artist $artist, ?string $name = null): static
|
||||||
{
|
{
|
||||||
return static::firstOrCreate([
|
return static::query()->firstOrCreate([ // @phpstan-ignore-line
|
||||||
'artist_id' => $artist->id,
|
'artist_id' => $artist->id,
|
||||||
'name' => trim($name) ?: self::UNKNOWN_NAME,
|
'name' => trim($name) ?: self::UNKNOWN_NAME,
|
||||||
]);
|
]);
|
||||||
|
@ -143,29 +142,6 @@ class Album extends Model
|
||||||
return Attribute::get(fn () => $this->artist_id === Artist::VARIOUS_ID);
|
return Attribute::get(fn () => $this->artist_id === Artist::VARIOUS_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scopeIsStandard(Builder $query): Builder
|
|
||||||
{
|
|
||||||
return $query->whereNot('albums.id', self::UNKNOWN_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function withMeta(User $scopedUser): BuilderContract
|
|
||||||
{
|
|
||||||
return static::query()
|
|
||||||
->with('artist')
|
|
||||||
->leftJoin('songs', 'albums.id', '=', 'songs.album_id')
|
|
||||||
->leftJoin('interactions', static function (JoinClause $join) use ($scopedUser): void {
|
|
||||||
$join->on('songs.id', '=', 'interactions.song_id')
|
|
||||||
->where('interactions.user_id', $scopedUser->id);
|
|
||||||
})
|
|
||||||
->groupBy('albums.id')
|
|
||||||
->select(
|
|
||||||
'albums.*',
|
|
||||||
DB::raw('CAST(SUM(interactions.play_count) AS UNSIGNED) AS play_count')
|
|
||||||
)
|
|
||||||
->withCount('songs AS song_count')
|
|
||||||
->withSum('songs AS length', 'length');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return array<mixed> */
|
/** @return array<mixed> */
|
||||||
public function toSearchableArray(): array
|
public function toSearchableArray(): array
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,18 +2,15 @@
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Builders\ArtistBuilder;
|
||||||
use App\Facades\Util;
|
use App\Facades\Util;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Contracts\Database\Query\Builder as BuilderContract;
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Query\JoinClause;
|
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Laravel\Scout\Searchable;
|
use Laravel\Scout\Searchable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,14 +27,6 @@ use Laravel\Scout\Searchable;
|
||||||
* @property string|int $song_count Total number of songs by the artist (dynamically calculated)
|
* @property string|int $song_count Total number of songs by the artist (dynamically calculated)
|
||||||
* @property string|int $album_count Total number of albums by the artist (dynamically calculated)
|
* @property string|int $album_count Total number of albums by the artist (dynamically calculated)
|
||||||
* @property Carbon $created_at
|
* @property Carbon $created_at
|
||||||
*
|
|
||||||
* @method static self find(int $id)
|
|
||||||
* @method static self firstOrCreate(array $where, array $params = [])
|
|
||||||
* @method static Builder where(...$params)
|
|
||||||
* @method static self first()
|
|
||||||
* @method static Builder whereName(string $name)
|
|
||||||
* @method static Builder orderBy(...$params)
|
|
||||||
* @method static Builder join(...$params)
|
|
||||||
*/
|
*/
|
||||||
class Artist extends Model
|
class Artist extends Model
|
||||||
{
|
{
|
||||||
|
@ -53,6 +42,16 @@ class Artist extends Model
|
||||||
protected $guarded = ['id'];
|
protected $guarded = ['id'];
|
||||||
protected $hidden = ['created_at', 'updated_at'];
|
protected $hidden = ['created_at', 'updated_at'];
|
||||||
|
|
||||||
|
public static function query(): ArtistBuilder
|
||||||
|
{
|
||||||
|
return parent::query();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newEloquentBuilder($query): ArtistBuilder
|
||||||
|
{
|
||||||
|
return new ArtistBuilder($query);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an Artist object from their name.
|
* Get an Artist object from their name.
|
||||||
* If such is not found, a new artist will be created.
|
* If such is not found, a new artist will be created.
|
||||||
|
@ -66,7 +65,7 @@ class Artist extends Model
|
||||||
$name = mb_convert_encoding($name, 'UTF-8', $encoding);
|
$name = mb_convert_encoding($name, 'UTF-8', $encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
return static::firstOrCreate(['name' => trim($name) ?: self::UNKNOWN_NAME]);
|
return static::query()->firstOrCreate(['name' => trim($name) ?: self::UNKNOWN_NAME]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function albums(): HasMany
|
public function albums(): HasMany
|
||||||
|
@ -120,29 +119,6 @@ class Artist extends Model
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scopeIsStandard(Builder $query): Builder
|
|
||||||
{
|
|
||||||
return $query->whereNotIn('artists.id', [self::UNKNOWN_ID, self::VARIOUS_ID]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function withMeta(User $scopedUser): BuilderContract
|
|
||||||
{
|
|
||||||
return static::query()
|
|
||||||
->leftJoin('songs', 'artists.id', '=', 'songs.artist_id')
|
|
||||||
->leftJoin('interactions', static function (JoinClause $join) use ($scopedUser): void {
|
|
||||||
$join->on('interactions.song_id', '=', 'songs.id')
|
|
||||||
->where('interactions.user_id', $scopedUser->id);
|
|
||||||
})
|
|
||||||
->groupBy('artists.id')
|
|
||||||
->select([
|
|
||||||
'artists.*',
|
|
||||||
DB::raw('CAST(SUM(interactions.play_count) AS UNSIGNED) AS play_count'),
|
|
||||||
DB::raw('COUNT(DISTINCT songs.album_id) AS album_count'),
|
|
||||||
])
|
|
||||||
->withCount('songs AS song_count')
|
|
||||||
->withSum('songs AS length', 'length');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return array<mixed> */
|
/** @return array<mixed> */
|
||||||
public function toSearchableArray(): array
|
public function toSearchableArray(): array
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
@ -14,12 +13,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
* @property User $user
|
* @property User $user
|
||||||
* @property int $id
|
* @property int $id
|
||||||
* @property string $song_id
|
* @property string $song_id
|
||||||
*
|
|
||||||
* @method static self firstOrCreate(array $where, array $params = [])
|
|
||||||
* @method static self find(int $id)
|
|
||||||
* @method static Builder whereSongIdAndUserId(string $songId, string $userId)
|
|
||||||
* @method static Builder whereIn(...$params)
|
|
||||||
* @method static Builder where(...$params)
|
|
||||||
*/
|
*/
|
||||||
class Interaction extends Model
|
class Interaction extends Model
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,6 @@ namespace App\Models;
|
||||||
|
|
||||||
use App\Casts\SmartPlaylistRulesCast;
|
use App\Casts\SmartPlaylistRulesCast;
|
||||||
use App\Values\SmartPlaylistRuleGroup;
|
use App\Values\SmartPlaylistRuleGroup;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
@ -22,8 +21,6 @@ use Laravel\Scout\Searchable;
|
||||||
* @property bool $is_smart
|
* @property bool $is_smart
|
||||||
* @property string $name
|
* @property string $name
|
||||||
* @property user $user
|
* @property user $user
|
||||||
*
|
|
||||||
* @method static Builder orderBy(string $field, string $order = 'asc')
|
|
||||||
*/
|
*/
|
||||||
class Playlist extends Model
|
class Playlist extends Model
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,7 +10,6 @@ use Illuminate\Database\Eloquent\Model;
|
||||||
* @property mixed $value
|
* @property mixed $value
|
||||||
*
|
*
|
||||||
* @method static self find(string $key)
|
* @method static self find(string $key)
|
||||||
* @method static self updateOrCreate(array $where, array $params)
|
|
||||||
*/
|
*/
|
||||||
class Setting extends Model
|
class Setting extends Model
|
||||||
{
|
{
|
||||||
|
@ -44,6 +43,6 @@ class Setting extends Model
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self::updateOrCreate(compact('key'), compact('value'));
|
self::query()->updateOrCreate(compact('key'), compact('value'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,14 @@
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Builders\SongBuilder;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Query\JoinClause;
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Laravel\Scout\Searchable;
|
use Laravel\Scout\Searchable;
|
||||||
|
|
||||||
|
@ -31,26 +29,13 @@ use Laravel\Scout\Searchable;
|
||||||
* @property ?bool $liked Whether the song is liked by the current user (dynamically calculated)
|
* @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 ?int $play_count The number of times the song has been played by the current user (dynamically calculated)
|
||||||
* @property Carbon $created_at
|
* @property Carbon $created_at
|
||||||
*
|
* @property array<mixed> $s3_params
|
||||||
* @method static self updateOrCreate(array $where, array $params)
|
|
||||||
* @method static Builder select(string $string)
|
|
||||||
* @method static Builder inDirectory(string $path)
|
|
||||||
* @method static self first()
|
|
||||||
* @method static Builder orderBy(...$args)
|
|
||||||
* @method static int count()
|
|
||||||
* @method static self|Collection|null find($id)
|
|
||||||
* @method static Builder take(int $count)
|
|
||||||
* @method static float|int sum(string $column)
|
|
||||||
* @method static Builder latest(string $column = 'created_at')
|
|
||||||
* @method static Builder where(...$params)
|
|
||||||
* @method static Song findOrFail(string $id)
|
|
||||||
*/
|
*/
|
||||||
class Song extends Model
|
class Song extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
use Searchable;
|
use Searchable;
|
||||||
use SupportsDeleteWhereValueNotIn;
|
use SupportsDeleteWhereValueNotIn;
|
||||||
use SupportsS3;
|
|
||||||
|
|
||||||
public const ID_REGEX = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}';
|
public const ID_REGEX = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}';
|
||||||
|
|
||||||
|
@ -73,6 +58,16 @@ class Song extends Model
|
||||||
static::creating(static fn (self $song) => $song->id = Str::uuid()->toString());
|
static::creating(static fn (self $song) => $song->id = Str::uuid()->toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function query(): SongBuilder
|
||||||
|
{
|
||||||
|
return parent::query();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newEloquentBuilder($query): SongBuilder
|
||||||
|
{
|
||||||
|
return new SongBuilder($query);
|
||||||
|
}
|
||||||
|
|
||||||
public function artist(): BelongsTo
|
public function artist(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Artist::class);
|
return $this->belongsTo(Artist::class);
|
||||||
|
@ -93,17 +88,6 @@ class Song extends Model
|
||||||
return $this->hasMany(Interaction::class);
|
return $this->hasMany(Interaction::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Scope a query to only include songs in a given directory.
|
|
||||||
*/
|
|
||||||
public function scopeInDirectory(Builder $query, string $path): Builder
|
|
||||||
{
|
|
||||||
// Make sure the path ends with a directory separator.
|
|
||||||
$path = rtrim(trim($path), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
|
||||||
|
|
||||||
return $query->where('path', 'LIKE', "$path%");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function title(): Attribute
|
protected function title(): Attribute
|
||||||
{
|
{
|
||||||
return new Attribute(
|
return new Attribute(
|
||||||
|
@ -120,30 +104,22 @@ class Song extends Model
|
||||||
return new Attribute(get: $normalizer, set: $normalizer);
|
return new Attribute(get: $normalizer, set: $normalizer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function withMeta(User $scopedUser, ?Builder $query = null): Builder
|
protected function s3Params(): Attribute
|
||||||
{
|
{
|
||||||
$query ??= static::query();
|
return Attribute::get(function (): ?array {
|
||||||
|
if (!preg_match('/^s3:\\/\\/(.*)/', $this->path, $matches)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return $query
|
[$bucket, $key] = explode('/', $matches[1], 2);
|
||||||
->with('artist', 'album', 'album.artist')
|
|
||||||
->leftJoin('interactions', static function (JoinClause $join) use ($scopedUser): void {
|
return compact('bucket', 'key');
|
||||||
$join->on('interactions.song_id', '=', 'songs.id')
|
});
|
||||||
->where('interactions.user_id', $scopedUser->id);
|
|
||||||
})
|
|
||||||
->join('albums', 'songs.album_id', '=', 'albums.id')
|
|
||||||
->join('artists', 'songs.artist_id', '=', 'artists.id')
|
|
||||||
->select(
|
|
||||||
'songs.*',
|
|
||||||
'albums.name',
|
|
||||||
'artists.name',
|
|
||||||
'interactions.liked',
|
|
||||||
'interactions.play_count'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scopeWithMeta(Builder $query, User $scopedUser): Builder
|
public static function getPathFromS3BucketAndKey(string $bucket, string $key): string
|
||||||
{
|
{
|
||||||
return static::withMeta($scopedUser, $query);
|
return "s3://$bucket/$key";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return array<mixed> */
|
/** @return array<mixed> */
|
||||||
|
|
|
@ -10,7 +10,6 @@ use ZipArchive;
|
||||||
class SongZipArchive
|
class SongZipArchive
|
||||||
{
|
{
|
||||||
private ZipArchive $archive;
|
private ZipArchive $archive;
|
||||||
|
|
||||||
private string $path;
|
private string $path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,7 +29,7 @@ class SongZipArchive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addSongs(Collection $songs): self
|
public function addSongs(Collection $songs): static
|
||||||
{
|
{
|
||||||
$songs->each(function (Song $song): void {
|
$songs->each(function (Song $song): void {
|
||||||
$this->addSong($song);
|
$this->addSong($song);
|
||||||
|
@ -39,7 +38,7 @@ class SongZipArchive
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addSong(Song $song): self
|
public function addSong(Song $song): static
|
||||||
{
|
{
|
||||||
attempt(function () use ($song): void {
|
attempt(function () use ($song): void {
|
||||||
$path = Download::fromSong($song);
|
$path = Download::fromSong($song);
|
||||||
|
@ -49,7 +48,7 @@ class SongZipArchive
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function finish(): self
|
public function finish(): static
|
||||||
{
|
{
|
||||||
$this->archive->close();
|
$this->archive->close();
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,7 @@ use Illuminate\Support\Facades\DB;
|
||||||
* MySQL and PostgresSQL seem to have a limit of 2^16-1 (65535) elements in an IN statement.
|
* MySQL and PostgresSQL seem to have a limit of 2^16-1 (65535) elements in an IN statement.
|
||||||
* This trait provides a method as a workaround to this limitation.
|
* This trait provides a method as a workaround to this limitation.
|
||||||
*
|
*
|
||||||
* @method static Builder whereIn($keys, array $values)
|
* @method static Builder query()
|
||||||
* @method static Builder whereNotIn($keys, array $values)
|
|
||||||
* @method static Builder select(string $string)
|
|
||||||
*/
|
*/
|
||||||
trait SupportsDeleteWhereValueNotIn
|
trait SupportsDeleteWhereValueNotIn
|
||||||
{
|
{
|
||||||
|
@ -25,18 +23,18 @@ trait SupportsDeleteWhereValueNotIn
|
||||||
|
|
||||||
// If the number of entries is lower than, or equals to maxChunkSize, just go ahead.
|
// If the number of entries is lower than, or equals to maxChunkSize, just go ahead.
|
||||||
if (count($values) <= $maxChunkSize) {
|
if (count($values) <= $maxChunkSize) {
|
||||||
static::whereNotIn($field, $values)->delete();
|
static::query()->whereNotIn($field, $values)->delete();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, we get the actual IDs that should be deleted…
|
// Otherwise, we get the actual IDs that should be deleted…
|
||||||
$allIDs = static::select($field)->get()->pluck($field)->all();
|
$allIDs = static::query()->select($field)->get()->pluck($field)->all();
|
||||||
$whereInIDs = array_diff($allIDs, $values);
|
$whereInIDs = array_diff($allIDs, $values);
|
||||||
|
|
||||||
// …and see if we can delete them instead.
|
// …and see if we can delete them instead.
|
||||||
if (count($whereInIDs) < $maxChunkSize) {
|
if (count($whereInIDs) < $maxChunkSize) {
|
||||||
static::whereIn($field, $whereInIDs)->delete();
|
static::query()->whereIn($field, $whereInIDs)->delete();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +48,7 @@ trait SupportsDeleteWhereValueNotIn
|
||||||
{
|
{
|
||||||
DB::transaction(static function () use ($values, $field, $chunkSize): void {
|
DB::transaction(static function () use ($values, $field, $chunkSize): void {
|
||||||
foreach (array_chunk($values, $chunkSize) as $chunk) {
|
foreach (array_chunk($values, $chunkSize) as $chunk) {
|
||||||
static::whereIn($field, $chunk)->delete();
|
static::query()->whereIn($field, $chunk)->delete();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @property array<string>|null $s3_params The bucket and key name of an S3 object.
|
|
||||||
*
|
|
||||||
* @method static Builder hostedOnS3()
|
|
||||||
*/
|
|
||||||
trait SupportsS3
|
|
||||||
{
|
|
||||||
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";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function scopeHostedOnS3(Builder $query): Builder
|
|
||||||
{
|
|
||||||
return $query->where('path', 'LIKE', 's3://%');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,7 +7,6 @@ use App\Values\UserPreferences;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Query\Builder;
|
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
@ -21,11 +20,6 @@ use Laravel\Sanctum\HasApiTokens;
|
||||||
* @property string $email
|
* @property string $email
|
||||||
* @property string $password
|
* @property string $password
|
||||||
* @property-read string $avatar
|
* @property-read string $avatar
|
||||||
*
|
|
||||||
* @method static self create(array $params)
|
|
||||||
* @method static int count()
|
|
||||||
* @method static Builder where(...$params)
|
|
||||||
* @method static self|null firstWhere(...$params)
|
|
||||||
*/
|
*/
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Repositories;
|
||||||
use App\Models\Album;
|
use App\Models\Album;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Repositories\Traits\Searchable;
|
use App\Repositories\Traits\Searchable;
|
||||||
|
use Illuminate\Contracts\Pagination\Paginator;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
class AlbumRepository extends Repository
|
class AlbumRepository extends Repository
|
||||||
|
@ -13,7 +14,8 @@ class AlbumRepository extends Repository
|
||||||
|
|
||||||
public function getOne(int $id, ?User $scopedUser = null): Album
|
public function getOne(int $id, ?User $scopedUser = null): Album
|
||||||
{
|
{
|
||||||
return Album::withMeta($scopedUser ?? $this->auth->user())
|
return Album::query()
|
||||||
|
->withMeta($scopedUser ?? $this->auth->user())
|
||||||
->where('albums.id', $id)
|
->where('albums.id', $id)
|
||||||
->first();
|
->first();
|
||||||
}
|
}
|
||||||
|
@ -21,7 +23,8 @@ class AlbumRepository extends Repository
|
||||||
/** @return Collection|array<array-key, Album> */
|
/** @return Collection|array<array-key, Album> */
|
||||||
public function getRecentlyAdded(int $count = 6, ?User $scopedUser = null): Collection
|
public function getRecentlyAdded(int $count = 6, ?User $scopedUser = null): Collection
|
||||||
{
|
{
|
||||||
return Album::withMeta($scopedUser ?? $this->auth->user())
|
return Album::query()
|
||||||
|
->withMeta($scopedUser ?? $this->auth->user())
|
||||||
->isStandard()
|
->isStandard()
|
||||||
->latest('albums.created_at')
|
->latest('albums.created_at')
|
||||||
->limit($count)
|
->limit($count)
|
||||||
|
@ -31,9 +34,8 @@ class AlbumRepository extends Repository
|
||||||
/** @return Collection|array<array-key, Album> */
|
/** @return Collection|array<array-key, Album> */
|
||||||
public function getMostPlayed(int $count = 6, ?User $scopedUser = null): Collection
|
public function getMostPlayed(int $count = 6, ?User $scopedUser = null): Collection
|
||||||
{
|
{
|
||||||
$scopedUser ??= $this->auth->user();
|
return Album::query()
|
||||||
|
->withMeta($scopedUser ?? $this->auth->user())
|
||||||
return Album::withMeta($scopedUser ?? $this->auth->user())
|
|
||||||
->isStandard()
|
->isStandard()
|
||||||
->orderByDesc('play_count')
|
->orderByDesc('play_count')
|
||||||
->limit($count)
|
->limit($count)
|
||||||
|
@ -43,8 +45,18 @@ class AlbumRepository extends Repository
|
||||||
/** @return Collection|array<array-key, Album> */
|
/** @return Collection|array<array-key, Album> */
|
||||||
public function getByIds(array $ids, ?User $scopedUser = null): Collection
|
public function getByIds(array $ids, ?User $scopedUser = null): Collection
|
||||||
{
|
{
|
||||||
return Album::withMeta($scopedUser ?? $this->auth->user())
|
return Album::query()
|
||||||
|
->withMeta($scopedUser ?? $this->auth->user())
|
||||||
->whereIn('albums.id', $ids)
|
->whereIn('albums.id', $ids)
|
||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function paginate(?User $scopedUser = null): Paginator
|
||||||
|
{
|
||||||
|
return Album::query()
|
||||||
|
->withMeta($scopedUser ?? $this->auth->user())
|
||||||
|
->isStandard()
|
||||||
|
->orderBy('albums.name')
|
||||||
|
->simplePaginate(21);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Repositories;
|
||||||
use App\Models\Artist;
|
use App\Models\Artist;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Repositories\Traits\Searchable;
|
use App\Repositories\Traits\Searchable;
|
||||||
|
use Illuminate\Contracts\Pagination\Paginator;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
|
||||||
class ArtistRepository extends Repository
|
class ArtistRepository extends Repository
|
||||||
|
@ -14,7 +15,8 @@ class ArtistRepository extends Repository
|
||||||
/** @return Collection|array<array-key, Artist> */
|
/** @return Collection|array<array-key, Artist> */
|
||||||
public function getMostPlayed(int $count = 6, ?User $scopedUser = null): Collection
|
public function getMostPlayed(int $count = 6, ?User $scopedUser = null): Collection
|
||||||
{
|
{
|
||||||
return Artist::withMeta($scopedUser ?? $this->auth->user())
|
return Artist::query()
|
||||||
|
->withMeta($scopedUser ?? $this->auth->user())
|
||||||
->isStandard()
|
->isStandard()
|
||||||
->orderByDesc('play_count')
|
->orderByDesc('play_count')
|
||||||
->limit($count)
|
->limit($count)
|
||||||
|
@ -23,7 +25,8 @@ class ArtistRepository extends Repository
|
||||||
|
|
||||||
public function getOne(int $id, ?User $scopedUser = null): Artist
|
public function getOne(int $id, ?User $scopedUser = null): Artist
|
||||||
{
|
{
|
||||||
return Artist::withMeta($scopedUser ?? $this->auth->user())
|
return Artist::query()
|
||||||
|
->withMeta($scopedUser ?? $this->auth->user())
|
||||||
->where('artists.id', $id)
|
->where('artists.id', $id)
|
||||||
->first();
|
->first();
|
||||||
}
|
}
|
||||||
|
@ -31,9 +34,19 @@ class ArtistRepository extends Repository
|
||||||
/** @return Collection|array<array-key, Artist> */
|
/** @return Collection|array<array-key, Artist> */
|
||||||
public function getByIds(array $ids, ?User $scopedUser = null): Collection
|
public function getByIds(array $ids, ?User $scopedUser = null): Collection
|
||||||
{
|
{
|
||||||
return Artist::withMeta($scopedUser ?? $this->auth->user())
|
return Artist::query()
|
||||||
|
->withMeta($scopedUser ?? $this->auth->user())
|
||||||
->isStandard()
|
->isStandard()
|
||||||
->whereIn('artists.id', $ids)
|
->whereIn('artists.id', $ids)
|
||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function paginate(?User $scopedUser = null): Paginator
|
||||||
|
{
|
||||||
|
return Artist::query()
|
||||||
|
->withMeta($scopedUser ?? $this->auth->user())
|
||||||
|
->isStandard()
|
||||||
|
->orderBy('artists.name')
|
||||||
|
->simplePaginate(21);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ namespace App\Repositories;
|
||||||
use App\Models\Interaction;
|
use App\Models\Interaction;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Repositories\Traits\ByCurrentUser;
|
use App\Repositories\Traits\ByCurrentUser;
|
||||||
use Illuminate\Database\Query\Builder;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
class InteractionRepository extends Repository
|
class InteractionRepository extends Repository
|
||||||
|
@ -15,10 +14,12 @@ class InteractionRepository extends Repository
|
||||||
/** @return Collection|array<Interaction> */
|
/** @return Collection|array<Interaction> */
|
||||||
public function getUserFavorites(User $user): Collection
|
public function getUserFavorites(User $user): Collection
|
||||||
{
|
{
|
||||||
return $this->model->where([
|
return $this->model
|
||||||
'user_id' => $user->id,
|
->newQuery()
|
||||||
'liked' => true,
|
->where([
|
||||||
])
|
'user_id' => $user->id,
|
||||||
|
'liked' => true,
|
||||||
|
])
|
||||||
->with('song')
|
->with('song')
|
||||||
->pluck('song');
|
->pluck('song');
|
||||||
}
|
}
|
||||||
|
@ -26,11 +27,11 @@ class InteractionRepository extends Repository
|
||||||
/** @return array<Interaction> */
|
/** @return array<Interaction> */
|
||||||
public function getRecentlyPlayed(User $user, ?int $count = null): array
|
public function getRecentlyPlayed(User $user, ?int $count = null): array
|
||||||
{
|
{
|
||||||
/** @var Builder $query */
|
|
||||||
$query = $this->model
|
$query = $this->model
|
||||||
|
->newQuery()
|
||||||
->where('user_id', $user->id)
|
->where('user_id', $user->id)
|
||||||
->where('play_count', '>', 0)
|
->where('play_count', '>', 0)
|
||||||
->orderBy('updated_at', 'DESC');
|
->latest('updated_at');
|
||||||
|
|
||||||
if ($count) {
|
if ($count) {
|
||||||
$query = $query->take($count);
|
$query = $query->take($count);
|
||||||
|
|
|
@ -31,27 +31,26 @@ class SongRepository extends Repository
|
||||||
|
|
||||||
public function getOneByPath(string $path): ?Song
|
public function getOneByPath(string $path): ?Song
|
||||||
{
|
{
|
||||||
return Song::where('path', $path)->first();
|
return Song::query()->where('path', $path)->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return Collection|array<Song> */
|
/** @return Collection|array<Song> */
|
||||||
public function getAllHostedOnS3(): Collection
|
public function getAllHostedOnS3(): Collection
|
||||||
{
|
{
|
||||||
return Song::hostedOnS3()->get();
|
return Song::query()->hostedOnS3()->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return Collection|array<array-key, Song> */
|
/** @return Collection|array<array-key, Song> */
|
||||||
public function getRecentlyAdded(int $count = 10, ?User $scopedUser = null): Collection
|
public function getRecentlyAdded(int $count = 10, ?User $scopedUser = null): Collection
|
||||||
{
|
{
|
||||||
return Song::withMeta($scopedUser ?? $this->auth->user())->latest()->limit($count)->get();
|
return Song::query()->withMeta($scopedUser ?? $this->auth->user())->latest()->limit($count)->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return Collection|array<array-key, Song> */
|
/** @return Collection|array<array-key, Song> */
|
||||||
public function getMostPlayed(int $count = 7, ?User $scopedUser = null): Collection
|
public function getMostPlayed(int $count = 7, ?User $scopedUser = null): Collection
|
||||||
{
|
{
|
||||||
$scopedUser ??= $this->auth->user();
|
return Song::query()
|
||||||
|
->withMeta($scopedUser ?? $this->auth->user())
|
||||||
return Song::withMeta($scopedUser)
|
|
||||||
->where('interactions.play_count', '>', 0)
|
->where('interactions.play_count', '>', 0)
|
||||||
->orderByDesc('interactions.play_count')
|
->orderByDesc('interactions.play_count')
|
||||||
->limit($count)
|
->limit($count)
|
||||||
|
@ -61,9 +60,8 @@ class SongRepository extends Repository
|
||||||
/** @return Collection|array<array-key, Song> */
|
/** @return Collection|array<array-key, Song> */
|
||||||
public function getRecentlyPlayed(int $count = 7, ?User $scopedUser = null): Collection
|
public function getRecentlyPlayed(int $count = 7, ?User $scopedUser = null): Collection
|
||||||
{
|
{
|
||||||
$scopedUser ??= $this->auth->user();
|
return Song::query()
|
||||||
|
->withMeta($scopedUser ?? $this->auth->user())
|
||||||
return Song::withMeta($scopedUser)
|
|
||||||
->where('interactions.play_count', '>', 0)
|
->where('interactions.play_count', '>', 0)
|
||||||
->orderByDesc('interactions.updated_at')
|
->orderByDesc('interactions.updated_at')
|
||||||
->limit($count)
|
->limit($count)
|
||||||
|
@ -77,7 +75,7 @@ class SongRepository extends Repository
|
||||||
int $perPage = 50
|
int $perPage = 50
|
||||||
): Paginator {
|
): Paginator {
|
||||||
return self::applySort(
|
return self::applySort(
|
||||||
Song::withMeta($scopedUser ?? $this->auth->user()),
|
Song::query()->withMeta($scopedUser ?? $this->auth->user()),
|
||||||
$sortColumn,
|
$sortColumn,
|
||||||
$sortDirection
|
$sortDirection
|
||||||
)
|
)
|
||||||
|
@ -92,7 +90,7 @@ class SongRepository extends Repository
|
||||||
?User $scopedUser = null,
|
?User $scopedUser = null,
|
||||||
): Collection {
|
): Collection {
|
||||||
return self::applySort(
|
return self::applySort(
|
||||||
Song::withMeta($scopedUser ?? $this->auth->user()),
|
Song::query()->withMeta($scopedUser ?? $this->auth->user()),
|
||||||
$sortColumn,
|
$sortColumn,
|
||||||
$sortDirection
|
$sortDirection
|
||||||
)
|
)
|
||||||
|
@ -103,13 +101,14 @@ class SongRepository extends Repository
|
||||||
/** @return Collection|array<array-key, Song> */
|
/** @return Collection|array<array-key, Song> */
|
||||||
public function getFavorites(?User $scopedUser = null): Collection
|
public function getFavorites(?User $scopedUser = null): Collection
|
||||||
{
|
{
|
||||||
return Song::withMeta($scopedUser ?? $this->auth->user())->where('interactions.liked', true)->get();
|
return Song::query()->withMeta($scopedUser ?? $this->auth->user())->where('interactions.liked', true)->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return Collection|array<array-key, Song> */
|
/** @return Collection|array<array-key, Song> */
|
||||||
public function getByAlbum(Album $album, ?User $scopedUser = null): Collection
|
public function getByAlbum(Album $album, ?User $scopedUser = null): Collection
|
||||||
{
|
{
|
||||||
return Song::withMeta($scopedUser ?? $this->auth->user())
|
return Song::query()
|
||||||
|
->withMeta($scopedUser ?? $this->auth->user())
|
||||||
->where('album_id', $album->id)
|
->where('album_id', $album->id)
|
||||||
->orderBy('songs.track')
|
->orderBy('songs.track')
|
||||||
->orderBy('songs.disc')
|
->orderBy('songs.disc')
|
||||||
|
@ -120,7 +119,8 @@ class SongRepository extends Repository
|
||||||
/** @return Collection|array<array-key, Song> */
|
/** @return Collection|array<array-key, Song> */
|
||||||
public function getByArtist(Artist $artist, ?User $scopedUser = null): Collection
|
public function getByArtist(Artist $artist, ?User $scopedUser = null): Collection
|
||||||
{
|
{
|
||||||
return Song::withMeta($scopedUser ?? $this->auth->user())
|
return Song::query()
|
||||||
|
->withMeta($scopedUser ?? $this->auth->user())
|
||||||
->where('songs.artist_id', $artist->id)
|
->where('songs.artist_id', $artist->id)
|
||||||
->orderBy('albums.name')
|
->orderBy('albums.name')
|
||||||
->orderBy('songs.track')
|
->orderBy('songs.track')
|
||||||
|
@ -132,7 +132,8 @@ class SongRepository extends Repository
|
||||||
/** @return Collection|array<array-key, Song> */
|
/** @return Collection|array<array-key, Song> */
|
||||||
public function getByStandardPlaylist(Playlist $playlist, ?User $scopedUser = null): Collection
|
public function getByStandardPlaylist(Playlist $playlist, ?User $scopedUser = null): Collection
|
||||||
{
|
{
|
||||||
return Song::withMeta($scopedUser ?? $this->auth->user())
|
return Song::query()
|
||||||
|
->withMeta($scopedUser ?? $this->auth->user())
|
||||||
->leftJoin('playlist_song', 'songs.id', '=', 'playlist_song.song_id')
|
->leftJoin('playlist_song', 'songs.id', '=', 'playlist_song.song_id')
|
||||||
->leftJoin('playlists', 'playlists.id', '=', 'playlist_song.playlist_id')
|
->leftJoin('playlists', 'playlists.id', '=', 'playlist_song.playlist_id')
|
||||||
->where('playlists.id', $playlist->id)
|
->where('playlists.id', $playlist->id)
|
||||||
|
@ -143,28 +144,28 @@ class SongRepository extends Repository
|
||||||
/** @return Collection|array<array-key, Song> */
|
/** @return Collection|array<array-key, Song> */
|
||||||
public function getRandom(int $limit, ?User $scopedUser = null): Collection
|
public function getRandom(int $limit, ?User $scopedUser = null): Collection
|
||||||
{
|
{
|
||||||
return Song::withMeta($scopedUser ?? $this->auth->user())->inRandomOrder()->limit($limit)->get();
|
return Song::query()->withMeta($scopedUser ?? $this->auth->user())->inRandomOrder()->limit($limit)->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return Collection|array<array-key, Song> */
|
/** @return Collection|array<array-key, Song> */
|
||||||
public function getByIds(array $ids, ?User $scopedUser = null): Collection
|
public function getByIds(array $ids, ?User $scopedUser = null): Collection
|
||||||
{
|
{
|
||||||
return Song::withMeta($scopedUser ?? $this->auth->user())->whereIn('songs.id', $ids)->get();
|
return Song::query()->withMeta($scopedUser ?? $this->auth->user())->whereIn('songs.id', $ids)->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getOne($id, ?User $scopedUser = null): Song
|
public function getOne($id, ?User $scopedUser = null): Song
|
||||||
{
|
{
|
||||||
return Song::withMeta($scopedUser ?? $this->auth->user())->findOrFail($id);
|
return Song::query()->withMeta($scopedUser ?? $this->auth->user())->findOrFail($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function count(): int
|
public function count(): int
|
||||||
{
|
{
|
||||||
return Song::count();
|
return Song::query()->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTotalLength(): float
|
public function getTotalLength(): float
|
||||||
{
|
{
|
||||||
return Song::sum('length');
|
return Song::query()->sum('length');
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function normalizeSortColumn(string $column): string
|
private static function normalizeSortColumn(string $column): string
|
||||||
|
|
|
@ -88,7 +88,7 @@ class FileSynchronizer
|
||||||
$data['album_id'] = $album->id;
|
$data['album_id'] = $album->id;
|
||||||
$data['artist_id'] = $artist->id;
|
$data['artist_id'] = $artist->id;
|
||||||
|
|
||||||
$this->song = Song::updateOrCreate(['path' => $this->filePath], $data);
|
$this->song = Song::query()->updateOrCreate(['path' => $this->filePath], $data); // @phpstan-ignore-line
|
||||||
|
|
||||||
return SyncResult::success($this->filePath);
|
return SyncResult::success($this->filePath);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ class InteractionService
|
||||||
*/
|
*/
|
||||||
public function increasePlayCount(string $songId, User $user): Interaction
|
public function increasePlayCount(string $songId, User $user): Interaction
|
||||||
{
|
{
|
||||||
return tap(Interaction::firstOrCreate([
|
return tap(Interaction::query()->firstOrCreate([
|
||||||
'song_id' => $songId,
|
'song_id' => $songId,
|
||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
]), static function (Interaction $interaction): void {
|
]), static function (Interaction $interaction): void {
|
||||||
|
@ -39,7 +39,7 @@ class InteractionService
|
||||||
*/
|
*/
|
||||||
public function toggleLike(string $songId, User $user): Interaction
|
public function toggleLike(string $songId, User $user): Interaction
|
||||||
{
|
{
|
||||||
return tap(Interaction::firstOrCreate([
|
return tap(Interaction::query()->firstOrCreate([
|
||||||
'song_id' => $songId,
|
'song_id' => $songId,
|
||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
]), static function (Interaction $interaction): void {
|
]), static function (Interaction $interaction): void {
|
||||||
|
@ -59,21 +59,18 @@ class InteractionService
|
||||||
*/
|
*/
|
||||||
public function batchLike(array $songIds, User $user): Collection
|
public function batchLike(array $songIds, User $user): Collection
|
||||||
{
|
{
|
||||||
$interactions = collect($songIds)->map(static fn ($songId): Interaction => tap(Interaction::firstOrCreate([
|
$interactions = collect($songIds)->map(static function ($songId) use ($user): Interaction {
|
||||||
'song_id' => $songId,
|
return tap(Interaction::query()->firstOrCreate([
|
||||||
'user_id' => $user->id,
|
'song_id' => $songId,
|
||||||
]), static function (Interaction $interaction): void {
|
'user_id' => $user->id,
|
||||||
if (!$interaction->exists) {
|
]), static function (Interaction $interaction): void {
|
||||||
$interaction->play_count = 0;
|
$interaction->play_count ??= 0;
|
||||||
}
|
$interaction->liked = true;
|
||||||
|
$interaction->save();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$interaction->liked = true;
|
event(new SongsBatchLiked($interactions->map(static fn (Interaction $item) => $item->song), $user));
|
||||||
$interaction->save();
|
|
||||||
}));
|
|
||||||
|
|
||||||
event(new SongsBatchLiked($interactions->map(static function (Interaction $interaction): Song {
|
|
||||||
return $interaction->song;
|
|
||||||
}), $user));
|
|
||||||
|
|
||||||
return $interactions;
|
return $interactions;
|
||||||
}
|
}
|
||||||
|
@ -85,10 +82,11 @@ class InteractionService
|
||||||
*/
|
*/
|
||||||
public function batchUnlike(array $songIds, User $user): void
|
public function batchUnlike(array $songIds, User $user): void
|
||||||
{
|
{
|
||||||
Interaction::whereIn('song_id', $songIds)
|
Interaction::query()
|
||||||
|
->whereIn('song_id', $songIds)
|
||||||
->where('user_id', $user->id)
|
->where('user_id', $user->id)
|
||||||
->update(['liked' => false]);
|
->update(['liked' => false]);
|
||||||
|
|
||||||
event(new SongsBatchUnliked(Song::find($songIds), $user));
|
event(new SongsBatchUnliked(Song::query()->find($songIds), $user));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ namespace App\Services;
|
||||||
use App\Models\Album;
|
use App\Models\Album;
|
||||||
use App\Models\Artist;
|
use App\Models\Artist;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Query\Builder;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class LibraryManager
|
class LibraryManager
|
||||||
|
@ -19,13 +18,13 @@ class LibraryManager
|
||||||
public function prune(bool $dryRun = false): array
|
public function prune(bool $dryRun = false): array
|
||||||
{
|
{
|
||||||
return DB::transaction(static function () use ($dryRun): array {
|
return DB::transaction(static function () use ($dryRun): array {
|
||||||
/** @var Builder $albumQuery */
|
$albumQuery = Album::query()
|
||||||
$albumQuery = Album::leftJoin('songs', 'songs.album_id', '=', 'albums.id')
|
->leftJoin('songs', 'songs.album_id', '=', 'albums.id')
|
||||||
->whereNull('songs.album_id')
|
->whereNull('songs.album_id')
|
||||||
->whereNotIn('albums.id', [Album::UNKNOWN_ID]);
|
->whereNotIn('albums.id', [Album::UNKNOWN_ID]);
|
||||||
|
|
||||||
/** @var Builder $artistQuery */
|
$artistQuery = Artist::query()
|
||||||
$artistQuery = Artist::leftJoin('songs', 'songs.artist_id', '=', 'artists.id')
|
->leftJoin('songs', 'songs.artist_id', '=', 'artists.id')
|
||||||
->leftJoin('albums', 'albums.artist_id', '=', 'artists.id')
|
->leftJoin('albums', 'albums.artist_id', '=', 'artists.id')
|
||||||
->whereNull('songs.artist_id')
|
->whereNull('songs.artist_id')
|
||||||
->whereNull('albums.artist_id')
|
->whereNull('albums.artist_id')
|
||||||
|
|
|
@ -38,8 +38,8 @@ class MediaCacheService
|
||||||
private function query(): array
|
private function query(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'albums' => Album::orderBy('name')->get(),
|
'albums' => Album::query()->orderBy('name')->get(),
|
||||||
'artists' => Artist::orderBy('name')->get(),
|
'artists' => Artist::query()->orderBy('name')->get(),
|
||||||
'songs' => Song::all(),
|
'songs' => Song::all(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,7 +153,7 @@ class MediaSyncService
|
||||||
|
|
||||||
private function handleDeletedDirectoryRecord(string $path): void
|
private function handleDeletedDirectoryRecord(string $path): void
|
||||||
{
|
{
|
||||||
$count = Song::inDirectory($path)->delete();
|
$count = Song::query()->inDirectory($path)->delete();
|
||||||
|
|
||||||
if ($count) {
|
if ($count) {
|
||||||
$this->logger->info("Deleted $count song(s) under $path");
|
$this->logger->info("Deleted $count song(s) under $path");
|
||||||
|
|
|
@ -66,7 +66,7 @@ class S3Service implements ObjectStorageInterface
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$song = Song::updateOrCreate(['id' => Helper::getFileHash($path)], [
|
$song = Song::query()->updateOrCreate(['id' => Helper::getFileHash($path)], [
|
||||||
'path' => $path,
|
'path' => $path,
|
||||||
'album_id' => $album->id,
|
'album_id' => $album->id,
|
||||||
'artist_id' => $artist->id,
|
'artist_id' => $artist->id,
|
||||||
|
|
|
@ -23,7 +23,7 @@ class SmartPlaylistService
|
||||||
{
|
{
|
||||||
throw_unless($playlist->is_smart, NonSmartPlaylistException::create($playlist));
|
throw_unless($playlist->is_smart, NonSmartPlaylistException::create($playlist));
|
||||||
|
|
||||||
$query = Song::withMeta($user ?? $this->auth->user());
|
$query = Song::query()->withMeta($user ?? $this->auth->user());
|
||||||
|
|
||||||
$playlist->rule_groups->each(static function (SmartPlaylistRuleGroup $group, int $index) use ($query): void {
|
$playlist->rule_groups->each(static function (SmartPlaylistRuleGroup $group, int $index) use ($query): void {
|
||||||
$clause = $index === 0 ? 'where' : 'orWhere';
|
$clause = $index === 0 ? 'where' : 'orWhere';
|
||||||
|
|
|
@ -13,7 +13,7 @@ class UserService
|
||||||
|
|
||||||
public function createUser(string $name, string $email, string $plainTextPassword, bool $isAdmin): User
|
public function createUser(string $name, string $email, string $plainTextPassword, bool $isAdmin): User
|
||||||
{
|
{
|
||||||
return User::create([
|
return User::query()->create([
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'email' => $email,
|
'email' => $email,
|
||||||
'password' => $this->hash->make($plainTextPassword),
|
'password' => $this->hash->make($plainTextPassword),
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace App\Services\V6;
|
namespace App\Services\V6;
|
||||||
|
|
||||||
|
use App\Builders\SongBuilder;
|
||||||
use App\Models\Album;
|
use App\Models\Album;
|
||||||
use App\Models\Artist;
|
use App\Models\Artist;
|
||||||
use App\Models\Song;
|
use App\Models\Song;
|
||||||
|
@ -10,7 +11,6 @@ use App\Repositories\AlbumRepository;
|
||||||
use App\Repositories\ArtistRepository;
|
use App\Repositories\ArtistRepository;
|
||||||
use App\Repositories\SongRepository;
|
use App\Repositories\SongRepository;
|
||||||
use App\Values\ExcerptSearchResult;
|
use App\Values\ExcerptSearchResult;
|
||||||
use Illuminate\Contracts\Database\Query\Builder;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
class SearchService
|
class SearchService
|
||||||
|
@ -55,7 +55,7 @@ class SearchService
|
||||||
int $limit = self::DEFAULT_MAX_SONG_RESULT_COUNT
|
int $limit = self::DEFAULT_MAX_SONG_RESULT_COUNT
|
||||||
): Collection {
|
): Collection {
|
||||||
return Song::search($keywords)
|
return Song::search($keywords)
|
||||||
->query(static function (Builder $builder) use ($scopedUser, $limit): void {
|
->query(static function (SongBuilder $builder) use ($scopedUser, $limit): void {
|
||||||
$builder->withMeta($scopedUser ?? auth()->user())->limit($limit);
|
$builder->withMeta($scopedUser ?? auth()->user())->limit($limit);
|
||||||
})
|
})
|
||||||
->get();
|
->get();
|
||||||
|
|
|
@ -25,7 +25,8 @@ class CreateVariousArtists extends Migration
|
||||||
|
|
||||||
Artist::unguard();
|
Artist::unguard();
|
||||||
|
|
||||||
$existingArtist = Artist::find(Artist::VARIOUS_ID);
|
/** @var Artist|null $existingArtist */
|
||||||
|
$existingArtist = Artist::query()->find(Artist::VARIOUS_ID);
|
||||||
|
|
||||||
if ($existingArtist) {
|
if ($existingArtist) {
|
||||||
if ($existingArtist->name === Artist::VARIOUS_NAME) {
|
if ($existingArtist->name === Artist::VARIOUS_NAME) {
|
||||||
|
@ -34,7 +35,8 @@ class CreateVariousArtists extends Migration
|
||||||
|
|
||||||
// There's an existing artist with that special ID, but it's not our Various Artist
|
// There's an existing artist with that special ID, but it's not our Various Artist
|
||||||
// We move it to the end of the table.
|
// We move it to the end of the table.
|
||||||
$latestArtist = Artist::orderBy('id', 'DESC')->first();
|
/** @var Artist $latestArtist */
|
||||||
|
$latestArtist = Artist::query()->orderByDesc('id')->first();
|
||||||
$existingArtist->id = $latestArtist->id + 1;
|
$existingArtist->id = $latestArtist->id + 1;
|
||||||
$existingArtist->save();
|
$existingArtist->save();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ class FixArtistAutoindexValue extends Migration
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var Artist $latestArtist */
|
/** @var Artist $latestArtist */
|
||||||
$latestArtist = Artist::orderBy('id', 'DESC')->first();
|
$latestArtist = Artist::query()->orderByDesc('id')->first();
|
||||||
DB::statement('ALTER TABLE artists AUTO_INCREMENT=' . ($latestArtist->id + 1));
|
DB::statement('ALTER TABLE artists AUTO_INCREMENT=' . ($latestArtist->id + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ class CopyArtistToContributingArtist extends Migration
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Song::with('album', 'album.artist')->get()->each(static function (Song $song): void {
|
Song::with('album', 'album.artist')->get()->each(static function (Song $song): void {
|
||||||
|
// @phpstan-ignore-line
|
||||||
if (!$song->contributing_artist_id) {
|
if (!$song->contributing_artist_id) {
|
||||||
$song->contributing_artist_id = $song->album->artist->id;
|
$song->contributing_artist_id = $song->album->artist->id;
|
||||||
$song->save();
|
$song->save();
|
||||||
|
|
|
@ -7,6 +7,6 @@ return new class extends Migration
|
||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Album::where('cover', 'unknown-album.png')->update(['cover' => '']);
|
Album::query()->where('cover', 'unknown-album.png')->update(['cover' => '']);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,9 +11,7 @@ class AlbumTableSeeder extends Seeder
|
||||||
{
|
{
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
Album::firstOrCreate([
|
Album::query()->firstOrCreate(['id' => Album::UNKNOWN_ID], [
|
||||||
'id' => Album::UNKNOWN_ID,
|
|
||||||
], [
|
|
||||||
'artist_id' => Artist::UNKNOWN_ID,
|
'artist_id' => Artist::UNKNOWN_ID,
|
||||||
'name' => Album::UNKNOWN_NAME,
|
'name' => Album::UNKNOWN_NAME,
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -10,11 +10,7 @@ class ArtistTableSeeder extends Seeder
|
||||||
{
|
{
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
Artist::firstOrCreate([
|
Artist::query()->firstOrCreate(['id' => Artist::UNKNOWN_ID], ['name' => Artist::UNKNOWN_NAME]);
|
||||||
'id' => Artist::UNKNOWN_ID,
|
|
||||||
], [
|
|
||||||
'name' => Artist::UNKNOWN_NAME,
|
|
||||||
]);
|
|
||||||
|
|
||||||
self::maybeResetPgsqlSerialValue();
|
self::maybeResetPgsqlSerialValue();
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,13 +21,12 @@ parameters:
|
||||||
- '#expects .*, Mockery\\LegacyMockInterface given#'
|
- '#expects .*, Mockery\\LegacyMockInterface given#'
|
||||||
- '#Call to an undefined method Illuminate\\Filesystem\\FilesystemAdapter::getAdapter\(\)#'
|
- '#Call to an undefined method Illuminate\\Filesystem\\FilesystemAdapter::getAdapter\(\)#'
|
||||||
- '#Call to an undefined method Mockery\\ExpectationInterface|Mockery\\HigherOrderMessage::with\(\)#'
|
- '#Call to an undefined method Mockery\\ExpectationInterface|Mockery\\HigherOrderMessage::with\(\)#'
|
||||||
- '#Call to an undefined method Laravel\\Scout\\Builder::with\(\)#'
|
- '#Call to private method .*\(\) of parent class Illuminate\\Database\\Eloquent\\Builder<Illuminate\\Database\\Eloquent\\Model>#'
|
||||||
- '#Call to an undefined method Illuminate\\Contracts\\Database\\Query\\Builder::isStandard\(\)#'
|
- '#should return App\\Models\\.*(\|null)? but returns .*Illuminate\\Database\\Eloquent\\Model(\|null)?#'
|
||||||
- '#Call to an undefined method Illuminate\\Contracts\\Database\\Query\\Builder::withMeta\(\)#'
|
|
||||||
- '#should return App\\Models\\.*(\|null)? but returns Illuminate\\Database\\Eloquent\\Model(\|null)?#'
|
|
||||||
# Laravel factories allow declaration of dynamic methods as "states"
|
# Laravel factories allow declaration of dynamic methods as "states"
|
||||||
- '#Call to an undefined method Illuminate\\Database\\Eloquent\\Factories\\Factory::#'
|
- '#Call to an undefined method Illuminate\\Database\\Eloquent\\Factories\\Factory::#'
|
||||||
- '#expects App\\Models\\User\|null, Illuminate\\Database\\Eloquent\\Collection\|Illuminate\\Database\\Eloquent\\Model given#'
|
- '#expects App\\Models\\User\|null, Illuminate\\Database\\Eloquent\\Collection\|Illuminate\\Database\\Eloquent\\Model given#'
|
||||||
|
- '#Method App\\Models\\.*::query\(\) should return App\\Builders\\.*Builder but returns Illuminate\\Database\\Eloquent\\Builder<Illuminate\\Database\\Eloquent\\Model>#'
|
||||||
|
|
||||||
excludePaths:
|
excludePaths:
|
||||||
- ./routes/console.php
|
- ./routes/console.php
|
||||||
|
|
|
@ -27,7 +27,8 @@ class DownloadTest extends TestCase
|
||||||
|
|
||||||
public function testNonLoggedInUserCannotDownload(): void
|
public function testNonLoggedInUserCannotDownload(): void
|
||||||
{
|
{
|
||||||
$song = Song::first();
|
/** @var Song $song */
|
||||||
|
$song = Song::query()->first();
|
||||||
|
|
||||||
$this->downloadService
|
$this->downloadService
|
||||||
->shouldReceive('from')
|
->shouldReceive('from')
|
||||||
|
@ -39,7 +40,8 @@ class DownloadTest extends TestCase
|
||||||
|
|
||||||
public function testDownloadOneSong(): void
|
public function testDownloadOneSong(): void
|
||||||
{
|
{
|
||||||
$song = Song::first();
|
/** @var Song $song */
|
||||||
|
$song = Song::query()->first();
|
||||||
|
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = User::factory()->create();
|
$user = User::factory()->create();
|
||||||
|
@ -62,7 +64,7 @@ class DownloadTest extends TestCase
|
||||||
$user = User::factory()->create();
|
$user = User::factory()->create();
|
||||||
|
|
||||||
/** @var array<Song>|Collection $songs */
|
/** @var array<Song>|Collection $songs */
|
||||||
$songs = Song::take(2)->orderBy('id')->get();
|
$songs = Song::query()->take(2)->orderBy('id')->get();
|
||||||
|
|
||||||
$this->downloadService
|
$this->downloadService
|
||||||
->shouldReceive('from')
|
->shouldReceive('from')
|
||||||
|
@ -84,7 +86,8 @@ class DownloadTest extends TestCase
|
||||||
|
|
||||||
public function testDownloadAlbum(): void
|
public function testDownloadAlbum(): void
|
||||||
{
|
{
|
||||||
$album = Album::first();
|
/** @var Album $album */
|
||||||
|
$album = Album::query()->first();
|
||||||
|
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = User::factory()->create();
|
$user = User::factory()->create();
|
||||||
|
@ -103,7 +106,8 @@ class DownloadTest extends TestCase
|
||||||
|
|
||||||
public function testDownloadArtist(): void
|
public function testDownloadArtist(): void
|
||||||
{
|
{
|
||||||
$artist = Artist::first();
|
/** @var Artist $artist */
|
||||||
|
$artist = Artist::query()->first();
|
||||||
|
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = User::factory()->create();
|
$user = User::factory()->create();
|
||||||
|
|
|
@ -25,7 +25,7 @@ class InteractionTest extends TestCase
|
||||||
$user = User::factory()->create();
|
$user = User::factory()->create();
|
||||||
|
|
||||||
/** @var Song $song */
|
/** @var Song $song */
|
||||||
$song = Song::orderBy('id')->first();
|
$song = Song::query()->orderBy('id')->first();
|
||||||
$this->postAs('api/interaction/play', ['song' => $song->id], $user);
|
$this->postAs('api/interaction/play', ['song' => $song->id], $user);
|
||||||
|
|
||||||
self::assertDatabaseHas('interactions', [
|
self::assertDatabaseHas('interactions', [
|
||||||
|
@ -52,7 +52,7 @@ class InteractionTest extends TestCase
|
||||||
$user = User::factory()->create();
|
$user = User::factory()->create();
|
||||||
|
|
||||||
/** @var Song $song */
|
/** @var Song $song */
|
||||||
$song = Song::orderBy('id')->first();
|
$song = Song::query()->orderBy('id')->first();
|
||||||
$this->postAs('api/interaction/like', ['song' => $song->id], $user);
|
$this->postAs('api/interaction/like', ['song' => $song->id], $user);
|
||||||
|
|
||||||
self::assertDatabaseHas('interactions', [
|
self::assertDatabaseHas('interactions', [
|
||||||
|
@ -79,7 +79,7 @@ class InteractionTest extends TestCase
|
||||||
$user = User::factory()->create();
|
$user = User::factory()->create();
|
||||||
|
|
||||||
/** @var Collection|array<Song> $songs */
|
/** @var Collection|array<Song> $songs */
|
||||||
$songs = Song::orderBy('id')->take(2)->get();
|
$songs = Song::query()->orderBy('id')->take(2)->get();
|
||||||
$songIds = array_pluck($songs->toArray(), 'id');
|
$songIds = array_pluck($songs->toArray(), 'id');
|
||||||
|
|
||||||
$this->postAs('api/interaction/batch/like', ['songs' => $songIds], $user);
|
$this->postAs('api/interaction/batch/like', ['songs' => $songIds], $user);
|
||||||
|
|
|
@ -34,7 +34,7 @@ class S3Test extends TestCase
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/** @var Song $song */
|
/** @var Song $song */
|
||||||
$song = Song::where('path', 's3://koel/sample.mp3')->firstOrFail();
|
$song = Song::query()->where('path', 's3://koel/sample.mp3')->firstOrFail();
|
||||||
|
|
||||||
self::assertSame('A Koel Song', $song->title);
|
self::assertSame('A Koel Song', $song->title);
|
||||||
self::assertSame('Koel Testing Vol. 1', $song->album->name);
|
self::assertSame('Koel Testing Vol. 1', $song->album->name);
|
||||||
|
|
|
@ -23,7 +23,7 @@ class PlaylistTest extends TestCase
|
||||||
$user = User::factory()->create();
|
$user = User::factory()->create();
|
||||||
|
|
||||||
/** @var array<Song>|Collection $songs */
|
/** @var array<Song>|Collection $songs */
|
||||||
$songs = Song::orderBy('id')->take(3)->get();
|
$songs = Song::query()->orderBy('id')->take(3)->get();
|
||||||
|
|
||||||
$response = $this->postAs('api/playlist', [
|
$response = $this->postAs('api/playlist', [
|
||||||
'name' => 'Foo Bar',
|
'name' => 'Foo Bar',
|
||||||
|
@ -34,7 +34,7 @@ class PlaylistTest extends TestCase
|
||||||
$response->assertOk();
|
$response->assertOk();
|
||||||
|
|
||||||
/** @var Playlist $playlist */
|
/** @var Playlist $playlist */
|
||||||
$playlist = Playlist::orderBy('id', 'desc')->first();
|
$playlist = Playlist::query()->orderByDesc('id')->first();
|
||||||
|
|
||||||
self::assertSame('Foo Bar', $playlist->name);
|
self::assertSame('Foo Bar', $playlist->name);
|
||||||
self::assertTrue($playlist->user->is($user));
|
self::assertTrue($playlist->user->is($user));
|
||||||
|
@ -63,7 +63,7 @@ class PlaylistTest extends TestCase
|
||||||
], $user);
|
], $user);
|
||||||
|
|
||||||
/** @var Playlist $playlist */
|
/** @var Playlist $playlist */
|
||||||
$playlist = Playlist::orderBy('id', 'desc')->first();
|
$playlist = Playlist::query()->orderByDesc('id')->first();
|
||||||
|
|
||||||
self::assertSame('Smart Foo Bar', $playlist->name);
|
self::assertSame('Smart Foo Bar', $playlist->name);
|
||||||
self::assertTrue($playlist->user->is($user));
|
self::assertTrue($playlist->user->is($user));
|
||||||
|
@ -88,11 +88,11 @@ class PlaylistTest extends TestCase
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'songs' => Song::orderBy('id')->take(3)->get()->pluck('id')->all(),
|
'songs' => Song::query()->orderBy('id')->take(3)->get()->pluck('id')->all(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/** @var Playlist $playlist */
|
/** @var Playlist $playlist */
|
||||||
$playlist = Playlist::orderBy('id', 'desc')->first();
|
$playlist = Playlist::query()->orderByDesc('id')->first();
|
||||||
|
|
||||||
self::assertSame('Smart Foo Bar', $playlist->name);
|
self::assertSame('Smart Foo Bar', $playlist->name);
|
||||||
self::assertEmpty($playlist->songs);
|
self::assertEmpty($playlist->songs);
|
||||||
|
|
|
@ -21,7 +21,9 @@ class SongTest extends TestCase
|
||||||
{
|
{
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = User::factory()->admin()->create();
|
$user = User::factory()->admin()->create();
|
||||||
$song = Song::first();
|
|
||||||
|
/** @var Song $song */
|
||||||
|
$song = Song::query()->first();
|
||||||
|
|
||||||
$this->putAs('/api/songs', [
|
$this->putAs('/api/songs', [
|
||||||
'songs' => [$song->id],
|
'songs' => [$song->id],
|
||||||
|
@ -37,11 +39,11 @@ class SongTest extends TestCase
|
||||||
->assertOk();
|
->assertOk();
|
||||||
|
|
||||||
/** @var Artist $artist */
|
/** @var Artist $artist */
|
||||||
$artist = Artist::where('name', 'John Cena')->first();
|
$artist = Artist::query()->where('name', 'John Cena')->first();
|
||||||
self::assertNotNull($artist);
|
self::assertNotNull($artist);
|
||||||
|
|
||||||
/** @var Album $album */
|
/** @var Album $album */
|
||||||
$album = Album::where('name', 'One by One')->first();
|
$album = Album::query()->where('name', 'One by One')->first();
|
||||||
self::assertNotNull($album);
|
self::assertNotNull($album);
|
||||||
|
|
||||||
self::assertDatabaseHas(Song::class, [
|
self::assertDatabaseHas(Song::class, [
|
||||||
|
@ -57,7 +59,10 @@ class SongTest extends TestCase
|
||||||
{
|
{
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = User::factory()->admin()->create();
|
$user = User::factory()->admin()->create();
|
||||||
$song = Song::first();
|
|
||||||
|
/** @var Song $song */
|
||||||
|
$song = Song::query()->first();
|
||||||
|
|
||||||
$originalArtistId = $song->artist->id;
|
$originalArtistId = $song->artist->id;
|
||||||
|
|
||||||
$this->putAs('/api/songs', [
|
$this->putAs('/api/songs', [
|
||||||
|
@ -73,17 +78,17 @@ class SongTest extends TestCase
|
||||||
->assertOk();
|
->assertOk();
|
||||||
|
|
||||||
// We don't expect the song's artist to change
|
// We don't expect the song's artist to change
|
||||||
self::assertEquals($originalArtistId, Song::find($song->id)->artist->id);
|
self::assertEquals($originalArtistId, $song->refresh()->artist->id);
|
||||||
|
|
||||||
// But we expect a new album to be created for this artist and contain this song
|
// But we expect a new album to be created for this artist and contain this song
|
||||||
self::assertEquals('One by One', Song::find($song->id)->album->name);
|
self::assertEquals('One by One', $song->album->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testMultipleUpdateNoCompilation(): void
|
public function testMultipleUpdateNoCompilation(): void
|
||||||
{
|
{
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = User::factory()->admin()->create();
|
$user = User::factory()->admin()->create();
|
||||||
$songIds = Song::latest()->take(3)->pluck('id')->toArray();
|
$songIds = Song::query()->latest()->take(3)->pluck('id')->toArray();
|
||||||
|
|
||||||
$this->putAs('/api/songs', [
|
$this->putAs('/api/songs', [
|
||||||
'songs' => $songIds,
|
'songs' => $songIds,
|
||||||
|
@ -97,7 +102,7 @@ class SongTest extends TestCase
|
||||||
], $user)
|
], $user)
|
||||||
->assertOk();
|
->assertOk();
|
||||||
|
|
||||||
$songs = Song::whereIn('id', $songIds)->get();
|
$songs = Song::query()->whereIn('id', $songIds)->get();
|
||||||
|
|
||||||
// All of these songs must now belong to a new album and artist set
|
// All of these songs must now belong to a new album and artist set
|
||||||
self::assertEquals('One by One', $songs[0]->album->name);
|
self::assertEquals('One by One', $songs[0]->album->name);
|
||||||
|
@ -115,7 +120,7 @@ class SongTest extends TestCase
|
||||||
$user = User::factory()->admin()->create();
|
$user = User::factory()->admin()->create();
|
||||||
|
|
||||||
/** @var array<array-key, Song>|Collection $originalSongs */
|
/** @var array<array-key, Song>|Collection $originalSongs */
|
||||||
$originalSongs = Song::latest()->take(3)->get();
|
$originalSongs = Song::query()->latest()->take(3)->get();
|
||||||
$songIds = $originalSongs->pluck('id')->toArray();
|
$songIds = $originalSongs->pluck('id')->toArray();
|
||||||
|
|
||||||
$this->putAs('/api/songs', [
|
$this->putAs('/api/songs', [
|
||||||
|
@ -131,7 +136,7 @@ class SongTest extends TestCase
|
||||||
->assertOk();
|
->assertOk();
|
||||||
|
|
||||||
/** @var array<Song>|Collection $songs */
|
/** @var array<Song>|Collection $songs */
|
||||||
$songs = Song::latest()->take(3)->get();
|
$songs = Song::query()->latest()->take(3)->get();
|
||||||
|
|
||||||
// Even though the album name doesn't change, a new artist should have been created
|
// Even though the album name doesn't change, a new artist should have been created
|
||||||
// and thus, a new album with the same name was created as well.
|
// and thus, a new album with the same name was created as well.
|
||||||
|
@ -152,7 +157,10 @@ class SongTest extends TestCase
|
||||||
{
|
{
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = User::factory()->admin()->create();
|
$user = User::factory()->admin()->create();
|
||||||
$song = Song::first();
|
|
||||||
|
/** @var Song $song */
|
||||||
|
$song = Song::query()->first();
|
||||||
|
|
||||||
|
|
||||||
$this->putAs('/api/songs', [
|
$this->putAs('/api/songs', [
|
||||||
'songs' => [$song->id],
|
'songs' => [$song->id],
|
||||||
|
@ -169,13 +177,13 @@ class SongTest extends TestCase
|
||||||
->assertOk();
|
->assertOk();
|
||||||
|
|
||||||
/** @var Album $album */
|
/** @var Album $album */
|
||||||
$album = Album::where('name', 'One by One')->first();
|
$album = Album::query()->where('name', 'One by One')->first();
|
||||||
|
|
||||||
/** @var Artist $albumArtist */
|
/** @var Artist $albumArtist */
|
||||||
$albumArtist = Artist::whereName('John Lennon')->first();
|
$albumArtist = Artist::query()->where('name', 'John Lennon')->first();
|
||||||
|
|
||||||
/** @var Artist $artist */
|
/** @var Artist $artist */
|
||||||
$artist = Artist::whereName('John Cena')->first();
|
$artist = Artist::query()->where('name', 'John Cena')->first();
|
||||||
|
|
||||||
self::assertDatabaseHas(Song::class, [
|
self::assertDatabaseHas(Song::class, [
|
||||||
'id' => $song->id,
|
'id' => $song->id,
|
||||||
|
@ -191,11 +199,11 @@ class SongTest extends TestCase
|
||||||
|
|
||||||
public function testDeletingByChunk(): void
|
public function testDeletingByChunk(): void
|
||||||
{
|
{
|
||||||
self::assertNotEquals(0, Song::count());
|
self::assertNotEquals(0, Song::query()->count());
|
||||||
$ids = Song::select('id')->get()->pluck('id')->all();
|
$ids = Song::query()->select('id')->get()->pluck('id')->all();
|
||||||
|
|
||||||
Song::deleteByChunk($ids, 'id', 1);
|
Song::deleteByChunk($ids, 'id', 1);
|
||||||
|
|
||||||
self::assertEquals(0, Song::count());
|
self::assertEquals(0, Song::query()->count());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,8 @@ class UserTest extends TestCase
|
||||||
], $admin)
|
], $admin)
|
||||||
->assertSuccessful();
|
->assertSuccessful();
|
||||||
|
|
||||||
$user = User::firstWhere('email', 'bar@baz.com');
|
/** @var User $user */
|
||||||
|
$user = User::query()->firstWhere('email', 'bar@baz.com');
|
||||||
|
|
||||||
self::assertTrue(Hash::check('secret', $user->password));
|
self::assertTrue(Hash::check('secret', $user->password));
|
||||||
self::assertSame('Foo', $user->name);
|
self::assertSame('Foo', $user->name);
|
||||||
|
|
|
@ -44,6 +44,12 @@ class PlayCountTest extends TestCase
|
||||||
'play_count',
|
'play_count',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
self::assertEquals(1, Interaction::whereSongIdAndUserId($song->id, $user->id)->first()->play_count);
|
/** @var Interaction $interaction */
|
||||||
|
$interaction = Interaction::query()
|
||||||
|
->where('song_id', $song->id)
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
self::assertEquals(1, $interaction->play_count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,13 +21,13 @@ class YouTubeTest extends TestCase
|
||||||
public function testSearchYouTubeVideos(): void
|
public function testSearchYouTubeVideos(): void
|
||||||
{
|
{
|
||||||
static::createSampleMediaSet();
|
static::createSampleMediaSet();
|
||||||
$song = Song::first();
|
|
||||||
|
/** @var Song $song */
|
||||||
|
$song = Song::query()->first();
|
||||||
|
|
||||||
$this->youTubeService
|
$this->youTubeService
|
||||||
->shouldReceive('searchVideosRelatedToSong')
|
->shouldReceive('searchVideosRelatedToSong')
|
||||||
->with(Mockery::on(static function (Song $retrievedSong) use ($song) {
|
->with(Mockery::on(static fn (Song $retrievedSong) => $song->is($retrievedSong)), 'foo')
|
||||||
return $song->id === $retrievedSong->id;
|
|
||||||
}), 'foo')
|
|
||||||
->once();
|
->once();
|
||||||
|
|
||||||
$this->getAs("/api/youtube/search/song/{$song->id}?pageToken=foo")
|
$this->getAs("/api/youtube/search/song/{$song->id}?pageToken=foo")
|
||||||
|
|
|
@ -27,11 +27,10 @@ class InteractionServiceTest extends TestCase
|
||||||
{
|
{
|
||||||
/** @var Interaction $interaction */
|
/** @var Interaction $interaction */
|
||||||
$interaction = Interaction::factory()->create();
|
$interaction = Interaction::factory()->create();
|
||||||
|
$currentCount = $interaction->play_count;
|
||||||
$this->interactionService->increasePlayCount($interaction->song, $interaction->user);
|
$this->interactionService->increasePlayCount($interaction->song, $interaction->user);
|
||||||
|
|
||||||
$updatedInteraction = Interaction::find($interaction->id);
|
self::assertEquals($currentCount + 1, $interaction->refresh()->play_count);
|
||||||
self::assertEquals($interaction->play_count + 1, $updatedInteraction->play_count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testToggleLike(): void
|
public function testToggleLike(): void
|
||||||
|
@ -40,11 +39,11 @@ class InteractionServiceTest extends TestCase
|
||||||
|
|
||||||
/** @var Interaction $interaction */
|
/** @var Interaction $interaction */
|
||||||
$interaction = Interaction::factory()->create();
|
$interaction = Interaction::factory()->create();
|
||||||
|
$currentLiked = $interaction->liked;
|
||||||
|
|
||||||
$this->interactionService->toggleLike($interaction->song, $interaction->user);
|
$this->interactionService->toggleLike($interaction->song, $interaction->user);
|
||||||
|
|
||||||
$updatedInteraction = Interaction::find($interaction->id);
|
self::assertNotSame($currentLiked, $interaction->refresh()->liked);
|
||||||
self::assertNotSame($interaction->liked, $updatedInteraction->liked);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLikeMultipleSongs(): void
|
public function testLikeMultipleSongs(): void
|
||||||
|
@ -60,7 +59,13 @@ class InteractionServiceTest extends TestCase
|
||||||
$this->interactionService->batchLike($songs->pluck('id')->all(), $user);
|
$this->interactionService->batchLike($songs->pluck('id')->all(), $user);
|
||||||
|
|
||||||
$songs->each(static function (Song $song) use ($user): void {
|
$songs->each(static function (Song $song) use ($user): void {
|
||||||
self::assertTrue(Interaction::whereSongIdAndUserId($song->id, $user->id)->first()->liked);
|
/** @var Interaction $interaction */
|
||||||
|
$interaction = Interaction::query()
|
||||||
|
->where('song_id', $song->id)
|
||||||
|
->where('user_id', $user->id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
self::assertTrue($interaction->liked);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +85,7 @@ class InteractionServiceTest extends TestCase
|
||||||
$this->interactionService->batchUnlike($interactions->pluck('song.id')->all(), $user);
|
$this->interactionService->batchUnlike($interactions->pluck('song.id')->all(), $user);
|
||||||
|
|
||||||
$interactions->each(static function (Interaction $interaction): void {
|
$interactions->each(static function (Interaction $interaction): void {
|
||||||
self::assertFalse(Interaction::find($interaction->id)->liked);
|
self::assertFalse($interaction->refresh()->liked);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,8 @@ class MediaCacheServiceTest extends TestCase
|
||||||
Song::factory(5)->create();
|
Song::factory(5)->create();
|
||||||
|
|
||||||
$this->cache->shouldReceive('rememberForever')->andReturn([
|
$this->cache->shouldReceive('rememberForever')->andReturn([
|
||||||
'albums' => Album::orderBy('name')->get(),
|
'albums' => Album::query()->orderBy('name')->get(),
|
||||||
'artists' => Artist::orderBy('name')->get(),
|
'artists' => Artist::query()->orderBy('name')->get(),
|
||||||
'songs' => Song::all(),
|
'songs' => Song::all(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ class MediaSyncServiceTest extends TestCase
|
||||||
// GitHub issue #380. folder.png should be copied and used as the cover for files
|
// GitHub issue #380. folder.png should be copied and used as the cover for files
|
||||||
// under subdir/
|
// under subdir/
|
||||||
/** @var Song $song */
|
/** @var Song $song */
|
||||||
$song = Song::where('path', $this->path('/subdir/back-in-black.ogg'))->first();
|
$song = Song::query()->where('path', $this->path('/subdir/back-in-black.ogg'))->first();
|
||||||
self::assertNotEmpty($song->album->cover);
|
self::assertNotEmpty($song->album->cover);
|
||||||
|
|
||||||
// File search shouldn't be case-sensitive.
|
// File search shouldn't be case-sensitive.
|
||||||
|
@ -72,12 +72,12 @@ class MediaSyncServiceTest extends TestCase
|
||||||
|
|
||||||
// Albums and artists should be correctly linked
|
// Albums and artists should be correctly linked
|
||||||
/** @var Album $album */
|
/** @var Album $album */
|
||||||
$album = Album::where('name', 'Koel Testing Vol. 1')->first();
|
$album = Album::query()->where('name', 'Koel Testing Vol. 1')->first();
|
||||||
self::assertEquals('Koel', $album->artist->name);
|
self::assertEquals('Koel', $album->artist->name);
|
||||||
|
|
||||||
// Compilation albums, artists and songs must be recognized
|
// Compilation albums, artists and songs must be recognized
|
||||||
/** @var Song $song */
|
/** @var Song $song */
|
||||||
$song = Song::where('title', 'This song belongs to a compilation')->first();
|
$song = Song::query()->where('title', 'This song belongs to a compilation')->first();
|
||||||
self::assertFalse($song->album->artist->is($song->artist));
|
self::assertFalse($song->album->artist->is($song->artist));
|
||||||
self::assertSame('Koel', $song->album->artist->name);
|
self::assertSame('Koel', $song->album->artist->name);
|
||||||
self::assertSame('Cuckoo', $song->artist->name);
|
self::assertSame('Cuckoo', $song->artist->name);
|
||||||
|
@ -87,7 +87,8 @@ class MediaSyncServiceTest extends TestCase
|
||||||
{
|
{
|
||||||
$this->mediaService->sync();
|
$this->mediaService->sync();
|
||||||
|
|
||||||
$song = Song::first();
|
/** @var Song $song */
|
||||||
|
$song = Song::query()->first();
|
||||||
|
|
||||||
touch($song->path, $time = time() + 1000);
|
touch($song->path, $time = time() + 1000);
|
||||||
$this->mediaService->sync();
|
$this->mediaService->sync();
|
||||||
|
@ -101,7 +102,8 @@ class MediaSyncServiceTest extends TestCase
|
||||||
|
|
||||||
$this->mediaService->sync();
|
$this->mediaService->sync();
|
||||||
|
|
||||||
$song = Song::first();
|
/** @var Song $song */
|
||||||
|
$song = Song::query()->first();
|
||||||
|
|
||||||
$song->update([
|
$song->update([
|
||||||
'title' => "It's John Cena!",
|
'title' => "It's John Cena!",
|
||||||
|
@ -121,7 +123,8 @@ class MediaSyncServiceTest extends TestCase
|
||||||
|
|
||||||
$this->mediaService->sync();
|
$this->mediaService->sync();
|
||||||
|
|
||||||
$song = Song::first();
|
/** @var Song $song */
|
||||||
|
$song = Song::query()->first();
|
||||||
|
|
||||||
$song->update([
|
$song->update([
|
||||||
'title' => "It's John Cena!",
|
'title' => "It's John Cena!",
|
||||||
|
@ -142,7 +145,8 @@ class MediaSyncServiceTest extends TestCase
|
||||||
|
|
||||||
$this->mediaService->sync();
|
$this->mediaService->sync();
|
||||||
|
|
||||||
$song = Song::first();
|
/** @var Song $song */
|
||||||
|
$song = Song::query()->first();
|
||||||
|
|
||||||
$song->update([
|
$song->update([
|
||||||
'title' => "It's John Cena!",
|
'title' => "It's John Cena!",
|
||||||
|
@ -161,14 +165,16 @@ class MediaSyncServiceTest extends TestCase
|
||||||
{
|
{
|
||||||
$this->mediaService->sync();
|
$this->mediaService->sync();
|
||||||
|
|
||||||
$song = Song::first();
|
/** @var Song $song */
|
||||||
|
$song = Song::query()->first();
|
||||||
|
|
||||||
$song->delete();
|
$song->delete();
|
||||||
|
|
||||||
$this->mediaService->sync(ignores: ['title', 'disc', 'track'], force: true);
|
$this->mediaService->sync(ignores: ['title', 'disc', 'track'], force: true);
|
||||||
|
|
||||||
// Song should be added back with all info
|
// Song should be added back with all info
|
||||||
self::assertEquals(
|
self::assertEquals(
|
||||||
Arr::except(Song::where('path', $song->path)->first()->toArray(), ['id', 'created_at']),
|
Arr::except(Song::query()->where('path', $song->path)->first()->toArray(), ['id', 'created_at']),
|
||||||
Arr::except($song->toArray(), ['id', 'created_at'])
|
Arr::except($song->toArray(), ['id', 'created_at'])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -188,8 +194,9 @@ class MediaSyncServiceTest extends TestCase
|
||||||
$this->expectsEvents(LibraryChanged::class);
|
$this->expectsEvents(LibraryChanged::class);
|
||||||
|
|
||||||
static::createSampleMediaSet();
|
static::createSampleMediaSet();
|
||||||
$song = Song::first();
|
|
||||||
self::assertModelExists($song);
|
/** @var Song $song */
|
||||||
|
$song = Song::query()->first();
|
||||||
|
|
||||||
$this->mediaService->syncByWatchRecord(new InotifyWatchRecord("DELETE $song->path"));
|
$this->mediaService->syncByWatchRecord(new InotifyWatchRecord("DELETE $song->path"));
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ trait CreatesApplication
|
||||||
{
|
{
|
||||||
$this->artisan->call('migrate');
|
$this->artisan->call('migrate');
|
||||||
|
|
||||||
if (!User::count()) {
|
if (!User::query()->count()) {
|
||||||
$this->artisan->call('db:seed');
|
$this->artisan->call('db:seed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ class AlbumTest extends TestCase
|
||||||
$artist = Artist::factory()->create();
|
$artist = Artist::factory()->create();
|
||||||
$name = 'Foo';
|
$name = 'Foo';
|
||||||
|
|
||||||
self::assertNull(Album::whereArtistIdAndName($artist->id, $name)->first());
|
self::assertNull(Album::query()->where('artist_id', $artist->id)->where('name', $name)->first());
|
||||||
|
|
||||||
$album = Album::getOrCreate($artist, $name);
|
$album = Album::getOrCreate($artist, $name);
|
||||||
self::assertSame('Foo', $album->name);
|
self::assertSame('Foo', $album->name);
|
||||||
|
|
|
@ -17,7 +17,7 @@ class ArtistTest extends TestCase
|
||||||
|
|
||||||
public function testNewArtistIsCreatedWithName(): void
|
public function testNewArtistIsCreatedWithName(): void
|
||||||
{
|
{
|
||||||
self::assertNull(Artist::whereName('Foo')->first());
|
self::assertNull(Artist::query()->where('name', 'Foo')->first());
|
||||||
self::assertSame('Foo', Artist::getOrCreate('Foo')->name);
|
self::assertSame('Foo', Artist::getOrCreate('Foo')->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ class MediaMetadataServiceTest extends TestCase
|
||||||
->with('/koel/public/img/album/foo.jpg', 'dummy-src');
|
->with('/koel/public/img/album/foo.jpg', 'dummy-src');
|
||||||
|
|
||||||
$this->mediaMetadataService->writeAlbumCover($album, 'dummy-src', 'jpg', $coverPath);
|
$this->mediaMetadataService->writeAlbumCover($album, 'dummy-src', 'jpg', $coverPath);
|
||||||
self::assertEquals(album_cover_url('foo.jpg'), Album::find($album->id)->cover);
|
self::assertEquals(album_cover_url('foo.jpg'), $album->refresh()->cover);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTryDownloadArtistImage(): void
|
public function testTryDownloadArtistImage(): void
|
||||||
|
@ -81,6 +81,7 @@ class MediaMetadataServiceTest extends TestCase
|
||||||
->with('/koel/public/img/artist/foo.jpg', 'dummy-src');
|
->with('/koel/public/img/artist/foo.jpg', 'dummy-src');
|
||||||
|
|
||||||
$this->mediaMetadataService->writeArtistImage($artist, 'dummy-src', 'jpg', $imagePath);
|
$this->mediaMetadataService->writeArtistImage($artist, 'dummy-src', 'jpg', $imagePath);
|
||||||
self::assertEquals(artist_image_url('foo.jpg'), Artist::find($artist->id)->image);
|
|
||||||
|
self::assertEquals(artist_image_url('foo.jpg'), $artist->refresh()->image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue