mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
chore: merge v6 into base API
This commit is contained in:
parent
050c992cf1
commit
48f6bcc105
83 changed files with 512 additions and 1207 deletions
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() ?: []);
|
||||
}
|
||||
}
|
|
@ -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() ?: []);
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -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')),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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(),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
|
@ -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;
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -3,7 +3,7 @@
|
|||
namespace App\Http\Requests\API;
|
||||
|
||||
/**
|
||||
* @property string $pageToken
|
||||
* @property-read string|null $pageToken
|
||||
*/
|
||||
class YouTubeSearchRequest extends Request
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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']);
|
||||
});
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Album;
|
||||
use App\Services\MediaInformationService;
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Album;
|
||||
use App\Models\Song;
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Album;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Artist;
|
||||
use App\Services\MediaInformationService;
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Artist;
|
||||
use App\Models\Song;
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Artist;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
class DataTest extends TestCase
|
||||
{
|
|
@ -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')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Album;
|
||||
use App\Models\Artist;
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Interaction;
|
||||
use App\Models\User;
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Song;
|
||||
use App\Values\Genre;
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Interaction;
|
||||
use App\Models\User;
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Events\SongStartedPlaying;
|
||||
use App\Models\Interaction;
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\PlaylistFolder;
|
||||
use App\Models\User;
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Song;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Interaction;
|
||||
use App\Models\User;
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\V6;
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Song;
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in a new issue