diff --git a/backend/app/Console/Commands/DB.php b/backend/app/Console/Commands/DB.php index 20d15aa..49cd471 100644 --- a/backend/app/Console/Commands/DB.php +++ b/backend/app/Console/Commands/DB.php @@ -2,37 +2,53 @@ namespace App\Console\Commands; + use App\Services\Models\GenreService; use App\User; use Illuminate\Console\Command; + use Illuminate\Support\Facades\DB as LaravelDB; class DB extends Command { - protected $signature = 'flox:db {username?} {password?}'; + private $genreService; + + protected $signature = 'flox:db {--fresh : Whether all data should be reset} {username?} {password?}'; protected $description = 'Create database migrations and admin account'; - public function __construct() + public function __construct(GenreService $genreService) { parent::__construct(); + + $this->genreService = $genreService; } public function handle() { + if($this->option('fresh')) { + $this->alert('ALL DATA WILL BE REMOVED'); + } + try { $this->createMigrations(); } catch(\Exception $e) { $this->error('Can not connect to the database. Error: ' . $e->getMessage()); $this->error('Make sure your database credentials in .env are correct'); - return; + return false; } - + $this->createUser(); } private function createMigrations() { $this->info('TRYING TO MIGRATE DATABASE'); - $this->call('migrate', ['--force' => true]); + + if($this->option('fresh')) { + $this->call('migrate:fresh', ['--force' => true]); + } else { + $this->call('migrate', ['--force' => true]); + } + $this->info('MIGRATION COMPLETED'); } @@ -41,6 +57,10 @@ $username = $this->ask('Enter your admin username', $this->argument("username")); $password = $this->ask('Enter your admin password', $this->argument("password")); + if($this->option('fresh')) { + LaravelDB::table('users')->delete(); + } + $user = new User(); $user->username = $username; $user->password = bcrypt($password); diff --git a/backend/app/Episode.php b/backend/app/Episode.php index 896a4b8..28b3c3b 100644 --- a/backend/app/Episode.php +++ b/backend/app/Episode.php @@ -32,7 +32,7 @@ public function getReleaseEpisodeHumanFormatAttribute() { - $now = Carbon::now(); + $now = now(); $release = Carbon::createFromTimestamp($this->release_episode); if($release > $now) { diff --git a/backend/app/Genre.php b/backend/app/Genre.php new file mode 100644 index 0000000..5abaf40 --- /dev/null +++ b/backend/app/Genre.php @@ -0,0 +1,20 @@ +where('name', $genre); + } + } diff --git a/backend/app/Http/Controllers/ExportImportController.php b/backend/app/Http/Controllers/ExportImportController.php index 1eb9ce2..3d838f2 100644 --- a/backend/app/Http/Controllers/ExportImportController.php +++ b/backend/app/Http/Controllers/ExportImportController.php @@ -9,6 +9,7 @@ use App\Services\Storage; use App\Setting; use Carbon\Carbon; + use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Input; use Symfony\Component\HttpFoundation\Response; @@ -71,30 +72,27 @@ $data = json_decode(file_get_contents($file)); - $this->importItems($data, $itemService); + $this->importSettings($data); $this->importEpisodes($data); $this->importAlternativeTitles($data); - $this->importSettings($data); + $this->importItems($data, $itemService); } private function importItems($data, ItemService $itemService) { logInfo("Import Movies"); + if(isset($data->items)) { - $this->item->truncate(); + DB::table('items')->delete(); + foreach($data->items as $item) { logInfo("Importing", [$item->title]); + // Fallback if export was from an older version of flox (<= 1.2.2). if( ! isset($item->last_seen_at)) { $item->last_seen_at = Carbon::createFromTimestamp($item->created_at); } - // For empty items (from file-parser) we don't need access to details. - if($item->tmdb_id) { - $item = $itemService->makeDataComplete((array) $item); - $this->storage->downloadImages($item['poster'], $item['backdrop']); - } - $this->item->create((array) $item); } @@ -107,7 +105,9 @@ { logInfo("Import Tv Shows"); if(isset($data->episodes)) { + $this->episodes->truncate(); + foreach($data->episodes as $episode) { logInfo("Importing", [$episode->name]); $this->episodes->create((array) $episode); @@ -119,7 +119,9 @@ private function importAlternativeTitles($data) { if(isset($data->alternative_titles)) { + $this->alternativeTitles->truncate(); + foreach($data->alternative_titles as $title) { $this->alternativeTitles->create((array) $title); } @@ -129,7 +131,9 @@ private function importSettings($data) { if(isset($data->settings)) { + $this->settings->truncate(); + foreach($data->settings as $setting) { $this->settings->create((array) $setting); } diff --git a/backend/app/Http/Controllers/GenreController.php b/backend/app/Http/Controllers/GenreController.php new file mode 100644 index 0000000..9500a2f --- /dev/null +++ b/backend/app/Http/Controllers/GenreController.php @@ -0,0 +1,28 @@ +genreService = $genreService; + $this->genre = $genre; + } + + public function allGenres() + { + return $this->genre->all(); + } + + public function updateGenreLists() + { + $this->genreService->updateGenreLists(); + } + } diff --git a/backend/app/Http/Controllers/ItemController.php b/backend/app/Http/Controllers/ItemController.php index cd6fe14..d2bcde2 100644 --- a/backend/app/Http/Controllers/ItemController.php +++ b/backend/app/Http/Controllers/ItemController.php @@ -78,11 +78,6 @@ $alternativeTitle->update(); } - public function updateGenre() - { - $this->itemService->updateGenre(); - } - public function toggleEpisode($id) { if( ! $this->episodeService->toggleSeen($id)) { diff --git a/backend/app/Http/Controllers/TMDBController.php b/backend/app/Http/Controllers/TMDBController.php index 8f82a81..6a342d6 100644 --- a/backend/app/Http/Controllers/TMDBController.php +++ b/backend/app/Http/Controllers/TMDBController.php @@ -23,6 +23,11 @@ { return $this->tmdb->suggestions($mediaType, $tmdbId); } + + public function genre($genre) + { + return $this->tmdb->byGenre($genre); + } public function trending() { diff --git a/backend/app/Item.php b/backend/app/Item.php index d1f0a4d..f931681 100644 --- a/backend/app/Item.php +++ b/backend/app/Item.php @@ -2,12 +2,18 @@ namespace App; - use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; class Item extends Model { - protected $dates = ['last_seen']; + protected $dates = [ + 'last_seen_at', + 'refreshed_at', + 'created_at', + 'updated_at', + ]; + + protected $with = ['genre']; protected $fillable = [ 'tmdb_id', @@ -16,8 +22,8 @@ 'poster', 'media_type', 'rating', + //'genre', 'released', - 'genre', 'fp_name', 'src', 'subtitles', @@ -32,6 +38,7 @@ 'youtube_key', 'slug', 'watchlist', + 'refreshed_at', ]; /** @@ -50,14 +57,14 @@ 'poster' => $data['poster'] ? $data['poster'] : '', 'rating' => 0, 'released' => $data['released'], - 'genre' => $data['genre'], + //'genre' => $data['genre'], 'overview' => $data['overview'], 'backdrop' => $data['backdrop'], 'tmdb_rating' => $data['tmdb_rating'], 'imdb_id' => $data['imdb_id'], 'imdb_rating' => $data['imdb_rating'], 'youtube_key' => $data['youtube_key'], - 'last_seen_at' => Carbon::now(), + 'last_seen_at' => now(), 'slug' => $data['slug'], ]); } @@ -79,7 +86,7 @@ 'released' => time(), 'src' => $data['src'], 'subtitles' => $data['subtitles'], - 'last_seen_at' => Carbon::now(), + 'last_seen_at' => now(), ]); } @@ -90,13 +97,18 @@ public function updateLastSeenAt($tmdbId) { return $this->where('tmdb_id', $tmdbId)->update([ - 'last_seen_at' => Carbon::now(), + 'last_seen_at' => now(), ]); } - + /* * Relations */ + + public function genre() + { + return $this->belongsToMany(Genre::class); + } public function episodes() { @@ -125,6 +137,13 @@ /* * Scopes */ + + public function scopeFindByGenreId($query, $genreId) + { + return $query->orWhereHas('genre', function($query) use ($genreId) { + $query->where('genre_id', $genreId); + }); + } public function scopeFindByTmdbId($query, $tmdbId) { diff --git a/backend/app/Services/FileParser.php b/backend/app/Services/FileParser.php index 032b167..b47de90 100644 --- a/backend/app/Services/FileParser.php +++ b/backend/app/Services/FileParser.php @@ -7,7 +7,6 @@ use App\Services\Models\EpisodeService; use App\Services\Models\ItemService; use App\Setting; - use Carbon\Carbon; use GuzzleHttp\Client; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Facades\DB; @@ -51,6 +50,7 @@ public function fetch() { logInfo("Making request to flox-file-parser..."); + $timestamp = $this->lastFetched()['last_fetch_to_file_parser']; $fpUrl = config('services.fp.host') . ':' . config('services.fp.port'); $fpUri = '/fetch/' . $timestamp; @@ -77,7 +77,7 @@ foreach((array) $files as $type => $items) { $this->itemCategory = $type; - + foreach($items as $item) { try { logInfo("Updating data"); @@ -100,6 +100,7 @@ * * @param $item * @return bool|mixed|void + * @throws \Exception */ private function handleStatus($item) { @@ -241,7 +242,7 @@ // Otherwise create a new item from the result. $created = $this->itemService->create($firstResult); - + return $this->store($item, $created->tmdb_id); } @@ -376,7 +377,7 @@ private function updateLastFetched() { Setting::first()->update([ - 'last_fetch_to_file_parser' => Carbon::now(), + 'last_fetch_to_file_parser' => now(), ]); } diff --git a/backend/app/Services/Models/EpisodeService.php b/backend/app/Services/Models/EpisodeService.php index f8c1877..404023b 100644 --- a/backend/app/Services/Models/EpisodeService.php +++ b/backend/app/Services/Models/EpisodeService.php @@ -18,8 +18,6 @@ * @param Model $model * @param TMDB $tmdb * @param Item $item - * @internal param ItemService $itemService - * @internal param Item $item */ public function __construct(Model $model, TMDB $tmdb, Item $item) { diff --git a/backend/app/Services/Models/GenreService.php b/backend/app/Services/Models/GenreService.php new file mode 100644 index 0000000..4a15f8b --- /dev/null +++ b/backend/app/Services/Models/GenreService.php @@ -0,0 +1,55 @@ +model = $model; + $this->tmdb = $tmdb; + } + + /** + * Sync the pivot table genre_item. + * + * @param $item + * @param $ids + */ + public function sync($item, $ids) + { + $item->genre()->sync($ids); + } + + /** + * Update the genres table. + */ + public function updateGenreLists() + { + $genres = $this->tmdb->getGenreLists(); + + DB::beginTransaction(); + + foreach($genres as $mediaType) { + foreach($mediaType->genres as $genre) { + $this->model->firstOrCreate( + ['id' => $genre->id], + ['name' => $genre->name] + ); + } + } + + DB::commit(); + } + } diff --git a/backend/app/Services/Models/ItemService.php b/backend/app/Services/Models/ItemService.php index 44a7908..d997428 100644 --- a/backend/app/Services/Models/ItemService.php +++ b/backend/app/Services/Models/ItemService.php @@ -8,6 +8,7 @@ use App\Services\TMDB; use GuzzleHttp\Client; use App\Setting; + use Illuminate\Support\Facades\DB; use Symfony\Component\HttpFoundation\Response; class ItemService { @@ -19,6 +20,7 @@ private $episodeService; private $imdb; private $setting; + private $genreService; /** * @param Model $model @@ -26,6 +28,7 @@ * @param Storage $storage * @param AlternativeTitleService $alternativeTitleService * @param EpisodeService $episodeService + * @param GenreService $genreService * @param IMDB $imdb * @param Setting $setting */ @@ -35,6 +38,7 @@ Storage $storage, AlternativeTitleService $alternativeTitleService, EpisodeService $episodeService, + GenreService $genreService, IMDB $imdb, Setting $setting ){ @@ -45,6 +49,7 @@ $this->episodeService = $episodeService; $this->imdb = $imdb; $this->setting = $setting; + $this->genreService = $genreService; } /** @@ -53,16 +58,21 @@ */ public function create($data) { + DB::beginTransaction(); + $data = $this->makeDataComplete($data); - + $item = $this->model->store($data); $this->episodeService->create($item); + $this->genreService->sync($item, $data['genre_ids']); $this->alternativeTitleService->create($item); $this->storage->downloadImages($item->poster, $item->backdrop); - return $item; + DB::commit(); + + return $item->fresh(); } /** @@ -110,9 +120,11 @@ public function refreshAll() { increaseTimeLimit(); + + $this->genreService->updateGenreLists(); - $this->model->all()->each(function($item) { - $this->refresh($item->id); + return $this->model->orderBy('refreshed_at')->get()->each(function($item) { + return $this->refresh($item->id, true); }); } @@ -160,6 +172,11 @@ $this->episodeService->create($item); $this->alternativeTitleService->create($item); + $this->genreService->sync( + $item, + collect($details->genres)->pluck('id')->all() + ); + $this->storage->downloadImages($item->poster, $item->backdrop); } @@ -322,26 +339,7 @@ { return $this->model->findByTitle($title)->with('latestEpisode')->withCount('episodesWithSrc')->get(); } - - /** - * Parse full genre list of all movies and tv shows in our database and save them. - */ - public function updateGenre() - { - increaseTimeLimit(); - - $items = $this->model->all(); - - $items->each(function($item) { - $genres = $this->tmdb->details($item->tmdb_id, $item->media_type)->genres; - $data = collect($genres)->pluck('name')->all(); - - $item->update([ - 'genre' => implode($data, ', '), - ]); - }); - } - + /** * See if we can find a item by title, fp_name, tmdb_id or src in our database. * diff --git a/backend/app/Services/Subpage.php b/backend/app/Services/Subpage.php index 0a93d83..88790a6 100644 --- a/backend/app/Services/Subpage.php +++ b/backend/app/Services/Subpage.php @@ -3,6 +3,7 @@ namespace App\Services; use App\Services\Models\ItemService; + use Symfony\Component\HttpFoundation\Response; class Subpage { @@ -22,6 +23,11 @@ } $found = $this->tmdb->details($tmdbId, $mediaType); + + if( ! (array) $found) { + return response('Not found', Response::HTTP_NOT_FOUND); + } + $found->genre_ids = collect($found->genres)->pluck('id')->all(); $item = $this->tmdb->createItem($found, $mediaType); diff --git a/backend/app/Services/TMDB.php b/backend/app/Services/TMDB.php index d9258bd..b31f5cb 100644 --- a/backend/app/Services/TMDB.php +++ b/backend/app/Services/TMDB.php @@ -2,6 +2,7 @@ namespace App\Services; + use App\Genre; use App\Item; use Carbon\Carbon; use GuzzleHttp\Client; @@ -15,7 +16,7 @@ private $client; private $apiKey; private $translation; - + private $base = 'https://api.themoviedb.org'; /** @@ -33,7 +34,7 @@ /** * Search TMDb by 'title'. * - * @param $title + * @param $title * @param null $mediaType * @return Collection */ @@ -169,25 +170,58 @@ return $this->filterItems($cache); } + /** + * Search TMDb by genre. + * + * @param $genre + * @return array + */ + public function byGenre($genre) + { + $genreId = Genre::findByName($genre)->firstOrFail()->id; + + $cache = Cache::remember('genre-' . $genre, $this->untilEndOfDay(), function() use ($genreId) { + + $responseMovies = $this->requestTmdb($this->base . '/3/discover/movie', ['with_genres' => $genreId]); + $responseTv = $this->requestTmdb($this->base . '/3/discover/tv', ['with_genres' => $genreId]); + + $movies = collect($this->createItems($responseMovies, 'movie')); + $tv = collect($this->createItems($responseTv, 'tv')); + + return $tv->merge($movies)->shuffle(); + }); + + //$inDB = Item::findByGenreId($genreId)->get(); + + return $this->filterItems($cache, $genreId); + } + /** * Merge the response with items from our database. * * @param Collection $items + * @param null $genreId * @return array */ - private function filterItems($items) + private function filterItems($items, $genreId = null) { $allId = $items->pluck('tmdb_id'); - // Get all movies/tv shows from trending/upcoming that are already in the database. - $inDB = Item::whereIn('tmdb_id', $allId)->withCount('episodesWithSrc')->get()->toArray(); + // Get all movies / tv shows that are already in our database. + $searchInDB = Item::whereIn('tmdb_id', $allId)->withCount('episodesWithSrc'); + + if($genreId) { + $searchInDB->findByGenreId($genreId); + } - // Remove all inDB movies / tv shows from trending / upcoming. - $filtered = $items->filter(function($item) use ($inDB) { - return ! in_array($item['tmdb_id'], array_column($inDB, 'tmdb_id')); + $foundInDB = $searchInDB->get()->toArray(); + + // Remove them from the TMDb response. + $filtered = $items->filter(function($item) use ($foundInDB) { + return ! in_array($item['tmdb_id'], array_column($foundInDB, 'tmdb_id')); }); - $merged = $filtered->merge($inDB); + $merged = $filtered->merge($foundInDB); // Reset array keys to display inDB items first. return array_values($merged->reverse()->toArray()); @@ -199,8 +233,8 @@ } /** - * @param $response - * @param $mediaType + * @param $response + * @param $mediaType * @return array */ private function createItems($response, $mediaType) @@ -222,7 +256,7 @@ ); $title = $data->name ?? $data->title; - + return [ 'tmdb_id' => $data->id, 'title' => $title, @@ -231,7 +265,8 @@ 'poster' => $data->poster_path, 'media_type' => $mediaType, 'released' => $release->getTimestamp(), - 'genre' => $this->parseGenre($data->genre_ids), + 'genre_ids' => $data->genre_ids, + 'genre' => Genre::whereIn('id', $data->genre_ids)->get(), 'episodes' => [], 'overview' => $data->overview, 'backdrop' => $data->backdrop_path, @@ -246,12 +281,12 @@ 'api_key' => $this->apiKey, 'language' => strtolower($this->translation) ], $query); - + try { $response = $this->client->get($url, [ 'query' => $query ]); - + if($this->hasLimitRemaining($response)) { return $response; } @@ -367,57 +402,16 @@ } /** - * Create genre string from genre_ids. - * - * @param $ids - * @return string + * Get the lists of genres from TMDb for tv shows and movies. */ - private function parseGenre($ids) - { - $genre = []; - - foreach($ids as $id) { - $genre[] = $this->genreList()[$id] ?? ''; - } - - return implode($genre, ', '); - } - - /** - * Current genre list from TMDb. - * - * @return array - */ - private function genreList() + public function getGenreLists() { + $movies = $this->requestTmdb($this->base . '/3/genre/movie/list'); + $tv = $this->requestTmdb($this->base . '/3/genre/tv/list'); + return [ - 28 => 'Action', - 12 => 'Adventure', - 16 => 'Animation', - 35 => 'Comedy', - 80 => 'Crime', - 99 => 'Documentary', - 18 => 'Drama', - 10751 => 'Family', - 14 => 'Fantasy', - 36 => 'History', - 27 => 'Horror', - 10402 => 'Music', - 9648 => 'Mystery', - 10749 => 'Romance', - 878 => 'Sci-Fi', - 10770 => 'TV Movie', - 53 => 'Thriller', - 10752 => 'War', - 37 => 'Western', - 10759 => 'Action & Adventure', - 10762 => 'Kids', - 10763 => 'News', - 10764 => 'Reality', - 10765 => 'Sci-Fi & Fantasy', - 10766 => 'Soap', - 10767 => 'Talk', - 10768 => 'War & Politics', + 'movies' => json_decode($movies->getBody()), + 'tv' => json_decode($tv->getBody()), ]; } @@ -429,7 +423,7 @@ { if($response->getStatusCode() == 429) { return false; - } + } return ((int) $response->getHeader('X-RateLimit-Remaining')[0]) > 1; } @@ -439,6 +433,6 @@ */ private function untilEndOfDay() { - return Carbon::now()->secondsUntilEndOfDay() / 60; + return now()->secondsUntilEndOfDay() / 60; } } diff --git a/backend/app/Setting.php b/backend/app/Setting.php index c59ea0d..aed1f9a 100644 --- a/backend/app/Setting.php +++ b/backend/app/Setting.php @@ -19,4 +19,11 @@ 'show_watchlist_everywhere', 'show_ratings', ]; + + protected $casts = [ + 'show_date' => 'boolean', + 'show_genre' => 'boolean', + 'episode_spoiler_protection' => 'boolean', + 'show_watchlist_everywhere' => 'boolean', + ]; } diff --git a/backend/database/factories/ModelFactory.php b/backend/database/factories/ModelFactory.php index a55424a..e431aec 100644 --- a/backend/database/factories/ModelFactory.php +++ b/backend/database/factories/ModelFactory.php @@ -25,7 +25,7 @@ return [ 'poster' => '', 'rating' => 1, - 'genre' => '', + //'genre' => '', 'released' => time(), 'last_seen_at' => Carbon::now(), 'src' => null, diff --git a/backend/database/migrations/2017_02_23_081946_add_release_dates_to_episodes.php b/backend/database/migrations/2017_02_23_081946_add_release_dates_to_episodes.php index a1d0881..543e62b 100644 --- a/backend/database/migrations/2017_02_23_081946_add_release_dates_to_episodes.php +++ b/backend/database/migrations/2017_02_23_081946_add_release_dates_to_episodes.php @@ -14,10 +14,5 @@ class AddReleaseDatesToEpisodes extends Migration }); } - public function down() - { - Schema::table('episodes', function (Blueprint $table) { - // - }); - } + public function down() {} } diff --git a/backend/database/migrations/2017_12_29_222510_create_genres_table.php b/backend/database/migrations/2017_12_29_222510_create_genres_table.php new file mode 100644 index 0000000..f545e40 --- /dev/null +++ b/backend/database/migrations/2017_12_29_222510_create_genres_table.php @@ -0,0 +1,37 @@ +genreService = app(GenreService::class); + } + + public function up() + { + Schema::create('genres', function (Blueprint $table) { + $table->integer('id'); + $table->string('name'); + }); + + if( ! app()->runningUnitTests()) { + try { + $this->genreService->updateGenreLists(); + } catch (\Exception $e) { + echo 'Can not connect to the TMDb Service on "CreateGenresTable". Error: ' . $e->getMessage(); + echo 'Make sure you set your TMDb API Key in .env'; + + abort(500); + } + } + } + + public function down() {} +} diff --git a/backend/database/migrations/2017_12_29_222633_create_genre_item_table.php b/backend/database/migrations/2017_12_29_222633_create_genre_item_table.php new file mode 100644 index 0000000..198d989 --- /dev/null +++ b/backend/database/migrations/2017_12_29_222633_create_genre_item_table.php @@ -0,0 +1,37 @@ +increments('id'); + $table->integer('item_id'); + $table->integer('genre_id'); + }); + + // We dont need the genre column more. + Schema::table('items', function (Blueprint $table) { + $table->dropColumn('genre'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('genre_item'); + } +} diff --git a/backend/routes/web.php b/backend/routes/web.php index 07a2b80..33b4aa9 100644 --- a/backend/routes/web.php +++ b/backend/routes/web.php @@ -12,6 +12,8 @@ Route::get('/imdb-rating/{imdbId}', 'SubpageController@imdbRating'); Route::get('/suggestions/{tmdbID}/{mediaType}', 'TMDBController@suggestions'); + Route::get('/genres', 'GenreController@allGenres'); + Route::get('/genre/{genre}', 'TMDBController@genre'); Route::get('/trending', 'TMDBController@trending'); Route::get('/upcoming', 'TMDBController@upcoming'); Route::get('/now-playing', 'TMDBController@nowPlaying'); @@ -28,6 +30,8 @@ Route::post('/watchlist', 'ItemController@watchlist'); Route::patch('/update-alternative-titles/{tmdbId?}', 'ItemController@updateAlternativeTitles'); Route::patch('/update-genre', 'ItemController@updateGenre'); + // todo: in patch + Route::get('/update-genre-lists', 'GenreController@updateGenreLists'); Route::patch('/toggle-episode/{id}', 'ItemController@toggleEpisode'); Route::patch('/toggle-season', 'ItemController@toggleSeason'); Route::patch('/change-rating/{itemId}', 'ItemController@changeRating'); diff --git a/backend/tests/CreatesApplication.php b/backend/tests/CreatesApplication.php index da2f2f4..314cd68 100644 --- a/backend/tests/CreatesApplication.php +++ b/backend/tests/CreatesApplication.php @@ -3,7 +3,8 @@ namespace Tests; use Illuminate\Contracts\Console\Kernel; - + use Illuminate\Support\Facades\Hash; + trait CreatesApplication { /** @@ -16,7 +17,9 @@ $app = require __DIR__ . '/../bootstrap/app.php'; $app->make(Kernel::class)->bootstrap(); - + + Hash::setRounds(4); + return $app; } } diff --git a/backend/tests/Services/GenreServiceTest.php b/backend/tests/Services/GenreServiceTest.php new file mode 100644 index 0000000..80cffb7 --- /dev/null +++ b/backend/tests/Services/GenreServiceTest.php @@ -0,0 +1,66 @@ +createGuzzleMock( + $this->tmdbFixtures('movie/genres'), + $this->tmdbFixtures('tv/genres') + ); + + $service = app(GenreService::class); + + $genresBeforeUpdate = Genre::all(); + + $service->updateGenreLists(); + + $genresAfterUpdate = Genre::all(); + + $this->assertCount(0, $genresBeforeUpdate); + $this->assertCount(27, $genresAfterUpdate); + } + + /** @test */ + public function it_should_sync_genres_for_an_item() + { + $genreIds = [28, 12, 16]; + + $this->createGuzzleMock( + $this->tmdbFixtures('movie/genres'), + $this->tmdbFixtures('tv/genres') + ); + + $item = $this->createMovie(); + + $service = app(GenreService::class); + $service->updateGenreLists(); + + $itemBeforeUpdate = Item::first(); + + $service->sync($item, $genreIds); + + $itemAfterUpdate = Item::first(); + + $this->assertCount(0, $itemBeforeUpdate->genre); + $this->assertCount(count($genreIds), $itemAfterUpdate->genre); + } + } diff --git a/backend/tests/Services/ItemServiceTest.php b/backend/tests/Services/ItemServiceTest.php index 19ec7d1..2d24a59 100644 --- a/backend/tests/Services/ItemServiceTest.php +++ b/backend/tests/Services/ItemServiceTest.php @@ -162,38 +162,6 @@ $this->assertNotNull($item1); $this->assertNull($item2); } - - /** @test */ - public function it_should_update_genre_for_a_movie() - { - $user = $this->createUser(); - $this->createMovie(); - - $this->createGuzzleMock($this->tmdbFixtures('movie/details')); - - $withoutGenre = Item::find(1); - $this->actingAs($user)->patchJson('api/update-genre'); - $withGenre = Item::find(1); - - $this->assertEmpty($withoutGenre->genre); - $this->assertNotEmpty($withGenre->genre); - } - - /** @test */ - public function it_should_update_genre_for_a_tv_show() - { - $user = $this->createUser(); - $this->createTv(); - - $this->createGuzzleMock($this->tmdbFixtures('tv/details')); - - $withoutGenre = Item::find(1); - $this->actingAs($user)->patchJson('api/update-genre'); - $withGenre = Item::find(1); - - $this->assertEmpty($withoutGenre->genre); - $this->assertNotEmpty($withGenre->genre); - } /** @test */ public function it_should_parse_correct_imdb_id() diff --git a/backend/tests/Services/TMDBTest.php b/backend/tests/Services/TMDBTest.php index 0660728..233a10e 100644 --- a/backend/tests/Services/TMDBTest.php +++ b/backend/tests/Services/TMDBTest.php @@ -53,6 +53,24 @@ $this->assertTrue($hasTv); $this->assertFalse($hasMovie); } + + /** @test */ + public function it_should_fetch_all_genres() + { + $this->createGuzzleMock( + $this->tmdbFixtures('movie/genres'), + $this->tmdbFixtures('tv/genres') + ); + + $tmdb = app(TMDB::class); + $genres = $tmdb->getGenreLists(); + + $this->assertArrayHasKey('tv', $genres); + $this->assertArrayHasKey('movies', $genres); + + $this->assertCount(16, $genres['tv']->genres); + $this->assertCount(19, $genres['movies']->genres); + } /** @test */ public function it_should_only_search_for_movies() diff --git a/backend/tests/fixtures/flox/movie.json b/backend/tests/fixtures/flox/movie.json index 189b8ac..d4d97b4 100644 --- a/backend/tests/fixtures/flox/movie.json +++ b/backend/tests/fixtures/flox/movie.json @@ -6,5 +6,6 @@ "media_type": "movie", "released": 1464185524, "genre": "Adventure, Fantasy, Action", + "genre_ids": [1,2,3], "episodes": [] -} \ No newline at end of file +} diff --git a/backend/tests/fixtures/flox/tv.json b/backend/tests/fixtures/flox/tv.json index d6ca083..c76ee75 100644 --- a/backend/tests/fixtures/flox/tv.json +++ b/backend/tests/fixtures/flox/tv.json @@ -6,5 +6,6 @@ "media_type": "tv", "released": 1303051450, "genre": "Sci-Fi & Fantasy, Action & Adventure, Drama", + "genre_ids": [1,2,3], "episodes": [] -} \ No newline at end of file +} diff --git a/backend/tests/fixtures/tmdb/movie/genres.json b/backend/tests/fixtures/tmdb/movie/genres.json new file mode 100644 index 0000000..6181289 --- /dev/null +++ b/backend/tests/fixtures/tmdb/movie/genres.json @@ -0,0 +1,80 @@ +{ + "genres": [ + { + "id": 28, + "name": "Action" + }, + { + "id": 12, + "name": "Adventure" + }, + { + "id": 16, + "name": "Animation" + }, + { + "id": 35, + "name": "Comedy" + }, + { + "id": 80, + "name": "Crime" + }, + { + "id": 99, + "name": "Documentary" + }, + { + "id": 18, + "name": "Drama" + }, + { + "id": 10751, + "name": "Family" + }, + { + "id": 14, + "name": "Fantasy" + }, + { + "id": 36, + "name": "History" + }, + { + "id": 27, + "name": "Horror" + }, + { + "id": 10402, + "name": "Music" + }, + { + "id": 9648, + "name": "Mystery" + }, + { + "id": 10749, + "name": "Romance" + }, + { + "id": 878, + "name": "Science Fiction" + }, + { + "id": 10770, + "name": "TV Movie" + }, + { + "id": 53, + "name": "Thriller" + }, + { + "id": 10752, + "name": "War" + }, + { + "id": 37, + "name": "Western" + } + ] +} diff --git a/backend/tests/fixtures/tmdb/tv/genres.json b/backend/tests/fixtures/tmdb/tv/genres.json new file mode 100644 index 0000000..d16e223 --- /dev/null +++ b/backend/tests/fixtures/tmdb/tv/genres.json @@ -0,0 +1,68 @@ +{ + "genres": [ + { + "id": 10759, + "name": "Action & Adventure" + }, + { + "id": 16, + "name": "Animation" + }, + { + "id": 35, + "name": "Comedy" + }, + { + "id": 80, + "name": "Crime" + }, + { + "id": 99, + "name": "Documentary" + }, + { + "id": 18, + "name": "Drama" + }, + { + "id": 10751, + "name": "Family" + }, + { + "id": 10762, + "name": "Kids" + }, + { + "id": 9648, + "name": "Mystery" + }, + { + "id": 10763, + "name": "News" + }, + { + "id": 10764, + "name": "Reality" + }, + { + "id": 10765, + "name": "Sci-Fi & Fantasy" + }, + { + "id": 10766, + "name": "Soap" + }, + { + "id": 10767, + "name": "Talk" + }, + { + "id": 10768, + "name": "War & Politics" + }, + { + "id": 37, + "name": "Western" + } + ] +} diff --git a/client/.babelrc b/client/.babelrc index f1c00ce..8efedc1 100644 --- a/client/.babelrc +++ b/client/.babelrc @@ -1,5 +1,13 @@ { "presets": ["es2015", "stage-2"], - "plugins": ["transform-runtime"], + "plugins": [ + "transform-runtime", + ["component", [ + { + "libraryName": "element-ui", + "styleLibraryName": "theme-chalk" + } + ]] + ], "comments": false -} \ No newline at end of file +} diff --git a/client/app/app.js b/client/app/app.js index 3d6763b..9411aa8 100644 --- a/client/app/app.js +++ b/client/app/app.js @@ -3,13 +3,17 @@ require('../resources/sass/app.scss'); import Vue from 'vue'; import { mapState, mapActions, mapMutations } from 'vuex' +import { Checkbox } from 'element-ui'; + +Vue.use(Checkbox); + import SiteHeader from './components/Header.vue'; import SiteFooter from './components/Footer.vue'; import Login from './components/Login.vue'; import Modal from './components/Modal/Index.vue'; import router from './routes'; -import store from './store/index'; +import store from './store'; const App = new Vue({ store, diff --git a/client/app/components/Content/Content.vue b/client/app/components/Content/Content.vue index 91b0ef1..6235dd2 100644 --- a/client/app/components/Content/Content.vue +++ b/client/app/components/Content/Content.vue @@ -1,6 +1,6 @@