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
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Resources\AlbumResource;
|
use App\Http\Resources\AlbumResource;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Resources\SongResource;
|
use App\Http\Resources\SongResource;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Resources\AlbumResource;
|
use App\Http\Resources\AlbumResource;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Resources\ArtistResource;
|
use App\Http\Resources\ArtistResource;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Resources\SongResource;
|
use App\Http\Resources\SongResource;
|
|
@ -3,57 +3,50 @@
|
||||||
namespace App\Http\Controllers\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
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\Models\User;
|
||||||
use App\Repositories\InteractionRepository;
|
|
||||||
use App\Repositories\PlaylistRepository;
|
|
||||||
use App\Repositories\SettingRepository;
|
use App\Repositories\SettingRepository;
|
||||||
use App\Repositories\UserRepository;
|
use App\Repositories\SongRepository;
|
||||||
use App\Services\ApplicationInformationService;
|
use App\Services\ApplicationInformationService;
|
||||||
use App\Services\ITunesService;
|
use App\Services\ITunesService;
|
||||||
use App\Services\LastfmService;
|
use App\Services\LastfmService;
|
||||||
use App\Services\MediaCacheService;
|
|
||||||
use App\Services\YouTubeService;
|
use App\Services\YouTubeService;
|
||||||
use Illuminate\Contracts\Auth\Authenticatable;
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
|
|
||||||
class DataController extends Controller
|
class DataController extends Controller
|
||||||
{
|
{
|
||||||
private const RECENTLY_PLAYED_EXCERPT_COUNT = 7;
|
/** @param User $user */
|
||||||
|
|
||||||
/** @param User $currentUser */
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private MediaCacheService $mediaCacheService,
|
private ITunesService $iTunesService,
|
||||||
private SettingRepository $settingRepository,
|
private SettingRepository $settingRepository,
|
||||||
private PlaylistRepository $playlistRepository,
|
private SongRepository $songRepository,
|
||||||
private InteractionRepository $interactionRepository,
|
|
||||||
private UserRepository $userRepository,
|
|
||||||
private ApplicationInformationService $applicationInformationService,
|
private ApplicationInformationService $applicationInformationService,
|
||||||
private ?Authenticatable $currentUser
|
private ?Authenticatable $user
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
return response()->json($this->mediaCacheService->get() + [
|
return response()->json([
|
||||||
'settings' => $this->currentUser->is_admin ? $this->settingRepository->getAllAsKeyValueArray() : [],
|
'settings' => $this->user->is_admin ? $this->settingRepository->getAllAsKeyValueArray() : [],
|
||||||
'playlists' => $this->playlistRepository->getAllByCurrentUser(),
|
'playlists' => PlaylistResource::collection($this->user->playlists),
|
||||||
'interactions' => $this->interactionRepository->getAllByCurrentUser(),
|
'playlist_folders' => PlaylistFolderResource::collection($this->user->playlist_folders),
|
||||||
'recentlyPlayed' => $this->interactionRepository->getRecentlyPlayed(
|
'current_user' => UserResource::make($this->user, true),
|
||||||
$this->currentUser,
|
'use_last_fm' => LastfmService::used(),
|
||||||
self::RECENTLY_PLAYED_EXCERPT_COUNT
|
'use_you_tube' => YouTubeService::enabled(),
|
||||||
),
|
'use_i_tunes' => $this->iTunesService->used(),
|
||||||
'users' => $this->currentUser->is_admin ? $this->userRepository->getAll() : [],
|
'allow_download' => config('koel.download.allow'),
|
||||||
'currentUser' => $this->currentUser,
|
'supports_transcoding' => config('koel.streaming.ffmpeg_path')
|
||||||
'useLastfm' => LastfmService::used(),
|
|
||||||
'useYouTube' => YouTubeService::enabled(),
|
|
||||||
'useiTunes' => ITunesService::used(),
|
|
||||||
'allowDownload' => config('koel.download.allow'),
|
|
||||||
'supportsTranscoding' => config('koel.streaming.ffmpeg_path')
|
|
||||||
&& is_executable(config('koel.streaming.ffmpeg_path')),
|
&& is_executable(config('koel.streaming.ffmpeg_path')),
|
||||||
'cdnUrl' => static_url(),
|
'cdn_url' => static_url(),
|
||||||
'currentVersion' => koel_version(),
|
'current_version' => koel_version(),
|
||||||
'latestVersion' => $this->currentUser->is_admin
|
'latest_version' => $this->user->is_admin
|
||||||
? $this->applicationInformationService->getLatestVersionNumber()
|
? $this->applicationInformationService->getLatestVersionNumber()
|
||||||
: koel_version(),
|
: koel_version(),
|
||||||
|
'song_count' => $this->songRepository->count(),
|
||||||
|
'song_length' => $this->songRepository->getTotalLength(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
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\Http\Resources\ExcerptSearchResource;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\V6\SearchService;
|
use App\Services\SearchService;
|
||||||
use Illuminate\Contracts\Auth\Authenticatable;
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
|
|
||||||
class ExcerptSearchController extends Controller
|
class ExcerptSearchController extends Controller
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Resources\SongResource;
|
use App\Http\Resources\SongResource;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Album;
|
use App\Models\Album;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Artist;
|
use App\Models\Artist;
|
|
@ -1,9 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
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\Http\Resources\SongResource;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Repositories\SongRepository;
|
use App\Repositories\SongRepository;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Resources\GenreResource;
|
use App\Http\Resources\GenreResource;
|
|
@ -1,9 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
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\Http\Resources\SongResource;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Repositories\SongRepository;
|
use App\Repositories\SongRepository;
|
|
@ -5,6 +5,7 @@ namespace App\Http\Controllers\API\Interaction;
|
||||||
use App\Events\SongStartedPlaying;
|
use App\Events\SongStartedPlaying;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\API\Interaction\StorePlayCountRequest;
|
use App\Http\Requests\API\Interaction\StorePlayCountRequest;
|
||||||
|
use App\Http\Resources\InteractionResource;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\InteractionService;
|
use App\Services\InteractionService;
|
||||||
use Illuminate\Contracts\Auth\Authenticatable;
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
|
@ -21,6 +22,6 @@ class PlayCountController extends Controller
|
||||||
$interaction = $this->interactionService->increasePlayCount($request->song, $this->user);
|
$interaction = $this->interactionService->increasePlayCount($request->song, $this->user);
|
||||||
event(new SongStartedPlaying($interaction->song, $interaction->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
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Resources\AlbumResource;
|
use App\Http\Resources\AlbumResource;
|
|
@ -6,9 +6,11 @@ use App\Exceptions\PlaylistBothSongsAndRulesProvidedException;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\API\PlaylistStoreRequest;
|
use App\Http\Requests\API\PlaylistStoreRequest;
|
||||||
use App\Http\Requests\API\PlaylistUpdateRequest;
|
use App\Http\Requests\API\PlaylistUpdateRequest;
|
||||||
|
use App\Http\Resources\PlaylistResource;
|
||||||
use App\Models\Playlist;
|
use App\Models\Playlist;
|
||||||
|
use App\Models\PlaylistFolder;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Repositories\PlaylistRepository;
|
use App\Repositories\PlaylistFolderRepository;
|
||||||
use App\Services\PlaylistService;
|
use App\Services\PlaylistService;
|
||||||
use App\Values\SmartPlaylistRuleGroupCollection;
|
use App\Values\SmartPlaylistRuleGroupCollection;
|
||||||
use Illuminate\Contracts\Auth\Authenticatable;
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
|
@ -19,31 +21,37 @@ class PlaylistController extends Controller
|
||||||
{
|
{
|
||||||
/** @param User $user */
|
/** @param User $user */
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private PlaylistRepository $playlistRepository,
|
|
||||||
private PlaylistService $playlistService,
|
private PlaylistService $playlistService,
|
||||||
|
private PlaylistFolderRepository $folderRepository,
|
||||||
private ?Authenticatable $user
|
private ?Authenticatable $user
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
return response()->json($this->playlistRepository->getAllByCurrentUser());
|
return PlaylistResource::collection($this->user->playlists);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(PlaylistStoreRequest $request)
|
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 {
|
try {
|
||||||
$playlist = $this->playlistService->createPlaylist(
|
$playlist = $this->playlistService->createPlaylist(
|
||||||
$request->name,
|
$request->name,
|
||||||
$this->user,
|
$this->user,
|
||||||
null,
|
$folder,
|
||||||
Arr::wrap($request->songs),
|
Arr::wrap($request->songs),
|
||||||
$request->rules ? SmartPlaylistRuleGroupCollection::create(Arr::wrap($request->rules)) : null
|
$request->rules ? SmartPlaylistRuleGroupCollection::create(Arr::wrap($request->rules)) : null
|
||||||
);
|
);
|
||||||
|
|
||||||
$playlist->songs = $playlist->songs->pluck('id')->toArray();
|
return PlaylistResource::make($playlist);
|
||||||
|
|
||||||
return response()->json($playlist);
|
|
||||||
} catch (PlaylistBothSongsAndRulesProvidedException $e) {
|
} catch (PlaylistBothSongsAndRulesProvidedException $e) {
|
||||||
throw ValidationException::withMessages(['songs' => [$e->getMessage()]]);
|
throw ValidationException::withMessages(['songs' => [$e->getMessage()]]);
|
||||||
}
|
}
|
||||||
|
@ -53,9 +61,22 @@ class PlaylistController extends Controller
|
||||||
{
|
{
|
||||||
$this->authorize('own', $playlist);
|
$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)
|
public function destroy(Playlist $playlist)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\V6\API\PlaylistFolderStoreRequest;
|
use App\Http\Requests\API\PlaylistFolderStoreRequest;
|
||||||
use App\Http\Requests\V6\API\PlaylistFolderUpdateRequest;
|
use App\Http\Requests\API\PlaylistFolderUpdateRequest;
|
||||||
use App\Http\Resources\PlaylistFolderResource;
|
use App\Http\Resources\PlaylistFolderResource;
|
||||||
use App\Models\PlaylistFolder;
|
use App\Models\PlaylistFolder;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
|
@ -1,10 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\V6\API\PlaylistFolderPlaylistDestroyRequest;
|
use App\Http\Requests\API\PlaylistFolderPlaylistDestroyRequest;
|
||||||
use App\Http\Requests\V6\API\PlaylistFolderPlaylistStoreRequest;
|
use App\Http\Requests\API\PlaylistFolderPlaylistStoreRequest;
|
||||||
use App\Models\PlaylistFolder;
|
use App\Models\PlaylistFolder;
|
||||||
use App\Services\PlaylistFolderService;
|
use App\Services\PlaylistFolderService;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
|
@ -3,21 +3,24 @@
|
||||||
namespace App\Http\Controllers\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
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\Playlist;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Repositories\SongRepository;
|
||||||
use App\Services\PlaylistService;
|
use App\Services\PlaylistService;
|
||||||
use App\Services\SmartPlaylistService;
|
use App\Services\SmartPlaylistService;
|
||||||
use Illuminate\Contracts\Auth\Authenticatable;
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Illuminate\Support\Arr;
|
|
||||||
|
|
||||||
class PlaylistSongController extends Controller
|
class PlaylistSongController extends Controller
|
||||||
{
|
{
|
||||||
/** @param User $user */
|
/** @param User $user */
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private SmartPlaylistService $smartPlaylistService,
|
private SongRepository $songRepository,
|
||||||
private PlaylistService $playlistService,
|
private PlaylistService $playlistService,
|
||||||
|
private SmartPlaylistService $smartPlaylistService,
|
||||||
private ?Authenticatable $user
|
private ?Authenticatable $user
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
@ -26,21 +29,31 @@ class PlaylistSongController extends Controller
|
||||||
{
|
{
|
||||||
$this->authorize('own', $playlist);
|
$this->authorize('own', $playlist);
|
||||||
|
|
||||||
return response()->json(
|
return SongResource::collection(
|
||||||
$playlist->is_smart
|
$playlist->is_smart
|
||||||
? $this->smartPlaylistService->getSongs($playlist, $this->user)->pluck('id')
|
? $this->smartPlaylistService->getSongs($playlist, $this->user)
|
||||||
: $playlist->songs->pluck('id')
|
: $this->songRepository->getByStandardPlaylist($playlist, $this->user)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated */
|
public function store(Playlist $playlist, AddSongsToPlaylistRequest $request)
|
||||||
public function update(PlaylistSongUpdateRequest $request, Playlist $playlist)
|
|
||||||
{
|
{
|
||||||
$this->authorize('own', $playlist);
|
$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();
|
return response()->noContent();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
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\Http\Resources\SongResource;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Repositories\SongRepository;
|
use App\Repositories\SongRepository;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Resources\SongResource;
|
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;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
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\Requests\API\SongUpdateRequest;
|
||||||
use App\Http\Resources\AlbumResource;
|
use App\Http\Resources\AlbumResource;
|
||||||
use App\Http\Resources\ArtistResource;
|
use App\Http\Resources\ArtistResource;
|
||||||
use App\Http\Resources\SongResource;
|
use App\Http\Resources\SongResource;
|
||||||
|
use App\Models\Song;
|
||||||
|
use App\Models\User;
|
||||||
use App\Repositories\AlbumRepository;
|
use App\Repositories\AlbumRepository;
|
||||||
use App\Repositories\ArtistRepository;
|
use App\Repositories\ArtistRepository;
|
||||||
|
use App\Repositories\SongRepository;
|
||||||
use App\Services\LibraryManager;
|
use App\Services\LibraryManager;
|
||||||
use App\Services\SongService;
|
use App\Services\SongService;
|
||||||
use App\Values\SongUpdateData;
|
use App\Values\SongUpdateData;
|
||||||
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
|
|
||||||
class SongController extends Controller
|
class SongController extends Controller
|
||||||
{
|
{
|
||||||
|
/** @param User $user */
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private SongService $songService,
|
private SongService $songService,
|
||||||
|
private SongRepository $songRepository,
|
||||||
private AlbumRepository $albumRepository,
|
private AlbumRepository $albumRepository,
|
||||||
private ArtistRepository $artistRepository,
|
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)
|
public function update(SongUpdateRequest $request)
|
||||||
{
|
{
|
||||||
$updatedSongs = $this->songService->updateSongs($request->songs, SongUpdateData::fromRequest($request));
|
$updatedSongs = $this->songService->updateSongs($request->songs, SongUpdateData::fromRequest($request));
|
||||||
|
@ -42,4 +67,13 @@ class SongController extends Controller
|
||||||
'removed' => $this->libraryManager->prune(),
|
'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
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Controllers\V6\API;
|
namespace App\Http\Controllers\API;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
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\Http\Resources\SongResource;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Services\V6\SearchService;
|
use App\Services\SearchService;
|
||||||
use Illuminate\Contracts\Auth\Authenticatable;
|
use Illuminate\Contracts\Auth\Authenticatable;
|
||||||
|
|
||||||
class SongSearchController extends Controller
|
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
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Requests\V6\API;
|
namespace App\Http\Requests\API;
|
||||||
|
|
||||||
use App\Http\Requests\API\Request;
|
|
||||||
use App\Models\Song;
|
use App\Models\Song;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Requests\V6\API;
|
namespace App\Http\Requests\API;
|
||||||
|
|
||||||
use App\Http\Requests\API\Request;
|
|
||||||
|
|
||||||
/** @property-read array<string> $songs */
|
/** @property-read array<string> $songs */
|
||||||
class DeleteSongsRequest extends Request
|
class DeleteSongsRequest extends Request
|
|
@ -1,8 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Requests\V6\API;
|
namespace App\Http\Requests\API;
|
||||||
|
|
||||||
use App\Http\Requests\API\Request;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property-read string $genre
|
* @property-read string $genre
|
|
@ -1,8 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Requests\V6\API;
|
namespace App\Http\Requests\API;
|
||||||
|
|
||||||
use App\Http\Requests\API\Request;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property-read string $order
|
* @property-read string $order
|
|
@ -1,8 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Requests\V6\API;
|
namespace App\Http\Requests\API;
|
||||||
|
|
||||||
use App\Http\Requests\API\Request;
|
|
||||||
use App\Models\Playlist;
|
use App\Models\Playlist;
|
||||||
use App\Rules\AllPlaylistsBelongToUser;
|
use App\Rules\AllPlaylistsBelongToUser;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
|
@ -1,8 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Requests\V6\API;
|
namespace App\Http\Requests\API;
|
||||||
|
|
||||||
use App\Http\Requests\API\Request;
|
|
||||||
use App\Models\Playlist;
|
use App\Models\Playlist;
|
||||||
use App\Rules\AllPlaylistsBelongToUser;
|
use App\Rules\AllPlaylistsBelongToUser;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
|
@ -1,8 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Requests\V6\API;
|
namespace App\Http\Requests\API;
|
||||||
|
|
||||||
use App\Http\Requests\API\Request;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property-read string $name
|
* @property-read string $name
|
|
@ -1,8 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Requests\V6\API;
|
namespace App\Http\Requests\API;
|
||||||
|
|
||||||
use App\Http\Requests\API\Request;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property-read string $name
|
* @property-read string $name
|
|
@ -1,8 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Requests\V6\API;
|
namespace App\Http\Requests\API;
|
||||||
|
|
||||||
use App\Http\Requests\API\Request;
|
|
||||||
use App\Repositories\SongRepository;
|
use App\Repositories\SongRepository;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Requests\V6\API;
|
namespace App\Http\Requests\API;
|
||||||
|
|
||||||
use App\Http\Requests\API\Request;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property-read array<string> $songs
|
* @property-read array<string> $songs
|
|
@ -1,8 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Requests\V6\API;
|
namespace App\Http\Requests\API;
|
||||||
|
|
||||||
use App\Http\Requests\API\Request;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property-read string $q
|
* @property-read string $q
|
|
@ -1,8 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Http\Requests\V6\API;
|
namespace App\Http\Requests\API;
|
||||||
|
|
||||||
use App\Http\Requests\API\Request;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property-read string $order
|
* @property-read string $order
|
|
@ -3,7 +3,7 @@
|
||||||
namespace App\Http\Requests\API;
|
namespace App\Http\Requests\API;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property string $pageToken
|
* @property-read string|null $pageToken
|
||||||
*/
|
*/
|
||||||
class YouTubeSearchRequest extends Request
|
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;
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Builders\SongBuilder;
|
||||||
use App\Models\Album;
|
use App\Models\Album;
|
||||||
use App\Models\Artist;
|
use App\Models\Artist;
|
||||||
use App\Models\Song;
|
use App\Models\Song;
|
||||||
|
use App\Models\User;
|
||||||
use App\Repositories\AlbumRepository;
|
use App\Repositories\AlbumRepository;
|
||||||
use App\Repositories\ArtistRepository;
|
use App\Repositories\ArtistRepository;
|
||||||
use App\Repositories\SongRepository;
|
use App\Repositories\SongRepository;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use App\Values\ExcerptSearchResult;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Laravel\Scout\Builder;
|
|
||||||
|
|
||||||
class SearchService
|
class SearchService
|
||||||
{
|
{
|
||||||
public const DEFAULT_EXCERPT_RESULT_COUNT = 6;
|
public const DEFAULT_EXCERPT_RESULT_COUNT = 6;
|
||||||
|
public const DEFAULT_MAX_SONG_RESULT_COUNT = 500;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private SongRepository $songRepository,
|
private SongRepository $songRepository,
|
||||||
|
@ -23,31 +25,33 @@ class SearchService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return array<mixed> */
|
public function excerptSearch(
|
||||||
public function excerptSearch(string $keywords, int $count): array
|
string $keywords,
|
||||||
{
|
?User $scopedUser = null,
|
||||||
return [
|
int $count = self::DEFAULT_EXCERPT_RESULT_COUNT
|
||||||
'songs' => self::getTopResults($this->songRepository->search($keywords), $count)
|
): ExcerptSearchResult {
|
||||||
->map(static fn (Song $song): string => $song->id),
|
$scopedUser ??= auth()->user();
|
||||||
'artists' => self::getTopResults($this->artistRepository->search($keywords), $count)
|
|
||||||
->map(static fn (Artist $artist): int => $artist->id),
|
return ExcerptSearchResult::make(
|
||||||
'albums' => self::getTopResults($this->albumRepository->search($keywords), $count)
|
$this->songRepository->getByIds(
|
||||||
->map(static fn (Album $album): int => $album->id),
|
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> */
|
/** @return Collection|array<array-key, Song> */
|
||||||
private static function getTopResults(Builder $query, int $count): Collection
|
public function searchSongs(
|
||||||
{
|
string $keywords,
|
||||||
return $query->take($count)->get();
|
?User $scopedUser = null,
|
||||||
}
|
int $limit = self::DEFAULT_MAX_SONG_RESULT_COUNT
|
||||||
|
): Collection {
|
||||||
/** @return Collection|array<string> */
|
return Song::search($keywords)
|
||||||
public function searchSongs(string $keywords): Collection
|
->query(static function (SongBuilder $builder) use ($scopedUser, $limit): void {
|
||||||
{
|
$builder->withMeta($scopedUser ?? auth()->user())->limit($limit);
|
||||||
return $this->songRepository
|
})
|
||||||
->search($keywords)
|
->get();
|
||||||
->get()
|
|
||||||
->map(static fn (Song $song): string => $song->id); // @phpstan-ignore-line
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
<?php
|
||||||
|
|
||||||
use App\Facades\YouTube;
|
use App\Facades\YouTube;
|
||||||
|
use App\Http\Controllers\API\AlbumController;
|
||||||
use App\Http\Controllers\API\AlbumCoverController;
|
use App\Http\Controllers\API\AlbumCoverController;
|
||||||
|
use App\Http\Controllers\API\AlbumSongController;
|
||||||
use App\Http\Controllers\API\AlbumThumbnailController;
|
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\ArtistImageController;
|
||||||
|
use App\Http\Controllers\API\ArtistSongController;
|
||||||
use App\Http\Controllers\API\AuthController;
|
use App\Http\Controllers\API\AuthController;
|
||||||
use App\Http\Controllers\API\DataController;
|
use App\Http\Controllers\API\DataController;
|
||||||
use App\Http\Controllers\API\DemoCreditController;
|
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\BatchLikeController;
|
||||||
use App\Http\Controllers\API\Interaction\LikeController;
|
use App\Http\Controllers\API\Interaction\LikeController;
|
||||||
use App\Http\Controllers\API\Interaction\PlayCountController;
|
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\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\ObjectStorage\S3\SongController as S3SongController;
|
||||||
|
use App\Http\Controllers\API\OverviewController;
|
||||||
use App\Http\Controllers\API\PlaylistController;
|
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\PlaylistSongController;
|
||||||
use App\Http\Controllers\API\ProfileController;
|
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\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\SettingController;
|
||||||
use App\Http\Controllers\API\SongController;
|
use App\Http\Controllers\API\SongController;
|
||||||
|
use App\Http\Controllers\API\SongSearchController;
|
||||||
use App\Http\Controllers\API\UploadController;
|
use App\Http\Controllers\API\UploadController;
|
||||||
use App\Http\Controllers\API\UserController;
|
use App\Http\Controllers\API\UserController;
|
||||||
use App\Http\Controllers\API\YouTubeController;
|
use App\Http\Controllers\API\YouTubeController;
|
||||||
|
use App\Models\Song;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Pusher\Pusher;
|
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);
|
return $pusher->socket_auth($request->channel_name, $request->socket_id);
|
||||||
})->name('broadcasting.auth');
|
})->name('broadcasting.auth');
|
||||||
|
|
||||||
|
Route::get('overview', [OverviewController::class, 'index']);
|
||||||
Route::get('data', [DataController::class, 'index']);
|
Route::get('data', [DataController::class, 'index']);
|
||||||
|
|
||||||
|
Route::get('queue/fetch', [QueueController::class, 'fetchSongs']);
|
||||||
|
|
||||||
Route::put('settings', [SettingController::class, 'update']);
|
Route::put('settings', [SettingController::class, 'update']);
|
||||||
|
|
||||||
/**
|
Route::apiResource('albums', AlbumController::class);
|
||||||
* @deprecated Use songs/{song}/scrobble instead
|
Route::apiResource('albums.songs', AlbumSongController::class);
|
||||||
*/
|
|
||||||
Route::post('{song}/scrobble', [ScrobbleController::class, 'store']);
|
Route::apiResource('artists', ArtistController::class);
|
||||||
Route::post('songs/{song}/scrobble', [ScrobbleController::class, 'store']);
|
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::put('songs', [SongController::class, 'update']);
|
||||||
|
Route::delete('songs', [SongController::class, 'destroy']);
|
||||||
|
|
||||||
Route::post('upload', UploadController::class);
|
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/like', [LikeController::class, 'store']);
|
||||||
Route::post('interaction/batch/like', [BatchLikeController::class, 'store']);
|
Route::post('interaction/batch/like', [BatchLikeController::class, 'store']);
|
||||||
Route::post('interaction/batch/unlike', [BatchLikeController::class, 'destroy']);
|
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
|
// 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::get('genres/{genre}/songs', GenreSongController::class)->where('genre', '.*');
|
||||||
Route::put('playlist/{playlist}/songs', [PlaylistSongController::class, 'update']);
|
Route::get('genres/{genre}/songs/random', FetchRandomSongsInGenreController::class)->where('genre', '.*');
|
||||||
Route::get('playlist/{playlist}/songs', [PlaylistSongController::class, 'index']);
|
Route::apiResource('genres', GenreController::class)->where(['genre' => '.*']);
|
||||||
|
|
||||||
|
Route::apiResource('users', UserController::class);
|
||||||
|
|
||||||
// User and user profile routes
|
// 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::get('me', [ProfileController::class, 'show']);
|
||||||
Route::put('me', [ProfileController::class, 'update']);
|
Route::put('me', [ProfileController::class, 'update']);
|
||||||
|
|
||||||
|
@ -96,20 +132,16 @@ Route::prefix('api')->middleware('api')->group(static function (): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Media information routes
|
// Media information routes
|
||||||
Route::get('album/{album}/info', [AlbumInformationController::class, 'show']);
|
Route::get('albums/{album}/information', FetchAlbumInformationController::class);
|
||||||
Route::get('artist/{artist}/info', [ArtistInformationController::class, 'show']);
|
Route::get('artists/{artist}/information', FetchArtistInformationController::class);
|
||||||
Route::get('song/{song}/info', [SongInformationController::class, 'show']);
|
|
||||||
|
|
||||||
// Cover/image upload routes
|
// Cover/image upload routes
|
||||||
Route::put('album/{album}/cover', [AlbumCoverController::class, 'update']);
|
Route::put('album/{album}/cover', [AlbumCoverController::class, 'update']);
|
||||||
Route::put('artist/{artist}/image', [ArtistImageController::class, 'update']);
|
Route::put('artist/{artist}/image', [ArtistImageController::class, 'update']);
|
||||||
Route::get('album/{album}/thumbnail', [AlbumThumbnailController::class, 'show']);
|
Route::get('album/{album}/thumbnail', [AlbumThumbnailController::class, 'show']);
|
||||||
|
|
||||||
// Search routes
|
Route::get('search', ExcerptSearchController::class);
|
||||||
Route::prefix('search')->group(static function (): void {
|
Route::get('search/songs', SongSearchController::class);
|
||||||
Route::get('/', [ExcerptSearchController::class, 'index']);
|
|
||||||
Route::get('songs', [SongSearchController::class, 'index']);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Object-storage (S3) routes
|
// 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
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\Album;
|
use App\Models\Album;
|
||||||
use App\Services\MediaInformationService;
|
use App\Services\MediaInformationService;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\Album;
|
use App\Models\Album;
|
||||||
use App\Models\Song;
|
use App\Models\Song;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\Album;
|
use App\Models\Album;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\Album;
|
use App\Models\Album;
|
||||||
use App\Models\Artist;
|
use App\Models\Artist;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\Artist;
|
use App\Models\Artist;
|
||||||
use App\Services\MediaInformationService;
|
use App\Services\MediaInformationService;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\Artist;
|
use App\Models\Artist;
|
||||||
use App\Models\Song;
|
use App\Models\Song;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\Artist;
|
use App\Models\Artist;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
class DataTest extends TestCase
|
class DataTest extends TestCase
|
||||||
{
|
{
|
|
@ -70,8 +70,8 @@ class DownloadTest extends TestCase
|
||||||
->shouldReceive('from')
|
->shouldReceive('from')
|
||||||
->once()
|
->once()
|
||||||
->with(Mockery::on(static function (Collection $retrievedSongs) use ($songs): bool {
|
->with(Mockery::on(static function (Collection $retrievedSongs) use ($songs): bool {
|
||||||
$retrievedIds = $retrievedSongs->pluck('id')->toArray();
|
$retrievedIds = $retrievedSongs->pluck('id')->all();
|
||||||
$requestedIds = $songs->pluck('id')->toArray();
|
$requestedIds = $songs->pluck('id')->all();
|
||||||
self::assertEqualsCanonicalizing($requestedIds, $retrievedIds);
|
self::assertEqualsCanonicalizing($requestedIds, $retrievedIds);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -131,9 +131,7 @@ class DownloadTest extends TestCase
|
||||||
$user = User::factory()->create();
|
$user = User::factory()->create();
|
||||||
|
|
||||||
/** @var Playlist $playlist */
|
/** @var Playlist $playlist */
|
||||||
$playlist = Playlist::factory()->create([
|
$playlist = Playlist::factory()->for($user)->create();
|
||||||
'user_id' => $user->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->downloadService
|
$this->downloadService
|
||||||
->shouldReceive('from')
|
->shouldReceive('from')
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\Album;
|
use App\Models\Album;
|
||||||
use App\Models\Artist;
|
use App\Models\Artist;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\Interaction;
|
use App\Models\Interaction;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\Song;
|
use App\Models\Song;
|
||||||
use App\Values\Genre;
|
use App\Values\Genre;
|
|
@ -80,7 +80,7 @@ class InteractionTest extends TestCase
|
||||||
|
|
||||||
/** @var Collection|array<Song> $songs */
|
/** @var Collection|array<Song> $songs */
|
||||||
$songs = Song::query()->orderBy('id')->take(2)->get();
|
$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);
|
$this->postAs('api/interaction/batch/like', ['songs' => $songIds], $user);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\Interaction;
|
use App\Models\Interaction;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Events\SongStartedPlaying;
|
use App\Events\SongStartedPlaying;
|
||||||
use App\Models\Interaction;
|
use App\Models\Interaction;
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\PlaylistFolder;
|
use App\Models\PlaylistFolder;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
|
@ -5,53 +5,141 @@ namespace Tests\Feature;
|
||||||
use App\Models\Playlist;
|
use App\Models\Playlist;
|
||||||
use App\Models\Song;
|
use App\Models\Song;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
class PlaylistSongTest extends TestCase
|
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 testGetSmartPlaylist(): void
|
||||||
public function testSyncPlaylist(): 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();
|
$user = User::factory()->create();
|
||||||
|
|
||||||
/** @var Playlist $playlist */
|
/** @var Playlist $playlist */
|
||||||
$playlist = Playlist::factory()->for($user)->create();
|
$playlist = Playlist::factory()->for($user)->create();
|
||||||
|
|
||||||
$toRemainSongs = Song::factory(3)->create();
|
/** @var Song $song */
|
||||||
$toBeRemovedSongs = Song::factory(2)->create();
|
$song = Song::factory()->create();
|
||||||
$playlist->songs()->attach($toRemainSongs->merge($toBeRemovedSongs));
|
|
||||||
|
|
||||||
$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();
|
$this->deleteAs('api/playlists/' . $playlist->id . '/songs', ['songs' => [$song->id]])
|
||||||
|
->assertForbidden();
|
||||||
self::assertEqualsCanonicalizing(
|
|
||||||
$toRemainSongs->pluck('id')->all(),
|
|
||||||
$playlist->refresh()->songs->pluck('id')->all()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetPlaylistSongs(): void
|
public function testSmartPlaylistContentCannotBeModified(): void
|
||||||
{
|
{
|
||||||
/** @var Playlist $playlist */
|
/** @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();
|
$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)
|
$this->postAs('api/playlists/' . $playlist->id . '/songs', ['songs' => $songIds], $playlist->user)
|
||||||
->json();
|
->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
|
class PlaylistTest extends TestCase
|
||||||
{
|
{
|
||||||
public function setUp(): void
|
private const JSON_STRUCTURE = [
|
||||||
{
|
'type',
|
||||||
parent::setUp();
|
'id',
|
||||||
|
'name',
|
||||||
static::createSampleMediaSet();
|
'folder_id',
|
||||||
}
|
'user_id',
|
||||||
|
'is_smart',
|
||||||
|
'rules',
|
||||||
|
'created_at',
|
||||||
|
];
|
||||||
|
|
||||||
public function testCreatingPlaylist(): void
|
public function testCreatingPlaylist(): void
|
||||||
{
|
{
|
||||||
|
@ -23,21 +27,21 @@ class PlaylistTest extends TestCase
|
||||||
$user = User::factory()->create();
|
$user = User::factory()->create();
|
||||||
|
|
||||||
/** @var array<Song>|Collection $songs */
|
/** @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',
|
'name' => 'Foo Bar',
|
||||||
'songs' => $songs->pluck('id')->toArray(),
|
'songs' => $songs->pluck('id')->all(),
|
||||||
'rules' => [],
|
'rules' => [],
|
||||||
], $user);
|
], $user)
|
||||||
|
->assertJsonStructure(self::JSON_STRUCTURE);
|
||||||
$response->assertOk();
|
|
||||||
|
|
||||||
/** @var Playlist $playlist */
|
/** @var Playlist $playlist */
|
||||||
$playlist = Playlist::query()->orderByDesc('id')->first();
|
$playlist = Playlist::query()->orderByDesc('id')->first();
|
||||||
|
|
||||||
self::assertSame('Foo Bar', $playlist->name);
|
self::assertSame('Foo Bar', $playlist->name);
|
||||||
self::assertTrue($playlist->user->is($user));
|
self::assertTrue($playlist->user->is($user));
|
||||||
|
self::assertNull($playlist->folder_id);
|
||||||
self::assertEqualsCanonicalizing($songs->pluck('id')->all(), $playlist->songs->pluck('id')->all());
|
self::assertEqualsCanonicalizing($songs->pluck('id')->all(), $playlist->songs->pluck('id')->all());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,15 +56,15 @@ class PlaylistTest extends TestCase
|
||||||
'value' => ['Bob Dylan'],
|
'value' => ['Bob Dylan'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->postAs('api/playlist', [
|
$this->postAs('api/playlists', [
|
||||||
'name' => 'Smart Foo Bar',
|
'name' => 'Smart Foo Bar',
|
||||||
'rules' => [
|
'rules' => [
|
||||||
[
|
[
|
||||||
'id' => '45368b8f-fec8-4b72-b826-6b295af0da65',
|
'id' => '2a4548cd-c67f-44d4-8fec-34ff75c8a026',
|
||||||
'rules' => [$rule->toArray()],
|
'rules' => [$rule->toArray()],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
], $user);
|
], $user)->assertJsonStructure(self::JSON_STRUCTURE);
|
||||||
|
|
||||||
/** @var Playlist $playlist */
|
/** @var Playlist $playlist */
|
||||||
$playlist = Playlist::query()->orderByDesc('id')->first();
|
$playlist = Playlist::query()->orderByDesc('id')->first();
|
||||||
|
@ -69,16 +73,17 @@ class PlaylistTest extends TestCase
|
||||||
self::assertTrue($playlist->user->is($user));
|
self::assertTrue($playlist->user->is($user));
|
||||||
self::assertTrue($playlist->is_smart);
|
self::assertTrue($playlist->is_smart);
|
||||||
self::assertCount(1, $playlist->rule_groups);
|
self::assertCount(1, $playlist->rule_groups);
|
||||||
|
self::assertNull($playlist->folder_id);
|
||||||
self::assertTrue($rule->equals($playlist->rule_groups[0]->rules[0]));
|
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',
|
'name' => 'Smart Foo Bar',
|
||||||
'rules' => [
|
'rules' => [
|
||||||
[
|
[
|
||||||
'id' => '45368b8f-fec8-4b72-b826-6b295af0da65',
|
'id' => '2a4548cd-c67f-44d4-8fec-34ff75c8a026',
|
||||||
'rules' => [
|
'rules' => [
|
||||||
SmartPlaylistRule::create([
|
SmartPlaylistRule::create([
|
||||||
'model' => 'artist.name',
|
'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();
|
])->assertUnprocessable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCreatingPlaylistWithNonExistentSongsFails(): void
|
public function testCreatingPlaylistWithNonExistentSongsFails(): void
|
||||||
{
|
{
|
||||||
$this->postAs('api/playlist', [
|
$this->postAs('api/playlists', [
|
||||||
'name' => 'Foo Bar',
|
'name' => 'Foo Bar',
|
||||||
'rules' => [],
|
'rules' => [],
|
||||||
'songs' => ['foo'],
|
'songs' => ['foo'],
|
||||||
|
@ -107,7 +112,8 @@ class PlaylistTest extends TestCase
|
||||||
/** @var Playlist $playlist */
|
/** @var Playlist $playlist */
|
||||||
$playlist = Playlist::factory()->create(['name' => 'Foo']);
|
$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);
|
self::assertSame('Bar', $playlist->refresh()->name);
|
||||||
}
|
}
|
||||||
|
@ -117,7 +123,8 @@ class PlaylistTest extends TestCase
|
||||||
/** @var Playlist $playlist */
|
/** @var Playlist $playlist */
|
||||||
$playlist = Playlist::factory()->create(['name' => 'Foo']);
|
$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
|
public function testDeletePlaylist(): void
|
||||||
|
@ -125,7 +132,7 @@ class PlaylistTest extends TestCase
|
||||||
/** @var Playlist $playlist */
|
/** @var Playlist $playlist */
|
||||||
$playlist = Playlist::factory()->create();
|
$playlist = Playlist::factory()->create();
|
||||||
|
|
||||||
$this->deleteAs("api/playlist/$playlist->id", [], $playlist->user);
|
$this->deleteAs("api/playlists/$playlist->id", [], $playlist->user);
|
||||||
|
|
||||||
self::assertModelMissing($playlist);
|
self::assertModelMissing($playlist);
|
||||||
}
|
}
|
||||||
|
@ -135,7 +142,7 @@ class PlaylistTest extends TestCase
|
||||||
/** @var Playlist $playlist */
|
/** @var Playlist $playlist */
|
||||||
$playlist = Playlist::factory()->create();
|
$playlist = Playlist::factory()->create();
|
||||||
|
|
||||||
$this->deleteAs("api/playlist/$playlist->id")->assertForbidden();
|
$this->deleteAs("api/playlists/$playlist->id")->assertForbidden();
|
||||||
|
|
||||||
self::assertModelExists($playlist);
|
self::assertModelExists($playlist);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\Song;
|
use App\Models\Song;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\Interaction;
|
use App\Models\Interaction;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
|
@ -28,7 +28,7 @@ class ScrobbleTest extends TestCase
|
||||||
)
|
)
|
||||||
->once();
|
->once();
|
||||||
|
|
||||||
$this->postAs("/api/$song->id/scrobble", ['timestamp' => 100], $user)
|
$this->postAs("/api/songs/$song->id/scrobble", ['timestamp' => 100], $user)
|
||||||
->assertNoContent();
|
->assertNoContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Tests\Feature\V6;
|
namespace Tests\Feature;
|
||||||
|
|
||||||
use App\Models\Song;
|
use App\Models\Song;
|
||||||
|
|
|
@ -10,15 +10,92 @@ use Illuminate\Support\Collection;
|
||||||
|
|
||||||
class SongTest extends TestCase
|
class SongTest extends TestCase
|
||||||
{
|
{
|
||||||
public function setUp(): void
|
public const JSON_STRUCTURE = [
|
||||||
{
|
'type',
|
||||||
parent::setUp();
|
'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
|
public function testSingleUpdateAllInfoNoCompilation(): void
|
||||||
{
|
{
|
||||||
|
static::createSampleMediaSet();
|
||||||
|
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = User::factory()->admin()->create();
|
$user = User::factory()->admin()->create();
|
||||||
|
|
||||||
|
@ -57,6 +134,8 @@ class SongTest extends TestCase
|
||||||
|
|
||||||
public function testSingleUpdateSomeInfoNoCompilation(): void
|
public function testSingleUpdateSomeInfoNoCompilation(): void
|
||||||
{
|
{
|
||||||
|
static::createSampleMediaSet();
|
||||||
|
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = User::factory()->admin()->create();
|
$user = User::factory()->admin()->create();
|
||||||
|
|
||||||
|
@ -86,9 +165,11 @@ class SongTest extends TestCase
|
||||||
|
|
||||||
public function testMultipleUpdateNoCompilation(): void
|
public function testMultipleUpdateNoCompilation(): void
|
||||||
{
|
{
|
||||||
|
static::createSampleMediaSet();
|
||||||
|
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = User::factory()->admin()->create();
|
$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', [
|
$this->putAs('/api/songs', [
|
||||||
'songs' => $songIds,
|
'songs' => $songIds,
|
||||||
|
@ -124,6 +205,8 @@ class SongTest extends TestCase
|
||||||
|
|
||||||
public function testMultipleUpdateCreatingNewAlbumsAndArtists(): void
|
public function testMultipleUpdateCreatingNewAlbumsAndArtists(): void
|
||||||
{
|
{
|
||||||
|
static::createSampleMediaSet();
|
||||||
|
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = User::factory()->admin()->create();
|
$user = User::factory()->admin()->create();
|
||||||
|
|
||||||
|
@ -131,7 +214,7 @@ class SongTest extends TestCase
|
||||||
$originalSongs = Song::query()->latest()->take(3)->get();
|
$originalSongs = Song::query()->latest()->take(3)->get();
|
||||||
|
|
||||||
$this->putAs('/api/songs', [
|
$this->putAs('/api/songs', [
|
||||||
'songs' => $originalSongs->pluck('id')->toArray(),
|
'songs' => $originalSongs->pluck('id')->all(),
|
||||||
'data' => [
|
'data' => [
|
||||||
'title' => 'Foo Bar',
|
'title' => 'Foo Bar',
|
||||||
'artist_name' => 'John Cena',
|
'artist_name' => 'John Cena',
|
||||||
|
@ -162,6 +245,8 @@ class SongTest extends TestCase
|
||||||
|
|
||||||
public function testSingleUpdateAllInfoWithCompilation(): void
|
public function testSingleUpdateAllInfoWithCompilation(): void
|
||||||
{
|
{
|
||||||
|
static::createSampleMediaSet();
|
||||||
|
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = User::factory()->admin()->create();
|
$user = User::factory()->admin()->create();
|
||||||
|
|
||||||
|
@ -205,6 +290,8 @@ class SongTest extends TestCase
|
||||||
|
|
||||||
public function testUpdateSingleSongWithEmptyTrackAndDisc(): void
|
public function testUpdateSingleSongWithEmptyTrackAndDisc(): void
|
||||||
{
|
{
|
||||||
|
static::createSampleMediaSet();
|
||||||
|
|
||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user = User::factory()->admin()->create();
|
$user = User::factory()->admin()->create();
|
||||||
|
|
||||||
|
@ -231,6 +318,8 @@ class SongTest extends TestCase
|
||||||
|
|
||||||
public function testDeletingByChunk(): void
|
public function testDeletingByChunk(): void
|
||||||
{
|
{
|
||||||
|
Song::factory(5)->create();
|
||||||
|
|
||||||
self::assertNotSame(0, Song::query()->count());
|
self::assertNotSame(0, Song::query()->count());
|
||||||
$ids = Song::query()->select('id')->get()->pluck('id')->all();
|
$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();
|
$user = User::factory()->create();
|
||||||
|
|
||||||
/** @var Collection $interactions */
|
/** @var Collection $interactions */
|
||||||
$interactions = Interaction::factory(3)->create([
|
$interactions = Interaction::factory(3)->for($user)->create(['liked' => true]);
|
||||||
'user_id' => $user->id,
|
|
||||||
'liked' => true,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->interactionService->batchUnlike($interactions->pluck('song.id')->all(), $user);
|
$this->interactionService->batchUnlike($interactions->pluck('song.id')->all(), $user);
|
||||||
|
|
||||||
|
|
|
@ -39,20 +39,15 @@ abstract class TestCase extends BaseTestCase
|
||||||
$artist = Artist::factory()->create();
|
$artist = Artist::factory()->create();
|
||||||
|
|
||||||
/** @var array<Album> $albums */
|
/** @var array<Album> $albums */
|
||||||
$albums = Album::factory(3)->create([
|
$albums = Album::factory(3)->for($artist)->create();
|
||||||
'artist_id' => $artist->id,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 7-15 songs per albums
|
// 7-15 songs per albums
|
||||||
foreach ($albums as $album) {
|
foreach ($albums as $album) {
|
||||||
Song::factory(random_int(7, 15))->create([
|
Song::factory(random_int(7, 15))->for($artist)->for($album)->create();
|
||||||
'album_id' => $album->id,
|
|
||||||
'artist_id' => $artist->id,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function getNonPublicProperty($object, string $property) // @phpcs:ignore
|
protected static function getNonPublicProperty($object, string $property): mixed
|
||||||
{
|
{
|
||||||
$reflection = new ReflectionClass($object);
|
$reflection = new ReflectionClass($object);
|
||||||
$property = $reflection->getProperty($property);
|
$property = $reflection->getProperty($property);
|
||||||
|
|
|
@ -51,7 +51,7 @@ class PlaylistFolderServiceTest extends TestCase
|
||||||
/** @var PlaylistFolder $folder */
|
/** @var PlaylistFolder $folder */
|
||||||
$folder = PlaylistFolder::factory()->create();
|
$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);
|
self::assertCount(3, $folder->playlists);
|
||||||
}
|
}
|
||||||
|
@ -62,9 +62,9 @@ class PlaylistFolderServiceTest extends TestCase
|
||||||
$folder = PlaylistFolder::factory()->create();
|
$folder = PlaylistFolder::factory()->create();
|
||||||
|
|
||||||
/** @var Collection|array<array-key, Playlist> $playlists */
|
/** @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);
|
self::assertCount(0, $folder->playlists);
|
||||||
|
|
||||||
|
|
|
@ -53,11 +53,8 @@ class SpotifyServiceTest extends TestCase
|
||||||
|
|
||||||
public function testTryGetAlbumImage(): void
|
public function testTryGetAlbumImage(): void
|
||||||
{
|
{
|
||||||
/** @var Artist $artist */
|
|
||||||
$artist = Artist::factory(['name' => 'Foo'])->create();
|
|
||||||
|
|
||||||
/** @var Album $album */
|
/** @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
|
$this->client
|
||||||
->shouldReceive('search')
|
->shouldReceive('search')
|
||||||
|
|
Loading…
Reference in a new issue