From 12e4bd473ff79722c386c759751e08c7bf255b32 Mon Sep 17 00:00:00 2001 From: An Phan Date: Wed, 30 Dec 2015 11:14:47 +0700 Subject: [PATCH] Basically completed jwt --- app/Http/Controllers/API/LastfmController.php | 13 +- app/Http/Controllers/API/SongController.php | 22 +++ app/Http/Controllers/Auth/AuthController.php | 68 ------- .../Controllers/Auth/PasswordController.php | 30 ---- app/Http/Kernel.php | 2 +- app/Http/Middleware/BaseMiddleware.php | 19 ++ app/Http/Middleware/GetUserFromToken.php | 39 ++++ app/Http/Requests/API/UserLoginRequest.php | 29 +++ app/Http/routes.php | 66 +++---- app/JWTAuth.php | 29 +++ app/Listeners/JWTEventListener.php | 27 +++ config/jwt.php | 168 ++++++++++++++++++ resources/assets/js/app.vue | 13 ++ .../components/main-wrapper/extra/index.vue | 25 ++- .../main-wrapper/main-content/albums.vue | 5 + .../main-wrapper/main-content/artists.vue | 5 + .../main-wrapper/main-content/profile.vue | 7 +- .../js/components/site-footer/index.vue | 6 + .../js/components/site-header/search-form.vue | 4 + .../js/components/site-header/user-badge.vue | 6 +- resources/assets/js/main.js | 12 +- resources/assets/js/mixins/infinite-scroll.js | 6 + resources/assets/js/services/http.js | 8 +- resources/assets/js/services/playback.js | 3 +- resources/assets/js/stores/favorite.js | 4 + resources/assets/js/stores/queue.js | 2 +- resources/assets/js/stores/shared.js | 32 ++-- resources/assets/js/stores/song.js | 2 + tests/LastfmTest.php | 7 +- 29 files changed, 493 insertions(+), 166 deletions(-) delete mode 100644 app/Http/Controllers/Auth/AuthController.php delete mode 100644 app/Http/Controllers/Auth/PasswordController.php create mode 100644 app/Http/Middleware/BaseMiddleware.php create mode 100644 app/Http/Middleware/GetUserFromToken.php create mode 100644 app/Http/Requests/API/UserLoginRequest.php create mode 100644 app/JWTAuth.php create mode 100644 app/Listeners/JWTEventListener.php create mode 100644 config/jwt.php diff --git a/app/Http/Controllers/API/LastfmController.php b/app/Http/Controllers/API/LastfmController.php index 5ad2f2b0..5523453b 100644 --- a/app/Http/Controllers/API/LastfmController.php +++ b/app/Http/Controllers/API/LastfmController.php @@ -6,6 +6,7 @@ use App\Services\Lastfm; use Illuminate\Contracts\Auth\Guard; use Illuminate\Http\Request; use Illuminate\Routing\Redirector; +use Tymon\JWTAuth\JWTAuth; class LastfmController extends Controller { @@ -31,19 +32,27 @@ class LastfmController extends Controller * * @param Redirector $redirector * @param Lastfm $lastfm + * @param JWTAuth $auth * * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse */ - public function connect(Redirector $redirector, Lastfm $lastfm) + public function connect(Redirector $redirector, Lastfm $lastfm, JWTAuth $auth = null) { if (!$lastfm->enabled()) { abort(401, 'Koel is not configured to use with Last.fm yet.'); } + $auth = $auth ?: $this->app['tymon.jwt.auth']; + + // A workaround to make sure Tymon's JWTAuth get the correct token via our custom + // "jwt-token" query string instead of the default "token". + // This is due to the problem that Last.fm returns the token via "token" as well. + $auth->parseToken('', '', 'jwt-token'); + return $redirector->to( 'https://www.last.fm/api/auth/?api_key=' .$lastfm->getKey() - .'&cb='.route('lastfm.callback') + .'&cb='.urlencode(route('lastfm.callback').'?jwt-token='.$auth->getToken()) ); } diff --git a/app/Http/Controllers/API/SongController.php b/app/Http/Controllers/API/SongController.php index b2b2d084..e7dbd5ff 100644 --- a/app/Http/Controllers/API/SongController.php +++ b/app/Http/Controllers/API/SongController.php @@ -2,10 +2,32 @@ namespace App\Http\Controllers\API; +use App\Http\Streamers\PHPStreamer; +use App\Http\Streamers\XAccelRedirectStreamer; +use App\Http\Streamers\XSendFileStreamer; use App\Models\Song; class SongController extends Controller { + /** + * Play a song. + * + * @link https://github.com/phanan/koel/wiki#streaming-music + * + * @param Song $song + */ + public function play(Song $song) + { + switch (env('STREAMING_METHOD')) { + case 'x-sendfile': + return (new XSendFileStreamer($song))->stream(); + case 'x-accel-redirect': + return (new XAccelRedirectStreamer($song))->stream(); + default: + return (new PHPStreamer($song))->stream(); + } + } + /** * Get extra information about a song via Last.fm. * diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php deleted file mode 100644 index e15f4e0a..00000000 --- a/app/Http/Controllers/Auth/AuthController.php +++ /dev/null @@ -1,68 +0,0 @@ -middleware('guest', ['except' => 'getLogout']); - } - - /** - * Get a validator for an incoming registration request. - * - * @param array $data - * - * @return \Illuminate\Contracts\Validation\Validator - */ - protected function validator(array $data) - { - return Validator::make($data, [ - 'name' => 'required|max:255', - 'email' => 'required|email|max:255|unique:users', - 'password' => 'required|confirmed', - ]); - } - - /** - * Create a new user instance after a valid registration. - * - * @param array $data - * - * @return User - */ - protected function create(array $data) - { - return User::create([ - 'name' => $data['name'], - 'email' => $data['email'], - 'password' => bcrypt($data['password']), - ]); - } -} diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php deleted file mode 100644 index 9c97bbaa..00000000 --- a/app/Http/Controllers/Auth/PasswordController.php +++ /dev/null @@ -1,30 +0,0 @@ -middleware('guest'); - } -} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 0039a189..a966406c 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -5,7 +5,7 @@ namespace App\Http; use App\Http\Middleware\Authenticate; use Illuminate\Foundation\Http\Kernel as HttpKernel; use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; -use Tymon\JWTAuth\Middleware\GetUserFromToken; +use App\Http\Middleware\GetUserFromToken; class Kernel extends HttpKernel { diff --git a/app/Http/Middleware/BaseMiddleware.php b/app/Http/Middleware/BaseMiddleware.php new file mode 100644 index 00000000..c3eb9183 --- /dev/null +++ b/app/Http/Middleware/BaseMiddleware.php @@ -0,0 +1,19 @@ +auth->setRequest($request)->getToken()) { + return $this->respond('tymon.jwt.absent', 'token_not_provided', 400); + } + + try { + $user = $this->auth->authenticate($token); + } catch (TokenExpiredException $e) { + return $this->respond('tymon.jwt.expired', 'token_expired', $e->getStatusCode(), [$e]); + } catch (JWTException $e) { + return $this->respond('tymon.jwt.invalid', 'token_invalid', $e->getStatusCode(), [$e]); + } + + if (! $user) { + return $this->respond('tymon.jwt.user_not_found', 'user_not_found', 404); + } + + $this->events->fire('tymon.jwt.valid', $user); + + return $next($request); + } +} diff --git a/app/Http/Requests/API/UserLoginRequest.php b/app/Http/Requests/API/UserLoginRequest.php new file mode 100644 index 00000000..1420c908 --- /dev/null +++ b/app/Http/Requests/API/UserLoginRequest.php @@ -0,0 +1,29 @@ + 'required|email', + 'password' => 'required', + ]; + } +} diff --git a/app/Http/routes.php b/app/Http/routes.php index 2b9f6d4a..f36604b8 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -4,39 +4,41 @@ Route::get('/', function () { return view('index'); }); -Route::get('{song}/play', 'PlaybackController@play'); +Route::group(['prefix' => 'api', 'namespace' => 'API'], function () { - -Route::group(['prefix' => 'api', 'middleware' => 'jwt.auth', 'namespace' => 'API'], function () { - Route::get('/', function () { - // Just acting as a ping service. - }); - - Route::get('data', 'DataController@index'); - - Route::post('settings', 'SettingController@save'); - - Route::post('{song}/scrobble/{timestamp}', 'SongController@scrobble')->where([ - 'timestamp' => '\d+', - ]); - Route::get('{song}/info', 'SongController@getInfo'); - - Route::post('interaction/play', 'InteractionController@play'); - Route::post('interaction/like', 'InteractionController@like'); - Route::post('interaction/batch/like', 'InteractionController@batchLike'); - Route::post('interaction/batch/unlike', 'InteractionController@batchUnlike'); - - Route::resource('playlist', 'PlaylistController', ['only' => ['store', 'update', 'destroy']]); - Route::put('playlist/{playlist}/sync', 'PlaylistController@sync')->where(['playlist' => '\d+']); - - Route::resource('user', 'UserController', ['only' => ['store', 'update', 'destroy']]); Route::post('me', 'UserController@login'); - Route::put('me', 'UserController@updateProfile'); - Route::get('lastfm/connect', 'LastfmController@connect'); - Route::get('lastfm/callback', [ - 'as' => 'lastfm.callback', - 'uses' => 'LastfmController@callback', - ]); - Route::delete('lastfm/disconnect', 'LastfmController@disconnect'); + Route::group(['middleware' => 'jwt.auth'], function () { + Route::get('/', function () { + // Just acting as a ping service. + }); + + Route::get('data', 'DataController@index'); + + Route::post('settings', 'SettingController@save'); + + Route::get('{song}/play', 'SongController@play'); + Route::post('{song}/scrobble/{timestamp}', 'SongController@scrobble')->where([ + 'timestamp' => '\d+', + ]); + Route::get('{song}/info', 'SongController@getInfo'); + + Route::post('interaction/play', 'InteractionController@play'); + Route::post('interaction/like', 'InteractionController@like'); + Route::post('interaction/batch/like', 'InteractionController@batchLike'); + Route::post('interaction/batch/unlike', 'InteractionController@batchUnlike'); + + Route::resource('playlist', 'PlaylistController', ['only' => ['store', 'update', 'destroy']]); + Route::put('playlist/{playlist}/sync', 'PlaylistController@sync')->where(['playlist' => '\d+']); + + Route::resource('user', 'UserController', ['only' => ['store', 'update', 'destroy']]); + Route::put('me', 'UserController@updateProfile'); + + Route::get('lastfm/connect', 'LastfmController@connect'); + Route::get('lastfm/callback', [ + 'as' => 'lastfm.callback', + 'uses' => 'LastfmController@callback', + ]); + Route::delete('lastfm/disconnect', 'LastfmController@disconnect'); + }); }); diff --git a/app/JWTAuth.php b/app/JWTAuth.php new file mode 100644 index 00000000..4b1815bc --- /dev/null +++ b/app/JWTAuth.php @@ -0,0 +1,29 @@ +setUser($event->user); + } + + /** + * Register the listeners for the subscriber. + * + * @param Illuminate\Events\Dispatcher $events + */ + public function subscribe($events) + { + $events->listen( + 'tymon.jwt.valid', + 'App\Listeners\JWTEventListener@onValidUser' + ); + } +} diff --git a/config/jwt.php b/config/jwt.php new file mode 100644 index 00000000..a146a835 --- /dev/null +++ b/config/jwt.php @@ -0,0 +1,168 @@ + env('JWT_SECRET', 'YTAOH41t3QYSCVYuJfGKzSSJ6l1sBtvG'), + + /* + |-------------------------------------------------------------------------- + | JWT time to live + |-------------------------------------------------------------------------- + | + | Specify the length of time (in minutes) that the token will be valid for. + | Defaults to 1 hour + | + */ + + 'ttl' => 60 * 24 * 7, + + /* + |-------------------------------------------------------------------------- + | Refresh time to live + |-------------------------------------------------------------------------- + | + | Specify the length of time (in minutes) that the token can be refreshed + | within. I.E. The user can refresh their token within a 2 week window of + | the original token being created until they must re-authenticate. + | Defaults to 2 weeks + | + */ + + 'refresh_ttl' => 20160, + + /* + |-------------------------------------------------------------------------- + | JWT hashing algorithm + |-------------------------------------------------------------------------- + | + | Specify the hashing algorithm that will be used to sign the token. + | + | See here: https://github.com/namshi/jose/tree/2.2.0/src/Namshi/JOSE/Signer + | for possible values + | + */ + + 'algo' => 'HS256', + + /* + |-------------------------------------------------------------------------- + | User Model namespace + |-------------------------------------------------------------------------- + | + | Specify the full namespace to your User model. + | e.g. 'Acme\Entities\User' + | + */ + + 'user' => App\Models\User::class, + + /* + |-------------------------------------------------------------------------- + | User identifier + |-------------------------------------------------------------------------- + | + | Specify a unique property of the user that will be added as the 'sub' + | claim of the token payload. + | + */ + + 'identifier' => 'id', + + /* + |-------------------------------------------------------------------------- + | Required Claims + |-------------------------------------------------------------------------- + | + | Specify the required claims that must exist in any token. + | A TokenInvalidException will be thrown if any of these claims are not + | present in the payload. + | + */ + + 'required_claims' => ['iss', 'iat', 'exp', 'nbf', 'sub', 'jti'], + + /* + |-------------------------------------------------------------------------- + | Blacklist Enabled + |-------------------------------------------------------------------------- + | + | In order to invalidate tokens, you must have the the blacklist enabled. + | If you do not want or need this functionality, then set this to false. + | + */ + + 'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true), + + /* + |-------------------------------------------------------------------------- + | Providers + |-------------------------------------------------------------------------- + | + | Specify the various providers used throughout the package. + | + */ + + 'providers' => [ + + /* + |-------------------------------------------------------------------------- + | User Provider + |-------------------------------------------------------------------------- + | + | Specify the provider that is used to find the user based + | on the subject claim + | + */ + + 'user' => Tymon\JWTAuth\Providers\User\EloquentUserAdapter::class, + + /* + |-------------------------------------------------------------------------- + | JWT Provider + |-------------------------------------------------------------------------- + | + | Specify the provider that is used to create and decode the tokens. + | + */ + + 'jwt' => Tymon\JWTAuth\Providers\JWT\NamshiAdapter::class, + + /* + |-------------------------------------------------------------------------- + | Authentication Provider + |-------------------------------------------------------------------------- + | + | Specify the provider that is used to authenticate users. + | + */ + + 'auth' => function ($app) { + return new Tymon\JWTAuth\Providers\Auth\IlluminateAuthAdapter($app['auth']); + }, + + /* + |-------------------------------------------------------------------------- + | Storage Provider + |-------------------------------------------------------------------------- + | + | Specify the provider that is used to store tokens in the blacklist + | + */ + + 'storage' => function ($app) { + return new Tymon\JWTAuth\Providers\Storage\IlluminateCacheAdapter($app['cache']); + } + + ] + +]; diff --git a/resources/assets/js/app.vue b/resources/assets/js/app.vue index 288a7adf..ac7bf750 100644 --- a/resources/assets/js/app.vue +++ b/resources/assets/js/app.vue @@ -29,6 +29,7 @@ import loginForm from './components/auth/login-form.vue'; import sharedStore from './stores/shared'; + import queueStore from './stores/queue'; import preferenceStore from './stores/preference'; import playback from './services/playback'; import ls from './services/ls'; @@ -203,6 +204,18 @@ setOverlayDimissable() { this.overlayState.dismissable = true; }, + + /** + * Log the current user out and reset the application state. + */ + logout() { + ls.remove('jwt-token'); + this.authenticated = false; + playback.stop(); + queueStore.clear(); + this.loadMainView('queue'); + this.$broadcast('koel:teardown'); + }, }, events: { diff --git a/resources/assets/js/components/main-wrapper/extra/index.vue b/resources/assets/js/components/main-wrapper/extra/index.vue index 9223278e..87dea364 100644 --- a/resources/assets/js/components/main-wrapper/extra/index.vue +++ b/resources/assets/js/components/main-wrapper/extra/index.vue @@ -48,6 +48,19 @@ } }, + methods: { + /** + * Reset all applicable child components' states + */ + resetChildrenStates() { + _.each(this.$refs, child => { + if (typeof child.resetState === 'function') { + child.resetState(); + } + }); + }, + }, + events: { 'main-content-view:load': function (view) { // Hide the panel away if a main view is triggered on mobile. @@ -57,17 +70,17 @@ }, 'song:play': function (song) { - // Reset all applicable child components' states - _.each(this.$refs, child => { - if (typeof child.resetState === 'function') { - child.resetState(); - } - }); + this.resetChildrenStates(); songStore.getInfo(song, () => { this.$broadcast('song:info-loaded', song); }); }, + + 'koel:teardown': function () { + this.currentView = 'lyrics'; + this.resetChildrenStates(); + }, }, }; diff --git a/resources/assets/js/components/main-wrapper/main-content/albums.vue b/resources/assets/js/components/main-wrapper/main-content/albums.vue index 329f5972..528b18db 100644 --- a/resources/assets/js/components/main-wrapper/main-content/albums.vue +++ b/resources/assets/js/components/main-wrapper/main-content/albums.vue @@ -76,6 +76,11 @@ 'koel:ready': function () { this.displayMore(); }, + + 'koel:teardown': function () { + this.q = ''; + this.numOfItems = 9; + }, }, }; diff --git a/resources/assets/js/components/main-wrapper/main-content/artists.vue b/resources/assets/js/components/main-wrapper/main-content/artists.vue index 515a010f..8196e966 100644 --- a/resources/assets/js/components/main-wrapper/main-content/artists.vue +++ b/resources/assets/js/components/main-wrapper/main-content/artists.vue @@ -76,6 +76,11 @@ 'koel:ready': function () { this.displayMore(); }, + + 'koel:teardown': function () { + this.q = ''; + this.numOfItems = 9; + }, }, }; diff --git a/resources/assets/js/components/main-wrapper/main-content/profile.vue b/resources/assets/js/components/main-wrapper/main-content/profile.vue index 49f31570..7551eafb 100644 --- a/resources/assets/js/components/main-wrapper/main-content/profile.vue +++ b/resources/assets/js/components/main-wrapper/main-content/profile.vue @@ -104,6 +104,7 @@ import preferenceStore from '../../../stores/preference'; import sharedStore from '../../../stores/shared'; import http from '../../../services/http'; + import ls from '../../../services/ls'; export default { data() { @@ -156,7 +157,11 @@ * Koel will reload once the connection is successful. */ connectToLastfm() { - window.open('/api/lastfm/connect', '_blank', 'toolbar=no,titlebar=no,location=no,width=1024,height=640'); + window.open( + `/api/lastfm/connect?jwt-token=${ls.get('jwt-token')}`, + '_blank', + 'toolbar=no,titlebar=no,location=no,width=1024,height=640' + ); }, /** diff --git a/resources/assets/js/components/site-footer/index.vue b/resources/assets/js/components/site-footer/index.vue index 790d3174..bc43028f 100644 --- a/resources/assets/js/components/site-footer/index.vue +++ b/resources/assets/js/components/site-footer/index.vue @@ -265,6 +265,12 @@ 'main-content-view:load': function (view) { this.viewingQueue = view === 'queue'; }, + + 'koel:teardown': function () { + this.song = songStore.stub; + this.playing = false; + this.liked = false; + }, }, }; diff --git a/resources/assets/js/components/site-header/search-form.vue b/resources/assets/js/components/site-header/search-form.vue index 0f4836f7..3d95baf7 100644 --- a/resources/assets/js/components/site-header/search-form.vue +++ b/resources/assets/js/components/site-header/search-form.vue @@ -38,6 +38,10 @@ 'search:toggle': function () { this.showing = !this.showing; }, + + 'koel:teardown': function () { + this.q = ''; + }, }, }; diff --git a/resources/assets/js/components/site-header/user-badge.vue b/resources/assets/js/components/site-header/user-badge.vue index b3a2f171..4f4eeb1a 100644 --- a/resources/assets/js/components/site-header/user-badge.vue +++ b/resources/assets/js/components/site-header/user-badge.vue @@ -5,7 +5,7 @@ {{ state.current.name }} - + @@ -26,6 +26,10 @@ loadProfileView() { this.$root.loadMainView('profile'); }, + + logout() { + this.$root.logout(); + }, }, }; diff --git a/resources/assets/js/main.js b/resources/assets/js/main.js index 90514941..7e13498c 100644 --- a/resources/assets/js/main.js +++ b/resources/assets/js/main.js @@ -3,6 +3,8 @@ import $ from 'jquery'; import ls from './services/ls'; window.Vue = require('vue'); +var app = new Vue(require('./app.vue')); + Vue.config.debug = false; Vue.use(require('vue-resource')); Vue.http.options.root = '/api'; @@ -11,15 +13,15 @@ Vue.http.interceptors.push({ var token = ls.get('jwt-token'); if (token) { - Vue.http.headers.common.Authorization = token; + Vue.http.headers.common.Authorization = `Bearer ${token}`; } return request; }, response(response) { - if (response.status && response.status.code == 401) { - ls.remove('jwt-token'); + if (response.status === 400 || response.status === 401) { + app.logout(); } if (response.headers && response.headers.Authorization) { @@ -27,7 +29,7 @@ Vue.http.interceptors.push({ } if (response.data && response.data.token && response.data.token.length > 10) { - ls.set('jwt-token', `Bearer ${response.data.token}`); + ls.set('jwt-token', response.data.token); } return response; @@ -38,4 +40,4 @@ Vue.http.interceptors.push({ // Enter night, // Take my hand, // We're off to never never land. -new Vue(require('./app.vue')).$mount('body'); +app.$mount('body'); diff --git a/resources/assets/js/mixins/infinite-scroll.js b/resources/assets/js/mixins/infinite-scroll.js index fe7b14ca..89cd2e5c 100644 --- a/resources/assets/js/mixins/infinite-scroll.js +++ b/resources/assets/js/mixins/infinite-scroll.js @@ -34,4 +34,10 @@ export default { } }, }, + + events: { + 'koel:teardown': function () { + this.numOfItems = 30; + }, + }, }; diff --git a/resources/assets/js/services/http.js b/resources/assets/js/services/http.js index c7754a64..ee009bd0 100644 --- a/resources/assets/js/services/http.js +++ b/resources/assets/js/services/http.js @@ -16,9 +16,9 @@ export default { case 'post': return Vue.http.post(url, data, options).then(successCb, errorCb); case 'put': - return Vue.http.put(url, data, cb, options).then(successCb, errorCb); + return Vue.http.put(url, data, options).then(successCb, errorCb); case 'delete': - return Vue.http.delete(url, data, cb, options).then(successCb, errorCb); + return Vue.http.delete(url, data, options).then(successCb, errorCb); default: break; } @@ -32,10 +32,6 @@ export default { return this.request('post', url, data, successCb, errorCb, options); }, - patch(url, data, successCb = null, errorCb = null, options = {}) { - return this.request('patch', url, data, successCb, errorCb, options); - }, - put(url, data, successCb = null, errorCb = null, options = {}) { return this.request('put', url, data, successCb, errorCb, options); }, diff --git a/resources/assets/js/services/playback.js b/resources/assets/js/services/playback.js index 15d12caa..78757e3c 100644 --- a/resources/assets/js/services/playback.js +++ b/resources/assets/js/services/playback.js @@ -6,6 +6,7 @@ import songStore from '../stores/song'; import artistStore from '../stores/artist'; import albumStore from '../stores/album'; import preferenceStore from '../stores/preference'; +import ls from '../services/ls'; import config from '../config'; export default { @@ -90,7 +91,7 @@ export default { this.app.$broadcast('song:play', song); $('title').text(`${song.title} ♫ Koel`); - this.player.source(`/${song.id}/play`); + this.player.source(`/api/${song.id}/play?jwt-token=${ls.get('jwt-token')}`); this.player.play(); // Register the play to the server diff --git a/resources/assets/js/stores/favorite.js b/resources/assets/js/stores/favorite.js index 145efd41..67e52422 100644 --- a/resources/assets/js/stores/favorite.js +++ b/resources/assets/js/stores/favorite.js @@ -12,6 +12,10 @@ export default { return this.state.songs; }, + clear() { + this.state.songs = []; + }, + /** * Toggle like/unlike a song. * A request to the server will be made. diff --git a/resources/assets/js/stores/queue.js b/resources/assets/js/stores/queue.js index 201d3f93..9925ba2d 100644 --- a/resources/assets/js/stores/queue.js +++ b/resources/assets/js/stores/queue.js @@ -13,7 +13,7 @@ export default { // How about another song then? // // LITTLE WING - // -- by Jimi Fucking Hendrick + // -- by Jimi Fucking Hendrix // // Well she's walking // Through the clouds diff --git a/resources/assets/js/stores/shared.js b/resources/assets/js/stores/shared.js index 20daee90..0b78e9ad 100644 --- a/resources/assets/js/stores/shared.js +++ b/resources/assets/js/stores/shared.js @@ -27,8 +27,10 @@ export default { }, init(successCb = null, errorCb = null) { - http.get('data', {}, response => { - assign(this.state, response.data); + this.reset(); + + http.get('data', data => { + assign(this.state, data); // If this is a new user, initialize his preferences to be an empty object. if (!this.state.currentUser.preferences) { @@ -43,15 +45,23 @@ export default { queueStore.init(); settingStore.init(this.state.settings); - window.useLastfm = this.state.useLastfm = response.data.useLastfm; + window.useLastfm = this.state.useLastfm = data.useLastfm; + }, successCb, errorCb); + }, - if (successCb) { - successCb(); - } - }, error => { - if (errorCb) { - errorCb(); - } - }); + reset() { + this.state.songs = []; + this.state.albums = []; + this.state.artists = []; + this.state.favorites = []; + this.state.queued = []; + this.state.interactions = []; + this.state.users = []; + this.state.settings = []; + this.state.currentUser = null; + this.state.playlists = []; + this.state.useLastfm = false; + this.state.currentVersion = ''; + this.state.latestVersion = ''; }, }; diff --git a/resources/assets/js/stores/song.js b/resources/assets/js/stores/song.js index f2d53a47..52df7e5a 100644 --- a/resources/assets/js/stores/song.js +++ b/resources/assets/js/stores/song.js @@ -40,6 +40,8 @@ export default { * @param {Array} interactions The array of interactions of the current user */ initInteractions(interactions) { + favoriteStore.clear(); + _.each(interactions, interaction => { var song = this.byId(interaction.song_id); diff --git a/tests/LastfmTest.php b/tests/LastfmTest.php index 96156cfa..9cb98007 100644 --- a/tests/LastfmTest.php +++ b/tests/LastfmTest.php @@ -17,6 +17,7 @@ use Illuminate\Foundation\Testing\WithoutMiddleware; use Illuminate\Http\Request; use Illuminate\Routing\Redirector; use Mockery as m; +use Tymon\JWTAuth\JWTAuth; class LastfmTest extends TestCase { @@ -134,8 +135,12 @@ class LastfmTest extends TestCase $redirector->shouldReceive('to')->once(); $guard = m::mock(Guard::class, ['user' => factory(User::class)->create()]); + $auth = m::mock(JWTAuth::class, [ + 'parseToken' => '', + 'getToken' => '', + ]); - (new LastfmController($guard))->connect($redirector, new Lastfm()); + (new LastfmController($guard))->connect($redirector, new Lastfm(), $auth); } public function testControllerCallback()