From 4c2978b2eb5de59ac78dcf8b3c5fa43d5a34f6d0 Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Wed, 19 Jan 2022 15:53:54 +0100 Subject: [PATCH 01/21] Add TV Genres --- .gitignore | 1 + ...22_01_19_150000_create_tv_genres_table.php | 24 +++++ src/Eloquent/Builders/TvGenreBuilder.php | 19 ++++ src/Models/TvGenre.php | 97 +++++++++++++++++++ src/Requests/TvGenre/ListAll.php | 25 +++++ tests/Datasets/Builder.php | 5 + tests/Feature/Models/TvGenreTest.php | 73 ++++++++++++++ .../Feature/Requests/ListMovieGenresTest.php | 4 +- tests/fixtures/genre/tv/list/language=en.json | 68 +++++++++++++ .../fixtures/genre/tv/list/language=foo.json | 68 +++++++++++++ tests/fixtures/genre/tv/list/language=it.json | 68 +++++++++++++ 11 files changed, 450 insertions(+), 2 deletions(-) create mode 100644 database/migrations/2022_01_19_150000_create_tv_genres_table.php create mode 100644 src/Eloquent/Builders/TvGenreBuilder.php create mode 100644 src/Models/TvGenre.php create mode 100644 src/Requests/TvGenre/ListAll.php create mode 100644 tests/Feature/Models/TvGenreTest.php create mode 100644 tests/fixtures/genre/tv/list/language=en.json create mode 100644 tests/fixtures/genre/tv/list/language=foo.json create mode 100644 tests/fixtures/genre/tv/list/language=it.json diff --git a/.gitignore b/.gitignore index 1f1d7cb..0780172 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ phpunit.xml .phpunit.result.cache composer.lock vendor/ +.dccache diff --git a/database/migrations/2022_01_19_150000_create_tv_genres_table.php b/database/migrations/2022_01_19_150000_create_tv_genres_table.php new file mode 100644 index 0000000..2bd6742 --- /dev/null +++ b/database/migrations/2022_01_19_150000_create_tv_genres_table.php @@ -0,0 +1,24 @@ +create(TvGenre::table(), static function (Blueprint $table): void { + $table->bigInteger('id')->unsigned()->primary(); + + $table->json('name'); + + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::connection(TvGenre::connection())->dropIfExists(TvGenre::table()); + } +}; diff --git a/src/Eloquent/Builders/TvGenreBuilder.php b/src/Eloquent/Builders/TvGenreBuilder.php new file mode 100644 index 0000000..5f4676b --- /dev/null +++ b/src/Eloquent/Builders/TvGenreBuilder.php @@ -0,0 +1,19 @@ + 'int', + ]; + + public array $translatable = [ + 'name', + ]; + + public static function all($columns = ['*']): EloquentCollection + { + $data = rescue(fn () => ListAll::request()->send()->collect('genres')); + + if ($data instanceof Collection) { + $data->each(fn (array $genre) => static::query()->updateOrCreate( + ['id' => $genre['id']], + ['name' => $genre['name']], + )); + } + + return parent::all($columns); + } + + // TODO: Build Tvshow + public function movies(): BelongsToMany + { + return $this->belongsToMany(Movie::class, 'movie_movie_genre'); + } + + public function fillFromTmdb(array $data, ?string $locale = null): static + { + $genre = $this->fill([ + 'id' => $data['id'], + ]); + + $locale ??= $this->getLocale(); + + $this->setTranslation('name', $locale, trim($data['name']) ?: null); + + return $genre; + } + + public function updateFromTmdb(?string $locale = null, array $with = []): bool + { + $data = rescue(fn () => ListAll::request()->language($locale)->send()->collect('genres')); + + if ($data === null) { + return false; + } + + $data = $data->keyBy('id'); + + if (! $data->has($this->id)) { + return false; + } + + return $this->fillFromTmdb($data->get($this->id), $locale)->save(); + } + + public function newEloquentBuilder($query): TvGenreBuilder + { + return new TvGenreBuilder($query); + } +} diff --git a/src/Requests/TvGenre/ListAll.php b/src/Requests/TvGenre/ListAll.php new file mode 100644 index 0000000..c709369 --- /dev/null +++ b/src/Requests/TvGenre/ListAll.php @@ -0,0 +1,25 @@ +request->get( + '/genre/tv/list', + [ + 'language' => $this->language ?? Tmdb::language(), + ] + )->throw(); + } +} diff --git a/tests/Datasets/Builder.php b/tests/Datasets/Builder.php index fbbbc5c..c83c2a4 100644 --- a/tests/Datasets/Builder.php +++ b/tests/Datasets/Builder.php @@ -4,6 +4,7 @@ use Astrotomic\Tmdb\Models\Movie; use Astrotomic\Tmdb\Models\MovieGenre; use Astrotomic\Tmdb\Models\Person; +use Astrotomic\Tmdb\Models\TvGenre; use Astrotomic\Tmdb\Models\WatchProvider; use Illuminate\Support\Collection; use Illuminate\Support\Fluent; @@ -14,6 +15,7 @@ MovieGenre::class => 35, Person::class => 561, Credit::class => '5a30d4a40e0a264cbe180b27', + TvGenre::class => 10751, WatchProvider::class => 8, \Astrotomic\Tmdb\Models\Collection::class => 529892, ]; @@ -29,6 +31,7 @@ MovieGenre::class => [35, 99], Person::class => [561, 10393], Credit::class => ['5a30d4a40e0a264cbe180b27', '5bb637c10e0a2633a7011036'], + TvGenre::class => [10751, 10759], WatchProvider::class => [8, 9], \Astrotomic\Tmdb\Models\Collection::class => [529892, 748], ]; @@ -46,6 +49,7 @@ MovieGenre::class => [35, 0], Person::class => [561, 0], Credit::class => ['5a30d4a40e0a264cbe180b27', ''], + TvGenre::class => [10751, 0], WatchProvider::class => [8, 0], \Astrotomic\Tmdb\Models\Collection::class => [529892, 0], ]; @@ -63,6 +67,7 @@ MovieGenre::class => [], Person::class => [], Credit::class => [], + TvGenre::class => [], WatchProvider::class => [], \Astrotomic\Tmdb\Models\Collection::class => [], ]; diff --git a/tests/Feature/Models/TvGenreTest.php b/tests/Feature/Models/TvGenreTest.php new file mode 100644 index 0000000..29fec69 --- /dev/null +++ b/tests/Feature/Models/TvGenreTest.php @@ -0,0 +1,73 @@ + 10762]); + $genre->updateFromTmdb(); + + Assert::assertSame(10762, $genre->id); + Assert::assertSame('Kids', $genre->name); +}); + +it('translates to undefined language on the fly', function (): void { + $genre = TvGenre::query()->find(16); + + expect($genre) + ->toBeInstanceOf(TvGenre::class) + ->exists->toBeTrue() + ->wasRecentlyCreated->toBeTrue() + ->id->toBe(35) + ->translate('name', 'it')->toBe('Commedia') + ->translate('name', 'en')->toBe('Comedy'); +}); + +it('does not translate empty language', function (): void { + $genre = TvGenre::query()->find(35); + $genre->setTranslation('name', 'en', null)->save(); + + expect($genre) + ->toBeInstanceOf(TvGenre::class) + ->exists->toBeTrue() + ->wasRecentlyCreated->toBeTrue() + ->id->toBe(35) + ->translate('name', 'it')->toBe('Commedia') + ->translate('name', 'en')->toBeNull(); +}); + +it('does return fallback for empty language', function (): void { + app()->setLocale('en'); + $genre = TvGenre::query()->find(35); + $genre->setTranslation('name', 'it', null)->save(); + + expect($genre) + ->toBeInstanceOf(TvGenre::class) + ->exists->toBeTrue() + ->wasRecentlyCreated->toBeTrue() + ->id->toBe(35) + ->translate('name', 'it', true)->toBe('Comedy') + ->translate('name', 'en', true)->toBe('Comedy'); +}); + +it('does return fallback for unknown language', function (): void { + app()->setLocale('en'); + $genre = TvGenre::query()->find(35); + + expect($genre) + ->toBeInstanceOf(TvGenre::class) + ->exists->toBeTrue() + ->wasRecentlyCreated->toBeTrue() + ->id->toBe(35) + ->translate('name', 'foo', true)->toBe('Comedy') + ->translate('name', 'en', true)->toBe('Comedy'); +}); + +it('loads all genres from tmdb', function (): void { + $genres = TvGenre::all(); + + expect($genres) + ->toBeInstanceOf(EloquentCollection::class) + ->toHaveCount(16); +}); diff --git a/tests/Feature/Requests/ListMovieGenresTest.php b/tests/Feature/Requests/ListMovieGenresTest.php index 7a79f59..ca0022c 100644 --- a/tests/Feature/Requests/ListMovieGenresTest.php +++ b/tests/Feature/Requests/ListMovieGenresTest.php @@ -1,8 +1,8 @@ send()->json(); expect($data) diff --git a/tests/fixtures/genre/tv/list/language=en.json b/tests/fixtures/genre/tv/list/language=en.json new file mode 100644 index 0000000..aa9b7a4 --- /dev/null +++ b/tests/fixtures/genre/tv/list/language=en.json @@ -0,0 +1,68 @@ +{ + "genres": [ + { + "id": 16, + "name": "Animation" + }, + { + "id": 18, + "name": "Drama" + }, + { + "id": 35, + "name": "Comedy" + }, + { + "id": 37, + "name": "Western" + }, + { + "id": 80, + "name": "Crime" + }, + { + "id": 99, + "name": "Documentary" + }, + { + "id": 9648, + "name": "Mistery" + }, + { + "id": 10751, + "name": "Family" + }, + { + "id": 10759, + "name": "Action & Adventure" + }, + { + "id": 10762, + "name": "Kids" + }, + { + "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" + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/genre/tv/list/language=foo.json b/tests/fixtures/genre/tv/list/language=foo.json new file mode 100644 index 0000000..aa9b7a4 --- /dev/null +++ b/tests/fixtures/genre/tv/list/language=foo.json @@ -0,0 +1,68 @@ +{ + "genres": [ + { + "id": 16, + "name": "Animation" + }, + { + "id": 18, + "name": "Drama" + }, + { + "id": 35, + "name": "Comedy" + }, + { + "id": 37, + "name": "Western" + }, + { + "id": 80, + "name": "Crime" + }, + { + "id": 99, + "name": "Documentary" + }, + { + "id": 9648, + "name": "Mistery" + }, + { + "id": 10751, + "name": "Family" + }, + { + "id": 10759, + "name": "Action & Adventure" + }, + { + "id": 10762, + "name": "Kids" + }, + { + "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" + } + ] +} \ No newline at end of file diff --git a/tests/fixtures/genre/tv/list/language=it.json b/tests/fixtures/genre/tv/list/language=it.json new file mode 100644 index 0000000..122e91b --- /dev/null +++ b/tests/fixtures/genre/tv/list/language=it.json @@ -0,0 +1,68 @@ +{ + "genres": [ + { + "id": 16, + "name": "Animazione" + }, + { + "id": 18, + "name": "Dramma" + }, + { + "id": 35, + "name": "Commedia" + }, + { + "id": 37, + "name": "Western" + }, + { + "id": 80, + "name": "Crime" + }, + { + "id": 99, + "name": "Documentario" + }, + { + "id": 9648, + "name": "Mistero" + }, + { + "id": 10751, + "name": "Famiglia" + }, + { + "id": 10759, + "name": "Action & Adventure" + }, + { + "id": 10762, + "name": "Kids" + }, + { + "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" + } + ] +} \ No newline at end of file From 99a0d5d9f72557dd4eeb7f5ee7c91d370160b773 Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Wed, 19 Jan 2022 18:12:25 +0100 Subject: [PATCH 02/21] Basic TV working --- .../2022_01_19_160000_create_tvs_table.php | 46 +++ .../2022_01_19_180000_create_tv_tv_gerne.php | 24 ++ src/Eloquent/Builders/TvBuilder.php | 19 ++ src/Enums/TvStatus.php | 33 ++ src/Enums/TvType.php | 23 ++ src/Models/Tv.php | 315 ++++++++++++++++++ src/Requests/Tv/Credits.php | 31 ++ src/Requests/Tv/Details.php | 34 ++ src/Requests/Tv/Popular.php | 61 ++++ src/Requests/Tv/Recommendations.php | 58 ++++ src/Requests/Tv/Similars.php | 58 ++++ src/Requests/Tv/TopRated.php | 61 ++++ src/Requests/Tv/WatchProviders.php | 27 ++ 13 files changed, 790 insertions(+) create mode 100644 database/migrations/2022_01_19_160000_create_tvs_table.php create mode 100644 database/migrations/2022_01_19_180000_create_tv_tv_gerne.php create mode 100644 src/Eloquent/Builders/TvBuilder.php create mode 100644 src/Enums/TvStatus.php create mode 100644 src/Enums/TvType.php create mode 100644 src/Models/Tv.php create mode 100644 src/Requests/Tv/Credits.php create mode 100644 src/Requests/Tv/Details.php create mode 100644 src/Requests/Tv/Popular.php create mode 100644 src/Requests/Tv/Recommendations.php create mode 100644 src/Requests/Tv/Similars.php create mode 100644 src/Requests/Tv/TopRated.php create mode 100644 src/Requests/Tv/WatchProviders.php diff --git a/database/migrations/2022_01_19_160000_create_tvs_table.php b/database/migrations/2022_01_19_160000_create_tvs_table.php new file mode 100644 index 0000000..a40a0b2 --- /dev/null +++ b/database/migrations/2022_01_19_160000_create_tvs_table.php @@ -0,0 +1,46 @@ +create(Tv::table(), static function (Blueprint $table): void { + $table->bigInteger('id')->unsigned()->primary(); + $table->string('backdrop_path')->nullable(); + $table->json('episode_run_time')->nullable(); + $table->date('first_air_date')->nullable(); + $table->json('homepage')->nullable(); + $table->boolean('in_production')->nullable(); + $table->json('languages')->nullable(); + $table->date('last_air_date')->nullable(); + $table->json('name')->nullable(); + $table->integer('number_of_episodes')->nullable(); + $table->integer('number_of_seasons')->nullable(); + $table->json('origin_country')->nullable(); + $table->string('original_language', 2)->nullable(); + $table->string('original_name')->nullable(); + $table->json('overview')->nullable(); + $table->decimal('popularity')->unsigned()->nullable(); + $table->json('poster_path')->nullable(); + $table->json('production_companies')->nullable(); + $table->json('production_countries')->nullable(); + $table->json('seasons')->nullable(); + $table->json('spoken_languages')->nullable(); + $table->string('status')->nullable(); + $table->json('tagline')->nullable(); + $table->string('type')->nullable(); + $table->decimal('vote_average')->nullable(); + $table->integer('vote_count')->default(0); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::connection(Tv::connection())->dropIfExists(Tv::table()); + } +}; diff --git a/database/migrations/2022_01_19_180000_create_tv_tv_gerne.php b/database/migrations/2022_01_19_180000_create_tv_tv_gerne.php new file mode 100644 index 0000000..79c8fbd --- /dev/null +++ b/database/migrations/2022_01_19_180000_create_tv_tv_gerne.php @@ -0,0 +1,24 @@ +create('tv_tv_genre', static function (Blueprint $table): void { + $table->foreignId('tv_id')->constrained(Tv::table()); + $table->foreignId('tv_genre_id')->constrained(TvGenre::table()); + + $table->unique(['tv_id', 'tv_genre_id']); + }); + } + + public function down(): void + { + Schema::connection(Tv::connection())->dropIfExists('tv_tv_genre'); + } +}; diff --git a/src/Eloquent/Builders/TvBuilder.php b/src/Eloquent/Builders/TvBuilder.php new file mode 100644 index 0000000..f2a74b7 --- /dev/null +++ b/src/Eloquent/Builders/TvBuilder.php @@ -0,0 +1,19 @@ + 'Rumored', + 'PLANNED' => 'Planned', + 'IN_PRODUCTION' => 'In Production', + 'POST_PRODUCTION' => 'Post Production', + 'RELEASED' => 'Released', + 'CANCELED' => 'Canceled', + 'RETURNING_SERIES' => 'Returning Series', + 'ENDED' => 'Ended', + // TODO: Implement more values, need testing, no official documentation available + ]; + } +} diff --git a/src/Enums/TvType.php b/src/Enums/TvType.php new file mode 100644 index 0000000..344a712 --- /dev/null +++ b/src/Enums/TvType.php @@ -0,0 +1,23 @@ + 'Scripted', + 'REALITY' => 'Reality', + 'MINISERIES' => 'Miniseries', + // TODO: Add more values, need testing, no official documentation available + ]; + } +} diff --git a/src/Models/Tv.php b/src/Models/Tv.php new file mode 100644 index 0000000..5009103 --- /dev/null +++ b/src/Models/Tv.php @@ -0,0 +1,315 @@ + 'int', + 'episode_run_time' => 'array', + 'languages' => 'array', + 'origin_country' => 'array', + 'vote_count' => 'int', + 'popularity' => 'float', + 'vote_average' => 'float', + 'first_air_date' => 'date', + 'last_air_date' => 'date', + 'production_countries' => 'array', + 'production_companies' => 'array', + 'seasons' => 'array', + 'spoken_languages' => 'array', + 'status' => TvStatus::class.':nullable', + 'type' => TvType::class.':nullable', + ]; + + public array $translatable = [ + 'name', + 'tagline', + 'overview', + 'poster_path', + 'homepage', + ]; + + public static function popular(?int $limit): EloquentCollection + { + $ids = Popular::request() + ->cursor() + ->when($limit, fn (LazyCollection $collection) => $collection->take($limit)) + ->pluck('id'); + + return static::query()->findMany($ids); + } + + public static function toprated(?int $limit): EloquentCollection + { + $ids = TopRated::request() + ->cursor() + ->when($limit, fn (LazyCollection $collection) => $collection->take($limit)) + ->pluck('id'); + + return static::query()->findMany($ids); + } + + public function genres(): BelongsToMany + { + return $this->belongsToMany(TvGenre::class, 'tv_tv_genre'); + } + + public function collection(): BelongsTo + { + return $this->belongsTo(Collection::class); + } + + public function credits(): MorphManyCredits + { + /** @var \Astrotomic\Tmdb\Models\Credit $instance */ + $instance = $this->newRelatedInstance(Credit::class); + + return new MorphManyCredits( + $instance->newQuery(), + $this, + $instance->qualifyColumn('media_type'), + $instance->qualifyColumn('media_id'), + $this->getKeyName() + ); + } + + public function cast(): MorphManyCredits + { + return $this->credits()->whereCreditType(CreditType::CAST()); + } + + public function crew(): MorphManyCredits + { + return $this->credits()->whereCreditType(CreditType::CREW()); + } + + public function fillFromTmdb(array $data, ?string $locale = null): static + { + $this->fill([ + 'id' => $data['id'], + 'backdrop_path' => $data['backdrop_path'] ?: null, + 'episode_run_time' => $data['episode_run_time'] ?: null, + 'first_air_date' => $data['first_air_date'] ?: null, + 'homepage' => $data['homepage'] ?: null, + 'in_production' => $data['in_production'] ?: null, + 'languages' => $data['languages'] ?: null, + 'last_air_date' => $data['last_air_date'] ?: null, + 'name' => $data['name'] ?: null, + 'number_of_episodes' => $data['number_of_episodes'] ?: null, + 'number_of_seasons' => $data['number_of_seasons'] ?: null, + 'origin_country' => array_column($data['origin_country'] ?? [], 'iso_3166_1'), + 'original_language' => $data['original_language'] ?: null, + 'original_name' => $data['original_name'] ?: null, + 'overview' => $data['overview'] ?: null, + 'popularity' => $data['popularity'] ?: null, + 'poster_path' => $data['poster_path'] ?: null, + 'production_companies' => array_column($data['production_companies'] ?? [], 'name'), + 'production_countries' => array_column($data['production_countries'] ?: [], 'iso_3166_1'), + 'seasons' => $data['seasons'] ?: null, + 'spoken_languages' => array_column($data['spoken_languages'] ?: [], 'iso_639_1'), + 'status' => $data['status'] ?: null, + 'tagline' => $data['tagline'] ?: null, + 'type' => $data['type'] ?: null, + 'vote_average' => $data['vote_average'] ?: null, + 'vote_count' => $data['vote_count'] ?: 0, + + ]); + + $locale ??= $this->getLocale(); + + $this->setTranslation('overview', $locale, trim($data['overview']) ?: null); + $this->setTranslation('tagline', $locale, trim($data['tagline']) ?: null); + $this->setTranslation('name', $locale, trim($data['name']) ?: null); + $this->setTranslation('poster_path', $locale, trim($data['poster_path']) ?: null); + + return $this; + } + + public function updateFromTmdb(?string $locale = null, array $with = []): bool + { + $append = collect($with) + ->map(fn (string $relation) => match ($relation) { + 'cast', 'crew', 'credits' => Details::APPEND_CREDITS, + default => null, + }) + ->filter() + ->unique() + ->values() + ->all(); + + $data = rescue( + fn () => Details::request($this->id) + ->language($locale) + ->append(...$append) + ->send() + ->json() + ); + + if ($data === null) { + return false; + } + + if (! $this->fillFromTmdb($data, $locale)->save()) { + return false; + } + + $this->genres()->sync( + collect($data['genres'] ?: []) + ->map(static function (array $data) use ($locale): TvGenre { + $genre = TvGenre::query()->findOrNew($data['id']); + $genre->fillFromTmdb($data, $locale)->save(); + + return $genre; + }) + ->pluck('id') + ); + + /*if ($data['belongs_to_collection']) { + $this->collection()->associate( + Collection::query()->findOrFail($data['belongs_to_collection']['id']) + )->save(); + }*/ + + if (isset($data['credits'])) { + if (in_array('credits', $with) || in_array('cast', $with)) { + foreach ($data['credits']['cast'] as $cast) { + Credit::query()->findOrFail($cast['credit_id']); + } + } + + if (in_array('credits', $with) || in_array('crew', $with)) { + foreach ($data['credits']['crew'] as $crew) { + Credit::query()->findOrFail($crew['credit_id']); + } + } + } + + return true; + } + + public function newEloquentBuilder($query): TvBuilder + { + return new TvBuilder($query); + } + + public function runtime(): ?CarbonInterval + { + if ($this->runtime === null) { + return null; + } + + return CarbonInterval::minutes($this->runtime)->cascade(); + } + + public function poster(): Poster + { + return new Poster( + $this->poster_path, + $this->title + ); + } + + public function backdrop(): Backdrop + { + return new Backdrop( + $this->backdrop_path, + $this->title + ); + } + + public function recommendations(?int $limit): EloquentCollection + { + $ids = Recommendations::request($this->id) + ->cursor() + ->when($limit, fn (LazyCollection $collection) => $collection->take($limit)) + ->pluck('id'); + + return static::query()->findMany($ids); + } + + public function similars(?int $limit): EloquentCollection + { + $ids = Similars::request($this->id) + ->cursor() + ->when($limit, fn (LazyCollection $collection) => $collection->take($limit)) + ->pluck('id'); + + return static::query()->findMany($ids); + } + + public function watchProviders(?string $region = null, ?WatchProviderType $type = null): EloquentCollection + { + return WatchProvider::query()->findMany( + WatchProviders::request($this->id)->send()->collect(sprintf( + 'results.%s.%s.*.provider_id', + $region ?? '*', + $type?->value ?? '*' + )) + ); + } +} diff --git a/src/Requests/Tv/Credits.php b/src/Requests/Tv/Credits.php new file mode 100644 index 0000000..0a7be3f --- /dev/null +++ b/src/Requests/Tv/Credits.php @@ -0,0 +1,31 @@ +request->get( + sprintf('/tv/%d/credits', $this->tvId), + array_filter([ + 'language' => $this->language ?? Tmdb::language(), + ]) + )->throw(); + } +} diff --git a/src/Requests/Tv/Details.php b/src/Requests/Tv/Details.php new file mode 100644 index 0000000..e1d26f8 --- /dev/null +++ b/src/Requests/Tv/Details.php @@ -0,0 +1,34 @@ +request->get( + sprintf('/tv/%d', $this->tvId), + array_filter([ + 'language' => $this->language ?? Tmdb::language(), + 'append_to_response' => implode(',', $this->append), + ]) + )->throw(); + } +} diff --git a/src/Requests/Tv/Popular.php b/src/Requests/Tv/Popular.php new file mode 100644 index 0000000..f12da07 --- /dev/null +++ b/src/Requests/Tv/Popular.php @@ -0,0 +1,61 @@ +page = $page; + + return $this; + } + + public function region(string $region): static + { + $this->region = $region; + + return $this; + } + + public function send(): Response + { + return $this->request->get( + '/tv/popular', + array_filter([ + 'language' => $this->language ?? Tmdb::language(), + 'region' => $this->region ?? Tmdb::region(), + 'page' => $this->page, + ]) + )->throw(); + } + + public function cursor(): LazyCollection + { + return LazyCollection::make(function (): Generator { + $this->page = 1; + do { + $response = $this->send()->json(); + + yield from $response['results']; + + $totalPages = $response['total_pages']; + $this->page++; + } while ($this->page <= $totalPages); + }); + } +} diff --git a/src/Requests/Tv/Recommendations.php b/src/Requests/Tv/Recommendations.php new file mode 100644 index 0000000..f48b40f --- /dev/null +++ b/src/Requests/Tv/Recommendations.php @@ -0,0 +1,58 @@ +page = $page; + + return $this; + } + + public function send(): Response + { + return $this->request->get( + sprintf('/tv/%d/recommendations', $this->tvId), + array_filter([ + 'language' => $this->language ?? Tmdb::language(), + 'page' => $this->page, + ]) + )->throw(); + } + + public function cursor(): LazyCollection + { + return LazyCollection::make(function (): Generator { + $this->page = 1; + do { + $response = $this->send()->json(); + + yield from $response['results']; + + $totalPages = $response['total_pages']; + $this->page++; + } while ($this->page <= $totalPages); + }); + } +} diff --git a/src/Requests/Tv/Similars.php b/src/Requests/Tv/Similars.php new file mode 100644 index 0000000..e7bb44c --- /dev/null +++ b/src/Requests/Tv/Similars.php @@ -0,0 +1,58 @@ +page = $page; + + return $this; + } + + public function send(): Response + { + return $this->request->get( + sprintf('/tv/%d/similar', $this->tvId), + array_filter([ + 'language' => $this->language ?? Tmdb::language(), + 'page' => $this->page, + ]) + )->throw(); + } + + public function cursor(): LazyCollection + { + return LazyCollection::make(function (): Generator { + $this->page = 1; + do { + $response = $this->send()->json(); + + yield from $response['results']; + + $totalPages = $response['total_pages']; + $this->page++; + } while ($this->page <= $totalPages); + }); + } +} diff --git a/src/Requests/Tv/TopRated.php b/src/Requests/Tv/TopRated.php new file mode 100644 index 0000000..8f9b3c6 --- /dev/null +++ b/src/Requests/Tv/TopRated.php @@ -0,0 +1,61 @@ +page = $page; + + return $this; + } + + public function region(string $region): static + { + $this->region = $region; + + return $this; + } + + public function send(): Response + { + return $this->request->get( + '/tv/top_rated', + array_filter([ + 'language' => $this->language ?? Tmdb::language(), + 'region' => $this->region ?? Tmdb::region(), + 'page' => $this->page, + ]) + )->throw(); + } + + public function cursor(): LazyCollection + { + return LazyCollection::make(function (): Generator { + $this->page = 1; + do { + $response = $this->send()->json(); + + yield from $response['results']; + + $totalPages = $response['total_pages']; + $this->page++; + } while ($this->page <= $totalPages); + }); + } +} diff --git a/src/Requests/Tv/WatchProviders.php b/src/Requests/Tv/WatchProviders.php new file mode 100644 index 0000000..c7e5eb3 --- /dev/null +++ b/src/Requests/Tv/WatchProviders.php @@ -0,0 +1,27 @@ +request->get( + sprintf('/tv/%d/watch/providers', $this->tvId), + )->throw(); + } +} From 151dd29933da2e1958a0a28515a30baee320dc13 Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Thu, 20 Jan 2022 12:03:44 +0100 Subject: [PATCH 03/21] Basic Network init --- ...022_01_19_153000_create_networks_table.php | 28 ++++ src/Eloquent/Builders/NetworkBuilder.php | 18 +++ src/Models/Network.php | 122 ++++++++++++++++++ src/Requests/Network/Details.php | 33 +++++ tests/Datasets/Builder.php | 24 ++-- 5 files changed, 216 insertions(+), 9 deletions(-) create mode 100644 database/migrations/2022_01_19_153000_create_networks_table.php create mode 100644 src/Eloquent/Builders/NetworkBuilder.php create mode 100644 src/Models/Network.php create mode 100644 src/Requests/Network/Details.php diff --git a/database/migrations/2022_01_19_153000_create_networks_table.php b/database/migrations/2022_01_19_153000_create_networks_table.php new file mode 100644 index 0000000..162481c --- /dev/null +++ b/database/migrations/2022_01_19_153000_create_networks_table.php @@ -0,0 +1,28 @@ +create(Network::table(), static function (Blueprint $table): void { + $table->bigInteger('id')->unsigned()->primary(); + $table->string('headquarters')->nullable(); + $table->json('homepage')->nullable(); + $table->json('logo_path')->nullable(); + $table->json('languages')->nullable(); + $table->json('name')->nullable(); + $table->string('origin_country')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::connection(Network::connection())->dropIfExists(Network::table()); + } +}; diff --git a/src/Eloquent/Builders/NetworkBuilder.php b/src/Eloquent/Builders/NetworkBuilder.php new file mode 100644 index 0000000..3578c12 --- /dev/null +++ b/src/Eloquent/Builders/NetworkBuilder.php @@ -0,0 +1,18 @@ + 'int', + 'headquarters' => 'string', + 'homepage' => 'string', + 'logo_path' => 'string', + 'name' => 'string', + 'origin_country' => 'array', + ]; + + public array $translatable = [ + 'name', + 'homepage', + 'logo_path', + ]; + + public function fillFromTmdb(array $data, ?string $locale = null): static + { + $this->fill([ + 'id' => $data['id'], + 'headquarters' => $data['headquarters'] ?? null, + 'homepage' => $data['homepage'] ?? null, + 'logo_path' => $data['logo_path'] ?? null, + 'name' => $data['name'] ?? null, + 'origin_country' => $data['origin_country'] ?? [], 'iso_3166_1', + ]); + + $locale ??= $this->getLocale(); + + $this->setTranslation('homepage', $locale, trim($data['homepage']) ?: null); + $this->setTranslation('name', $locale, trim($data['name']) ?: null); + $this->setTranslation('logo_path', $locale, trim($data['logo_path']) ?: null); + + return $this; + } + + public function updateFromTmdb(?string $locale = null, array $with = []): bool + { + /*$append = collect($with) + ->map(fn (string $relation) => match ($relation) { + 'cast', 'crew', 'credits' => Details::APPEND_CREDITS, + default => null, + }) + ->filter() + ->unique() + ->values() + ->all();*/ + + $data = rescue( + fn () => Details::request($this->id) + ->language($locale) + //->append(...$append) + ->send() + ->json() + ); + + if ($data === null) { + return false; + } + + if (!$this->fillFromTmdb($data, $locale)->save()) { + return false; + } + + return true; + } + + public function newEloquentBuilder($query): NetworkBuilder + { + return new NetworkBuilder($query); + } + + + public function logo(): Logo + { + return new Logo( + $this->logo_path, + $this->name + ); + } +} diff --git a/src/Requests/Network/Details.php b/src/Requests/Network/Details.php new file mode 100644 index 0000000..9777599 --- /dev/null +++ b/src/Requests/Network/Details.php @@ -0,0 +1,33 @@ +request->get( + sprintf('/network/%d', $this->networkId), + array_filter([ + 'language' => $this->language ?? Tmdb::language(), + 'append_to_response' => implode(',', $this->append), + ]) + )->throw(); + } +} diff --git a/tests/Datasets/Builder.php b/tests/Datasets/Builder.php index c83c2a4..b7b7c10 100644 --- a/tests/Datasets/Builder.php +++ b/tests/Datasets/Builder.php @@ -4,7 +4,9 @@ use Astrotomic\Tmdb\Models\Movie; use Astrotomic\Tmdb\Models\MovieGenre; use Astrotomic\Tmdb\Models\Person; +use Astrotomic\Tmdb\Models\Tv; use Astrotomic\Tmdb\Models\TvGenre; +use Astrotomic\Tmdb\Models\Network; use Astrotomic\Tmdb\Models\WatchProvider; use Illuminate\Support\Collection; use Illuminate\Support\Fluent; @@ -16,6 +18,7 @@ Person::class => 561, Credit::class => '5a30d4a40e0a264cbe180b27', TvGenre::class => 10751, + Tv::class => 63174, WatchProvider::class => 8, \Astrotomic\Tmdb\Models\Collection::class => 529892, ]; @@ -32,14 +35,15 @@ Person::class => [561, 10393], Credit::class => ['5a30d4a40e0a264cbe180b27', '5bb637c10e0a2633a7011036'], TvGenre::class => [10751, 10759], + Tv::class => [63174, 71914], WatchProvider::class => [8, 9], \Astrotomic\Tmdb\Models\Collection::class => [529892, 748], ]; foreach ($set as $model => $ids) { - yield $model::table().':array' => [$model, $ids]; - yield $model::table().':collection' => [$model, new Collection($ids)]; - yield $model::table().':fluent' => [$model, new Fluent($ids)]; + yield $model::table() . ':array' => [$model, $ids]; + yield $model::table() . ':collection' => [$model, new Collection($ids)]; + yield $model::table() . ':fluent' => [$model, new Fluent($ids)]; } }); @@ -50,14 +54,15 @@ Person::class => [561, 0], Credit::class => ['5a30d4a40e0a264cbe180b27', ''], TvGenre::class => [10751, 0], + Tv::class => [63174, 0], WatchProvider::class => [8, 0], \Astrotomic\Tmdb\Models\Collection::class => [529892, 0], ]; foreach ($set as $model => $ids) { - yield $model::table().':array' => [$model, $ids]; - yield $model::table().':collection' => [$model, new Collection($ids)]; - yield $model::table().':fluent' => [$model, new Fluent($ids)]; + yield $model::table() . ':array' => [$model, $ids]; + yield $model::table() . ':collection' => [$model, new Collection($ids)]; + yield $model::table() . ':fluent' => [$model, new Fluent($ids)]; } }); @@ -68,13 +73,14 @@ Person::class => [], Credit::class => [], TvGenre::class => [], + Tv::class => [], WatchProvider::class => [], \Astrotomic\Tmdb\Models\Collection::class => [], ]; foreach ($set as $model => $ids) { - yield $model::table().':array' => [$model, $ids]; - yield $model::table().':collection' => [$model, new Collection($ids)]; - yield $model::table().':fluent' => [$model, new Fluent($ids)]; + yield $model::table() . ':array' => [$model, $ids]; + yield $model::table() . ':collection' => [$model, new Collection($ids)]; + yield $model::table() . ':fluent' => [$model, new Fluent($ids)]; } }); From 5525579cbb66f20f4cd9e0680d6256dd86dce0eb Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Thu, 20 Jan 2022 15:47:55 +0100 Subject: [PATCH 04/21] Many TV to Many TvGenre --- src/Models/TvGenre.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Models/TvGenre.php b/src/Models/TvGenre.php index 698bd58..e1fbba1 100644 --- a/src/Models/TvGenre.php +++ b/src/Models/TvGenre.php @@ -54,10 +54,9 @@ public static function all($columns = ['*']): EloquentCollection return parent::all($columns); } - // TODO: Build Tvshow - public function movies(): BelongsToMany + public function tvs(): BelongsToMany { - return $this->belongsToMany(Movie::class, 'movie_movie_genre'); + return $this->belongsToMany(Tv::class, 'tv_tv_genre'); } public function fillFromTmdb(array $data, ?string $locale = null): static @@ -83,7 +82,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool $data = $data->keyBy('id'); - if (! $data->has($this->id)) { + if (!$data->has($this->id)) { return false; } From 5f144d196667cfc7fe0e6d89ceb3a49d11557f33 Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Thu, 20 Jan 2022 15:48:31 +0100 Subject: [PATCH 05/21] Tv get many Networks --- src/Models/Tv.php | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/Models/Tv.php b/src/Models/Tv.php index 5009103..3957b39 100644 --- a/src/Models/Tv.php +++ b/src/Models/Tv.php @@ -82,8 +82,8 @@ class Tv extends Model 'production_companies' => 'array', 'seasons' => 'array', 'spoken_languages' => 'array', - 'status' => TvStatus::class.':nullable', - 'type' => TvType::class.':nullable', + 'status' => TvStatus::class . ':nullable', + 'type' => TvType::class . ':nullable', ]; public array $translatable = [ @@ -119,6 +119,11 @@ public function genres(): BelongsToMany return $this->belongsToMany(TvGenre::class, 'tv_tv_genre'); } + public function networks(): BelongsToMany + { + return $this->belongsToMany(Network::class, 'network_tv'); + } + public function collection(): BelongsTo { return $this->belongsTo(Collection::class); @@ -194,7 +199,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool { $append = collect($with) ->map(fn (string $relation) => match ($relation) { - 'cast', 'crew', 'credits' => Details::APPEND_CREDITS, + 'networks' => Details::APPEND_NETWORKS, default => null, }) ->filter() @@ -214,7 +219,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool return false; } - if (! $this->fillFromTmdb($data, $locale)->save()) { + if (!$this->fillFromTmdb($data, $locale)->save()) { return false; } @@ -229,13 +234,25 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool ->pluck('id') ); + + $this->networks()->sync( + collect($data['networks'] ?: []) + ->map(static function (array $data) use ($locale): Network { + $network = Network::query()->findOrNew($data['id']); + $network->fillFromTmdb($data, $locale)->save(); + + return $network; + }) + ->pluck('id') + ); + /*if ($data['belongs_to_collection']) { $this->collection()->associate( Collection::query()->findOrFail($data['belongs_to_collection']['id']) )->save(); }*/ - if (isset($data['credits'])) { + /*if (isset($data['credits'])) { if (in_array('credits', $with) || in_array('cast', $with)) { foreach ($data['credits']['cast'] as $cast) { Credit::query()->findOrFail($cast['credit_id']); @@ -247,7 +264,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool Credit::query()->findOrFail($crew['credit_id']); } } - } + }*/ return true; } From 78e6538558d9b38e65e7939eef9a4eb0d2928e28 Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Thu, 20 Jan 2022 15:49:32 +0100 Subject: [PATCH 06/21] Network has many Tvs --- src/Models/Network.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Models/Network.php b/src/Models/Network.php index 2b25a8f..f355a59 100644 --- a/src/Models/Network.php +++ b/src/Models/Network.php @@ -7,7 +7,7 @@ use Astrotomic\Tmdb\Images\Logo; use Astrotomic\Tmdb\Requests\Network\Details; -use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; /** * @property int $id @@ -55,6 +55,11 @@ class Network extends Model 'logo_path', ]; + public function tvs(): BelongsToMany + { + return $this->belongsToMany(Tv::class, 'tv_network'); + } + public function fillFromTmdb(array $data, ?string $locale = null): static { $this->fill([ From 2e5ef8d059c600bad820c82575cbacc90cddc953 Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Thu, 20 Jan 2022 15:49:55 +0100 Subject: [PATCH 07/21] Network as many Tvs --- ...2_01_20_121500_create_network_tv_table.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 database/migrations/2022_01_20_121500_create_network_tv_table.php diff --git a/database/migrations/2022_01_20_121500_create_network_tv_table.php b/database/migrations/2022_01_20_121500_create_network_tv_table.php new file mode 100644 index 0000000..40599f9 --- /dev/null +++ b/database/migrations/2022_01_20_121500_create_network_tv_table.php @@ -0,0 +1,25 @@ +create('network_tv', static function (Blueprint $table): void { + $table->foreignId('tv_id')->constrained(Tv::table()); + $table->foreignId('network_id')->constrained(Network::table()); + + $table->unique(['tv_id', 'network_id']); + }); + } + + public function down(): void + { + Schema::connection(Tv::connection())->dropIfExists('network_tv'); + } +}; From 1544748a303deca5f3fa05dc339e456d06fc0ca4 Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Thu, 20 Jan 2022 22:50:33 +0100 Subject: [PATCH 08/21] TV Season init --- src/Eloquent/Builders/TvSeasonBuilder.php | 19 +++ src/Eloquent/Relations/HasManyTvSeasons.php | 17 +++ src/Models/Tv.php | 45 +++++-- src/Models/TvSeason.php | 123 ++++++++++++++++++++ src/Requests/TvSeason/Details.php | 33 ++++++ 5 files changed, 228 insertions(+), 9 deletions(-) create mode 100644 src/Eloquent/Builders/TvSeasonBuilder.php create mode 100644 src/Eloquent/Relations/HasManyTvSeasons.php create mode 100644 src/Models/TvSeason.php create mode 100644 src/Requests/TvSeason/Details.php diff --git a/src/Eloquent/Builders/TvSeasonBuilder.php b/src/Eloquent/Builders/TvSeasonBuilder.php new file mode 100644 index 0000000..7995ee8 --- /dev/null +++ b/src/Eloquent/Builders/TvSeasonBuilder.php @@ -0,0 +1,19 @@ +getParentKey())->send()->json('parts.*.id'); + + return $this->query->findMany($ids, $columns); + } +} diff --git a/src/Models/Tv.php b/src/Models/Tv.php index 3957b39..f21300a 100644 --- a/src/Models/Tv.php +++ b/src/Models/Tv.php @@ -3,6 +3,7 @@ namespace Astrotomic\Tmdb\Models; use Astrotomic\Tmdb\Eloquent\Builders\TvBuilder; +use Astrotomic\Tmdb\Eloquent\Relations\HasManyTvSeasons; use Astrotomic\Tmdb\Eloquent\Relations\MorphManyCredits; use Astrotomic\Tmdb\Enums\CreditType; use Astrotomic\Tmdb\Enums\TvStatus; @@ -66,6 +67,7 @@ class Tv extends Model { use HasTranslations; + // TODO: Fill with all available fields in $fillable instead of $guarded protected $guarded = []; protected $casts = [ @@ -80,10 +82,9 @@ class Tv extends Model 'last_air_date' => 'date', 'production_countries' => 'array', 'production_companies' => 'array', - 'seasons' => 'array', 'spoken_languages' => 'array', - 'status' => TvStatus::class . ':nullable', - 'type' => TvType::class . ':nullable', + 'status' => TvStatus::class.':nullable', + 'type' => TvType::class.':nullable', ]; public array $translatable = [ @@ -153,6 +154,19 @@ public function crew(): MorphManyCredits return $this->credits()->whereCreditType(CreditType::CREW()); } + public function seasons(): HasManyTvSeasons + { + /** @var \Astrotomic\Tmdb\Models\TvSeason $instance */ + $instance = $this->newRelatedInstance(TvSeason::class); + + return new HasManyTvSeasons( + $instance->newQuery(), + $this, + $instance->qualifyColumn($this->getForeignKey()), + $this->getKeyName() + ); + } + public function fillFromTmdb(array $data, ?string $locale = null): static { $this->fill([ @@ -175,7 +189,6 @@ public function fillFromTmdb(array $data, ?string $locale = null): static 'poster_path' => $data['poster_path'] ?: null, 'production_companies' => array_column($data['production_companies'] ?? [], 'name'), 'production_countries' => array_column($data['production_countries'] ?: [], 'iso_3166_1'), - 'seasons' => $data['seasons'] ?: null, 'spoken_languages' => array_column($data['spoken_languages'] ?: [], 'iso_639_1'), 'status' => $data['status'] ?: null, 'tagline' => $data['tagline'] ?: null, @@ -197,7 +210,7 @@ public function fillFromTmdb(array $data, ?string $locale = null): static public function updateFromTmdb(?string $locale = null, array $with = []): bool { - $append = collect($with) + /*$append = collect($with) ->map(fn (string $relation) => match ($relation) { 'networks' => Details::APPEND_NETWORKS, default => null, @@ -205,12 +218,12 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool ->filter() ->unique() ->values() - ->all(); + ->all();*/ $data = rescue( fn () => Details::request($this->id) ->language($locale) - ->append(...$append) + //->append(...$append) ->send() ->json() ); @@ -219,7 +232,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool return false; } - if (!$this->fillFromTmdb($data, $locale)->save()) { + if (! $this->fillFromTmdb($data, $locale)->save()) { return false; } @@ -234,7 +247,6 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool ->pluck('id') ); - $this->networks()->sync( collect($data['networks'] ?: []) ->map(static function (array $data) use ($locale): Network { @@ -246,6 +258,19 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool ->pluck('id') ); + if (isset($data['seasons'])) { + $this->seasons()->saveMany( + (collect($data['seasons']) + ->map(static function (array $data) use ($locale): TvSeason { + $season = TvSeason::query()->findOrNew($data['id']); + $season->fillFromTmdb($data, $locale)->save(); + + return $season; + }) + ->all()) + ); + } + /*if ($data['belongs_to_collection']) { $this->collection()->associate( Collection::query()->findOrFail($data['belongs_to_collection']['id']) @@ -319,6 +344,8 @@ public function similars(?int $limit): EloquentCollection return static::query()->findMany($ids); } + //TODO: Make TopRated, Popular, OnTheAir, AiringToday + public function watchProviders(?string $region = null, ?WatchProviderType $type = null): EloquentCollection { return WatchProvider::query()->findMany( diff --git a/src/Models/TvSeason.php b/src/Models/TvSeason.php new file mode 100644 index 0000000..db8995c --- /dev/null +++ b/src/Models/TvSeason.php @@ -0,0 +1,123 @@ + 'int', + 'air_date' => 'string', + 'name' => 'string', + 'overview' => 'string', + 'poster_path' => 'string', + 'season_number' => 'int', + ]; + + public array $translatable = [ + 'name', + 'overview', + 'poster_path', + ]; + + public function fillFromTmdb(array $data, ?string $locale = null): static + { + $this->fill([ + 'id' => $data['id'], + 'air_date' => $data['air_date'] ?: null, + 'name' => $data['name'] ?: null, + 'overview' => $data['overview'] ?: null, + 'poster_path' => $data['poster_path'] ?: null, + 'season_number' => $data['season_number'] ?: null, + ]); + + $locale ??= $this->getLocale(); + + $this->setTranslation('overview', $locale, trim($data['overview']) ?: null); + $this->setTranslation('name', $locale, trim($data['name']) ?: null); + $this->setTranslation('poster_path', $locale, trim($data['poster_path']) ?: null); + + return $this; + } + + public function updateFromTmdb(?string $locale = null, array $with = []): bool + { + /*$append = collect($with) + ->map(fn (string $relation) => match ($relation) { + 'networks' => Details::APPEND_NETWORKS, + default => null, + }) + ->filter() + ->unique() + ->values() + ->all();*/ + + $data = rescue( + fn () => Details::request($this->tv_id, $this->id) + ->language($locale) + //->append(...$append) + ->send() + ->json() + ); + + if ($data === null) { + return false; + } + + if (! $this->fillFromTmdb($data, $locale)->save()) { + return false; + } + + if ($data['belongs_to_tv']) { + $this->tv()->associate( + Tv::query()->findOrFail($data['belongs_to_tv']['id']) + )->save(); + } + + return true; + } + + public function newEloquentBuilder($query): TvSeasonBuilder + { + return new TvSeasonBuilder($query); + } + + public function poster(): Poster + { + return new Poster( + $this->poster_path, + $this->title + ); + } +} diff --git a/src/Requests/TvSeason/Details.php b/src/Requests/TvSeason/Details.php new file mode 100644 index 0000000..07d7955 --- /dev/null +++ b/src/Requests/TvSeason/Details.php @@ -0,0 +1,33 @@ +request->get( + sprintf('/tv/%d/season/%d', $this->tvId, $this->tvSeasonId), + array_filter([ + 'language' => $this->language ?? Tmdb::language(), + 'append_to_response' => implode(',', $this->append), + ]) + )->throw(); + } +} From febbc95ae28168d9c2461e23c27f6904b5dab378 Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Thu, 20 Jan 2022 22:50:43 +0100 Subject: [PATCH 09/21] CS fixer --- ..._09_22_200148_create_collections_table.php | 2 +- .../2021_09_22_223137_create_movies_table.php | 2 +- ...09_23_111637_create_movie_genres_table.php | 2 +- ..._113841_create_movie_movie_genre_table.php | 2 +- .../2021_09_23_173708_create_people_table.php | 2 +- ...2021_09_23_173721_create_credits_table.php | 2 +- ...30_233401_create_watch_providers_table.php | 2 +- ...22_01_19_150000_create_tv_genres_table.php | 2 +- ...022_01_19_153000_create_networks_table.php | 3 +- .../2022_01_19_160000_create_tvs_table.php | 3 +- .../2022_01_19_180000_create_tv_tv_gerne.php | 2 +- ...2_01_20_121500_create_network_tv_table.php | 3 +- ...2_01_20_130000_create_tv_seasons_table.php | 29 +++++++++++++++++++ src/Models/Network.php | 4 +-- src/Models/TvGenre.php | 4 +-- src/Requests/Network/Details.php | 1 - tests/Datasets/Builder.php | 19 ++++++------ 17 files changed, 53 insertions(+), 31 deletions(-) create mode 100644 database/migrations/2022_01_20_130000_create_tv_seasons_table.php diff --git a/database/migrations/2021_09_22_200148_create_collections_table.php b/database/migrations/2021_09_22_200148_create_collections_table.php index 963ed7a..b341959 100644 --- a/database/migrations/2021_09_22_200148_create_collections_table.php +++ b/database/migrations/2021_09_22_200148_create_collections_table.php @@ -5,7 +5,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class() extends Migration { +return new class () extends Migration { public function up(): void { Schema::connection(Collection::connection())->create(Collection::table(), static function (Blueprint $table): void { diff --git a/database/migrations/2021_09_22_223137_create_movies_table.php b/database/migrations/2021_09_22_223137_create_movies_table.php index a5727a6..c4329ff 100644 --- a/database/migrations/2021_09_22_223137_create_movies_table.php +++ b/database/migrations/2021_09_22_223137_create_movies_table.php @@ -6,7 +6,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class() extends Migration { +return new class () extends Migration { public function up(): void { Schema::connection(Movie::connection())->create(Movie::table(), static function (Blueprint $table): void { diff --git a/database/migrations/2021_09_23_111637_create_movie_genres_table.php b/database/migrations/2021_09_23_111637_create_movie_genres_table.php index c150799..20b9f02 100644 --- a/database/migrations/2021_09_23_111637_create_movie_genres_table.php +++ b/database/migrations/2021_09_23_111637_create_movie_genres_table.php @@ -5,7 +5,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class() extends Migration { +return new class () extends Migration { public function up(): void { Schema::connection(MovieGenre::connection())->create(MovieGenre::table(), static function (Blueprint $table): void { diff --git a/database/migrations/2021_09_23_113841_create_movie_movie_genre_table.php b/database/migrations/2021_09_23_113841_create_movie_movie_genre_table.php index 52e748a..8edaba9 100644 --- a/database/migrations/2021_09_23_113841_create_movie_movie_genre_table.php +++ b/database/migrations/2021_09_23_113841_create_movie_movie_genre_table.php @@ -6,7 +6,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class() extends Migration { +return new class () extends Migration { public function up(): void { Schema::connection(Movie::connection())->create('movie_movie_genre', static function (Blueprint $table): void { diff --git a/database/migrations/2021_09_23_173708_create_people_table.php b/database/migrations/2021_09_23_173708_create_people_table.php index ec38aa1..a6ad41e 100644 --- a/database/migrations/2021_09_23_173708_create_people_table.php +++ b/database/migrations/2021_09_23_173708_create_people_table.php @@ -5,7 +5,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class() extends Migration { +return new class () extends Migration { public function up(): void { Schema::connection(Person::connection())->create(Person::table(), static function (Blueprint $table): void { diff --git a/database/migrations/2021_09_23_173721_create_credits_table.php b/database/migrations/2021_09_23_173721_create_credits_table.php index 4415de1..780d725 100644 --- a/database/migrations/2021_09_23_173721_create_credits_table.php +++ b/database/migrations/2021_09_23_173721_create_credits_table.php @@ -6,7 +6,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class() extends Migration { +return new class () extends Migration { public function up(): void { Schema::connection(Credit::connection())->create(Credit::table(), static function (Blueprint $table): void { diff --git a/database/migrations/2021_09_30_233401_create_watch_providers_table.php b/database/migrations/2021_09_30_233401_create_watch_providers_table.php index 69fd48e..904614f 100644 --- a/database/migrations/2021_09_30_233401_create_watch_providers_table.php +++ b/database/migrations/2021_09_30_233401_create_watch_providers_table.php @@ -5,7 +5,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class() extends Migration { +return new class () extends Migration { public function up(): void { Schema::connection(WatchProvider::connection())->create(WatchProvider::table(), static function (Blueprint $table): void { diff --git a/database/migrations/2022_01_19_150000_create_tv_genres_table.php b/database/migrations/2022_01_19_150000_create_tv_genres_table.php index 2bd6742..905a5ff 100644 --- a/database/migrations/2022_01_19_150000_create_tv_genres_table.php +++ b/database/migrations/2022_01_19_150000_create_tv_genres_table.php @@ -5,7 +5,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class() extends Migration { +return new class () extends Migration { public function up(): void { Schema::connection(TvGenre::connection())->create(TvGenre::table(), static function (Blueprint $table): void { diff --git a/database/migrations/2022_01_19_153000_create_networks_table.php b/database/migrations/2022_01_19_153000_create_networks_table.php index 162481c..0784469 100644 --- a/database/migrations/2022_01_19_153000_create_networks_table.php +++ b/database/migrations/2022_01_19_153000_create_networks_table.php @@ -5,8 +5,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class() extends Migration -{ +return new class () extends Migration { public function up(): void { Schema::connection(Network::connection())->create(Network::table(), static function (Blueprint $table): void { diff --git a/database/migrations/2022_01_19_160000_create_tvs_table.php b/database/migrations/2022_01_19_160000_create_tvs_table.php index a40a0b2..2b05791 100644 --- a/database/migrations/2022_01_19_160000_create_tvs_table.php +++ b/database/migrations/2022_01_19_160000_create_tvs_table.php @@ -5,7 +5,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class() extends Migration { +return new class () extends Migration { public function up(): void { Schema::connection(Tv::connection())->create(Tv::table(), static function (Blueprint $table): void { @@ -28,7 +28,6 @@ public function up(): void $table->json('poster_path')->nullable(); $table->json('production_companies')->nullable(); $table->json('production_countries')->nullable(); - $table->json('seasons')->nullable(); $table->json('spoken_languages')->nullable(); $table->string('status')->nullable(); $table->json('tagline')->nullable(); diff --git a/database/migrations/2022_01_19_180000_create_tv_tv_gerne.php b/database/migrations/2022_01_19_180000_create_tv_tv_gerne.php index 79c8fbd..174b160 100644 --- a/database/migrations/2022_01_19_180000_create_tv_tv_gerne.php +++ b/database/migrations/2022_01_19_180000_create_tv_tv_gerne.php @@ -6,7 +6,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class() extends Migration { +return new class () extends Migration { public function up(): void { Schema::connection(Tv::connection())->create('tv_tv_genre', static function (Blueprint $table): void { diff --git a/database/migrations/2022_01_20_121500_create_network_tv_table.php b/database/migrations/2022_01_20_121500_create_network_tv_table.php index 40599f9..2d8c65d 100644 --- a/database/migrations/2022_01_20_121500_create_network_tv_table.php +++ b/database/migrations/2022_01_20_121500_create_network_tv_table.php @@ -6,8 +6,7 @@ use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -return new class() extends Migration -{ +return new class () extends Migration { public function up(): void { Schema::connection(Tv::connection())->create('network_tv', static function (Blueprint $table): void { diff --git a/database/migrations/2022_01_20_130000_create_tv_seasons_table.php b/database/migrations/2022_01_20_130000_create_tv_seasons_table.php new file mode 100644 index 0000000..076275c --- /dev/null +++ b/database/migrations/2022_01_20_130000_create_tv_seasons_table.php @@ -0,0 +1,29 @@ +create(TvSeason::table(), static function (Blueprint $table): void { + $table->bigInteger('id')->unsigned()->primary(); + $table->date('air_date')->nullable(); + $table->json('name')->nullable(); + $table->json('overview')->nullable(); + $table->json('poster_path')->nullable(); + $table->integer('season_number')->nullable(); + $table->foreignId('tv_id')->nullable()->constrained(Tv::table()); + + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::connection(TvSeason::connection())->dropIfExists(TvSeason::table()); + } +}; diff --git a/src/Models/Network.php b/src/Models/Network.php index f355a59..216d147 100644 --- a/src/Models/Network.php +++ b/src/Models/Network.php @@ -4,7 +4,6 @@ use Astrotomic\Tmdb\Eloquent\Builders\NetworkBuilder; use Astrotomic\Tmdb\Models\Concerns\HasTranslations; - use Astrotomic\Tmdb\Images\Logo; use Astrotomic\Tmdb\Requests\Network\Details; use Illuminate\Database\Eloquent\Relations\BelongsToMany; @@ -104,7 +103,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool return false; } - if (!$this->fillFromTmdb($data, $locale)->save()) { + if (! $this->fillFromTmdb($data, $locale)->save()) { return false; } @@ -116,7 +115,6 @@ public function newEloquentBuilder($query): NetworkBuilder return new NetworkBuilder($query); } - public function logo(): Logo { return new Logo( diff --git a/src/Models/TvGenre.php b/src/Models/TvGenre.php index e1fbba1..51eb1a1 100644 --- a/src/Models/TvGenre.php +++ b/src/Models/TvGenre.php @@ -15,7 +15,7 @@ * @property \Carbon\Carbon|null $created_at * @property \Carbon\Carbon|null $updated_at * @property-read array $translations - * @property-read \Illuminate\Database\Eloquent\Collection|\Astrotomic\Tmdb\Models\Movie[] $movies + * @property-read \Illuminate\Database\Eloquent\Collection|\Astrotomic\Tmdb\Models\Tv[] $tvs * * @method \Astrotomic\Tmdb\Eloquent\Builders\TvGenreBuilder newModelQuery() * @method \Astrotomic\Tmdb\Eloquent\Builders\TvGenreBuilder newQuery() @@ -82,7 +82,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool $data = $data->keyBy('id'); - if (!$data->has($this->id)) { + if (! $data->has($this->id)) { return false; } diff --git a/src/Requests/Network/Details.php b/src/Requests/Network/Details.php index 9777599..fc61a51 100644 --- a/src/Requests/Network/Details.php +++ b/src/Requests/Network/Details.php @@ -8,7 +8,6 @@ class Details extends Request { - public function __construct( protected int $networkId, ) { diff --git a/tests/Datasets/Builder.php b/tests/Datasets/Builder.php index b7b7c10..dd399f3 100644 --- a/tests/Datasets/Builder.php +++ b/tests/Datasets/Builder.php @@ -6,7 +6,6 @@ use Astrotomic\Tmdb\Models\Person; use Astrotomic\Tmdb\Models\Tv; use Astrotomic\Tmdb\Models\TvGenre; -use Astrotomic\Tmdb\Models\Network; use Astrotomic\Tmdb\Models\WatchProvider; use Illuminate\Support\Collection; use Illuminate\Support\Fluent; @@ -41,9 +40,9 @@ ]; foreach ($set as $model => $ids) { - yield $model::table() . ':array' => [$model, $ids]; - yield $model::table() . ':collection' => [$model, new Collection($ids)]; - yield $model::table() . ':fluent' => [$model, new Fluent($ids)]; + yield $model::table().':array' => [$model, $ids]; + yield $model::table().':collection' => [$model, new Collection($ids)]; + yield $model::table().':fluent' => [$model, new Fluent($ids)]; } }); @@ -60,9 +59,9 @@ ]; foreach ($set as $model => $ids) { - yield $model::table() . ':array' => [$model, $ids]; - yield $model::table() . ':collection' => [$model, new Collection($ids)]; - yield $model::table() . ':fluent' => [$model, new Fluent($ids)]; + yield $model::table().':array' => [$model, $ids]; + yield $model::table().':collection' => [$model, new Collection($ids)]; + yield $model::table().':fluent' => [$model, new Fluent($ids)]; } }); @@ -79,8 +78,8 @@ ]; foreach ($set as $model => $ids) { - yield $model::table() . ':array' => [$model, $ids]; - yield $model::table() . ':collection' => [$model, new Collection($ids)]; - yield $model::table() . ':fluent' => [$model, new Fluent($ids)]; + yield $model::table().':array' => [$model, $ids]; + yield $model::table().':collection' => [$model, new Collection($ids)]; + yield $model::table().':fluent' => [$model, new Fluent($ids)]; } }); From 3667c38c55e10cc7365cb0db717dec104a1d8e1e Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Mon, 24 Jan 2022 10:58:13 +0100 Subject: [PATCH 10/21] Partial TvSeason / TvEpisodes --- README.md | 4 + ..._01_21_110000_create_tv_episodes_table.php | 33 +++ src/Eloquent/Builders/TvEpisodeBuilder.php | 19 ++ src/Eloquent/Relations/HasManyTvEpisodes.php | 17 ++ src/Enums/CreditType.php | 1 + src/Enums/TvType.php | 2 + src/Images/Still.php | 27 +++ src/Models/Tv.php | 10 +- src/Models/TvEpisode.php | 217 ++++++++++++++++++ src/Models/TvSeason.php | 31 ++- src/Models/backend-nospoiler.code-workspace | 14 ++ src/Requests/TvEpisode/Details.php | 36 +++ src/Requests/TvSeason/Details.php | 8 +- 13 files changed, 410 insertions(+), 9 deletions(-) create mode 100644 database/migrations/2022_01_21_110000_create_tv_episodes_table.php create mode 100644 src/Eloquent/Builders/TvEpisodeBuilder.php create mode 100644 src/Eloquent/Relations/HasManyTvEpisodes.php create mode 100644 src/Images/Still.php create mode 100644 src/Models/TvEpisode.php create mode 100644 src/Models/backend-nospoiler.code-workspace create mode 100644 src/Requests/TvEpisode/Details.php diff --git a/README.md b/README.md index 2531217..f991e55 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ ![](.github/banner.png) +### Please do not use this package but instead use the official one. + # Laravel TMDB [![Latest Version](http://img.shields.io/packagist/v/astrotomic/laravel-tmdb.svg?label=Release&style=for-the-badge)](https://packagist.org/packages/astrotomic/laravel-tmdb) @@ -68,9 +70,11 @@ This will do one HTTP call per model and save multiple HTTP calls in the future. ```php use Astrotomic\Tmdb\Models\MovieGenre; +use Astrotomic\Tmdb\Models\TvGenre; use Astrotomic\Tmdb\Models\WatchProvider; MovieGenre::all(); +TvGenre::all(); WatchProvider::all(); ``` diff --git a/database/migrations/2022_01_21_110000_create_tv_episodes_table.php b/database/migrations/2022_01_21_110000_create_tv_episodes_table.php new file mode 100644 index 0000000..a6fecc1 --- /dev/null +++ b/database/migrations/2022_01_21_110000_create_tv_episodes_table.php @@ -0,0 +1,33 @@ +create(TvEpisode::table(), static function (Blueprint $table): void { + $table->bigInteger('id')->unsigned()->primary(); + $table->date('air_date')->nullable(); + $table->json('name')->nullable(); + $table->json('overview')->nullable(); + $table->string('production_code')->nullable(); + $table->integer('season_number')->nullable(); + $table->string('still_path')->nullable(); + $table->decimal('vote_average')->nullable(); + $table->integer('vote_count')->default(0); + $table->foreignId('tv_season_id')->nullable()->constrained(TvSeason::table()); + + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::connection(TvEpisode::connection())->dropIfExists(TvEpisode::table()); + } +}; diff --git a/src/Eloquent/Builders/TvEpisodeBuilder.php b/src/Eloquent/Builders/TvEpisodeBuilder.php new file mode 100644 index 0000000..1c16a1d --- /dev/null +++ b/src/Eloquent/Builders/TvEpisodeBuilder.php @@ -0,0 +1,19 @@ +getParentKey())->send()->json('parts.*.id'); + + return $this->query->findMany($ids, $columns); + } +} diff --git a/src/Enums/CreditType.php b/src/Enums/CreditType.php index 22181d1..ea8bf08 100644 --- a/src/Enums/CreditType.php +++ b/src/Enums/CreditType.php @@ -15,6 +15,7 @@ protected static function values(): array return [ 'CAST' => 'cast', 'CREW' => 'crew', + 'GUEST_STARS' => 'guest_stars', ]; } } diff --git a/src/Enums/TvType.php b/src/Enums/TvType.php index 344a712..6ec678d 100644 --- a/src/Enums/TvType.php +++ b/src/Enums/TvType.php @@ -8,6 +8,7 @@ * @method static self SCRIPTED() * @method static self REALITY() * @method static self MINISERIES() + * @method static self DOCUMENTARY() */ class TvType extends Enum { @@ -17,6 +18,7 @@ protected static function values(): array 'SCRIPTED' => 'Scripted', 'REALITY' => 'Reality', 'MINISERIES' => 'Miniseries', + 'DOCUMENTARY' => 'Documentary', // TODO: Add more values, need testing, no official documentation available ]; } diff --git a/src/Images/Still.php b/src/Images/Still.php new file mode 100644 index 0000000..655885d --- /dev/null +++ b/src/Images/Still.php @@ -0,0 +1,27 @@ +size ?? 2000; + } + + public function height(): int + { + return $this->size + ? $this->size / 2 * 3 + : 3000; + } +} diff --git a/src/Models/Tv.php b/src/Models/Tv.php index f21300a..de67996 100644 --- a/src/Models/Tv.php +++ b/src/Models/Tv.php @@ -83,8 +83,8 @@ class Tv extends Model 'production_countries' => 'array', 'production_companies' => 'array', 'spoken_languages' => 'array', - 'status' => TvStatus::class.':nullable', - 'type' => TvType::class.':nullable', + 'status' => TvStatus::class . ':nullable', + 'type' => TvType::class . ':nullable', ]; public array $translatable = [ @@ -232,7 +232,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool return false; } - if (! $this->fillFromTmdb($data, $locale)->save()) { + if (!$this->fillFromTmdb($data, $locale)->save()) { return false; } @@ -258,6 +258,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool ->pluck('id') ); + if (isset($data['seasons'])) { $this->seasons()->saveMany( (collect($data['seasons']) @@ -265,6 +266,9 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool $season = TvSeason::query()->findOrNew($data['id']); $season->fillFromTmdb($data, $locale)->save(); + $seasonFetch = TvSeason::query()->updateFromTmdb($locale, [$season->tv_id, $season->season_number]); + ray($seasonFetch); + return $season; }) ->all()) diff --git a/src/Models/TvEpisode.php b/src/Models/TvEpisode.php new file mode 100644 index 0000000..c678700 --- /dev/null +++ b/src/Models/TvEpisode.php @@ -0,0 +1,217 @@ + 'int', + 'air_date' => 'date', + 'episode_number' => 'int', + 'name' => 'string', + 'overview' => 'string', + 'production_code' => 'string', + 'season_number' => 'int', + 'still_path' => 'string', + 'vote_average' => 'float', + 'vote_count' => 'int', + 'tv_season_id' => 'int', + ]; + + public array $translatable = [ + 'name', + 'overview', + 'still_path', + ]; + + public function season(): BelongsTo + { + return $this->belongsTo(TvSeason::class, 'tv_season_id'); + } + + public function credits(): MorphManyCredits + { + /** @var \Astrotomic\Tmdb\Models\Credit $instance */ + $instance = $this->newRelatedInstance(Credit::class); + + return new MorphManyCredits( + $instance->newQuery(), + $this, + $instance->qualifyColumn('media_type'), + $instance->qualifyColumn('media_id'), + $this->getKeyName() + ); + } + + public function guest_stars(): MorphManyCredits + { + return $this->guest_stars()->whereCreditType(CreditType::GUEST_STARS()); + } + + public function cast(): MorphManyCredits + { + return $this->credits()->whereCreditType(CreditType::CAST()); + } + + public function crew(): MorphManyCredits + { + return $this->credits()->whereCreditType(CreditType::CREW()); + } + + public function fillFromTmdb(array $data, ?string $locale = null): static + { + $this->fill([ + 'id' => $data['id'], + 'air_date' => $data['air_date'] ?: null, + 'episode_number' => $data['episode_number'] ?: null, + 'name' => $data['name'] ?: null, + 'overview' => $data['overview'] ?: null, + 'production_code' => $data['production_code'] ?: null, + 'season_number' => $data['season_number'] ?: null, + 'still_path' => $data['still_path'] ?: null, + 'vote_average' => $data['vote_average'] ?: null, + 'vote_count' => $data['vote_count'] ?: null, + ]); + + $locale ??= $this->getLocale(); + + $this->setTranslation('overview', $locale, trim($data['overview']) ?: null); + $this->setTranslation('name', $locale, trim($data['title']) ?: null); + $this->setTranslation('still_path', $locale, trim($data['poster_path']) ?: null); + + return $this; + } + + public function updateFromTmdb(?string $locale = null, array $with = []): bool + { + $append = collect($with) + ->map(fn (string $relation) => match ($relation) { + 'cast', 'crew', 'credits' => Details::APPEND_CREDITS, + default => null, + }) + ->filter() + ->unique() + ->values() + ->all(); + + $data = rescue( + fn () => Details::request($this->id) + ->language($locale) + ->append(...$append) + ->send() + ->json() + ); + + if ($data === null) { + return false; + } + + if (!$this->fillFromTmdb($data, $locale)->save()) { + return false; + } + + $this->genres()->sync( + collect($data['genres'] ?: []) + ->map(static function (array $data) use ($locale): MovieGenre { + $genre = MovieGenre::query()->findOrNew($data['id']); + $genre->fillFromTmdb($data, $locale)->save(); + + return $genre; + }) + ->pluck('id') + ); + + if ($data['belongs_to_collection']) { + $this->collection()->associate( + Collection::query()->findOrFail($data['belongs_to_collection']['id']) + )->save(); + } + + if (isset($data['credits'])) { + if (in_array('credits', $with) || in_array('cast', $with)) { + foreach ($data['credits']['cast'] as $cast) { + Credit::query()->findOrFail($cast['credit_id']); + } + } + + if (in_array('credits', $with) || in_array('crew', $with)) { + foreach ($data['credits']['crew'] as $crew) { + Credit::query()->findOrFail($crew['credit_id']); + } + } + } + + return true; + } + + public function newEloquentBuilder($query): TvEpisodeBuilder + { + return new TvEpisodeBuilder($query); + } + + public function runtime(): ?CarbonInterval + { + if ($this->runtime === null) { + return null; + } + + return CarbonInterval::minutes($this->runtime)->cascade(); + } +} diff --git a/src/Models/TvSeason.php b/src/Models/TvSeason.php index db8995c..fec937a 100644 --- a/src/Models/TvSeason.php +++ b/src/Models/TvSeason.php @@ -3,6 +3,7 @@ namespace Astrotomic\Tmdb\Models; use Astrotomic\Tmdb\Eloquent\Builders\TvSeasonBuilder; +use Astrotomic\Tmdb\Eloquent\Relations\HasManyTvEpisodes; use Astrotomic\Tmdb\Images\Poster; use Astrotomic\Tmdb\Models\Concerns\HasTranslations; use Astrotomic\Tmdb\Requests\TvSeason\Details; @@ -51,6 +52,19 @@ class TvSeason extends Model 'poster_path', ]; + public function episodes(): HasManyTvEpisodes + { + /** @var \Astrotomic\Tmdb\Models\TvEpisode $instance */ + $instance = $this->newRelatedInstance(TvEpisode::class); + + return new HasManyTvEpisodes( + $instance->newQuery(), + $this, + $instance->qualifyColumn($this->getForeignKey()), + $this->getKeyName() + ); + } + public function fillFromTmdb(array $data, ?string $locale = null): static { $this->fill([ @@ -84,7 +98,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool ->all();*/ $data = rescue( - fn () => Details::request($this->tv_id, $this->id) + fn () => Details::request($this->tv_id, $this->season_number) ->language($locale) //->append(...$append) ->send() @@ -95,10 +109,23 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool return false; } - if (! $this->fillFromTmdb($data, $locale)->save()) { + if (!$this->fillFromTmdb($data, $locale)->save()) { return false; } + if (isset($data['episodes'])) { + $this->seasons()->saveMany( + (collect($data['episodes']) + ->map(static function (array $data) use ($locale): TvEpisode { + $season = TvEpisode::query()->findOrNew($data['id']); + $season->fillFromTmdb($data, $locale)->save(); + + return $season; + }) + ->all()) + ); + } + if ($data['belongs_to_tv']) { $this->tv()->associate( Tv::query()->findOrFail($data['belongs_to_tv']['id']) diff --git a/src/Models/backend-nospoiler.code-workspace b/src/Models/backend-nospoiler.code-workspace new file mode 100644 index 0000000..7f8f070 --- /dev/null +++ b/src/Models/backend-nospoiler.code-workspace @@ -0,0 +1,14 @@ +{ + "folders": [ + { + "path": "../../../../.." + }, + { + "path": "../.." + } + ], + "settings": { + "workbench.preferredDarkColorTheme": "Monokai Dimmed", + "workbench.colorTheme": "One Dark Pro Darker" + } +} \ No newline at end of file diff --git a/src/Requests/TvEpisode/Details.php b/src/Requests/TvEpisode/Details.php new file mode 100644 index 0000000..9511ec1 --- /dev/null +++ b/src/Requests/TvEpisode/Details.php @@ -0,0 +1,36 @@ +request->get( + sprintf('/tv/%d/season/%d/episode/%d', [$this->tvId, $this->tvSeasonNumber, $this->tvEpisodeNumber]), + array_filter([ + 'language' => $this->language ?? Tmdb::language(), + 'append_to_response' => implode(',', $this->append), + ]) + )->throw(); + } +} diff --git a/src/Requests/TvSeason/Details.php b/src/Requests/TvSeason/Details.php index 07d7955..0fc451f 100644 --- a/src/Requests/TvSeason/Details.php +++ b/src/Requests/TvSeason/Details.php @@ -10,20 +10,20 @@ class Details extends Request { public function __construct( protected int $tvId, - protected int $tvSeasonId, + protected int $tvSeasonNumber, ) { parent::__construct(); } - public static function request(int $tvId, int $tvSeasonId): static + public static function request(int $tvId, int $tvSeasonNumber): static { - return new static($tvId, $tvSeasonId); + return new static($tvId, $tvSeasonNumber); } public function send(): Response { return $this->request->get( - sprintf('/tv/%d/season/%d', $this->tvId, $this->tvSeasonId), + sprintf('/tv/%d/season/%d', [$this->tvId, $this->tvSeasonNumber]), array_filter([ 'language' => $this->language ?? Tmdb::language(), 'append_to_response' => implode(',', $this->append), From c6ea9d5de663f220c4b1d8776416230e6aad7ad1 Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Thu, 17 Feb 2022 10:21:37 +0100 Subject: [PATCH 11/21] Laravel 9 --- composer.json | 10 +++++----- src/Enums/TvType.php | 4 ++++ src/Models/Movie.php | 8 ++++++-- src/Models/Person.php | 4 ++-- src/Models/Tv.php | 8 ++++---- src/Models/TvSeason.php | 4 ++-- 6 files changed, 23 insertions(+), 15 deletions(-) diff --git a/composer.json b/composer.json index b476cee..8cb4304 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "astrotomic/laravel-tmdb", + "name": "murdercode/laravel-tmdb", "description": "Interact with TMDB data in your Laravel application.", "homepage": "https://github.com/Astrotomic/laravel-tmdb", "license": "MIT", @@ -15,11 +15,11 @@ "php": "^8.0", "ext-json": "*", "guzzlehttp/guzzle": "^6.5.5 || ^7.0.1", - "illuminate/database": "^8.0", - "illuminate/http": "^8.0", - "illuminate/support": "^8.0", + "illuminate/database": "^9.0", + "illuminate/http": "^9.0", + "illuminate/support": "^9.0", "nesbot/carbon": "^2.31", - "spatie/laravel-enum": "^2.5", + "spatie/laravel-enum": "^3.0", "spatie/laravel-translatable": "^5.0" }, "require-dev": { diff --git a/src/Enums/TvType.php b/src/Enums/TvType.php index 6ec678d..90e9586 100644 --- a/src/Enums/TvType.php +++ b/src/Enums/TvType.php @@ -9,6 +9,8 @@ * @method static self REALITY() * @method static self MINISERIES() * @method static self DOCUMENTARY() + * @method static self TALK_SHOW() + * @method static self NEWS() */ class TvType extends Enum { @@ -19,6 +21,8 @@ protected static function values(): array 'REALITY' => 'Reality', 'MINISERIES' => 'Miniseries', 'DOCUMENTARY' => 'Documentary', + 'TALK_SHOW' => 'Talk Show', + 'NEWS' => 'News', // TODO: Add more values, need testing, no official documentation available ]; } diff --git a/src/Models/Movie.php b/src/Models/Movie.php index 05ea35a..5c1c84b 100644 --- a/src/Models/Movie.php +++ b/src/Models/Movie.php @@ -104,7 +104,7 @@ class Movie extends Model 'release_date' => 'date', 'production_countries' => 'array', 'spoken_languages' => 'array', - 'status' => MovieStatus::class.':nullable', + 'status' => MovieStatus::class . ':nullable', 'collection_id' => 'int', ]; @@ -237,7 +237,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool return false; } - if (! $this->fillFromTmdb($data, $locale)->save()) { + if (!$this->fillFromTmdb($data, $locale)->save()) { return false; } @@ -325,6 +325,10 @@ public function similars(?int $limit): EloquentCollection return static::query()->findMany($ids); } + public function getWatchProvidersAttribute() + { + return $this->watchProviders; + } public function watchProviders(?string $region = null, ?WatchProviderType $type = null): EloquentCollection { return WatchProvider::query()->findMany( diff --git a/src/Models/Person.php b/src/Models/Person.php index 1f78506..a2ddb89 100644 --- a/src/Models/Person.php +++ b/src/Models/Person.php @@ -95,7 +95,7 @@ public function fillFromTmdb(array $data, ?string $locale = null): static 'name' => $data['name'] ?: null, 'also_known_as' => $data['also_known_as'] ?: [], 'homepage' => $data['homepage'] ?: null, - 'imdb_id' => trim($data['imdb_id']) ?: null, + 'imdb_id' => $data['imdb_id'] ? trim($data['imdb_id']) : null, 'birthday' => $data['birthday'] ?: null, 'deathday' => $data['deathday'] ?: null, 'gender' => $data['gender'] ?: 0, @@ -136,7 +136,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool return false; } - if (! $this->fillFromTmdb($data, $locale)->save()) { + if (!$this->fillFromTmdb($data, $locale)->save()) { return false; } diff --git a/src/Models/Tv.php b/src/Models/Tv.php index de67996..60b0936 100644 --- a/src/Models/Tv.php +++ b/src/Models/Tv.php @@ -247,7 +247,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool ->pluck('id') ); - $this->networks()->sync( + /*$this->networks()->sync( collect($data['networks'] ?: []) ->map(static function (array $data) use ($locale): Network { $network = Network::query()->findOrNew($data['id']); @@ -256,7 +256,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool return $network; }) ->pluck('id') - ); + );*/ if (isset($data['seasons'])) { @@ -266,8 +266,8 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool $season = TvSeason::query()->findOrNew($data['id']); $season->fillFromTmdb($data, $locale)->save(); - $seasonFetch = TvSeason::query()->updateFromTmdb($locale, [$season->tv_id, $season->season_number]); - ray($seasonFetch); + //$seasonFetch = TvSeason::query()->updateFromTmdb($locale, [$season->tv_id, $season->season_number]); + //ray($seasonFetch); return $season; }) diff --git a/src/Models/TvSeason.php b/src/Models/TvSeason.php index fec937a..ee62b19 100644 --- a/src/Models/TvSeason.php +++ b/src/Models/TvSeason.php @@ -113,7 +113,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool return false; } - if (isset($data['episodes'])) { + /*if (isset($data['episodes'])) { $this->seasons()->saveMany( (collect($data['episodes']) ->map(static function (array $data) use ($locale): TvEpisode { @@ -124,7 +124,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool }) ->all()) ); - } + }*/ if ($data['belongs_to_tv']) { $this->tv()->associate( From 130c5ee861b5fb736c51fef3d2c18f5c5c806fc6 Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Tue, 8 Mar 2022 13:38:04 +0100 Subject: [PATCH 12/21] Add Video to Type TV --- src/Enums/TvType.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Enums/TvType.php b/src/Enums/TvType.php index 90e9586..ff06338 100644 --- a/src/Enums/TvType.php +++ b/src/Enums/TvType.php @@ -11,6 +11,7 @@ * @method static self DOCUMENTARY() * @method static self TALK_SHOW() * @method static self NEWS() + * @method static self VIDEO() */ class TvType extends Enum { @@ -23,6 +24,7 @@ protected static function values(): array 'DOCUMENTARY' => 'Documentary', 'TALK_SHOW' => 'Talk Show', 'NEWS' => 'News', + 'VIDEO' => 'Video', // TODO: Add more values, need testing, no official documentation available ]; } From 082de6c3653ddaee9fa1c22b41cdded60514885b Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Wed, 30 Mar 2022 10:44:29 +0200 Subject: [PATCH 13/21] Prevent error spamming --- src/Models/Tv.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Models/Tv.php b/src/Models/Tv.php index 60b0936..800fe4e 100644 --- a/src/Models/Tv.php +++ b/src/Models/Tv.php @@ -259,7 +259,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool );*/ - if (isset($data['seasons'])) { + /*if (isset($data['seasons'])) { $this->seasons()->saveMany( (collect($data['seasons']) ->map(static function (array $data) use ($locale): TvSeason { @@ -273,7 +273,7 @@ public function updateFromTmdb(?string $locale = null, array $with = []): bool }) ->all()) ); - } + }*/ /*if ($data['belongs_to_collection']) { $this->collection()->associate( From d9026efe50e1bb9384cce40fcfe5dd8fe42e7066 Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Wed, 13 Apr 2022 16:34:05 +0200 Subject: [PATCH 14/21] Add trending endpoint --- src/Requests/Trending/Trending.php | 61 ++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/Requests/Trending/Trending.php diff --git a/src/Requests/Trending/Trending.php b/src/Requests/Trending/Trending.php new file mode 100644 index 0000000..5094341 --- /dev/null +++ b/src/Requests/Trending/Trending.php @@ -0,0 +1,61 @@ +page = $page; + + return $this; + } + + public function region(string $region): static + { + $this->region = $region; + + return $this; + } + + public function send(): Response + { + return $this->request->get( + '/trending/all/day', + array_filter([ + 'language' => $this->language ?? Tmdb::language(), + 'region' => $this->region ?? Tmdb::region(), + 'page' => $this->page, + ]) + )->throw(); + } + + public function cursor(): LazyCollection + { + return LazyCollection::make(function (): Generator { + $this->page = 1; + do { + $response = $this->send()->json(); + + yield from $response['results']; + + $totalPages = $response['total_pages']; + $this->page++; + } while ($this->page <= $totalPages); + }); + } +} From 9925feb7ac892c025ed9e73012eacb167077da0b Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Fri, 20 May 2022 11:44:39 +0200 Subject: [PATCH 15/21] Add Pilot to TV enums --- src/Enums/TvType.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Enums/TvType.php b/src/Enums/TvType.php index ff06338..92c807c 100644 --- a/src/Enums/TvType.php +++ b/src/Enums/TvType.php @@ -12,6 +12,7 @@ * @method static self TALK_SHOW() * @method static self NEWS() * @method static self VIDEO() + * @method static self PILOT() */ class TvType extends Enum { @@ -25,6 +26,7 @@ protected static function values(): array 'TALK_SHOW' => 'Talk Show', 'NEWS' => 'News', 'VIDEO' => 'Video', + 'PILOT' => 'Pilot', // TODO: Add more values, need testing, no official documentation available ]; } From 6dda205971bf6afd5b5b1bbed201ab4a19f88c25 Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Fri, 20 May 2022 11:52:52 +0200 Subject: [PATCH 16/21] Add Pilot to Enum TV Status --- src/Enums/TvStatus.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Enums/TvStatus.php b/src/Enums/TvStatus.php index d92385f..c120651 100644 --- a/src/Enums/TvStatus.php +++ b/src/Enums/TvStatus.php @@ -13,6 +13,7 @@ * @method static self CANCELED() * @method static self RETURNING_SERIES() * @method static self ENDED() + * @method static self PILOT() */ class TvStatus extends Enum { @@ -27,6 +28,7 @@ protected static function values(): array 'CANCELED' => 'Canceled', 'RETURNING_SERIES' => 'Returning Series', 'ENDED' => 'Ended', + 'PILOT' => 'Pilot', // TODO: Implement more values, need testing, no official documentation available ]; } From f81b4681bb1352d9c61606aca75fa2e2ddd57a4b Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Thu, 16 Mar 2023 09:28:36 +0100 Subject: [PATCH 17/21] Ignore jetbrains ide --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0780172..b7cbc5b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ phpunit.xml composer.lock vendor/ .dccache +.idea \ No newline at end of file From 2c6441a4d62c4570ec083084eb7b25da573a40e5 Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Thu, 16 Mar 2023 09:30:15 +0100 Subject: [PATCH 18/21] Pest, fix deprecated static trait method --- tests/Pest.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/Pest.php b/tests/Pest.php index 506dfd4..62c5fdf 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -14,11 +14,17 @@ use Astrotomic\PhpunitAssertions\UrlAssertions; use Astrotomic\Tmdb\Models\Model; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Http; use Pest\Expectation; uses(\Tests\Feature\TestCase::class)->in('Feature'); uses(\Tests\TestCase::class)->in('Live'); +class Pest +{ + use UrlAssertions; +} + /* |-------------------------------------------------------------------------- | Expectations @@ -26,13 +32,16 @@ */ expect()->extend('assert', function (Closure $assertions): Expectation { + $helper = new Pest(); + $assertions = $assertions->bindTo($helper, $helper); $assertions($this->value); return $this; }); expect()->extend('toBeUrl', function (string $expected): Expectation { - UrlAssertions::assertValidLoose($this->value); + $helper = new Pest(); + $helper->assertValidLoose($this->value); return $this->toBe($expected); }); From 02fea4e0d19c9f9efe9c0b3a405dabf3d991daa8 Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Thu, 16 Mar 2023 09:32:18 +0100 Subject: [PATCH 19/21] Add support to PHP 8.1 & 8.2 + Laravel 10 --- composer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 43f73d7..cf33a4f 100644 --- a/composer.json +++ b/composer.json @@ -15,12 +15,12 @@ "issues": "https://github.com/Astrotomic/laravel-tmdb/issues" }, "require": { - "php": "^8.0", + "php": "^8.0 || ^8.1 || ^8.2", "ext-json": "*", "guzzlehttp/guzzle": "^6.5.5 || ^7.0.1", - "illuminate/database": "^8.0 || ^9.0", - "illuminate/http": "^8.0 || ^9.0", - "illuminate/support": "^8.0 || ^9.0", + "illuminate/database": "^8.0 || ^9.0 || ^10.0", + "illuminate/http": "^8.0 || ^9.0 || ^10.0", + "illuminate/support": "^8.0 || ^9.0 || ^10.0", "nesbot/carbon": "^2.31", "spatie/laravel-enum": "^3.0", "spatie/laravel-translatable": "^5.0" From 16806fbc0bd9c60f464de3666eb5bcf38b764a0b Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Thu, 16 Mar 2023 10:02:44 +0100 Subject: [PATCH 20/21] Update Laravel Translatable --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index cf33a4f..41051ff 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "illuminate/support": "^8.0 || ^9.0 || ^10.0", "nesbot/carbon": "^2.31", "spatie/laravel-enum": "^3.0", - "spatie/laravel-translatable": "^5.0" + "spatie/laravel-translatable": "^6.3.0" }, "require-dev": { "astrotomic/phpunit-assertions": "^0.6", From 6bb80674ac9d0bab5eee24a8e15790dd3ae92d87 Mon Sep 17 00:00:00 2001 From: Stefano Novelli Date: Mon, 18 Mar 2024 14:56:01 +0100 Subject: [PATCH 21/21] Updated Illuminate dependencies to allow for compatibility with Laravel 11. --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 41051ff..d2e56b4 100644 --- a/composer.json +++ b/composer.json @@ -18,9 +18,9 @@ "php": "^8.0 || ^8.1 || ^8.2", "ext-json": "*", "guzzlehttp/guzzle": "^6.5.5 || ^7.0.1", - "illuminate/database": "^8.0 || ^9.0 || ^10.0", - "illuminate/http": "^8.0 || ^9.0 || ^10.0", - "illuminate/support": "^8.0 || ^9.0 || ^10.0", + "illuminate/database": "^8.0 || ^9.0 || ^10.0 || ^11.0", + "illuminate/http": "^8.0 || ^9.0 || ^10.0 || ^11.0", + "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0", "nesbot/carbon": "^2.31", "spatie/laravel-enum": "^3.0", "spatie/laravel-translatable": "^6.3.0"