chore: merge v6 into base API

This commit is contained in:
Phan An 2023-06-04 23:51:53 +02:00
parent 050c992cf1
commit 48f6bcc105
No known key found for this signature in database
GPG key ID: A81E4477F0BB6FDC
83 changed files with 512 additions and 1207 deletions

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Resources\AlbumResource;

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Resources\SongResource;

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Resources\AlbumResource;

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Resources\ArtistResource;

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Resources\SongResource;

View file

@ -3,57 +3,50 @@
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Resources\PlaylistFolderResource;
use App\Http\Resources\PlaylistResource;
use App\Http\Resources\UserResource;
use App\Models\User;
use App\Repositories\InteractionRepository;
use App\Repositories\PlaylistRepository;
use App\Repositories\SettingRepository;
use App\Repositories\UserRepository;
use App\Repositories\SongRepository;
use App\Services\ApplicationInformationService;
use App\Services\ITunesService;
use App\Services\LastfmService;
use App\Services\MediaCacheService;
use App\Services\YouTubeService;
use Illuminate\Contracts\Auth\Authenticatable;
class DataController extends Controller
{
private const RECENTLY_PLAYED_EXCERPT_COUNT = 7;
/** @param User $currentUser */
/** @param User $user */
public function __construct(
private MediaCacheService $mediaCacheService,
private ITunesService $iTunesService,
private SettingRepository $settingRepository,
private PlaylistRepository $playlistRepository,
private InteractionRepository $interactionRepository,
private UserRepository $userRepository,
private SongRepository $songRepository,
private ApplicationInformationService $applicationInformationService,
private ?Authenticatable $currentUser
private ?Authenticatable $user
) {
}
public function index()
{
return response()->json($this->mediaCacheService->get() + [
'settings' => $this->currentUser->is_admin ? $this->settingRepository->getAllAsKeyValueArray() : [],
'playlists' => $this->playlistRepository->getAllByCurrentUser(),
'interactions' => $this->interactionRepository->getAllByCurrentUser(),
'recentlyPlayed' => $this->interactionRepository->getRecentlyPlayed(
$this->currentUser,
self::RECENTLY_PLAYED_EXCERPT_COUNT
),
'users' => $this->currentUser->is_admin ? $this->userRepository->getAll() : [],
'currentUser' => $this->currentUser,
'useLastfm' => LastfmService::used(),
'useYouTube' => YouTubeService::enabled(),
'useiTunes' => ITunesService::used(),
'allowDownload' => config('koel.download.allow'),
'supportsTranscoding' => config('koel.streaming.ffmpeg_path')
return response()->json([
'settings' => $this->user->is_admin ? $this->settingRepository->getAllAsKeyValueArray() : [],
'playlists' => PlaylistResource::collection($this->user->playlists),
'playlist_folders' => PlaylistFolderResource::collection($this->user->playlist_folders),
'current_user' => UserResource::make($this->user, true),
'use_last_fm' => LastfmService::used(),
'use_you_tube' => YouTubeService::enabled(),
'use_i_tunes' => $this->iTunesService->used(),
'allow_download' => config('koel.download.allow'),
'supports_transcoding' => config('koel.streaming.ffmpeg_path')
&& is_executable(config('koel.streaming.ffmpeg_path')),
'cdnUrl' => static_url(),
'currentVersion' => koel_version(),
'latestVersion' => $this->currentUser->is_admin
'cdn_url' => static_url(),
'current_version' => koel_version(),
'latest_version' => $this->user->is_admin
? $this->applicationInformationService->getLatestVersionNumber()
: koel_version(),
'song_count' => $this->songRepository->count(),
'song_length' => $this->songRepository->getTotalLength(),
]);
}
}

View file

@ -1,12 +1,12 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Requests\V6\API\SearchRequest;
use App\Http\Requests\API\SearchRequest;
use App\Http\Resources\ExcerptSearchResource;
use App\Models\User;
use App\Services\V6\SearchService;
use App\Services\SearchService;
use Illuminate\Contracts\Auth\Authenticatable;
class ExcerptSearchController extends Controller

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Resources\SongResource;

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Models\Album;

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Models\Artist;

View file

@ -1,9 +1,9 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Requests\V6\API\FetchRandomSongsInGenreRequest;
use App\Http\Requests\API\FetchRandomSongsInGenreRequest;
use App\Http\Resources\SongResource;
use App\Models\User;
use App\Repositories\SongRepository;

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Resources\GenreResource;

View file

@ -1,9 +1,9 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Requests\V6\API\GenreFetchSongRequest;
use App\Http\Requests\API\GenreFetchSongRequest;
use App\Http\Resources\SongResource;
use App\Models\User;
use App\Repositories\SongRepository;

View file

@ -5,6 +5,7 @@ namespace App\Http\Controllers\API\Interaction;
use App\Events\SongStartedPlaying;
use App\Http\Controllers\Controller;
use App\Http\Requests\API\Interaction\StorePlayCountRequest;
use App\Http\Resources\InteractionResource;
use App\Models\User;
use App\Services\InteractionService;
use Illuminate\Contracts\Auth\Authenticatable;
@ -21,6 +22,6 @@ class PlayCountController extends Controller
$interaction = $this->interactionService->increasePlayCount($request->song, $this->user);
event(new SongStartedPlaying($interaction->song, $interaction->user));
return response()->json($interaction);
return InteractionResource::make($interaction);
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace App\Http\Controllers\API\MediaInformation;
use App\Http\Controllers\Controller;
use App\Models\Album;
use App\Services\MediaInformationService;
class AlbumController extends Controller
{
public function __construct(private MediaInformationService $mediaInformationService)
{
}
public function show(Album $album)
{
return response()->json($this->mediaInformationService->getAlbumInformation($album)?->toArray() ?: []);
}
}

View file

@ -1,19 +0,0 @@
<?php
namespace App\Http\Controllers\API\MediaInformation;
use App\Http\Controllers\Controller;
use App\Models\Artist;
use App\Services\MediaInformationService;
class ArtistController extends Controller
{
public function __construct(private MediaInformationService $mediaInformationService)
{
}
public function show(Artist $artist)
{
return response()->json($this->mediaInformationService->getArtistInformation($artist)?->toArray() ?: []);
}
}

View file

@ -1,27 +0,0 @@
<?php
namespace App\Http\Controllers\API\MediaInformation;
use App\Http\Controllers\Controller;
use App\Models\Song;
use App\Services\MediaInformationService;
use App\Services\YouTubeService;
class SongController extends Controller
{
public function __construct(
private MediaInformationService $mediaInformationService,
private YouTubeService $youTubeService
) {
}
public function show(Song $song)
{
return response()->json([
'lyrics' => nl2br($song->lyrics), // backward compat
'album_info' => $this->mediaInformationService->getAlbumInformation($song->album)?->toArray() ?: [],
'artist_info' => $this->mediaInformationService->getArtistInformation($song->artist)?->toArray() ?: [],
'youtube' => $this->youTubeService->searchVideosRelatedToSong($song),
]);
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Resources\AlbumResource;

View file

@ -6,9 +6,11 @@ use App\Exceptions\PlaylistBothSongsAndRulesProvidedException;
use App\Http\Controllers\Controller;
use App\Http\Requests\API\PlaylistStoreRequest;
use App\Http\Requests\API\PlaylistUpdateRequest;
use App\Http\Resources\PlaylistResource;
use App\Models\Playlist;
use App\Models\PlaylistFolder;
use App\Models\User;
use App\Repositories\PlaylistRepository;
use App\Repositories\PlaylistFolderRepository;
use App\Services\PlaylistService;
use App\Values\SmartPlaylistRuleGroupCollection;
use Illuminate\Contracts\Auth\Authenticatable;
@ -19,31 +21,37 @@ class PlaylistController extends Controller
{
/** @param User $user */
public function __construct(
private PlaylistRepository $playlistRepository,
private PlaylistService $playlistService,
private PlaylistFolderRepository $folderRepository,
private ?Authenticatable $user
) {
}
public function index()
{
return response()->json($this->playlistRepository->getAllByCurrentUser());
return PlaylistResource::collection($this->user->playlists);
}
public function store(PlaylistStoreRequest $request)
{
$folder = null;
if ($request->folder_id) {
/** @var PlaylistFolder $folder */
$folder = $this->folderRepository->getOneById($request->folder_id);
$this->authorize('own', $folder);
}
try {
$playlist = $this->playlistService->createPlaylist(
$request->name,
$this->user,
null,
$folder,
Arr::wrap($request->songs),
$request->rules ? SmartPlaylistRuleGroupCollection::create(Arr::wrap($request->rules)) : null
);
$playlist->songs = $playlist->songs->pluck('id')->toArray();
return response()->json($playlist);
return PlaylistResource::make($playlist);
} catch (PlaylistBothSongsAndRulesProvidedException $e) {
throw ValidationException::withMessages(['songs' => [$e->getMessage()]]);
}
@ -53,9 +61,22 @@ class PlaylistController extends Controller
{
$this->authorize('own', $playlist);
$playlist->update($request->only('name', 'rules'));
$folder = null;
return response()->json($playlist);
if ($request->folder_id) {
/** @var PlaylistFolder $folder */
$folder = $this->folderRepository->getOneById($request->folder_id);
$this->authorize('own', $folder);
}
return PlaylistResource::make(
$this->playlistService->updatePlaylist(
$playlist,
$request->name,
$folder,
$request->rules ? SmartPlaylistRuleGroupCollection::create(Arr::wrap($request->rules)) : null
)
);
}
public function destroy(Playlist $playlist)

View file

@ -1,10 +1,10 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Requests\V6\API\PlaylistFolderStoreRequest;
use App\Http\Requests\V6\API\PlaylistFolderUpdateRequest;
use App\Http\Requests\API\PlaylistFolderStoreRequest;
use App\Http\Requests\API\PlaylistFolderUpdateRequest;
use App\Http\Resources\PlaylistFolderResource;
use App\Models\PlaylistFolder;
use App\Models\User;

View file

@ -1,10 +1,10 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Requests\V6\API\PlaylistFolderPlaylistDestroyRequest;
use App\Http\Requests\V6\API\PlaylistFolderPlaylistStoreRequest;
use App\Http\Requests\API\PlaylistFolderPlaylistDestroyRequest;
use App\Http\Requests\API\PlaylistFolderPlaylistStoreRequest;
use App\Models\PlaylistFolder;
use App\Services\PlaylistFolderService;
use Illuminate\Support\Arr;

View file

@ -3,21 +3,24 @@
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Requests\API\PlaylistSongUpdateRequest;
use App\Http\Requests\API\AddSongsToPlaylistRequest;
use App\Http\Requests\API\RemoveSongsFromPlaylistRequest;
use App\Http\Resources\SongResource;
use App\Models\Playlist;
use App\Models\User;
use App\Repositories\SongRepository;
use App\Services\PlaylistService;
use App\Services\SmartPlaylistService;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\Response;
use Illuminate\Support\Arr;
class PlaylistSongController extends Controller
{
/** @param User $user */
public function __construct(
private SmartPlaylistService $smartPlaylistService,
private SongRepository $songRepository,
private PlaylistService $playlistService,
private SmartPlaylistService $smartPlaylistService,
private ?Authenticatable $user
) {
}
@ -26,21 +29,31 @@ class PlaylistSongController extends Controller
{
$this->authorize('own', $playlist);
return response()->json(
return SongResource::collection(
$playlist->is_smart
? $this->smartPlaylistService->getSongs($playlist, $this->user)->pluck('id')
: $playlist->songs->pluck('id')
? $this->smartPlaylistService->getSongs($playlist, $this->user)
: $this->songRepository->getByStandardPlaylist($playlist, $this->user)
);
}
/** @deprecated */
public function update(PlaylistSongUpdateRequest $request, Playlist $playlist)
public function store(Playlist $playlist, AddSongsToPlaylistRequest $request)
{
$this->authorize('own', $playlist);
abort_if($playlist->is_smart, Response::HTTP_FORBIDDEN, 'A smart playlist cannot be populated manually.');
abort_if($playlist->is_smart, Response::HTTP_FORBIDDEN);
$this->playlistService->populatePlaylist($playlist, Arr::wrap($request->songs));
$this->playlistService->addSongsToPlaylist($playlist, $request->songs);
return response()->noContent();
}
public function destroy(Playlist $playlist, RemoveSongsFromPlaylistRequest $request)
{
$this->authorize('own', $playlist);
abort_if($playlist->is_smart, Response::HTTP_FORBIDDEN);
$this->playlistService->removeSongsFromPlaylist($playlist, $request->songs);
return response()->noContent();
}

View file

@ -1,9 +1,9 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Requests\V6\API\QueueFetchSongRequest;
use App\Http\Requests\API\QueueFetchSongRequest;
use App\Http\Resources\SongResource;
use App\Models\User;
use App\Repositories\SongRepository;

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Resources\SongResource;

View file

@ -1,27 +0,0 @@
<?php
namespace App\Http\Controllers\API\Search;
use App\Http\Controllers\Controller;
use App\Services\SearchService;
use Illuminate\Http\Request;
use InvalidArgumentException;
class ExcerptSearchController extends Controller
{
public function __construct(private SearchService $searchService)
{
}
public function index(Request $request)
{
throw_unless((bool) $request->get('q'), new InvalidArgumentException('A search query is required.'));
$count = (int) $request->get('count', SearchService::DEFAULT_EXCERPT_RESULT_COUNT);
throw_if($count < 0, new InvalidArgumentException('Invalid count parameter.'));
return [
'results' => $this->searchService->excerptSearch($request->get('q'), $count),
];
}
}

View file

@ -1,24 +0,0 @@
<?php
namespace App\Http\Controllers\API\Search;
use App\Http\Controllers\Controller;
use App\Services\SearchService;
use Illuminate\Http\Request;
use InvalidArgumentException;
class SongSearchController extends Controller
{
public function __construct(private SearchService $searchService)
{
}
public function index(Request $request)
{
throw_unless((bool) $request->get('q'), new InvalidArgumentException('A search query is required.'));
return [
'songs' => $this->searchService->searchSongs($request->get('q')),
];
}
}

View file

@ -3,26 +3,51 @@
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Requests\API\DeleteSongsRequest;
use App\Http\Requests\API\SongListRequest;
use App\Http\Requests\API\SongUpdateRequest;
use App\Http\Resources\AlbumResource;
use App\Http\Resources\ArtistResource;
use App\Http\Resources\SongResource;
use App\Models\Song;
use App\Models\User;
use App\Repositories\AlbumRepository;
use App\Repositories\ArtistRepository;
use App\Repositories\SongRepository;
use App\Services\LibraryManager;
use App\Services\SongService;
use App\Values\SongUpdateData;
use Illuminate\Contracts\Auth\Authenticatable;
class SongController extends Controller
{
/** @param User $user */
public function __construct(
private SongService $songService,
private SongRepository $songRepository,
private AlbumRepository $albumRepository,
private ArtistRepository $artistRepository,
private LibraryManager $libraryManager
private LibraryManager $libraryManager,
private ?Authenticatable $user
) {
}
public function index(SongListRequest $request)
{
return SongResource::collection(
$this->songRepository->getForListing(
$request->sort ?: 'songs.title',
$request->order ?: 'asc',
$this->user
)
);
}
public function show(Song $song)
{
return SongResource::make($this->songRepository->getOne($song->id));
}
public function update(SongUpdateRequest $request)
{
$updatedSongs = $this->songService->updateSongs($request->songs, SongUpdateData::fromRequest($request));
@ -42,4 +67,13 @@ class SongController extends Controller
'removed' => $this->libraryManager->prune(),
]);
}
public function destroy(DeleteSongsRequest $request)
{
$this->authorize('admin', $this->user);
$this->songService->deleteSongs($request->songs);
return response()->noContent();
}
}

View file

@ -1,12 +1,12 @@
<?php
namespace App\Http\Controllers\V6\API;
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Http\Requests\V6\API\SearchRequest;
use App\Http\Requests\API\SearchRequest;
use App\Http\Resources\SongResource;
use App\Models\User;
use App\Services\V6\SearchService;
use App\Services\SearchService;
use Illuminate\Contracts\Auth\Authenticatable;
class SongSearchController extends Controller

View file

@ -1,52 +0,0 @@
<?php
namespace App\Http\Controllers\V6\API;
use App\Http\Controllers\Controller;
use App\Http\Resources\PlaylistFolderResource;
use App\Http\Resources\PlaylistResource;
use App\Http\Resources\UserResource;
use App\Models\User;
use App\Repositories\SettingRepository;
use App\Repositories\SongRepository;
use App\Services\ApplicationInformationService;
use App\Services\ITunesService;
use App\Services\LastfmService;
use App\Services\YouTubeService;
use Illuminate\Contracts\Auth\Authenticatable;
class DataController extends Controller
{
/** @param User $user */
public function __construct(
private ITunesService $iTunesService,
private SettingRepository $settingRepository,
private SongRepository $songRepository,
private ApplicationInformationService $applicationInformationService,
private ?Authenticatable $user
) {
}
public function index()
{
return response()->json([
'settings' => $this->user->is_admin ? $this->settingRepository->getAllAsKeyValueArray() : [],
'playlists' => PlaylistResource::collection($this->user->playlists),
'playlist_folders' => PlaylistFolderResource::collection($this->user->playlist_folders),
'current_user' => UserResource::make($this->user, true),
'use_last_fm' => LastfmService::used(),
'use_you_tube' => YouTubeService::enabled(),
'use_i_tunes' => $this->iTunesService->used(),
'allow_download' => config('koel.download.allow'),
'supports_transcoding' => config('koel.streaming.ffmpeg_path')
&& is_executable(config('koel.streaming.ffmpeg_path')),
'cdn_url' => static_url(),
'current_version' => koel_version(),
'latest_version' => $this->user->is_admin
? $this->applicationInformationService->getLatestVersionNumber()
: koel_version(),
'song_count' => $this->songRepository->count(),
'song_length' => $this->songRepository->getTotalLength(),
]);
}
}

View file

@ -1,20 +0,0 @@
<?php
namespace App\Http\Controllers\V6\API;
use App\Http\Controllers\Controller;
use App\Http\Requests\V6\API\DeleteSongsRequest;
use App\Services\SongService;
use Illuminate\Contracts\Auth\Authenticatable;
class DeleteSongsController extends Controller
{
public function __invoke(DeleteSongsRequest $request, SongService $service, Authenticatable $user)
{
$this->authorize('admin', $user);
$service->deleteSongs($request->songs);
return response()->noContent();
}
}

View file

@ -1,27 +0,0 @@
<?php
namespace App\Http\Controllers\V6\API;
use App\Events\SongStartedPlaying;
use App\Http\Controllers\Controller;
use App\Http\Requests\API\Interaction\StorePlayCountRequest;
use App\Http\Resources\InteractionResource;
use App\Models\User;
use App\Services\InteractionService;
use Illuminate\Contracts\Auth\Authenticatable;
class PlayCountController extends Controller
{
/** @param User $user */
public function __construct(private InteractionService $interactionService, private ?Authenticatable $user)
{
}
public function store(StorePlayCountRequest $request)
{
$interaction = $this->interactionService->increasePlayCount($request->song, $this->user);
event(new SongStartedPlaying($interaction->song, $interaction->user));
return InteractionResource::make($interaction);
}
}

View file

@ -1,90 +0,0 @@
<?php
namespace App\Http\Controllers\V6\API;
use App\Exceptions\PlaylistBothSongsAndRulesProvidedException;
use App\Http\Controllers\Controller;
use App\Http\Requests\API\PlaylistStoreRequest;
use App\Http\Requests\API\PlaylistUpdateRequest;
use App\Http\Resources\PlaylistResource;
use App\Models\Playlist;
use App\Models\PlaylistFolder;
use App\Models\User;
use App\Repositories\PlaylistFolderRepository;
use App\Services\PlaylistService;
use App\Values\SmartPlaylistRuleGroupCollection;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Arr;
use Illuminate\Validation\ValidationException;
class PlaylistController extends Controller
{
/** @param User $user */
public function __construct(
private PlaylistService $playlistService,
private PlaylistFolderRepository $folderRepository,
private ?Authenticatable $user
) {
}
public function index()
{
return PlaylistResource::collection($this->user->playlists);
}
public function store(PlaylistStoreRequest $request)
{
$folder = null;
if ($request->folder_id) {
/** @var PlaylistFolder $folder */
$folder = $this->folderRepository->getOneById($request->folder_id);
$this->authorize('own', $folder);
}
try {
$playlist = $this->playlistService->createPlaylist(
$request->name,
$this->user,
$folder,
Arr::wrap($request->songs),
$request->rules ? SmartPlaylistRuleGroupCollection::create(Arr::wrap($request->rules)) : null
);
return PlaylistResource::make($playlist);
} catch (PlaylistBothSongsAndRulesProvidedException $e) {
throw ValidationException::withMessages(['songs' => [$e->getMessage()]]);
}
}
public function update(PlaylistUpdateRequest $request, Playlist $playlist)
{
$this->authorize('own', $playlist);
$folder = null;
if ($request->folder_id) {
/** @var PlaylistFolder $folder */
$folder = $this->folderRepository->getOneById($request->folder_id);
$this->authorize('own', $folder);
}
return PlaylistResource::make(
$this->playlistService->updatePlaylist(
$playlist,
$request->name,
$folder,
$request->rules ? SmartPlaylistRuleGroupCollection::create(Arr::wrap($request->rules)) : null
)
);
}
public function destroy(Playlist $playlist)
{
$this->authorize('own', $playlist);
$playlist->delete();
return response()->noContent();
}
}

View file

@ -1,60 +0,0 @@
<?php
namespace App\Http\Controllers\V6\API;
use App\Http\Controllers\Controller;
use App\Http\Requests\V6\API\AddSongsToPlaylistRequest;
use App\Http\Requests\V6\API\RemoveSongsFromPlaylistRequest;
use App\Http\Resources\SongResource;
use App\Models\Playlist;
use App\Models\User;
use App\Repositories\SongRepository;
use App\Services\PlaylistService;
use App\Services\SmartPlaylistService;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\Response;
class PlaylistSongController extends Controller
{
/** @param User $user */
public function __construct(
private SongRepository $songRepository,
private PlaylistService $playlistService,
private SmartPlaylistService $smartPlaylistService,
private ?Authenticatable $user
) {
}
public function index(Playlist $playlist)
{
$this->authorize('own', $playlist);
return SongResource::collection(
$playlist->is_smart
? $this->smartPlaylistService->getSongs($playlist, $this->user)
: $this->songRepository->getByStandardPlaylist($playlist, $this->user)
);
}
public function store(Playlist $playlist, AddSongsToPlaylistRequest $request)
{
$this->authorize('own', $playlist);
abort_if($playlist->is_smart, Response::HTTP_FORBIDDEN);
$this->playlistService->addSongsToPlaylist($playlist, $request->songs);
return response()->noContent();
}
public function destroy(Playlist $playlist, RemoveSongsFromPlaylistRequest $request)
{
$this->authorize('own', $playlist);
abort_if($playlist->is_smart, Response::HTTP_FORBIDDEN);
$this->playlistService->removeSongsFromPlaylist($playlist, $request->songs);
return response()->noContent();
}
}

View file

@ -1,35 +0,0 @@
<?php
namespace App\Http\Controllers\V6\API;
use App\Http\Controllers\Controller;
use App\Http\Requests\V6\API\SongListRequest;
use App\Http\Resources\SongResource;
use App\Models\Song;
use App\Models\User;
use App\Repositories\SongRepository;
use Illuminate\Contracts\Auth\Authenticatable;
class SongController extends Controller
{
/** @param User $user */
public function __construct(private SongRepository $songRepository, private ?Authenticatable $user)
{
}
public function show(Song $song)
{
return SongResource::make($this->songRepository->getOne($song->id));
}
public function index(SongListRequest $request)
{
return SongResource::collection(
$this->songRepository->getForListing(
$request->sort ?: 'songs.title',
$request->order ?: 'asc',
$this->user
)
);
}
}

View file

@ -1,8 +1,7 @@
<?php
namespace App\Http\Requests\V6\API;
namespace App\Http\Requests\API;
use App\Http\Requests\API\Request;
use App\Models\Song;
use Illuminate\Validation\Rule;

View file

@ -1,8 +1,6 @@
<?php
namespace App\Http\Requests\V6\API;
use App\Http\Requests\API\Request;
namespace App\Http\Requests\API;
/** @property-read array<string> $songs */
class DeleteSongsRequest extends Request

View file

@ -1,8 +1,6 @@
<?php
namespace App\Http\Requests\V6\API;
use App\Http\Requests\API\Request;
namespace App\Http\Requests\API;
/**
* @property-read string $genre

View file

@ -1,8 +1,6 @@
<?php
namespace App\Http\Requests\V6\API;
use App\Http\Requests\API\Request;
namespace App\Http\Requests\API;
/**
* @property-read string $order

View file

@ -1,8 +1,7 @@
<?php
namespace App\Http\Requests\V6\API;
namespace App\Http\Requests\API;
use App\Http\Requests\API\Request;
use App\Models\Playlist;
use App\Rules\AllPlaylistsBelongToUser;
use Illuminate\Validation\Rule;

View file

@ -1,8 +1,7 @@
<?php
namespace App\Http\Requests\V6\API;
namespace App\Http\Requests\API;
use App\Http\Requests\API\Request;
use App\Models\Playlist;
use App\Rules\AllPlaylistsBelongToUser;
use Illuminate\Validation\Rule;

View file

@ -1,8 +1,6 @@
<?php
namespace App\Http\Requests\V6\API;
use App\Http\Requests\API\Request;
namespace App\Http\Requests\API;
/**
* @property-read string $name

View file

@ -1,8 +1,6 @@
<?php
namespace App\Http\Requests\V6\API;
use App\Http\Requests\API\Request;
namespace App\Http\Requests\API;
/**
* @property-read string $name

View file

@ -1,8 +1,7 @@
<?php
namespace App\Http\Requests\V6\API;
namespace App\Http\Requests\API;
use App\Http\Requests\API\Request;
use App\Repositories\SongRepository;
use Illuminate\Validation\Rule;

View file

@ -1,8 +1,6 @@
<?php
namespace App\Http\Requests\V6\API;
use App\Http\Requests\API\Request;
namespace App\Http\Requests\API;
/**
* @property-read array<string> $songs

View file

@ -1,8 +1,6 @@
<?php
namespace App\Http\Requests\V6\API;
use App\Http\Requests\API\Request;
namespace App\Http\Requests\API;
/**
* @property-read string $q

View file

@ -1,8 +1,6 @@
<?php
namespace App\Http\Requests\V6\API;
use App\Http\Requests\API\Request;
namespace App\Http\Requests\API;
/**
* @property-read string $order

View file

@ -3,7 +3,7 @@
namespace App\Http\Requests\API;
/**
* @property string $pageToken
* @property-read string|null $pageToken
*/
class YouTubeSearchRequest extends Request
{

View file

@ -1,12 +0,0 @@
<?php
namespace App\Http\Requests\V6\API;
use App\Http\Requests\API\Request;
/**
* @property-read string|null $pageToken
*/
class YouTubeSearchRequest extends Request
{
}

View file

@ -2,19 +2,21 @@
namespace App\Services;
use App\Builders\SongBuilder;
use App\Models\Album;
use App\Models\Artist;
use App\Models\Song;
use App\Models\User;
use App\Repositories\AlbumRepository;
use App\Repositories\ArtistRepository;
use App\Repositories\SongRepository;
use Illuminate\Database\Eloquent\Model;
use App\Values\ExcerptSearchResult;
use Illuminate\Support\Collection;
use Laravel\Scout\Builder;
class SearchService
{
public const DEFAULT_EXCERPT_RESULT_COUNT = 6;
public const DEFAULT_MAX_SONG_RESULT_COUNT = 500;
public function __construct(
private SongRepository $songRepository,
@ -23,31 +25,33 @@ class SearchService
) {
}
/** @return array<mixed> */
public function excerptSearch(string $keywords, int $count): array
{
return [
'songs' => self::getTopResults($this->songRepository->search($keywords), $count)
->map(static fn (Song $song): string => $song->id),
'artists' => self::getTopResults($this->artistRepository->search($keywords), $count)
->map(static fn (Artist $artist): int => $artist->id),
'albums' => self::getTopResults($this->albumRepository->search($keywords), $count)
->map(static fn (Album $album): int => $album->id),
];
public function excerptSearch(
string $keywords,
?User $scopedUser = null,
int $count = self::DEFAULT_EXCERPT_RESULT_COUNT
): ExcerptSearchResult {
$scopedUser ??= auth()->user();
return ExcerptSearchResult::make(
$this->songRepository->getByIds(
Song::search($keywords)->get()->take($count)->pluck('id')->all(),
$scopedUser
),
$this->artistRepository->getByIds(Artist::search($keywords)->get()->take($count)->pluck('id')->all()),
$this->albumRepository->getByIds(Album::search($keywords)->get()->take($count)->pluck('id')->all()),
);
}
/** @return Collection|array<Model> */
private static function getTopResults(Builder $query, int $count): Collection
{
return $query->take($count)->get();
}
/** @return Collection|array<string> */
public function searchSongs(string $keywords): Collection
{
return $this->songRepository
->search($keywords)
->get()
->map(static fn (Song $song): string => $song->id); // @phpstan-ignore-line
/** @return Collection|array<array-key, Song> */
public function searchSongs(
string $keywords,
?User $scopedUser = null,
int $limit = self::DEFAULT_MAX_SONG_RESULT_COUNT
): Collection {
return Song::search($keywords)
->query(static function (SongBuilder $builder) use ($scopedUser, $limit): void {
$builder->withMeta($scopedUser ?? auth()->user())->limit($limit);
})
->get();
}
}

View file

@ -1,57 +0,0 @@
<?php
namespace App\Services\V6;
use App\Builders\SongBuilder;
use App\Models\Album;
use App\Models\Artist;
use App\Models\Song;
use App\Models\User;
use App\Repositories\AlbumRepository;
use App\Repositories\ArtistRepository;
use App\Repositories\SongRepository;
use App\Values\ExcerptSearchResult;
use Illuminate\Support\Collection;
class SearchService
{
public const DEFAULT_EXCERPT_RESULT_COUNT = 6;
public const DEFAULT_MAX_SONG_RESULT_COUNT = 500;
public function __construct(
private SongRepository $songRepository,
private AlbumRepository $albumRepository,
private ArtistRepository $artistRepository
) {
}
public function excerptSearch(
string $keywords,
?User $scopedUser = null,
int $count = self::DEFAULT_EXCERPT_RESULT_COUNT
): ExcerptSearchResult {
$scopedUser ??= auth()->user();
return ExcerptSearchResult::make(
$this->songRepository->getByIds(
Song::search($keywords)->get()->take($count)->pluck('id')->all(),
$scopedUser
),
$this->artistRepository->getByIds(Artist::search($keywords)->get()->take($count)->pluck('id')->all()),
$this->albumRepository->getByIds(Album::search($keywords)->get()->take($count)->pluck('id')->all()),
);
}
/** @return Collection|array<array-key, Song> */
public function searchSongs(
string $keywords,
?User $scopedUser = null,
int $limit = self::DEFAULT_MAX_SONG_RESULT_COUNT
): Collection {
return Song::search($keywords)
->query(static function (SongBuilder $builder) use ($scopedUser, $limit): void {
$builder->withMeta($scopedUser ?? auth()->user())->limit($limit);
})
->get();
}
}

View file

@ -1,32 +1,45 @@
<?php
use App\Facades\YouTube;
use App\Http\Controllers\API\AlbumController;
use App\Http\Controllers\API\AlbumCoverController;
use App\Http\Controllers\API\AlbumSongController;
use App\Http\Controllers\API\AlbumThumbnailController;
use App\Http\Controllers\API\ArtistAlbumController;
use App\Http\Controllers\API\ArtistController;
use App\Http\Controllers\API\ArtistImageController;
use App\Http\Controllers\API\ArtistSongController;
use App\Http\Controllers\API\AuthController;
use App\Http\Controllers\API\DataController;
use App\Http\Controllers\API\DemoCreditController;
use App\Http\Controllers\API\ExcerptSearchController;
use App\Http\Controllers\API\FavoriteSongController;
use App\Http\Controllers\API\FetchAlbumInformationController;
use App\Http\Controllers\API\FetchArtistInformationController;
use App\Http\Controllers\API\FetchRandomSongsInGenreController;
use App\Http\Controllers\API\GenreController;
use App\Http\Controllers\API\GenreSongController;
use App\Http\Controllers\API\Interaction\BatchLikeController;
use App\Http\Controllers\API\Interaction\LikeController;
use App\Http\Controllers\API\Interaction\PlayCountController;
use App\Http\Controllers\API\Interaction\RecentlyPlayedController;
use App\Http\Controllers\API\LastfmController;
use App\Http\Controllers\API\MediaInformation\AlbumController as AlbumInformationController;
use App\Http\Controllers\API\MediaInformation\ArtistController as ArtistInformationController;
use App\Http\Controllers\API\MediaInformation\SongController as SongInformationController;
use App\Http\Controllers\API\ObjectStorage\S3\SongController as S3SongController;
use App\Http\Controllers\API\OverviewController;
use App\Http\Controllers\API\PlaylistController;
use App\Http\Controllers\API\PlaylistFolderController;
use App\Http\Controllers\API\PlaylistFolderPlaylistController;
use App\Http\Controllers\API\PlaylistSongController;
use App\Http\Controllers\API\ProfileController;
use App\Http\Controllers\API\QueueController;
use App\Http\Controllers\API\RecentlyPlayedSongController;
use App\Http\Controllers\API\ScrobbleController;
use App\Http\Controllers\API\Search\ExcerptSearchController;
use App\Http\Controllers\API\Search\SongSearchController;
use App\Http\Controllers\API\SettingController;
use App\Http\Controllers\API\SongController;
use App\Http\Controllers\API\SongSearchController;
use App\Http\Controllers\API\UploadController;
use App\Http\Controllers\API\UserController;
use App\Http\Controllers\API\YouTubeController;
use App\Models\Song;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Pusher\Pusher;
@ -52,16 +65,28 @@ Route::prefix('api')->middleware('api')->group(static function (): void {
return $pusher->socket_auth($request->channel_name, $request->socket_id);
})->name('broadcasting.auth');
Route::get('overview', [OverviewController::class, 'index']);
Route::get('data', [DataController::class, 'index']);
Route::get('queue/fetch', [QueueController::class, 'fetchSongs']);
Route::put('settings', [SettingController::class, 'update']);
/**
* @deprecated Use songs/{song}/scrobble instead
*/
Route::post('{song}/scrobble', [ScrobbleController::class, 'store']);
Route::post('songs/{song}/scrobble', [ScrobbleController::class, 'store']);
Route::apiResource('albums', AlbumController::class);
Route::apiResource('albums.songs', AlbumSongController::class);
Route::apiResource('artists', ArtistController::class);
Route::apiResource('artists.albums', ArtistAlbumController::class);
Route::apiResource('artists.songs', ArtistSongController::class);
Route::post('songs/{song}/scrobble', [ScrobbleController::class, 'store'])->where(['song' => Song::ID_REGEX]);
Route::apiResource('songs', SongController::class)
->except('update', 'destroy')
->where(['song' => Song::ID_REGEX]);
Route::put('songs', [SongController::class, 'update']);
Route::delete('songs', [SongController::class, 'destroy']);
Route::post('upload', UploadController::class);
@ -70,19 +95,30 @@ Route::prefix('api')->middleware('api')->group(static function (): void {
Route::post('interaction/like', [LikeController::class, 'store']);
Route::post('interaction/batch/like', [BatchLikeController::class, 'store']);
Route::post('interaction/batch/unlike', [BatchLikeController::class, 'destroy']);
Route::get('interaction/recently-played/{count?}', [RecentlyPlayedController::class, 'index'])->where([
'count' => '\d+',
]);
Route::get('songs/recently-played', [RecentlyPlayedSongController::class, 'index']);
Route::get('songs/favorite', [FavoriteSongController::class, 'index']);
Route::apiResource('playlist-folders', PlaylistFolderController::class);
Route::apiResource('playlist-folders.playlists', PlaylistFolderPlaylistController::class)->except('destroy');
Route::delete(
'playlist-folders/{playlistFolder}/playlists',
[PlaylistFolderPlaylistController::class, 'destroy']
);
// Playlist routes
Route::apiResource('playlist', PlaylistController::class);
Route::apiResource('playlists', PlaylistController::class);
Route::apiResource('playlists.songs', PlaylistSongController::class)->except('destroy');
Route::delete('playlists/{playlist}/songs', [PlaylistSongController::class, 'destroy']);
Route::put('playlist/{playlist}/sync', [PlaylistSongController::class, 'update']); // @deprecated
Route::put('playlist/{playlist}/songs', [PlaylistSongController::class, 'update']);
Route::get('playlist/{playlist}/songs', [PlaylistSongController::class, 'index']);
Route::get('genres/{genre}/songs', GenreSongController::class)->where('genre', '.*');
Route::get('genres/{genre}/songs/random', FetchRandomSongsInGenreController::class)->where('genre', '.*');
Route::apiResource('genres', GenreController::class)->where(['genre' => '.*']);
Route::apiResource('users', UserController::class);
// User and user profile routes
Route::apiResource('user', UserController::class)->only('store', 'update', 'destroy');
Route::apiResource('user', UserController::class);
Route::get('me', [ProfileController::class, 'show']);
Route::put('me', [ProfileController::class, 'update']);
@ -96,20 +132,16 @@ Route::prefix('api')->middleware('api')->group(static function (): void {
}
// Media information routes
Route::get('album/{album}/info', [AlbumInformationController::class, 'show']);
Route::get('artist/{artist}/info', [ArtistInformationController::class, 'show']);
Route::get('song/{song}/info', [SongInformationController::class, 'show']);
Route::get('albums/{album}/information', FetchAlbumInformationController::class);
Route::get('artists/{artist}/information', FetchArtistInformationController::class);
// Cover/image upload routes
Route::put('album/{album}/cover', [AlbumCoverController::class, 'update']);
Route::put('artist/{artist}/image', [ArtistImageController::class, 'update']);
Route::get('album/{album}/thumbnail', [AlbumThumbnailController::class, 'show']);
// Search routes
Route::prefix('search')->group(static function (): void {
Route::get('/', [ExcerptSearchController::class, 'index']);
Route::get('songs', [SongSearchController::class, 'index']);
});
Route::get('search', ExcerptSearchController::class);
Route::get('search/songs', SongSearchController::class);
});
// Object-storage (S3) routes

View file

@ -1,74 +0,0 @@
<?php
use App\Http\Controllers\API\UserController;
use App\Http\Controllers\V6\API\AlbumController;
use App\Http\Controllers\V6\API\AlbumSongController;
use App\Http\Controllers\V6\API\ArtistAlbumController;
use App\Http\Controllers\V6\API\ArtistController;
use App\Http\Controllers\V6\API\ArtistSongController;
use App\Http\Controllers\V6\API\DataController;
use App\Http\Controllers\V6\API\DeleteSongsController;
use App\Http\Controllers\V6\API\ExcerptSearchController;
use App\Http\Controllers\V6\API\FavoriteSongController;
use App\Http\Controllers\V6\API\FetchAlbumInformationController;
use App\Http\Controllers\V6\API\FetchArtistInformationController;
use App\Http\Controllers\V6\API\FetchRandomSongsInGenreController;
use App\Http\Controllers\V6\API\GenreController;
use App\Http\Controllers\V6\API\GenreSongController;
use App\Http\Controllers\V6\API\OverviewController;
use App\Http\Controllers\V6\API\PlayCountController;
use App\Http\Controllers\V6\API\PlaylistController;
use App\Http\Controllers\V6\API\PlaylistFolderController;
use App\Http\Controllers\V6\API\PlaylistFolderPlaylistController;
use App\Http\Controllers\V6\API\PlaylistSongController;
use App\Http\Controllers\V6\API\QueueController;
use App\Http\Controllers\V6\API\RecentlyPlayedSongController;
use App\Http\Controllers\V6\API\SongController;
use App\Http\Controllers\V6\API\SongSearchController;
use App\Models\Song;
use Illuminate\Support\Facades\Route;
Route::prefix('api')->middleware('api')->group(static function (): void {
Route::middleware('auth')->group(static function (): void {
Route::get('overview', [OverviewController::class, 'index']);
Route::get('data', [DataController::class, 'index']);
Route::apiResource('albums', AlbumController::class);
Route::apiResource('albums.songs', AlbumSongController::class);
Route::apiResource('artists', ArtistController::class);
Route::apiResource('artists.songs', ArtistSongController::class);
Route::apiResource('artists.albums', ArtistAlbumController::class);
Route::get('albums/{album}/information', FetchAlbumInformationController::class);
Route::get('artists/{artist}/information', FetchArtistInformationController::class);
Route::apiResource('playlist-folders', PlaylistFolderController::class);
Route::apiResource('playlist-folders.playlists', PlaylistFolderPlaylistController::class)->except('destroy');
Route::delete(
'playlist-folders/{playlistFolder}/playlists',
[PlaylistFolderPlaylistController::class, 'destroy']
);
Route::apiResource('playlists', PlaylistController::class);
Route::apiResource('playlists.songs', PlaylistSongController::class)->except('destroy');
Route::delete('playlists/{playlist}/songs', [PlaylistSongController::class, 'destroy']);
Route::apiResource('songs', SongController::class)->where(['song' => Song::ID_REGEX]);
Route::get('songs/recently-played', [RecentlyPlayedSongController::class, 'index']);
Route::get('songs/favorite', [FavoriteSongController::class, 'index']);
Route::delete('songs', DeleteSongsController::class);
Route::get('genres/{genre}/songs', GenreSongController::class)->where('genre', '.*');
Route::get('genres/{genre}/songs/random', FetchRandomSongsInGenreController::class)->where('genre', '.*');
Route::apiResource('genres', GenreController::class)->where(['genre' => '.*']);
Route::apiResource('users', UserController::class);
Route::get('search', ExcerptSearchController::class);
Route::get('search/songs', SongSearchController::class);
Route::get('queue/fetch', [QueueController::class, 'fetchSongs']);
Route::post('interaction/play', [PlayCountController::class, 'store']);
});
});

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
use App\Models\Album;
use App\Services\MediaInformationService;

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
use App\Models\Album;
use App\Models\Song;

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
use App\Models\Album;

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
use App\Models\Album;
use App\Models\Artist;

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
use App\Models\Artist;
use App\Services\MediaInformationService;

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
use App\Models\Artist;
use App\Models\Song;

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
use App\Models\Artist;

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
class DataTest extends TestCase
{

View file

@ -70,8 +70,8 @@ class DownloadTest extends TestCase
->shouldReceive('from')
->once()
->with(Mockery::on(static function (Collection $retrievedSongs) use ($songs): bool {
$retrievedIds = $retrievedSongs->pluck('id')->toArray();
$requestedIds = $songs->pluck('id')->toArray();
$retrievedIds = $retrievedSongs->pluck('id')->all();
$requestedIds = $songs->pluck('id')->all();
self::assertEqualsCanonicalizing($requestedIds, $retrievedIds);
return true;
@ -131,9 +131,7 @@ class DownloadTest extends TestCase
$user = User::factory()->create();
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create([
'user_id' => $user->id,
]);
$playlist = Playlist::factory()->for($user)->create();
$this->downloadService
->shouldReceive('from')

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
use App\Models\Album;
use App\Models\Artist;

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
use App\Models\Interaction;
use App\Models\User;

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
use App\Models\Song;
use App\Values\Genre;
@ -35,7 +35,7 @@ class GenreTest extends TestCase
->assertJsonStructure(self::JSON_STRUCTURE)
->assertJsonFragment(['name' => 'Rock', 'song_count' => 5]);
}
public function testGetNonExistingGenreThrowsNotFound(): void
{
$this->getAs('api/genres/NonExistingGenre')->assertNotFound();

View file

@ -80,7 +80,7 @@ class InteractionTest extends TestCase
/** @var Collection|array<Song> $songs */
$songs = Song::query()->orderBy('id')->take(2)->get();
$songIds = array_pluck($songs->toArray(), 'id');
$songIds = $songs->pluck('id')->all();
$this->postAs('api/interaction/batch/like', ['songs' => $songIds], $user);

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
use App\Models\Interaction;
use App\Models\User;

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
use App\Events\SongStartedPlaying;
use App\Models\Interaction;

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
use App\Models\PlaylistFolder;
use App\Models\User;

View file

@ -5,53 +5,141 @@ namespace Tests\Feature;
use App\Models\Playlist;
use App\Models\Song;
use App\Models\User;
use Illuminate\Support\Collection;
class PlaylistSongTest extends TestCase
{
public function testUpdatePlaylistSongs(): void
public function testGetNormalPlaylist(): void
{
$this->doTestUpdatePlaylistSongs();
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
$playlist->songs()->attach(Song::factory(5)->create());
$this->getAs('api/playlists/' . $playlist->id . '/songs', $playlist->user)
->assertJsonStructure(['*' => SongTest::JSON_STRUCTURE]);
}
/** @deprecated */
public function testSyncPlaylist(): void
public function testGetSmartPlaylist(): void
{
$this->doTestUpdatePlaylistSongs(true);
Song::factory()->create(['title' => 'A foo song']);
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create([
'rules' => [
[
'id' => '45368b8f-fec8-4b72-b826-6b295af0da65',
'rules' => [
[
'id' => '2a4548cd-c67f-44d4-8fec-34ff75c8a026',
'model' => 'title',
'operator' => 'contains',
'value' => ['foo'],
],
],
],
],
]);
$this->getAs("api/playlists/$playlist->id/songs", $playlist->user)
->assertJsonStructure(['*' => SongTest::JSON_STRUCTURE]);
}
private function doTestUpdatePlaylistSongs(bool $useDeprecatedRoute = false): void
public function testNonOwnerCannotAccessPlaylist(): void
{
$user = User::factory()->create();
/** @var Playlist $playlist */
$playlist = Playlist::factory()->for($user)->create();
$playlist->songs()->attach(Song::factory(5)->create());
$this->getAs('api/playlists/' . $playlist->id . '/songs')
->assertForbidden();
}
public function testAddSongsToPlaylist(): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
/** @var Collection|array<array-key, Song> $songs */
$songs = Song::factory(2)->create();
$this->postAs('api/playlists/' . $playlist->id . '/songs', [
'songs' => $songs->map(static fn (Song $song) => $song->id)->all(),
], $playlist->user)
->assertNoContent();
self::assertEqualsCanonicalizing($songs->pluck('id')->all(), $playlist->songs->pluck('id')->all());
}
public function testRemoveSongsFromPlaylist(): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
$toRemainSongs = Song::factory(5)->create();
/** @var Collection|array<array-key, Song> $toBeRemovedSongs */
$toBeRemovedSongs = Song::factory(2)->create();
$playlist->songs()->attach($toRemainSongs->merge($toBeRemovedSongs));
self::assertCount(7, $playlist->songs);
$this->deleteAs('api/playlists/' . $playlist->id . '/songs', [
'songs' => $toBeRemovedSongs->map(static fn (Song $song) => $song->id)->all(),
], $playlist->user)
->assertNoContent();
$playlist->refresh();
self::assertEqualsCanonicalizing($toRemainSongs->pluck('id')->all(), $playlist->songs->pluck('id')->all());
}
public function testNonOwnerCannotModifyPlaylist(): void
{
/** @var User $user */
$user = User::factory()->create();
/** @var Playlist $playlist */
$playlist = Playlist::factory()->for($user)->create();
$toRemainSongs = Song::factory(3)->create();
$toBeRemovedSongs = Song::factory(2)->create();
$playlist->songs()->attach($toRemainSongs->merge($toBeRemovedSongs));
/** @var Song $song */
$song = Song::factory()->create();
$path = $useDeprecatedRoute ? "api/playlist/$playlist->id/sync" : "api/playlist/$playlist->id/songs";
$this->postAs('api/playlists/' . $playlist->id . '/songs', ['songs' => [$song->id]])
->assertForbidden();
$this->putAs($path, ['songs' => $toRemainSongs->pluck('id')->all()], $user)->assertNoContent();
self::assertEqualsCanonicalizing(
$toRemainSongs->pluck('id')->all(),
$playlist->refresh()->songs->pluck('id')->all()
);
$this->deleteAs('api/playlists/' . $playlist->id . '/songs', ['songs' => [$song->id]])
->assertForbidden();
}
public function testGetPlaylistSongs(): void
public function testSmartPlaylistContentCannotBeModified(): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
$playlist = Playlist::factory()->create([
'rules' => [
[
'id' => '45368b8f-fec8-4b72-b826-6b295af0da65',
'rules' => [
[
'id' => '2a4548cd-c67f-44d4-8fec-34ff75c8a026',
'model' => 'title',
'operator' => 'contains',
'value' => ['foo'],
],
],
],
],
]);
/** @var Collection|array<array-key, Song> $songs */
$songs = Song::factory(2)->create();
$playlist->songs()->saveMany($songs);
$songIds = $songs->map(static fn (Song $song) => $song->id)->all();
$responseIds = $this->getAs("api/playlist/$playlist->id/songs", $playlist->user)
->json();
$this->postAs('api/playlists/' . $playlist->id . '/songs', ['songs' => $songIds], $playlist->user)
->assertForbidden();
self::assertEqualsCanonicalizing($responseIds, $songs->pluck('id')->all());
$this->deleteAs('api/playlists/' . $playlist->id . '/songs', ['songs' => $songIds], $playlist->user)
->assertForbidden();
}
}

View file

@ -10,12 +10,16 @@ use Illuminate\Support\Collection;
class PlaylistTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
static::createSampleMediaSet();
}
private const JSON_STRUCTURE = [
'type',
'id',
'name',
'folder_id',
'user_id',
'is_smart',
'rules',
'created_at',
];
public function testCreatingPlaylist(): void
{
@ -23,21 +27,21 @@ class PlaylistTest extends TestCase
$user = User::factory()->create();
/** @var array<Song>|Collection $songs */
$songs = Song::query()->orderBy('id')->take(3)->get();
$songs = Song::factory(4)->create();
$response = $this->postAs('api/playlist', [
$this->postAs('api/playlists', [
'name' => 'Foo Bar',
'songs' => $songs->pluck('id')->toArray(),
'songs' => $songs->pluck('id')->all(),
'rules' => [],
], $user);
$response->assertOk();
], $user)
->assertJsonStructure(self::JSON_STRUCTURE);
/** @var Playlist $playlist */
$playlist = Playlist::query()->orderByDesc('id')->first();
self::assertSame('Foo Bar', $playlist->name);
self::assertTrue($playlist->user->is($user));
self::assertNull($playlist->folder_id);
self::assertEqualsCanonicalizing($songs->pluck('id')->all(), $playlist->songs->pluck('id')->all());
}
@ -52,15 +56,15 @@ class PlaylistTest extends TestCase
'value' => ['Bob Dylan'],
]);
$this->postAs('api/playlist', [
$this->postAs('api/playlists', [
'name' => 'Smart Foo Bar',
'rules' => [
[
'id' => '45368b8f-fec8-4b72-b826-6b295af0da65',
'id' => '2a4548cd-c67f-44d4-8fec-34ff75c8a026',
'rules' => [$rule->toArray()],
],
],
], $user);
], $user)->assertJsonStructure(self::JSON_STRUCTURE);
/** @var Playlist $playlist */
$playlist = Playlist::query()->orderByDesc('id')->first();
@ -69,16 +73,17 @@ class PlaylistTest extends TestCase
self::assertTrue($playlist->user->is($user));
self::assertTrue($playlist->is_smart);
self::assertCount(1, $playlist->rule_groups);
self::assertNull($playlist->folder_id);
self::assertTrue($rule->equals($playlist->rule_groups[0]->rules[0]));
}
public function testCreatingPlaylistCannotHaveBothSongsAndRules(): void
public function testCreatingSmartPlaylistFailsIfSongsProvided(): void
{
$this->postAs('api/playlist', [
$this->postAs('api/playlists', [
'name' => 'Smart Foo Bar',
'rules' => [
[
'id' => '45368b8f-fec8-4b72-b826-6b295af0da65',
'id' => '2a4548cd-c67f-44d4-8fec-34ff75c8a026',
'rules' => [
SmartPlaylistRule::create([
'model' => 'artist.name',
@ -88,13 +93,13 @@ class PlaylistTest extends TestCase
],
],
],
'songs' => Song::query()->orderBy('id')->take(3)->get()->pluck('id')->all(),
'songs' => Song::factory(3)->create()->pluck('id')->all(),
])->assertUnprocessable();
}
public function testCreatingPlaylistWithNonExistentSongsFails(): void
{
$this->postAs('api/playlist', [
$this->postAs('api/playlists', [
'name' => 'Foo Bar',
'rules' => [],
'songs' => ['foo'],
@ -107,7 +112,8 @@ class PlaylistTest extends TestCase
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create(['name' => 'Foo']);
$this->putAs("api/playlist/$playlist->id", ['name' => 'Bar'], $playlist->user);
$this->putAs("api/playlists/$playlist->id", ['name' => 'Bar'], $playlist->user)
->assertJsonStructure(self::JSON_STRUCTURE);
self::assertSame('Bar', $playlist->refresh()->name);
}
@ -117,7 +123,8 @@ class PlaylistTest extends TestCase
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create(['name' => 'Foo']);
$this->putAs("api/playlist/$playlist->id", ['name' => 'Qux'])->assertForbidden();
$this->putAs("api/playlists/$playlist->id", ['name' => 'Qux'])->assertForbidden();
self::assertSame('Foo', $playlist->refresh()->name);
}
public function testDeletePlaylist(): void
@ -125,7 +132,7 @@ class PlaylistTest extends TestCase
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
$this->deleteAs("api/playlist/$playlist->id", [], $playlist->user);
$this->deleteAs("api/playlists/$playlist->id", [], $playlist->user);
self::assertModelMissing($playlist);
}
@ -135,7 +142,7 @@ class PlaylistTest extends TestCase
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
$this->deleteAs("api/playlist/$playlist->id")->assertForbidden();
$this->deleteAs("api/playlists/$playlist->id")->assertForbidden();
self::assertModelExists($playlist);
}

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
use App\Models\Song;

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
use App\Models\Interaction;
use App\Models\User;

View file

@ -28,7 +28,7 @@ class ScrobbleTest extends TestCase
)
->once();
$this->postAs("/api/$song->id/scrobble", ['timestamp' => 100], $user)
$this->postAs("/api/songs/$song->id/scrobble", ['timestamp' => 100], $user)
->assertNoContent();
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace Tests\Feature\V6;
namespace Tests\Feature;
use App\Models\Song;

View file

@ -10,15 +10,92 @@ use Illuminate\Support\Collection;
class SongTest extends TestCase
{
public function setUp(): void
{
parent::setUp();
public const JSON_STRUCTURE = [
'type',
'id',
'title',
'lyrics',
'album_id',
'album_name',
'artist_id',
'artist_name',
'album_artist_id',
'album_artist_name',
'album_cover',
'length',
'liked',
'play_count',
'track',
'genre',
'year',
'disc',
'created_at',
];
static::createSampleMediaSet();
public const JSON_COLLECTION_STRUCTURE = [
'data' => [
'*' => self::JSON_STRUCTURE,
],
'links' => [
'first',
'last',
'prev',
'next',
],
'meta' => [
'current_page',
'from',
'path',
'per_page',
'to',
],
];
public function testIndex(): void
{
Song::factory(10)->create();
$this->getAs('api/songs')->assertJsonStructure(self::JSON_COLLECTION_STRUCTURE);
$this->getAs('api/songs?sort=title&order=desc')->assertJsonStructure(self::JSON_COLLECTION_STRUCTURE);
}
public function testShow(): void
{
/** @var Song $song */
$song = Song::factory()->create();
$this->getAs('api/songs/' . $song->id)->assertJsonStructure(self::JSON_STRUCTURE);
}
public function testDelete(): void
{
/** @var Collection|array<array-key, Song> $songs */
$songs = Song::factory(3)->create();
/** @var User $admin */
$admin = User::factory()->admin()->create();
$this->deleteAs('api/songs', ['songs' => $songs->pluck('id')->all()], $admin)
->assertNoContent();
$songs->each(fn (Song $song) => $this->assertModelMissing($song));
}
public function testUnauthorizedDelete(): void
{
/** @var Collection|array<array-key, Song> $songs */
$songs = Song::factory(3)->create();
$this->deleteAs('api/songs', ['songs' => $songs->pluck('id')->all()])
->assertForbidden();
$songs->each(fn (Song $song) => $this->assertModelExists($song));
}
public function testSingleUpdateAllInfoNoCompilation(): void
{
static::createSampleMediaSet();
/** @var User $user */
$user = User::factory()->admin()->create();
@ -57,6 +134,8 @@ class SongTest extends TestCase
public function testSingleUpdateSomeInfoNoCompilation(): void
{
static::createSampleMediaSet();
/** @var User $user */
$user = User::factory()->admin()->create();
@ -86,9 +165,11 @@ class SongTest extends TestCase
public function testMultipleUpdateNoCompilation(): void
{
static::createSampleMediaSet();
/** @var User $user */
$user = User::factory()->admin()->create();
$songIds = Song::query()->latest()->take(3)->pluck('id')->toArray();
$songIds = Song::query()->latest()->take(3)->pluck('id')->all();
$this->putAs('/api/songs', [
'songs' => $songIds,
@ -124,6 +205,8 @@ class SongTest extends TestCase
public function testMultipleUpdateCreatingNewAlbumsAndArtists(): void
{
static::createSampleMediaSet();
/** @var User $user */
$user = User::factory()->admin()->create();
@ -131,7 +214,7 @@ class SongTest extends TestCase
$originalSongs = Song::query()->latest()->take(3)->get();
$this->putAs('/api/songs', [
'songs' => $originalSongs->pluck('id')->toArray(),
'songs' => $originalSongs->pluck('id')->all(),
'data' => [
'title' => 'Foo Bar',
'artist_name' => 'John Cena',
@ -162,6 +245,8 @@ class SongTest extends TestCase
public function testSingleUpdateAllInfoWithCompilation(): void
{
static::createSampleMediaSet();
/** @var User $user */
$user = User::factory()->admin()->create();
@ -205,6 +290,8 @@ class SongTest extends TestCase
public function testUpdateSingleSongWithEmptyTrackAndDisc(): void
{
static::createSampleMediaSet();
/** @var User $user */
$user = User::factory()->admin()->create();
@ -231,6 +318,8 @@ class SongTest extends TestCase
public function testDeletingByChunk(): void
{
Song::factory(5)->create();
self::assertNotSame(0, Song::query()->count());
$ids = Song::query()->select('id')->get()->pluck('id')->all();

View file

@ -1,145 +0,0 @@
<?php
namespace Tests\Feature\V6;
use App\Models\Playlist;
use App\Models\Song;
use App\Models\User;
use Illuminate\Support\Collection;
class PlaylistSongTest extends TestCase
{
public function testGetNormalPlaylist(): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
$playlist->songs()->attach(Song::factory(5)->create());
$this->getAs('api/playlists/' . $playlist->id . '/songs', $playlist->user)
->assertJsonStructure(['*' => SongTest::JSON_STRUCTURE]);
}
public function testGetSmartPlaylist(): void
{
Song::factory()->create(['title' => 'A foo song']);
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create([
'rules' => [
[
'id' => '45368b8f-fec8-4b72-b826-6b295af0da65',
'rules' => [
[
'id' => '2a4548cd-c67f-44d4-8fec-34ff75c8a026',
'model' => 'title',
'operator' => 'contains',
'value' => ['foo'],
],
],
],
],
]);
$this->getAs('api/playlists/' . $playlist->id . '/songs', $playlist->user)
->assertJsonStructure(['*' => SongTest::JSON_STRUCTURE]);
}
public function testNonOwnerCannotAccessPlaylist(): void
{
$user = User::factory()->create();
/** @var Playlist $playlist */
$playlist = Playlist::factory()->for($user)->create();
$playlist->songs()->attach(Song::factory(5)->create());
$this->getAs('api/playlists/' . $playlist->id . '/songs')
->assertForbidden();
}
public function testAddSongsToPlaylist(): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
/** @var Collection|array<array-key, Song> $songs */
$songs = Song::factory(2)->create();
$this->postAs('api/playlists/' . $playlist->id . '/songs', [
'songs' => $songs->map(static fn (Song $song) => $song->id)->all(),
], $playlist->user)
->assertNoContent();
self::assertEqualsCanonicalizing($songs->pluck('id')->all(), $playlist->songs->pluck('id')->all());
}
public function testRemoveSongsFromPlaylist(): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
$toRemainSongs = Song::factory(5)->create();
/** @var Collection|array<array-key, Song> $toBeRemovedSongs */
$toBeRemovedSongs = Song::factory(2)->create();
$playlist->songs()->attach($toRemainSongs->merge($toBeRemovedSongs));
self::assertCount(7, $playlist->songs);
$this->deleteAs('api/playlists/' . $playlist->id . '/songs', [
'songs' => $toBeRemovedSongs->map(static fn (Song $song) => $song->id)->all(),
], $playlist->user)
->assertNoContent();
$playlist->refresh();
self::assertEqualsCanonicalizing($toRemainSongs->pluck('id')->all(), $playlist->songs->pluck('id')->all());
}
public function testNonOwnerCannotModifyPlaylist(): void
{
$user = User::factory()->create();
/** @var Playlist $playlist */
$playlist = Playlist::factory()->for($user)->create();
/** @var Song $song */
$song = Song::factory()->create();
$this->postAs('api/playlists/' . $playlist->id . '/songs', ['songs' => [$song->id]])
->assertForbidden();
$this->deleteAs('api/playlists/' . $playlist->id . '/songs', ['songs' => [$song->id]])
->assertForbidden();
}
public function testSmartPlaylistContentCannotBeModified(): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create([
'rules' => [
[
'id' => '45368b8f-fec8-4b72-b826-6b295af0da65',
'rules' => [
[
'id' => '2a4548cd-c67f-44d4-8fec-34ff75c8a026',
'model' => 'title',
'operator' => 'contains',
'value' => ['foo'],
],
],
],
],
]);
/** @var Collection|array<array-key, Song> $songs */
$songs = Song::factory(2)->create();
$songIds = $songs->map(static fn (Song $song) => $song->id)->all();
$this->postAs('api/playlists/' . $playlist->id . '/songs', ['songs' => $songIds], $playlist->user)
->assertForbidden();
$this->deleteAs('api/playlists/' . $playlist->id . '/songs', ['songs' => $songIds], $playlist->user)
->assertForbidden();
}
}

View file

@ -1,149 +0,0 @@
<?php
namespace Tests\Feature\V6;
use App\Models\Playlist;
use App\Models\Song;
use App\Models\User;
use App\Values\SmartPlaylistRule;
use Illuminate\Support\Collection;
class PlaylistTest extends TestCase
{
private const JSON_STRUCTURE = [
'type',
'id',
'name',
'folder_id',
'user_id',
'is_smart',
'rules',
'created_at',
];
public function testCreatingPlaylist(): void
{
/** @var User $user */
$user = User::factory()->create();
/** @var array<Song>|Collection $songs */
$songs = Song::factory(4)->create();
$this->postAs('api/playlists', [
'name' => 'Foo Bar',
'songs' => $songs->pluck('id')->all(),
'rules' => [],
], $user)
->assertJsonStructure(self::JSON_STRUCTURE);
/** @var Playlist $playlist */
$playlist = Playlist::query()->orderByDesc('id')->first();
self::assertSame('Foo Bar', $playlist->name);
self::assertTrue($playlist->user->is($user));
self::assertNull($playlist->folder_id);
self::assertEqualsCanonicalizing($songs->pluck('id')->all(), $playlist->songs->pluck('id')->all());
}
public function testCreatingSmartPlaylist(): void
{
/** @var User $user */
$user = User::factory()->create();
$rule = SmartPlaylistRule::create([
'model' => 'artist.name',
'operator' => SmartPlaylistRule::OPERATOR_IS,
'value' => ['Bob Dylan'],
]);
$this->postAs('api/playlists', [
'name' => 'Smart Foo Bar',
'rules' => [
[
'id' => '2a4548cd-c67f-44d4-8fec-34ff75c8a026',
'rules' => [$rule->toArray()],
],
],
], $user)->assertJsonStructure(self::JSON_STRUCTURE);
/** @var Playlist $playlist */
$playlist = Playlist::query()->orderByDesc('id')->first();
self::assertSame('Smart Foo Bar', $playlist->name);
self::assertTrue($playlist->user->is($user));
self::assertTrue($playlist->is_smart);
self::assertCount(1, $playlist->rule_groups);
self::assertNull($playlist->folder_id);
self::assertTrue($rule->equals($playlist->rule_groups[0]->rules[0]));
}
public function testCreatingSmartPlaylistFailsIfSongsProvided(): void
{
$this->postAs('api/playlists', [
'name' => 'Smart Foo Bar',
'rules' => [
[
'id' => '2a4548cd-c67f-44d4-8fec-34ff75c8a026',
'rules' => [
SmartPlaylistRule::create([
'model' => 'artist.name',
'operator' => SmartPlaylistRule::OPERATOR_IS,
'value' => ['Bob Dylan'],
])->toArray(),
],
],
],
'songs' => Song::factory(3)->create()->pluck('id')->all(),
])->assertUnprocessable();
}
public function testCreatingPlaylistWithNonExistentSongsFails(): void
{
$this->postAs('api/playlists', [
'name' => 'Foo Bar',
'rules' => [],
'songs' => ['foo'],
])
->assertUnprocessable();
}
public function testUpdatePlaylistName(): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create(['name' => 'Foo']);
$this->putAs("api/playlists/$playlist->id", ['name' => 'Bar'], $playlist->user)
->assertJsonStructure(self::JSON_STRUCTURE);
self::assertSame('Bar', $playlist->refresh()->name);
}
public function testNonOwnerCannotUpdatePlaylist(): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create(['name' => 'Foo']);
$this->putAs("api/playlists/$playlist->id", ['name' => 'Qux'])->assertForbidden();
self::assertSame('Foo', $playlist->refresh()->name);
}
public function testDeletePlaylist(): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
$this->deleteAs("api/playlists/$playlist->id", [], $playlist->user);
self::assertModelMissing($playlist);
}
public function testNonOwnerCannotDeletePlaylist(): void
{
/** @var Playlist $playlist */
$playlist = Playlist::factory()->create();
$this->deleteAs("api/playlists/$playlist->id")->assertForbidden();
self::assertModelExists($playlist);
}
}

View file

@ -1,92 +0,0 @@
<?php
namespace Tests\Feature\V6;
use App\Models\Song;
use App\Models\User;
use Illuminate\Support\Collection;
class SongTest extends TestCase
{
public const JSON_STRUCTURE = [
'type',
'id',
'title',
'lyrics',
'album_id',
'album_name',
'artist_id',
'artist_name',
'album_artist_id',
'album_artist_name',
'album_cover',
'length',
'liked',
'play_count',
'track',
'genre',
'year',
'disc',
'created_at',
];
public const JSON_COLLECTION_STRUCTURE = [
'data' => [
'*' => self::JSON_STRUCTURE,
],
'links' => [
'first',
'last',
'prev',
'next',
],
'meta' => [
'current_page',
'from',
'path',
'per_page',
'to',
],
];
public function testIndex(): void
{
Song::factory(10)->create();
$this->getAs('api/songs')->assertJsonStructure(self::JSON_COLLECTION_STRUCTURE);
$this->getAs('api/songs?sort=title&order=desc')->assertJsonStructure(self::JSON_COLLECTION_STRUCTURE);
}
public function testShow(): void
{
/** @var Song $song */
$song = Song::factory()->create();
$this->getAs('api/songs/' . $song->id)->assertJsonStructure(self::JSON_STRUCTURE);
}
public function testDelete(): void
{
/** @var Collection|array<array-key, Song> $songs */
$songs = Song::factory(3)->create();
/** @var User $admin */
$admin = User::factory()->admin()->create();
$this->deleteAs('api/songs', ['songs' => $songs->pluck('id')->toArray()], $admin)
->assertNoContent();
$songs->each(fn (Song $song) => $this->assertModelMissing($song));
}
public function testUnauthorizedDelete(): void
{
/** @var Collection|array<array-key, Song> $songs */
$songs = Song::factory(3)->create();
$this->deleteAs('api/songs', ['songs' => $songs->pluck('id')->toArray()])
->assertForbidden();
$songs->each(fn (Song $song) => $this->assertModelExists($song));
}
}

View file

@ -1,15 +0,0 @@
<?php
namespace Tests\Feature\V6;
use Tests\Feature\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
public function setUp(): void
{
putenv('X_API_VERSION=v6');
parent::setUp();
}
}

View file

@ -77,10 +77,7 @@ class InteractionServiceTest extends TestCase
$user = User::factory()->create();
/** @var Collection $interactions */
$interactions = Interaction::factory(3)->create([
'user_id' => $user->id,
'liked' => true,
]);
$interactions = Interaction::factory(3)->for($user)->create(['liked' => true]);
$this->interactionService->batchUnlike($interactions->pluck('song.id')->all(), $user);

View file

@ -39,20 +39,15 @@ abstract class TestCase extends BaseTestCase
$artist = Artist::factory()->create();
/** @var array<Album> $albums */
$albums = Album::factory(3)->create([
'artist_id' => $artist->id,
]);
$albums = Album::factory(3)->for($artist)->create();
// 7-15 songs per albums
foreach ($albums as $album) {
Song::factory(random_int(7, 15))->create([
'album_id' => $album->id,
'artist_id' => $artist->id,
]);
Song::factory(random_int(7, 15))->for($artist)->for($album)->create();
}
}
protected static function getNonPublicProperty($object, string $property) // @phpcs:ignore
protected static function getNonPublicProperty($object, string $property): mixed
{
$reflection = new ReflectionClass($object);
$property = $reflection->getProperty($property);

View file

@ -51,7 +51,7 @@ class PlaylistFolderServiceTest extends TestCase
/** @var PlaylistFolder $folder */
$folder = PlaylistFolder::factory()->create();
$this->service->addPlaylistsToFolder($folder, $playlists->pluck('id')->toArray());
$this->service->addPlaylistsToFolder($folder, $playlists->pluck('id')->all());
self::assertCount(3, $folder->playlists);
}
@ -62,9 +62,9 @@ class PlaylistFolderServiceTest extends TestCase
$folder = PlaylistFolder::factory()->create();
/** @var Collection|array<array-key, Playlist> $playlists */
$playlists = Playlist::factory()->count(3)->create(['folder_id' => $folder->id]);
$playlists = Playlist::factory()->count(3)->for($folder, 'folder')->create();
$this->service->movePlaylistsToRootLevel($playlists->pluck('id')->toArray());
$this->service->movePlaylistsToRootLevel($playlists->pluck('id')->all());
self::assertCount(0, $folder->playlists);

View file

@ -53,11 +53,8 @@ class SpotifyServiceTest extends TestCase
public function testTryGetAlbumImage(): void
{
/** @var Artist $artist */
$artist = Artist::factory(['name' => 'Foo'])->create();
/** @var Album $album */
$album = Album::factory(['name' => 'Bar', 'artist_id' => $artist->id])->create();
$album = Album::factory(['name' => 'Bar'])->for(Artist::factory(['name' => 'Foo']))->create();
$this->client
->shouldReceive('search')