mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
feat: move non-API routes out of API namespace
This commit is contained in:
parent
0b22627dd1
commit
84b05c449f
30 changed files with 301 additions and 171 deletions
|
@ -2,83 +2,24 @@
|
|||
|
||||
namespace App\Http\Controllers\API;
|
||||
|
||||
use App\Http\Requests\API\LastfmCallbackRequest;
|
||||
use App\Http\Requests\API\LastfmSetSessionKeyRequest;
|
||||
use App\Models\User;
|
||||
use App\Services\LastfmService;
|
||||
use App\Services\TokenManager;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
/**
|
||||
* @group Last.fm integration
|
||||
*/
|
||||
class LastfmController extends Controller
|
||||
{
|
||||
private $lastfmService;
|
||||
private $tokenManager;
|
||||
|
||||
/** @var User */
|
||||
private $currentUser;
|
||||
|
||||
public function __construct(LastfmService $lastfmService, TokenManager $tokenManager, ?Authenticatable $currentUser)
|
||||
public function __construct(?Authenticatable $currentUser)
|
||||
{
|
||||
$this->lastfmService = $lastfmService;
|
||||
$this->tokenManager = $tokenManager;
|
||||
$this->currentUser = $currentUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to Last.fm
|
||||
*
|
||||
* [Connect](https://www.last.fm/api/authentication) the current user to Last.fm.
|
||||
* This is actually NOT an API request. The application should instead redirect the current user to this route,
|
||||
* which will send them to Last.fm for authentication. After authentication is successful, the user will be
|
||||
* redirected back to `lastfm/callback?token=<Last.fm token>`.
|
||||
*
|
||||
* @queryParam jwt-token required The JWT token of the user. (Deprecated. Use api_token instead).
|
||||
* @queryParam api_token required Authentication token of the current user.
|
||||
* @response []
|
||||
*
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
abort_unless(
|
||||
$this->lastfmService->enabled(),
|
||||
Response::HTTP_NOT_IMPLEMENTED,
|
||||
'Koel is not configured to use with Last.fm yet.'
|
||||
);
|
||||
|
||||
$callbackUrl = urlencode(sprintf(
|
||||
'%s?api_token=%s',
|
||||
route('lastfm.callback'),
|
||||
request('api_token')
|
||||
));
|
||||
|
||||
$url = sprintf('https://www.last.fm/api/auth/?api_key=%s&cb=%s', $this->lastfmService->getKey(), $callbackUrl);
|
||||
|
||||
return redirect($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve the callback request from Last.fm.
|
||||
*/
|
||||
public function callback(LastfmCallbackRequest $request)
|
||||
{
|
||||
$this->currentUser = $this->tokenManager->getUserFromPlainTextToken($request->api_token);
|
||||
abort_unless((bool) $this->currentUser, Response::HTTP_UNAUTHORIZED);
|
||||
|
||||
$sessionKey = $this->lastfmService->getSessionKey($request->token);
|
||||
abort_unless($sessionKey, Response::HTTP_INTERNAL_SERVER_ERROR, 'Invalid token key.');
|
||||
|
||||
$this->currentUser->savePreference('lastfm_session_key', $sessionKey);
|
||||
|
||||
return view('api.lastfm.callback');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Last.fm session key
|
||||
*
|
||||
|
@ -98,7 +39,7 @@ class LastfmController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* Disconnect the current user from Last.fm.
|
||||
* Disconnect the current user from Last.fm
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
|
|
|
@ -29,7 +29,7 @@ class PlaylistController extends Controller
|
|||
public function __construct(
|
||||
PlaylistRepository $playlistRepository,
|
||||
SmartPlaylistService $smartPlaylistService,
|
||||
Authenticatable $currentUser
|
||||
?Authenticatable $currentUser
|
||||
) {
|
||||
$this->playlistRepository = $playlistRepository;
|
||||
$this->smartPlaylistService = $smartPlaylistService;
|
||||
|
@ -88,7 +88,7 @@ class PlaylistController extends Controller
|
|||
*/
|
||||
public function update(Request $request, Playlist $playlist)
|
||||
{
|
||||
abort_unless($this->currentUser->can('owner', $playlist), Response::HTTP_FORBIDDEN);
|
||||
$this->authorize('owner', $playlist);
|
||||
|
||||
$playlist->update($request->only('name', 'rules'));
|
||||
|
||||
|
|
|
@ -2,65 +2,28 @@
|
|||
|
||||
namespace App\Http\Controllers\API;
|
||||
|
||||
use App\Factories\StreamerFactory;
|
||||
use App\Http\Requests\API\SongPlayRequest;
|
||||
use App\Http\Requests\API\SongUpdateRequest;
|
||||
use App\Models\Song;
|
||||
use App\Repositories\AlbumRepository;
|
||||
use App\Repositories\ArtistRepository;
|
||||
use App\Services\MediaInformationService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Routing\Redirector;
|
||||
|
||||
/**
|
||||
* @group 3. Song interactions
|
||||
*/
|
||||
class SongController extends Controller
|
||||
{
|
||||
private $mediaInformationService;
|
||||
private $streamerFactory;
|
||||
private $artistRepository;
|
||||
private $albumRepository;
|
||||
|
||||
public function __construct(
|
||||
MediaInformationService $mediaInformationService,
|
||||
StreamerFactory $streamerFactory,
|
||||
ArtistRepository $artistRepository,
|
||||
AlbumRepository $albumRepository
|
||||
) {
|
||||
$this->mediaInformationService = $mediaInformationService;
|
||||
$this->streamerFactory = $streamerFactory;
|
||||
$this->artistRepository = $artistRepository;
|
||||
$this->albumRepository = $albumRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a song
|
||||
*
|
||||
* The GET request to play/stream a song. By default Koel will serve the file as-is, unless it's a FLAC.
|
||||
* If the value of `transcode` is truthy, Koel will attempt to transcode the file into `bitRate`kbps using ffmpeg.
|
||||
*
|
||||
* @response {}
|
||||
*
|
||||
* @queryParam jwt-token required The JWT token.
|
||||
*
|
||||
* @see https://github.com/phanan/koel/wiki#streaming-music
|
||||
*
|
||||
* @param bool|null $transcode Whether to force transcoding the song.
|
||||
* If this is omitted, by default Koel will transcode FLAC.
|
||||
* @param int|null $bitRate The target bit rate to transcode, defaults to OUTPUT_BIT_RATE.
|
||||
* Only taken into account if $transcode is truthy.
|
||||
*
|
||||
* @return RedirectResponse|Redirector
|
||||
*/
|
||||
public function play(SongPlayRequest $request, Song $song, ?bool $transcode = null, ?int $bitRate = null)
|
||||
{
|
||||
return $this->streamerFactory
|
||||
->createStreamer($song, $transcode, $bitRate, floatval($request->time))
|
||||
->stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update song information
|
||||
*
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API\Download;
|
||||
namespace App\Http\Controllers\Download;
|
||||
|
||||
use App\Models\Album;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API\Download;
|
||||
namespace App\Http\Controllers\Download;
|
||||
|
||||
use App\Models\Artist;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API\Download;
|
||||
namespace App\Http\Controllers\Download;
|
||||
|
||||
use App\Http\Controllers\API\Controller as BaseController;
|
||||
use App\Services\DownloadService;
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API\Download;
|
||||
namespace App\Http\Controllers\Download;
|
||||
|
||||
use App\Http\Requests\API\Download\Request;
|
||||
use App\Http\Requests\Download\Request;
|
||||
use App\Repositories\InteractionRepository;
|
||||
use App\Services\DownloadService;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API\Download;
|
||||
namespace App\Http\Controllers\Download;
|
||||
|
||||
use App\Models\Playlist;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API\Download;
|
||||
namespace App\Http\Controllers\Download;
|
||||
|
||||
use App\Http\Requests\API\Download\SongRequest;
|
||||
use App\Http\Requests\Download\SongRequest;
|
||||
use App\Repositories\SongRepository;
|
||||
use App\Services\DownloadService;
|
||||
|
79
app/Http/Controllers/LastfmController.php
Normal file
79
app/Http/Controllers/LastfmController.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\API\LastfmCallbackRequest;
|
||||
use App\Models\User;
|
||||
use App\Services\LastfmService;
|
||||
use App\Services\TokenManager;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
/**
|
||||
* @group Last.fm integration
|
||||
*/
|
||||
class LastfmController extends Controller
|
||||
{
|
||||
private $lastfmService;
|
||||
private $tokenManager;
|
||||
|
||||
/** @var User */
|
||||
private $currentUser;
|
||||
|
||||
public function __construct(LastfmService $lastfmService, TokenManager $tokenManager, ?Authenticatable $currentUser)
|
||||
{
|
||||
$this->lastfmService = $lastfmService;
|
||||
$this->tokenManager = $tokenManager;
|
||||
$this->currentUser = $currentUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to Last.fm
|
||||
*
|
||||
* [Connect](https://www.last.fm/api/authentication) the current user to Last.fm.
|
||||
* This is actually NOT an API request. The application should instead redirect the current user to this route,
|
||||
* which will send them to Last.fm for authentication. After authentication is successful, the user will be
|
||||
* redirected back to `lastfm/callback?token=<Last.fm token>`.
|
||||
*
|
||||
* @queryParam api_token required Authentication token of the current user.
|
||||
* @response []
|
||||
*
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function connect()
|
||||
{
|
||||
abort_unless(
|
||||
$this->lastfmService->enabled(),
|
||||
Response::HTTP_NOT_IMPLEMENTED,
|
||||
'Koel is not configured to use with Last.fm yet.'
|
||||
);
|
||||
|
||||
$callbackUrl = urlencode(sprintf(
|
||||
'%s?api_token=%s',
|
||||
route('lastfm.callback'),
|
||||
// for enhanced security, create a temporary token that can be deleted later
|
||||
$this->tokenManager->createToken($this->currentUser)->plainTextToken
|
||||
));
|
||||
|
||||
$url = sprintf('https://www.last.fm/api/auth/?api_key=%s&cb=%s', $this->lastfmService->getKey(), $callbackUrl);
|
||||
|
||||
return redirect($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve the callback request from Last.fm.
|
||||
*/
|
||||
public function callback(LastfmCallbackRequest $request)
|
||||
{
|
||||
$sessionKey = $this->lastfmService->getSessionKey($request->token);
|
||||
abort_unless($sessionKey, Response::HTTP_INTERNAL_SERVER_ERROR, 'Invalid token key.');
|
||||
|
||||
$this->currentUser->savePreference('lastfm_session_key', $sessionKey);
|
||||
|
||||
// delete the tmp. token we created earlier
|
||||
$this->tokenManager->deleteTokenByPlainTextToken($request->api_token);
|
||||
|
||||
return view('lastfm.callback');
|
||||
}
|
||||
}
|
48
app/Http/Controllers/PlayController.php
Normal file
48
app/Http/Controllers/PlayController.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Factories\StreamerFactory;
|
||||
use App\Http\Requests\SongPlayRequest;
|
||||
use App\Models\Song;
|
||||
use App\Services\TokenManager;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Routing\Redirector;
|
||||
|
||||
class PlayController extends Controller
|
||||
{
|
||||
private $tokenManager;
|
||||
private $streamerFactory;
|
||||
|
||||
public function __construct(TokenManager $tokenManager, StreamerFactory $streamerFactory)
|
||||
{
|
||||
$this->tokenManager = $tokenManager;
|
||||
$this->streamerFactory = $streamerFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Play a song
|
||||
*
|
||||
* The GET request to play/stream a song. By default Koel will serve the file as-is, unless it's a FLAC.
|
||||
* If the value of `transcode` is truthy, Koel will attempt to transcode the file into `bitRate`kbps using ffmpeg.
|
||||
*
|
||||
* @response {}
|
||||
*
|
||||
* @queryParam api_token required The API token.
|
||||
*
|
||||
* @see https://github.com/phanan/koel/wiki#streaming-music
|
||||
*
|
||||
* @param bool|null $transcode Whether to force transcoding the song.
|
||||
* If this is omitted, by default Koel will transcode FLAC.
|
||||
* @param int|null $bitRate The target bit rate to transcode, defaults to OUTPUT_BIT_RATE.
|
||||
* Only taken into account if $transcode is truthy.
|
||||
*
|
||||
* @return RedirectResponse|Redirector
|
||||
*/
|
||||
public function show(SongPlayRequest $request, Song $song, ?bool $transcode = null, ?int $bitRate = null)
|
||||
{
|
||||
return $this->streamerFactory
|
||||
->createStreamer($song, $transcode, $bitRate, floatval($request->time))
|
||||
->stream();
|
||||
}
|
||||
}
|
|
@ -1,19 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API;
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\API\ViewSongOnITunesRequest;
|
||||
use App\Models\Album;
|
||||
use App\Services\iTunesService;
|
||||
use App\Services\TokenManager;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class iTunesController extends Controller
|
||||
{
|
||||
private $iTunesService;
|
||||
private $tokenManager;
|
||||
|
||||
public function __construct(iTunesService $iTunesService)
|
||||
public function __construct(iTunesService $iTunesService, TokenManager $tokenManager)
|
||||
{
|
||||
$this->iTunesService = $iTunesService;
|
||||
$this->tokenManager = $tokenManager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,6 +27,11 @@ class iTunesController extends Controller
|
|||
*/
|
||||
public function viewSong(ViewSongOnITunesRequest $request, Album $album)
|
||||
{
|
||||
abort_unless(
|
||||
(bool) $this->tokenManager->getUserFromPlainTextToken($request->api_token),
|
||||
Response::HTTP_UNAUTHORIZED
|
||||
);
|
||||
|
||||
$url = $this->iTunesService->getTrackUrl($request->q, $album->name, $album->artist->name);
|
||||
abort_unless($url, 404, "Koel can't find such a song on iTunes Store.");
|
||||
|
|
@ -36,7 +36,7 @@ class Kernel extends HttpKernel
|
|||
*/
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
// Koel doesn't use any default Laravel middleware for web.
|
||||
'bindings',
|
||||
],
|
||||
'api' => [
|
||||
'throttle:60,1',
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\API;
|
||||
|
||||
/**
|
||||
* @property float $time
|
||||
*/
|
||||
class SongPlayRequest extends Request
|
||||
{
|
||||
}
|
|
@ -4,6 +4,7 @@ namespace App\Http\Requests\API;
|
|||
|
||||
/**
|
||||
* @property string $q
|
||||
* @property string api_token
|
||||
*/
|
||||
class ViewSongOnITunesRequest extends Request
|
||||
{
|
||||
|
@ -11,6 +12,7 @@ class ViewSongOnITunesRequest extends Request
|
|||
{
|
||||
return [
|
||||
'q' => 'required',
|
||||
'api_token' => 'required',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\API\Download;
|
||||
namespace App\Http\Requests\Download;
|
||||
|
||||
use App\Http\Requests\API\Request as BaseRequest;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\API\Download;
|
||||
namespace App\Http\Requests\Download;
|
||||
|
||||
/**
|
||||
* @property array $songs
|
11
app/Http/Requests/SongPlayRequest.php
Normal file
11
app/Http/Requests/SongPlayRequest.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
/**
|
||||
* @property float $time
|
||||
* @property string $api_token
|
||||
*/
|
||||
class SongPlayRequest extends AbstractRequest
|
||||
{
|
||||
}
|
|
@ -29,6 +29,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
|||
* @method static self firstOrCreate(array $where, array $params = [])
|
||||
* @method static self|null find(int $id)
|
||||
* @method static Builder where(...$params)
|
||||
* @method static self first()
|
||||
*/
|
||||
class Album extends Model
|
||||
{
|
||||
|
|
|
@ -25,6 +25,7 @@ use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
|||
* @method static self find(int $id)
|
||||
* @method static self firstOrCreate(array $where, array $params = [])
|
||||
* @method static Builder where(...$params)
|
||||
* @method static self first()
|
||||
*/
|
||||
class Artist extends Model
|
||||
{
|
||||
|
|
|
@ -34,6 +34,7 @@ use Illuminate\Support\Collection;
|
|||
* @method static EloquentCollection orderBy(...$args)
|
||||
* @method static int count()
|
||||
* @method static self|null find($id)
|
||||
* @method static Builder take(int $count)
|
||||
*/
|
||||
class Song extends Model
|
||||
{
|
||||
|
|
|
@ -6,7 +6,10 @@ use App\Models\Playlist;
|
|||
use App\Models\User;
|
||||
use App\Policies\PlaylistPolicy;
|
||||
use App\Policies\UserPolicy;
|
||||
use App\Services\TokenManager;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
@ -26,5 +29,12 @@ class AuthServiceProvider extends ServiceProvider
|
|||
public function boot()
|
||||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
Auth::viaRequest('token-via-query-parameter', static function (Request $request): ?User {
|
||||
/** @var TokenManager $tokenManager */
|
||||
$tokenManager = app(TokenManager::class);
|
||||
|
||||
return $tokenManager->getUserFromPlainTextToken($request->api_token ?: '');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,15 @@ class TokenManager
|
|||
$user->tokens()->delete();
|
||||
}
|
||||
|
||||
public function deleteTokenByPlainTextToken(string $plainTextToken): void
|
||||
{
|
||||
$token = PersonalAccessToken::findToken($plainTextToken);
|
||||
|
||||
if ($token) {
|
||||
$token->delete();
|
||||
}
|
||||
}
|
||||
|
||||
public function getUserFromPlainTextToken(string $plainTextToken): ?User
|
||||
{
|
||||
$token = PersonalAccessToken::findToken($plainTextToken);
|
||||
|
|
|
@ -33,7 +33,7 @@ return [
|
|||
*/
|
||||
'guards' => [
|
||||
'web' => [
|
||||
'driver' => 'session',
|
||||
'driver' => 'token-via-query-parameter',
|
||||
'provider' => 'users',
|
||||
],
|
||||
'api' => [
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit b19f6631881a7a3062563a93de2f4c6816f8822b
|
||||
Subproject commit af31eddbc2ae71c46d403f985c06e79ad52ae11b
|
|
@ -1,5 +1,5 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Authentication successful!</title>
|
||||
<meta charset="utf-8">
|
|
@ -29,10 +29,7 @@ Route::group(['namespace' => 'API'], function () {
|
|||
|
||||
Route::post('settings', 'SettingController@store');
|
||||
|
||||
Route::get('{song}/play/{transcode?}/{bitrate?}', 'SongController@play')->name('song.play');
|
||||
Route::post('{song}/scrobble/{timestamp}', 'ScrobbleController@store')->where([
|
||||
'timestamp' => '\d+',
|
||||
]);
|
||||
Route::post('{song}/scrobble/{timestamp}', 'ScrobbleController@store')->where(['timestamp' => '\d+']);
|
||||
Route::put('songs', 'SongController@update');
|
||||
|
||||
Route::resource('upload', 'UploadController');
|
||||
|
@ -65,15 +62,6 @@ Route::group(['namespace' => 'API'], function () {
|
|||
Route::get('youtube/search/song/{song}', 'YouTubeController@searchVideosRelatedToSong');
|
||||
}
|
||||
|
||||
// Download routes
|
||||
Route::group(['prefix' => 'download', 'namespace' => 'Download'], function () {
|
||||
Route::get('songs', 'SongController@show');
|
||||
Route::get('album/{album}', 'AlbumController@show');
|
||||
Route::get('artist/{artist}', 'ArtistController@show');
|
||||
Route::get('playlist/{playlist}', 'PlaylistController@show');
|
||||
Route::get('favorites', 'FavoritesController@show');
|
||||
});
|
||||
|
||||
// Info routes
|
||||
Route::group(['namespace' => 'MediaInformation'], function () {
|
||||
Route::get('album/{album}/info', 'AlbumController@show');
|
||||
|
@ -87,11 +75,6 @@ Route::group(['namespace' => 'API'], function () {
|
|||
Route::put('artist/{artist}/image', 'ArtistImageController@update');
|
||||
|
||||
Route::get('album/{album}/thumbnail', 'AlbumThumbnailController@get');
|
||||
|
||||
// iTunes routes
|
||||
if (iTunes::used()) {
|
||||
Route::get('itunes/song/{album}', 'iTunesController@viewSong')->name('iTunes.viewSong');
|
||||
}
|
||||
});
|
||||
|
||||
Route::group(['middleware' => 'os.auth', 'prefix' => 'os', 'namespace' => 'ObjectStorage'], function () {
|
||||
|
|
|
@ -1,22 +1,34 @@
|
|||
<?php
|
||||
|
||||
use App\Facades\iTunes;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/', function () {
|
||||
Route::get('/', static function () {
|
||||
return view('index');
|
||||
});
|
||||
|
||||
// Some backward compatibilities.
|
||||
Route::get('/♫', function () {
|
||||
return redirect('/');
|
||||
});
|
||||
|
||||
Route::get('/remote', function () {
|
||||
Route::get('/remote', static function () {
|
||||
return view('remote');
|
||||
});
|
||||
|
||||
Route::get('/lastfm/connect', 'API\LastfmController@connect')
|
||||
->name('lastfm.connect');
|
||||
Route::group(['middleware' => 'auth'], static function (): void {
|
||||
Route::get('/{song}/play/{transcode?}/{bitrate?}', 'PlayController@show')
|
||||
->name('song.play');
|
||||
|
||||
Route::get('/lastfm/callback', 'API\LastfmController@callback')
|
||||
->name('lastfm.callback');
|
||||
Route::group(['prefix' => 'lastfm'], static function (): void {
|
||||
Route::get('connect', 'LastfmController@connect')->name('lastfm.connect');
|
||||
Route::get('callback', 'LastfmController@callback')->name('lastfm.callback');
|
||||
});
|
||||
|
||||
if (iTunes::used()) {
|
||||
Route::get('itunes/song/{album}', 'iTunesController@viewSong')->name('iTunes.viewSong');
|
||||
}
|
||||
|
||||
Route::group(['prefix' => 'download', 'namespace' => 'Download'], static function (): void {
|
||||
Route::get('songs', 'SongController@show');
|
||||
Route::get('album/{album}', 'AlbumController@show');
|
||||
Route::get('artist/{artist}', 'ArtistController@show');
|
||||
Route::get('playlist/{playlist}', 'PlaylistController@show');
|
||||
Route::get('favorites', 'FavoritesController@show');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ use App\Models\User;
|
|||
use App\Repositories\InteractionRepository;
|
||||
use App\Services\DownloadService;
|
||||
use Exception;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Collection;
|
||||
use Mockery;
|
||||
use Mockery\MockInterface;
|
||||
|
@ -27,14 +28,30 @@ class DownloadTest extends TestCase
|
|||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
static::createSampleMediaSet();
|
||||
$this->downloadService = static::mockIocDependency(DownloadService::class);
|
||||
}
|
||||
|
||||
public function testNonLoggedInUserCannotDownload(): void
|
||||
{
|
||||
$song = Song::first();
|
||||
|
||||
$this->downloadService
|
||||
->shouldReceive('from')
|
||||
->never();
|
||||
|
||||
$this->get("download/songs?songs[]={$song->id}")
|
||||
->assertRedirect('/');
|
||||
}
|
||||
|
||||
public function testDownloadOneSong(): void
|
||||
{
|
||||
$song = Song::first();
|
||||
|
||||
/** @var User $user */
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
$this->downloadService
|
||||
->shouldReceive('from')
|
||||
->once()
|
||||
|
@ -43,18 +60,21 @@ class DownloadTest extends TestCase
|
|||
}))
|
||||
->andReturn($this->mediaPath.'/blank.mp3');
|
||||
|
||||
$this->getAsUser("api/download/songs?songs[]={$song->id}")
|
||||
$this->get("download/songs?songs[]={$song->id}&api_token=".$user->createToken('Koel')->plainTextToken)
|
||||
->assertOk();
|
||||
}
|
||||
|
||||
public function testDownloadMultipleSongs(): void
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
$songs = Song::take(2)->orderBy('id')->get();
|
||||
|
||||
$this->downloadService
|
||||
->shouldReceive('from')
|
||||
->once()
|
||||
->with(Mockery::on(static function (Collection $retrievedSongs) use ($songs) {
|
||||
->with(Mockery::on(static function (Collection $retrievedSongs) use ($songs): bool {
|
||||
$retrievedIds = $retrievedSongs->pluck('id')->toArray();
|
||||
$requestedIds = $songs->pluck('id')->toArray();
|
||||
|
||||
|
@ -62,7 +82,10 @@ class DownloadTest extends TestCase
|
|||
}))
|
||||
->andReturn($this->mediaPath.'/blank.mp3'); // should be a zip file, but we're testing here…
|
||||
|
||||
$this->getAsUser("api/download/songs?songs[]={$songs[0]->id}&songs[]={$songs[1]->id}")
|
||||
$this->get(
|
||||
"download/songs?songs[]={$songs[0]->id}&songs[]={$songs[1]->id}&api_token="
|
||||
.$user->createToken('Koel')->plainTextToken
|
||||
)
|
||||
->assertOk();
|
||||
}
|
||||
|
||||
|
@ -70,15 +93,18 @@ class DownloadTest extends TestCase
|
|||
{
|
||||
$album = Album::first();
|
||||
|
||||
/** @var User $user */
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
$this->downloadService
|
||||
->shouldReceive('from')
|
||||
->once()
|
||||
->with(Mockery::on(static function (Album $retrievedAlbum) use ($album) {
|
||||
->with(Mockery::on(static function (Album $retrievedAlbum) use ($album): bool {
|
||||
return $retrievedAlbum->id === $album->id;
|
||||
}))
|
||||
->andReturn($this->mediaPath.'/blank.mp3');
|
||||
|
||||
$this->getAsUser("api/download/album/{$album->id}")
|
||||
$this->get("download/album/{$album->id}?api_token=".$user->createToken('Koel')->plainTextToken)
|
||||
->assertOk();
|
||||
}
|
||||
|
||||
|
@ -86,15 +112,18 @@ class DownloadTest extends TestCase
|
|||
{
|
||||
$artist = Artist::first();
|
||||
|
||||
/** @var User $user */
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
$this->downloadService
|
||||
->shouldReceive('from')
|
||||
->once()
|
||||
->with(Mockery::on(static function (Artist $retrievedArtist) use ($artist) {
|
||||
->with(Mockery::on(static function (Artist $retrievedArtist) use ($artist): bool {
|
||||
return $retrievedArtist->id === $artist->id;
|
||||
}))
|
||||
->andReturn($this->mediaPath.'/blank.mp3');
|
||||
|
||||
$this->getAsUser("api/download/artist/{$artist->id}")
|
||||
$this->get("download/artist/{$artist->id}?api_token=".$user->createToken('Koel')->plainTextToken)
|
||||
->assertOk();
|
||||
}
|
||||
|
||||
|
@ -110,13 +139,13 @@ class DownloadTest extends TestCase
|
|||
|
||||
$this->downloadService
|
||||
->shouldReceive('from')
|
||||
->with(Mockery::on(static function (Playlist $retrievedPlaylist) use ($playlist) {
|
||||
->with(Mockery::on(static function (Playlist $retrievedPlaylist) use ($playlist): bool {
|
||||
return $retrievedPlaylist->id === $playlist->id;
|
||||
}))
|
||||
->once()
|
||||
->andReturn($this->mediaPath.'/blank.mp3');
|
||||
|
||||
$this->getAsUser("api/download/playlist/{$playlist->id}", $user)
|
||||
$this->get("download/playlist/{$playlist->id}?api_token=".$user->createToken('Koel')->plainTextToken)
|
||||
->assertOk();
|
||||
}
|
||||
|
||||
|
@ -125,8 +154,11 @@ class DownloadTest extends TestCase
|
|||
/** @var Playlist $playlist */
|
||||
$playlist = factory(Playlist::class)->create();
|
||||
|
||||
$this->getAsUser("api/download/playlist/{$playlist->id}")
|
||||
->assertStatus(403);
|
||||
/** @var User $user */
|
||||
$user = factory(User::class)->create();
|
||||
|
||||
$this->get("download/playlist/{$playlist->id}?api_token=".$user->createToken('Koel')->plainTextToken)
|
||||
->assertStatus(Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
public function testDownloadFavorites(): void
|
||||
|
@ -138,7 +170,7 @@ class DownloadTest extends TestCase
|
|||
static::mockIocDependency(InteractionRepository::class)
|
||||
->shouldReceive('getUserFavorites')
|
||||
->once()
|
||||
->with(Mockery::on(static function (User $retrievedUser) use ($user) {
|
||||
->with(Mockery::on(static function (User $retrievedUser) use ($user): bool {
|
||||
return $retrievedUser->id === $user->id;
|
||||
}))
|
||||
->andReturn($favorites);
|
||||
|
@ -149,7 +181,7 @@ class DownloadTest extends TestCase
|
|||
->once()
|
||||
->andReturn($this->mediaPath.'/blank.mp3');
|
||||
|
||||
$this->getAsUser('api/download/favorites', $user)
|
||||
->assertStatus(200);
|
||||
$this->get('download/favorites?api_token='.$user->createToken('Koel')->plainTextToken)
|
||||
->assertOk();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ use GuzzleHttp\Client;
|
|||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Contracts\Cache\Repository as Cache;
|
||||
use Illuminate\Log\Logger;
|
||||
use Laravel\Sanctum\NewAccessToken;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
use Mockery;
|
||||
|
||||
class LastfmTest extends TestCase
|
||||
|
@ -40,13 +42,49 @@ class LastfmTest extends TestCase
|
|||
$user = factory(User::class)->create();
|
||||
$token = $user->createToken('Koel')->plainTextToken;
|
||||
|
||||
$temporaryToken = Mockery::mock(NewAccessToken::class);
|
||||
$temporaryToken->plainTextToken = 'tmp-token';
|
||||
|
||||
$tokenManager = static::mockIocDependency(TokenManager::class);
|
||||
|
||||
$tokenManager->shouldReceive('getUserFromPlainTextToken')
|
||||
->with($token)
|
||||
->andReturn($user);
|
||||
|
||||
$tokenManager->shouldReceive('createToken')
|
||||
->once()
|
||||
->with($user)
|
||||
->andReturn($temporaryToken);
|
||||
|
||||
$this->get('lastfm/connect?api_token='.$token)
|
||||
->assertRedirect(
|
||||
'https://www.last.fm/api/auth/?api_key=foo&cb=http%3A%2F%2Flocalhost%2Flastfm%2Fcallback%3Fapi_token%3D'
|
||||
.urlencode($token)
|
||||
'https://www.last.fm/api/auth/?api_key=foo&cb=http%3A%2F%2Flocalhost%2Flastfm%2Fcallback%3Fapi_token%3Dtmp-token'
|
||||
);
|
||||
}
|
||||
|
||||
public function testCallback(): void
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = factory(User::class)->create();
|
||||
$token = $user->createToken('Koel')->plainTextToken;
|
||||
|
||||
self::assertNotNull(PersonalAccessToken::findToken($token));
|
||||
|
||||
$lastfm = static::mockIocDependency(LastfmService::class);
|
||||
|
||||
$lastfm->shouldReceive('getSessionKey')
|
||||
->with('lastfm-token')
|
||||
->once()
|
||||
->andReturn('my-session-key');
|
||||
|
||||
$this->get('lastfm/callback?token=lastfm-token&api_token='.urlencode($token))
|
||||
->assertOk();
|
||||
|
||||
self::assertSame('my-session-key', $user->refresh()->lastfm_session_key);
|
||||
// make sure the user's api token is deleted
|
||||
self::assertNull(PersonalAccessToken::findToken($token));
|
||||
}
|
||||
|
||||
public function testRetrieveAndStoreSessionKey(): void
|
||||
{
|
||||
/** @var User $user */
|
||||
|
|
Loading…
Reference in a new issue