first commit
Some checks failed
Build / run (push) Has been cancelled

This commit is contained in:
maher
2025-10-29 11:42:25 +01:00
commit 703f50a09d
4595 changed files with 385164 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Actions\People;
use App\Models\Person;
use Illuminate\Support\Facades\DB;
class DeletePeople
{
public function execute(array $ids): void
{
Person::withoutGlobalScope('adult')
->whereIn('id', $ids)
->delete();
DB::table('creditables')
->whereIn('person_id', $ids)
->delete();
}
}

View File

@@ -0,0 +1,283 @@
<?php
namespace App\Actions\People;
use App\Models\Person;
use App\Models\Title;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
class GetPersonCredits
{
private ?int $titleId;
public function execute(Person $person, $options = []): array
{
$this->titleId = Arr::get($options, 'titleId');
$credits = $this->titleId ? [] : $this->getTitleCredits($person);
$seasonCredits = $this->getSeasonCredits($person);
$episodeCredits = $this->getEpisodeCredits($person);
$mergedCredits = $this->mergeCredits(
Arr::get($credits, 'all', []),
$seasonCredits,
$episodeCredits,
);
$mergedCredits = $this->separateSelfCreditsAndSort($mergedCredits);
return [
'credits' => $mergedCredits,
'knownFor' => Arr::get($credits, 'knownFor', []),
'total_credits_count' => array_reduce(
$mergedCredits,
fn($carry, $item) => $carry + count($item),
0,
),
];
}
private function mergeCredits($credits1, $credits2, $credits3): array
{
$mergedCredits = array_merge_recursive($credits1, $credits2, $credits3);
return array_map(function ($titles) {
// sort titles by year
usort($titles, fn($a, $b) => $b['year'] - $a['year']);
$unique = [];
// if this title already exists and existing
// title has episodes property, continue,
// otherwise push title into 'unique' array
foreach ($titles as $title) {
$existing = Arr::get($unique, $title['id']);
if ($existing) {
$existing['credited_episode_count'] =
Arr::get($existing, 'credited_episode_count', 0) +
Arr::get($title, 'credited_episode_count', 0);
$existing['episodes'] = collect(
array_merge(
Arr::get($existing, 'episodes', []),
Arr::get($title, 'episodes', []),
),
)
->unique('id')
->toArray();
if (!$this->titleId) {
$existing['episodes'] = array_slice(
$existing['episodes'],
0,
5,
);
}
$unique[$title['id']] = $existing;
} else {
$unique[$title['id']] = $title;
}
}
return array_values($unique);
}, $mergedCredits);
}
private function getTitleCredits(Person $person): array
{
$credits = $person->credits()->get();
// generate known for list for actors "known_for" department.
$allKnownFor = $credits
->filter(function (Title $credit) use ($person) {
$knownFor =
strtolower($person->known_for) === 'acting'
? 'actors'
: $person->known_for;
return $credit->pivot->department === strtolower($knownFor);
})
->unique();
$knownFor = $allKnownFor->where('pivot.order', '<', 10);
if ($knownFor->count() < 4) {
$knownFor = $allKnownFor;
}
// sort by person credit "order" for title as well as title popularity
$knownFor = $knownFor
->sortBy(function ($title) {
$order = $title->pivot->order;
$popularity = $title->popularity;
return $order - $popularity;
})
->slice(0, 6)
->values();
// cast to array, so poster/backdrop is not removed later.
$knownFor = $knownFor->load(['primaryVideo'])->toArray();
// remove any data not needed to render person filmography
$credits = $credits
->map(function (Title $credit) {
unset($credit['backdrop']);
return $credit;
})
->groupBy('pivot.department');
return ['all' => $credits->toArray(), 'knownFor' => $knownFor];
}
/**
* Get credits for all series seasons person is attached to.
*/
private function getSeasonCredits(Person $person): array
{
$seasons = $person
->seasonCredits($this->titleId)
->with([
'title' => fn($query) => $query->select(
'id',
'name',
'release_date',
'poster',
),
'episodes' => fn($query) => $query
->select(
'id',
'name',
'release_date',
'season_id',
'season_number',
'episode_number',
'title_id',
)
->orderBy('season_number', 'desc')
->orderBy('episode_number', 'desc'),
])
->get();
// group all seasons by department, for example "production"
$groupedSeasons = $seasons->groupBy('pivot.department');
return $groupedSeasons
->map(function (Collection $departmentGroup) {
$seasonsGroupedByTitle = $departmentGroup->groupBy('title.id');
// attach episodes from all seasons to title
return $seasonsGroupedByTitle
->map(function (Collection $titleSeasons) {
$title = $titleSeasons
->first(fn($s) => $s->title)
->title->toArray();
//get episodes from each season and move season "pivot" data to each episode
$episodesFromAllSeasons = $titleSeasons
->pluck('episodes')
->flatten()
->values()
->map(function ($episode) use ($titleSeasons) {
$episode->pivot = $titleSeasons
->first()
->pivot->toArray();
return $episode;
});
$title[
'credited_episode_count'
] = $episodesFromAllSeasons->count();
if (!$this->titleId) {
$episodesFromAllSeasons = $episodesFromAllSeasons->take(
5,
);
}
$title['episodes'] = $episodesFromAllSeasons->toArray();
return $title;
})
->values();
})
->toArray();
}
/**
* Get all individual episodes person is credited for.
*
* This will return array grouped by department, and
* series with all episodes person is credited for attached
* to that series.
*
* @param Person $person
* @return array
*/
private function getEpisodeCredits(Person $person)
{
$episodes = $person
->episodeCredits($this->titleId)
->with([
'title' => function (BelongsTo $query) {
$query->select('id', 'name', 'release_date', 'poster');
},
])
->get();
$groupedByDep = $episodes->groupBy('pivot.department');
return $groupedByDep
->map(
fn(Collection $episodes) => $episodes
->groupBy('title.id')
->map(function (Collection $episodes) {
if (!$episodes->first()->title) {
return null;
}
$title = $episodes->first()->title->toArray();
$episodes = $episodes->map(function ($episode) {
unset($episode->title);
return $episode;
});
$title['credited_episode_count'] = $episodes->count();
if (!$this->titleId) {
$episodes = $episodes->take(5);
}
$title['episodes'] = $episodes->toArray();
return $title;
})
->filter()
->values(),
)
->toArray();
}
private function separateSelfCreditsAndSort(array $credits): array
{
if (!isset($credits['cast'])) {
return $credits;
}
$cast = [];
$self = [];
foreach ($credits['cast'] as $credit) {
$char = isset($credit['pivot']['character'])
? strtolower($credit['pivot']['character'])
: null;
if (
$char &&
($char === 'self' || Str::contains($char, 'himself'))
) {
$self[] = $credit;
} else {
$cast[] = $credit;
}
}
$credits['cast'] = $cast;
// sort before adding "self" to array as that should be last always
uksort(
$credits,
fn($a, $b) => (is_countable($credits[$b])
? count($credits[$b])
: 0) - (is_countable($credits[$a]) ? count($credits[$a]) : 0),
);
$credits['self'] = $self;
return $credits;
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace App\Actions\People;
use App\Models\Title;
use Illuminate\Database\QueryException;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
class LoadPrimaryCredit
{
public function execute(Collection|AbstractPaginator $people): void
{
$prefix = DB::getTablePrefix();
$titleSelect = Title::select([
'titles.id',
'titles.is_series',
'titles.name',
DB::raw("{$prefix}creditables.person_id as pivot_person_id"),
DB::raw(
"{$prefix}creditables.creditable_id as pivot_creditable_id",
),
DB::raw(
"{$prefix}creditables.creditable_type as pivot_creditable_type",
),
DB::raw("{$prefix}creditables.job as pivot_job"),
DB::raw("{$prefix}creditables.department as pivot_department"),
DB::raw(
"row_number() over (partition by {$prefix}creditables.person_id order by {$prefix}titles.popularity desc) as laravel_row",
),
])
->join('creditables', 'titles.id', '=', 'creditables.creditable_id')
->where('creditables.creditable_type', Title::MODEL_TYPE)
->whereIn('creditables.person_id', $people->pluck('id'))
// this scope will mess up binding merging below
->withoutGlobalScope('adult')
->when(
!config('tmdb.includeAdult'),
fn($q) => $q->where('adult', false),
);
// cache syntax error, if mysql version does not support partition
try {
$items = DB::table(
DB::raw("({$titleSelect->toSql()}) as laravel_table"),
)
->select('*')
->mergeBindings($titleSelect->getQuery())
->where('laravel_row', '<=', 1)
->orderBy('laravel_row')
->get();
} catch (QueryException $e) {
return;
}
$credits = Title::hydrate($items->toArray());
$people->each(function ($person) use ($credits) {
$credit = $credits->first(
fn($credit) => $credit->pivot_person_id === $person->id,
);
if ($credit) {
$person->primary_credit = [
'id' => $credit->id,
'is_series' => $credit->is_series,
'name' => $credit->name,
'year' => $credit->year,
'model_type' => $credit->model_type,
];
}
});
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace App\Actions\People;
use App\Models\Person;
use Common\Database\Datasource\Datasource;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class PaginatePeople
{
public function execute(array $params, $builder = null): AbstractPaginator
{
$builder = $builder ?? Person::query();
$isCompact = Arr::get($params, 'compact');
if ($isCompact) {
$builder->select([
'id',
'name',
'birth_date',
'death_date',
'poster',
'popularity',
]);
}
$datasource = new Datasource($builder, $params);
// prevent duplicate items when ordering by columns that are not
// guaranteed to be unique (name, popularity, age etc.)
$datasource->secondaryOrderCol = 'id';
if (!Arr::get($params, 'order') && !Arr::get($params, 'orderBy')) {
$datasource->order = [
'col' => 'popularity',
'dir' => 'desc',
];
}
if (
$datasource->getOrder()['col'] === 'popularity' &&
($min = config('content.people_index_min_popularity'))
) {
$builder->where('popularity', '>', $min);
}
$pagination = $datasource->paginate();
if (!$isCompact) {
$pagination->transform(function (Person $person) {
$person->description = Str::limit($person->description, 500);
return $person;
});
//app(LoadPrimaryCredit::class)->execute($pagination);
}
return $pagination;
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Actions\People;
use App\Actions\Titles\Store\StoreCredits;
use App\Actions\Titles\StoresMediaImages;
use App\Models\Person;
class StorePersonData
{
use StoresMediaImages;
private ?Person $person = null;
private ?array $data = null;
public function execute(Person $person, array $data): Person
{
$this->person = $person;
$this->data = $data;
$this->persistData();
$this->persistRelations();
return $this->person;
}
private function persistData(): void
{
$personData = array_filter($this->data, function (
$value, // make sure we don't overwrite existing values with null
) {
if (is_array($value)) {
return false;
}
// if fully_synced is true, override everything and erase any previously set values.
// For example if "death_date" was previously set on a person and tmdb now returns null for "death_date", set "death_date" to null in database.
if (
config('common.site.tmdb_delete_when_sync') &&
$this->data['fully_synced']
) {
return true;
}
// if "tmdb_delete_when_sync" is false, don't clear existing values, as values set from admin manually might be erased
return !is_null($value);
});
$this->person->fill($personData)->save();
}
private function persistRelations(): void
{
$relations = array_filter($this->data, fn($value) => is_array($value));
foreach ($relations as $name => $values) {
switch ($name) {
case 'credits':
app(StoreCredits::class)->execute($this->person, $values);
break;
case 'images':
$this->storeImages($values, $this->person);
break;
}
}
}
}