diff --git a/backend/app/Http/Controllers/ItemController.php b/backend/app/Http/Controllers/ItemController.php index 4495245..e52db55 100644 --- a/backend/app/Http/Controllers/ItemController.php +++ b/backend/app/Http/Controllers/ItemController.php @@ -65,11 +65,11 @@ $title = Input::get('q'); if(config('scout.driver')) { - return $this->item->search($title)->get(); + return $this->item->search($title)->with('latestEpisode')->get(); } // We don't have an smart search driver and return an simple 'like' query. - return $this->item->findByTitle($title)->get(); + return $this->item->findByTitle($title)->with('latestEpisode')->get(); } /** diff --git a/backend/app/Item.php b/backend/app/Item.php index 2da433b..706fdad 100644 --- a/backend/app/Item.php +++ b/backend/app/Item.php @@ -102,11 +102,14 @@ return $query->where('tmdb_id', $tmdbId); } - public function scopeFindByFPName($query, $item) + public function scopeFindByFPName($query, $item, $mediaType) { $changed = isset($item->changed->name) ? $item->changed->name : $item->name; - return $query->where('fp_name', $item->name)->orWhere('fp_name', $changed); + return $query->where('media_type', $mediaType) + ->where(function($query) use ($item, $changed) { + return $query->where('fp_name', $item->name)->orWhere('fp_name', $changed); + }); } public function scopeFindBySrc($query, $src) @@ -114,22 +117,32 @@ return $query->where('src', $src); } - public function scopeFindByTitle($query, $title) + public function scopeFindByTitle($query, $title, $mediaType = null) { - return $query->where('title', 'like', '%' . $title . '%') - ->orWhere('original_title', 'like', '%' . $title . '%') - ->orWhereHas('alternativeTitles', function($query) use ($title) { - $query->where('title', 'like', '%' . $title . '%'); - }); + // Only necessarily if we search from file-parser. + if($mediaType) { + $query->where('media_type', $mediaType); + } + + return $query->where(function($query) use ($title) { + return $query->where('title', 'like', '%' . $title . '%') + ->orWhere('original_title', 'like', '%' . $title . '%') + ->orWhereHas('alternativeTitles', function($query) use ($title) { + return $query->where('title', 'like', '%' . $title . '%'); + }); + }); } - public function scopeFindByTitleStrict($query, $title) + public function scopeFindByTitleStrict($query, $title, $mediaType) { - return $query->where('title', $title) - ->orWhere('original_title', $title) - ->orWhere('fp_name', $title) - ->orWhereHas('alternativeTitles', function($query) use ($title) { - $query->where('title', $title); + return $query->where('media_type', $mediaType) + ->where(function($query) use ($title) { + $query->where('title', $title) + ->orWhere('original_title', $title) + ->orWhere('fp_name', $title) + ->orWhereHas('alternativeTitles', function($query) use ($title) { + $query->where('title', $title); + }); }); } } diff --git a/backend/app/Services/FileParser.php b/backend/app/Services/FileParser.php index e48a102..8588391 100644 --- a/backend/app/Services/FileParser.php +++ b/backend/app/Services/FileParser.php @@ -107,7 +107,7 @@ } /** - * See if it can find the item in our database. + * See if it can find the item in our database, filtered by media_type. * Otherwise search in TMDb or create an empty item. * * @param $item @@ -116,7 +116,7 @@ private function validateStore($item) { // See if file is already in our database. - if($found = $this->itemService->findBy('title_strict', $item->name)) { + if($found = $this->itemService->findBy('title_strict', $item->name, $this->itemCategory)) { return $this->store($item, $found->tmdb_id); } @@ -132,7 +132,7 @@ } /** - * See if it can find the item in our database. + * See if it can find the item in our database, filtered by media_type. * Check if it is an empty item and search against TMDb and update them. * Otherwise create an empty item. * @@ -142,7 +142,7 @@ private function validateUpdate($item) { // See if file is already in our database. - if($found = $this->itemService->findBy('fp_name', $item)) { + if($found = $this->itemService->findBy('fp_name', $item, $this->itemCategory)) { if( ! $found->tmdb_id) { return $this->searchTmdbAndUpdateEmptyItem($found, $item); } @@ -194,7 +194,7 @@ */ private function searchTmdb($item) { - $found = $this->tmdb->search($this->getFileName($item)); + $found = $this->tmdb->search($this->getFileName($item), $this->itemCategory); if( ! $found) { return false; @@ -207,7 +207,6 @@ * If TMDb can't find anything, create a simple item with data from local file. * * @param $item - * @param $mediaType * @return mixed */ private function createEmptyItem($item) diff --git a/backend/app/Services/Models/ItemService.php b/backend/app/Services/Models/ItemService.php index 1cab881..00ddd63 100644 --- a/backend/app/Services/Models/ItemService.php +++ b/backend/app/Services/Models/ItemService.php @@ -134,21 +134,29 @@ } /** - * See if we can find a item by title, tmdb_id or src in our database. + * See if we can find a item by title, fp_name, tmdb_id or src in our database. + * + * If we search from file-parser, we also need to filter the results by media_type. + * If we have e.g. 'Avatar' as tv show, we don't want results like the 'Avatar' movie. * * @param $type * @param $value + * @param $mediaType * @return mixed */ - public function findBy($type, $value) + public function findBy($type, $value, $mediaType = null) { + if($mediaType) { + $mediaType = $mediaType == 'movies' ? 'movie' : 'tv'; + } + switch($type) { case 'title': - return $this->model->findByTitle($value)->first(); + return $this->model->findByTitle($value, $mediaType)->first(); case 'title_strict': - return $this->model->findByTitleStrict($value)->first(); + return $this->model->findByTitleStrict($value, $mediaType)->first(); case 'fp_name': - return $this->model->findByFPName($value)->first(); + return $this->model->findByFPName($value, $mediaType)->first(); case 'tmdb_id': return $this->model->findByTmdbId($value)->first(); case 'src': diff --git a/backend/app/Services/TMDB.php b/backend/app/Services/TMDB.php index a333c2b..cbabd7a 100644 --- a/backend/app/Services/TMDB.php +++ b/backend/app/Services/TMDB.php @@ -19,7 +19,7 @@ private $base = 'http://api.themoviedb.org'; /** - * Get the API Key for TMDB and create an instance of Guzzle. + * Get the API Key for TMDb and create an instance of Guzzle. * * @param Client $client */ @@ -31,22 +31,38 @@ } /** - * Search TMDB by 'title'. + * Search TMDb by 'title'. * - * @param $title - * @return array + * @param $title + * @param null $mediaType + * @return Collection */ - public function search($title) + public function search($title, $mediaType = null) { - $response = $this->requestTmdb($this->base . '/3/search/multi', [ - 'query' => $title - ]); + $tv = collect(); + $movies = collect(); - return $this->createItems($response); + if( ! $mediaType || $mediaType == 'tv') { + $response = $this->fetchSearch($title, 'tv'); + $tv = collect($this->createItems($response, 'tv')); + } + + if( ! $mediaType || $mediaType == 'movies') { + $response = $this->fetchSearch($title, 'movie'); + $movies = collect($this->createItems($response, 'movie')); + } + + return $tv->merge($movies)->toArray(); + } + + private function fetchSearch($title, $mediaType) { + return $this->requestTmdb($this->base . '/3/search/' . $mediaType, [ + 'query' => $title, + ]); } /** - * Search TMDB for recommendations and similar movies. + * Search TMDb for recommendations and similar movies. * * @param $mediaType * @param $tmdbID @@ -82,7 +98,7 @@ } /** - * Search TMDB for upcoming movies. + * Search TMDb for upcoming movies. * * @return array */ @@ -98,7 +114,7 @@ } /** - * Search TMDB for current popular movies and tv shows. + * Search TMDb for current popular movies and tv shows. * * @return array */ @@ -148,21 +164,16 @@ /** * @param $response - * @param null $type - * + * @param $mediaType * @return array + * */ - private function createItems($response, $type = null) + private function createItems($response, $mediaType) { $items = []; $response = json_decode($response->getBody()); foreach($response->results as $result) { - // Suggestions doesn't deliver 'media type' by default - $mediaType = $type ?: $result->media_type; - - if($mediaType == 'person') continue; - $dtime = DateTime::createFromFormat('Y-m-d', (array_key_exists('release_date', $result) ? ($result->release_date ?: '1970-12-1') : ($result->first_air_date ?: '1970-12-1') diff --git a/backend/tests/Services/ItemServiceTest.php b/backend/tests/Services/ItemServiceTest.php index d59ddc6..2d5a609 100644 --- a/backend/tests/Services/ItemServiceTest.php +++ b/backend/tests/Services/ItemServiceTest.php @@ -67,7 +67,7 @@ { $this->createMovie(); - $itemFromTitle = $this->itemService->findBy('title', 'craft'); + $itemFromTitle = $this->itemService->findBy('title', 'craft', 'movies'); $itemFromId = $this->itemService->findBy('tmdb_id', 68735); $this->assertEquals(68735, $itemFromTitle->tmdb_id); @@ -79,8 +79,8 @@ { $this->createMovie(); - $notFound = $this->itemService->findBy('title_strict', 'craft'); - $found = $this->itemService->findBy('title_strict', 'Warcraft: The Beginning'); + $notFound = $this->itemService->findBy('title_strict', 'craft', 'movies'); + $found = $this->itemService->findBy('title_strict', 'Warcraft: The Beginning', 'movies'); $this->assertNull($notFound); $this->assertEquals(68735, $found->tmdb_id); diff --git a/backend/tests/Services/TMDBTest.php b/backend/tests/Services/TMDBTest.php index 0b4af02..fd6ffbc 100644 --- a/backend/tests/Services/TMDBTest.php +++ b/backend/tests/Services/TMDBTest.php @@ -11,6 +11,48 @@ use DatabaseMigrations; + /** @test */ + public function it_should_search_and_merge_movies_and_tv() + { + $this->createGuzzleMock($this->tmdbFixtures('tv/search'), $this->tmdbFixtures('movie/search')); + + $result = $this->callSearch(); + + $hasTv = $this->in_array_r('Avatar: The Last Airbender', $result); + $hasMovie = $this->in_array_r('Avatar: Aufbruch nach Pandora', $result); + + $this->assertTrue($hasTv); + $this->assertTrue($hasMovie); + } + + /** @test */ + public function it_should_only_search_for_tv() + { + $this->createGuzzleMock($this->tmdbFixtures('tv/search'), $this->tmdbFixtures('movie/search')); + + $result = $this->callSearch('tv'); + + $hasTv = $this->in_array_r('Avatar: The Last Airbender', $result); + $hasMovie = $this->in_array_r('Avatar: Aufbruch nach Pandora', $result); + + $this->assertTrue($hasTv); + $this->assertFalse($hasMovie); + } + + /** @test */ + public function it_should_only_search_for_movies() + { + $this->createGuzzleMock($this->tmdbFixtures('movie/search'), $this->tmdbFixtures('tv/search')); + + $result = $this->callSearch('movies'); + + $hasTv = $this->in_array_r('Avatar: The Last Airbender', $result); + $hasMovie = $this->in_array_r('Avatar: Aufbruch nach Pandora', $result); + + $this->assertFalse($hasTv); + $this->assertTrue($hasMovie); + } + /** @test */ public function it_should_fetch_and_merge_movies_and_tv_in_trending() { @@ -70,17 +112,24 @@ $this->app->instance(Client::class, new Client(['handler' => $handler])); $tmdb = app(TMDB::class); - $result = $tmdb->search('Avatar - Legend of Korra'); + $result = $tmdb->search('Avatar - Legend of Korra', 'tv'); $this->assertCount(1, $result); $this->assertArrayHasKey('tmdb_id', $result[0]); } + private function callSearch($type = null) + { + $tmdb = app(TMDB::class); + + return $tmdb->search('Avatar', $type); + } + private function in_array_r($item , $array){ return (bool) preg_match('/"' . $item . '"/i' , json_encode($array)); } - private function createGuzzleMock($fixture, $fixture2 = null) + private function createGuzzleMock($fixture, $fixture2) { $mock = new MockHandler([ new Response(200, ['X-RateLimit-Remaining' => [40]], $fixture), diff --git a/backend/tests/fixtures/tmdb/movie/search.json b/backend/tests/fixtures/tmdb/movie/search.json new file mode 100644 index 0000000..0626ade --- /dev/null +++ b/backend/tests/fixtures/tmdb/movie/search.json @@ -0,0 +1,46 @@ +{ + "page": 1, + "results": [ + { + "poster_path": "/kmcqlZGaSh20zpTbuoF0Cdn07dT.jpg", + "adult": false, + "overview": "overview", + "release_date": "2009-12-10", + "genre_ids": [ + 28, + 12, + 14, + 878 + ], + "id": 19995, + "original_title": "Avatar", + "original_language": "en", + "title": "Avatar: Aufbruch nach Pandora", + "backdrop_path": "/5XPPB44RQGfkBrbJxmtdndKz05n.jpg", + "popularity": 13.117401, + "vote_count": 8721, + "video": false, + "vote_average": 7.1 + }, + { + "poster_path": "/famiFiwZPQ3A8WVjKFhCplKrZgr.jpg", + "adult": false, + "overview": "overview", + "release_date": "2011-04-30", + "genre_ids": [ + 27 + ], + "id": 282908, + "original_title": "Abataa", + "original_language": "ja", + "title": "Avatar", + "backdrop_path": null, + "popularity": 1.001713, + "vote_count": 0, + "video": false, + "vote_average": 0 + } + ], + "total_results": 29, + "total_pages": 2 +} \ No newline at end of file diff --git a/backend/tests/fixtures/tmdb/tv/search.json b/backend/tests/fixtures/tmdb/tv/search.json new file mode 100644 index 0000000..370db52 --- /dev/null +++ b/backend/tests/fixtures/tmdb/tv/search.json @@ -0,0 +1,51 @@ +{ + "page": 1, + "results": [ + { + "poster_path": "/4KgScXaTeVZWgsBDJNbDYbeqRjF.jpg", + "popularity": 3.04828, + "id": 246, + "backdrop_path": "/14UEjm0MDQ8C22BqYqdzn3gsAiX.jpg", + "vote_average": 7.8, + "overview": "overview", + "first_air_date": "2005-02-21", + "origin_country": [ + "US" + ], + "genre_ids": [ + 28, + 12, + 16, + 14 + ], + "original_language": "en", + "vote_count": 67, + "name": "Avatar: The Last Airbender", + "original_name": "Avatar: The Last Airbender" + }, + { + "poster_path": "/8uOOycL6r4vqOT8tgw4behO5MmB.jpg", + "popularity": 3.52741, + "id": 33880, + "backdrop_path": "/r1oTzR9Ke7pICxe1eiP8ZjoGJju.jpg", + "vote_average": 7.52, + "overview": "overview", + "first_air_date": "2012-04-14", + "origin_country": [ + "US" + ], + "genre_ids": [ + 10765, + 16, + 18, + 10751 + ], + "original_language": "en", + "vote_count": 44, + "name": "The Legend of Korra", + "original_name": "The Legend of Korra" + } + ], + "total_results": 2, + "total_pages": 1 +} \ No newline at end of file diff --git a/client/app/components/Content/Item.vue b/client/app/components/Content/Item.vue index 6b56ec7..8b6053f 100644 --- a/client/app/components/Content/Item.vue +++ b/client/app/components/Content/Item.vue @@ -5,7 +5,7 @@ - +