add person page

This commit is contained in:
Constantin Plaiasu 2024-08-29 05:49:54 +03:00
parent 5759606bca
commit a17082eea6
15 changed files with 179 additions and 15 deletions

View file

@ -6,19 +6,14 @@
use App\Http\Requests\MoreMoviesRequest; use App\Http\Requests\MoreMoviesRequest;
use App\Http\Resources\MoreTitles; use App\Http\Resources\MoreTitles;
use DipeshSukhia\LaravelHtmlMinify\LaravelHtmlMinifyFacade; use DipeshSukhia\LaravelHtmlMinify\LaravelHtmlMinifyFacade;
use DipeshSukhia\LaravelHtmlMinify\Middleware\LaravelMinifyHtml;
use Illuminate\Http\Request;
use App\Services\ApiClient; use App\Services\ApiClient;
use App\Services\TmdbClient; use App\Services\TmdbClient;
use App\Supports\Traits\CleanItems; use App\Supports\Traits\CleanItems;
use Illuminate\Support\Facades\Cache;
use App\Supports\SchemaBuilder; use App\Supports\SchemaBuilder;
use Illuminate\Support\Facades\View;
use App\Supports\Traits\Helpers; use App\Supports\Traits\Helpers;
use App\Supports\Traits\TopContent; use App\Supports\Traits\TopContent;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Support\Facades\Vite;
class MovieController extends Controller class MovieController extends Controller
{ {

View file

@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\MorePersonCastRequest;
use DipeshSukhia\LaravelHtmlMinify\LaravelHtmlMinifyFacade;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use App\Services\ApiClient;
use App\Services\TmdbClient;
use App\Supports\Traits\CleanItems;
use App\Supports\SchemaBuilder;
use App\Supports\Traits\Helpers;
use App\Supports\Traits\TopContent;
use Route;
class PeopleController extends Controller
{
use CleanItems;
public function index(TmdbClient $tmdbClient, int $id, string $slug) {
$results = $tmdbClient->getPerson($id);
$page = request()->query('page', 1);
['data' => $data, 'pagination' => $pagination, 'name' => $name] = $this->formatTmdbPersonResponse($results, $page);
$page_text = ($page > 1)
? sprintf(' - Page %s', $page)
: '';
// dd($pagination);
$data = $pagination->items();
$meta = [];
$meta['title'] = str(config('site.people.cast.title'))->replace(['{NAME}'], $name)->replace(['{PAGE}'], $page_text)->apa();
$meta['page_title'] = str(config('site.people.cast.page_title'))->replace(['{NAME}'], $name)->replace(['{PAGE}'], $page_text)->apa();
$meta['description'] = str(config('site.people.cast.description'))->replace(['{NAME}'], $name)->replace(['{PAGE}'], $page_text);
$meta['keywords'] = config('site.people.cast.keywords', false);
$meta['image'] = asset('images/cover.jpg');
$meta['route'] = Route::current();
// dd($meta['route']);
// $query = str($query)->apa();
// sleep(2);
return view('list', compact('data', 'pagination', 'meta', 'id'));
}
public function more(TmdbClient $tmdbClient, MorePersonCastRequest $request):JsonResponse {
$results = $tmdbClient->getPerson($request->validated()['person_id']);
$page = request()->query('page', 1);
['data' => $data, 'pagination' => $pagination, 'name' => $name] = $this->formatTmdbPersonResponse($results, $request->validated()['page']);
$items = $pagination->items();
$has_more_pages = $pagination->hasMorePages();
$current_page = $pagination->currentPage();
$html = LaravelHtmlMinifyFacade::htmlMinify(view('components.more_titles', compact('items'))->render());
return response()->json(compact('current_page', 'has_more_pages', 'html'));
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class MorePersonCastRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'person_id' => 'integer|required',
'page' => 'integer|required'
];
}
}

View file

@ -5,6 +5,7 @@
use App\Http\Resources\MovieDetail; use App\Http\Resources\MovieDetail;
use App\Services\TmdbClient as ServicesTmdbClient; use App\Services\TmdbClient as ServicesTmdbClient;
use App\Supports\Traits\CleanItems; use App\Supports\Traits\CleanItems;
use App\Supports\Traits\Helpers;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Symfony\Component\Cache\Adapter\RedisAdapter; use Symfony\Component\Cache\Adapter\RedisAdapter;
@ -24,7 +25,7 @@
class TmdbClient class TmdbClient
{ {
use CleanItems; use CleanItems, Helpers;
private static $instance; private static $instance;
private $ttl = 3600*24; private $ttl = 3600*24;
private $use_cache = true; private $use_cache = true;
@ -272,4 +273,14 @@ public function getSearch(string $query, int $page = 1)
return $call(); return $call();
} }
public function getPerson($id)
{
$call = fn() => self::filterTmdbPersonData($this->client->getPeopleApi()->getPerson($id, ['language' => 'en-US', 'append_to_response' => 'movie_credits,tv_credits']));
if ($this->use_cache)
return Cache::remember('person_movie_credits_'.$id, $this->ttl, $call);
return $call();
}
} }

View file

@ -361,10 +361,38 @@ public function formatTmdbResponse(array $data, $with_pagination = false, $route
]; ];
} }
} }
$response['pagination']['links'] = $links; $response['pagination']['links'] = $links;
} }
// dd($response); // dd($response);
return $response; return $response;
} }
public function formatTmdbPersonResponse(array $data, $page = 1, $limit = false):array
{
$response = [];
$results = collect([]);
$response['name'] = $data['name'];
$results = collect($data['titles'])->map(function($item) {
$item = (Object)$item;
return [
'id' => self::encodeId($item->id),
'title' => $item->title,
'image' => self::getImageUrl($item->poster_path, 'w300', null, null),
'slug' => Str::slug($item->title) === '' ? sprintf('f%sl', $item->id) : Str::slug($item->title),
'year' => Carbon::parse($item->date)->format('Y'),
'date' => $item->date ?? $item->first_air_date ?? Carbon::now()->format('Y-m-d'),
'type' => $item->type,
'route' => $item->type,
];
})->when($limit, fn($collection) => $collection->take($limit));//->sortByDesc('popularity');
$response['data'] = $results;
$response['pagination'] = new LengthAwarePaginator($results->forPage($page, 20), count($results), 20, $page, [
'path' => request()->url()
]);
return $response;
}
} }

View file

@ -85,6 +85,7 @@ public static function getCast(array $actors, int $limit = 5): array
return collect($actors)->map(fn ($actor) => [ return collect($actors)->map(fn ($actor) => [
'id' => $actor['id'], 'id' => $actor['id'],
'name' => $actor['name'], 'name' => $actor['name'],
'slug' => str()->slug($actor['name']) !== '' ? str()->slug($actor['name']) : sprintf('a%sn', $actor['name']),
'as' => $actor['character'], 'as' => $actor['character'],
'image' => self::getImageUrl($actor['profile_path'], 'w500', 200, 300), 'image' => self::getImageUrl($actor['profile_path'], 'w500', 200, 300),
])->take($limit)->all(); ])->take($limit)->all();
@ -256,4 +257,33 @@ public static function getShareLinks(string $url, string $title, string $descrip
]; ];
} }
public static function filterTmdbPersonData(array $data):array
{
$movies = collect($data['movie_credits']['cast'] ?? [])->where('adult', false)->map(function($movie){
return [
'id' => $movie['id'],
'title' => $movie['title'],
'poster_path' => $movie['poster_path'],
'date' => $movie['release_date'],
'type' => 'movie'
];
});
$shows = collect($data['tv_credits']['cast'] ?? [])->where('adult', false)->map(function($show){
return [
'id' => $show['id'],
'title' => $show['name'],
'poster_path' => $show['poster_path'],
'date' => $show['first_air_date'],
'type' => 'show'
];
});
return [
'id' => $data['id'],
'name' => $data['name'],
'titles' => $movies->merge($shows)->whereNotNull('poster_path')->sortByDesc('date')->values()->all(),
];
}
} }

View file

@ -114,17 +114,20 @@
'page_title' => 'Browse %s Movies', 'page_title' => 'Browse %s Movies',
'description' => 'Movies List Page Description for genre %s', 'description' => 'Movies List Page Description for genre %s',
], ],
'cast' => [
'title' => 'Watch %s Movies Online{PAGE} - ' . config('app.name'),
'page_title' => 'Browse %s Movies',
'description' => '%s Movies{PAGE}: viewing or downloading the greatest films and TV series for free in all languages, in high definition, on safe and legal websites......',
],
'top_imdb' => [ 'top_imdb' => [
'title' => 'IMDb Top 250', 'title' => 'IMDb Top 250',
'page_title' => 'IMDb Top 250', 'page_title' => 'IMDb Top 250',
'description' => 'Top IMDb Titles Description', 'description' => 'Top IMDb Titles Description',
], ],
], ],
'people' => [
'cast' => [
'title' => 'Watch {NAME} Movies & Series Online{PAGE} - ' . config('app.name'),
'page_title' => 'Browse {NAME} Titles',
'description' => '%s Movies{PAGE}: viewing or downloading the greatest films and TV series for free in all languages, in high definition, on safe and legal websites......',
],
],
]; ];

View file

@ -1 +0,0 @@
window.lazyFunctions={more:function(a){const n=a,e=JSON.parse(atob(a.dataset.params)),r=new Headers;r.append("Content-Type","application/json"),r.append("Accept","application/json");const o={page:e.page,route:e.route};"genre"in e.route_parameters&&(o.genre=e.route_parameters.genre),"search"in e&&(o.search=e.search);const s={method:"POST",headers:r,body:JSON.stringify(o)};fetch(e.route,s).then(t=>t.json()).then(t=>{const i=a.parentNode;i.innerHTML+=t.html,window.lLoad.update(),a.remove(),document.getElementById(a.getAttribute("id")).remove(),t.has_more_pages&&(e.page=t.current_page+1,n.setAttribute("href",n.getAttribute("href").replace(new RegExp(t.current_page+"$"),t.current_page+1)),n.setAttribute("data-params",btoa(JSON.stringify(e))),n.removeAttribute("data-ll-status"),n.classList.remove("entered"),i.appendChild(n),window.lMore.update())}).catch(t=>console.error(t))}};function c(a){var n=a.getAttribute("data-lazy-function"),e=window.lazyFunctions[n];e&&e(a)}window.lMore=new window.LazyLoad({elements_selector:"#next-page",unobserve_entered:!0,callback_enter:c});

View file

@ -0,0 +1 @@
window.lazyFunctions={more:function(a){const r=a,e=JSON.parse(atob(a.dataset.params)),o=new Headers;o.append("Content-Type","application/json"),o.append("Accept","application/json");const n={page:e.page,route:e.route};"genre"in e.route_parameters&&(n.genre=e.route_parameters.genre),"person_id"in e.route_parameters&&(n.person_id=e.route_parameters.person_id),"search"in e&&(n.search=e.search);const s={method:"POST",headers:o,body:JSON.stringify(n)};fetch(e.route,s).then(t=>t.json()).then(t=>{const i=a.parentNode;i.innerHTML+=t.html,window.lLoad.update(),a.remove(),document.getElementById(a.getAttribute("id")).remove(),t.has_more_pages&&(e.page=t.current_page+1,r.setAttribute("href",r.getAttribute("href").replace(new RegExp(t.current_page+"$"),t.current_page+1)),r.setAttribute("data-params",btoa(JSON.stringify(e))),r.removeAttribute("data-ll-status"),r.classList.remove("entered"),i.appendChild(r),window.lMore.update())}).catch(t=>console.error(t))}};function p(a){var r=a.getAttribute("data-lazy-function"),e=window.lazyFunctions[r];e&&e(a)}window.lMore=new window.LazyLoad({elements_selector:"#next-page",unobserve_entered:!0,callback_enter:p});

View file

@ -24,7 +24,7 @@
"isEntry": true "isEntry": true
}, },
"resources/js/load-more.js": { "resources/js/load-more.js": {
"file": "assets/load-more-BlXjt7JF.js", "file": "assets/load-more-Ck2SWgku.js",
"name": "load-more", "name": "load-more",
"src": "resources/js/load-more.js", "src": "resources/js/load-more.js",
"isEntry": true "isEntry": true

View file

@ -18,6 +18,9 @@ window.lazyFunctions = {
if ('genre' in params.route_parameters) { if ('genre' in params.route_parameters) {
postBody.genre = params.route_parameters.genre; postBody.genre = params.route_parameters.genre;
} }
if ('person_id' in params.route_parameters) {
postBody.person_id = params.route_parameters.person_id;
}
if ('search' in params) { if ('search' in params) {
postBody.search = params.search; postBody.search = params.search;
} }

View file

@ -123,7 +123,7 @@
<span class="caption">Cast:</span> <span class="caption">Cast:</span>
<span class="value"> <span class="value">
@foreach($movie['cast'] as $actor) @foreach($movie['cast'] as $actor)
{{ $actor['name'] }}@if(!$loop->last), @endif <a href="{{ route('person', ['person_id' => $actor['id'], 'slug' => $actor['slug']]) }}">{{ $actor['name'] }}</a> @if(!$loop->last), @endif
@endforeach</span> @endforeach</span>
</li> </li>
<li> <li>

View file

@ -119,7 +119,7 @@
<span class="caption">Cast:</span> <span class="caption">Cast:</span>
<span class="value"> <span class="value">
@foreach($show['cast'] as $actor) @foreach($show['cast'] as $actor)
{{ $actor['name'] }}@if(!$loop->last), @endif <a href="{{ route('person', ['person_id' => $actor['id'], 'slug' => $actor['slug']]) }}">{{ $actor['name'] }}</a> @if(!$loop->last), @endif
@endforeach</span> @endforeach</span>
</li> </li>
<li> <li>

View file

@ -3,6 +3,7 @@
use App\Http\Controllers\HomeController; use App\Http\Controllers\HomeController;
use App\Http\Controllers\MovieController; use App\Http\Controllers\MovieController;
use App\Http\Controllers\ShowController; use App\Http\Controllers\ShowController;
use App\Http\Controllers\PeopleController;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@ -23,4 +24,5 @@
Route::post('/movies', [MovieController::class, 'movies_more'])->name('api.movies'); Route::post('/movies', [MovieController::class, 'movies_more'])->name('api.movies');
Route::post('/movies/genre', [MovieController::class, 'movies_genre_more'])->name('api.movies.genre'); Route::post('/movies/genre', [MovieController::class, 'movies_genre_more'])->name('api.movies.genre');
Route::post('/tv-shows', [ShowController::class, 'shows_more'])->name('api.shows'); Route::post('/tv-shows', [ShowController::class, 'shows_more'])->name('api.shows');
Route::post('/person', [PeopleController::class, 'more'])->name('api.person');
Route::post('/search', [HomeController::class, 'search_more'])->name('api.search'); Route::post('/search', [HomeController::class, 'search_more'])->name('api.search');

View file

@ -6,6 +6,7 @@
use App\Http\Controllers\ShowController; use App\Http\Controllers\ShowController;
use Illuminate\Foundation\Application; use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PeopleController;
/* /*
@ -22,6 +23,7 @@
Route::pattern('page', '\d+'); Route::pattern('page', '\d+');
Route::pattern('season', '\d+'); Route::pattern('season', '\d+');
Route::pattern('id', '[0-9]+'); Route::pattern('id', '[0-9]+');
Route::pattern('person_id', '[0-9]+');
Route::pattern('p', 'page'); Route::pattern('p', 'page');
Route::pattern('country', '[a-z]{2}'); Route::pattern('country', '[a-z]{2}');
@ -34,6 +36,7 @@
Route::get('/movie/{id}/{slug}', [MovieController::class, 'index'])->name('movie'); Route::get('/movie/{id}/{slug}', [MovieController::class, 'index'])->name('movie');
Route::get('/series/{id}/{slug}', [ShowController::class, 'index'])->name('show'); Route::get('/series/{id}/{slug}', [ShowController::class, 'index'])->name('show');
Route::get('/series/{id}/{slug}/season/{season}', [ShowController::class, 'season'])->name('show.season'); Route::get('/series/{id}/{slug}/season/{season}', [ShowController::class, 'season'])->name('show.season');
Route::get('/person/{person_id}/{slug}', [PeopleController::class, 'index'])->name('person');
Route::get('/search', [HomeController::class, 'search'])->name('search'); Route::get('/search', [HomeController::class, 'search'])->name('search');