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,399 @@
<?php
namespace App\Actions\Plays;
use App\Actions\Album;
use App\Actions\TrackPlay;
use App\Models\Episode;
use App\Models\Movie;
use App\Models\Season;
use App\Models\Series;
use App\Models\Title;
use App\Models\User;
use App\Models\Video;
use App\Models\VideoPlay;
use Common\Core\Values\ValueLists;
use Common\Database\Metrics\MetricDateRange;
use Common\Database\Metrics\Partition;
use Common\Database\Metrics\Trend;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Gate;
class BuildPlaysReport
{
protected Builder $builder;
protected array $params = [];
protected MetricDateRange $dateRange;
protected int $modelId;
public function execute(array $params): array
{
$this->params = $params;
$this->builder = $this->createBuilder();
$this->dateRange = new MetricDateRange(
start: $this->params['startDate'] ?? null,
end: $this->params['endDate'] ?? null,
timezone: $this->params['timezone'] ?? null,
);
$metrics = explode(',', Arr::get($params, 'metrics', 'plays'));
return collect($metrics)
->mapWithKeys(function ($metric) {
if ($metric === 'movies') {
return ['movies' => $this->getTitlesMetric(false)];
} elseif ($metric === 'series') {
return ['series' => $this->getTitlesMetric(true)];
} else {
$method = sprintf('get%sMetric', ucfirst($metric));
if (method_exists($this, $method)) {
return [$metric => $this->$method()];
}
return [$metric => []];
}
})
->toArray();
}
protected function createBuilder(): Builder
{
$model = Arr::get($this->params, 'model', '');
$parts = explode('=', $model);
// might send track_play=0, check if variable is set, instead of being truthy
if (!isset($parts[0]) || !isset($parts[1])) {
$parts = ['video_play', 0];
}
$model = modelTypeToNamespace($parts[0]);
$this->modelId = (int) $parts[1];
switch ($model) {
case VideoPlay::class:
// all plays, not scoped to any resource (for admin area)
Gate::authorize('admin.access');
$builder = VideoPlay::query();
break;
case Video::class:
$video = Video::findOrFail($this->modelId);
Gate::authorize('update', $video);
$builder = $video->plays()->getQuery();
break;
case Movie::class:
case Series::class:
case Title::class:
$title = Title::findOrFail($this->modelId);
Gate::authorize('update', $title);
$builder = $title->plays()->getQuery();
break;
case Season::class:
$season = Season::with(['title'])->findOrFail($this->modelId);
Gate::authorize('update', $season->title);
$builder = VideoPlay::join(
'videos',
'video_plays.video_id',
'=',
'videos.id',
)
->join('titles', 'videos.title_id', '=', 'titles.id')
->where('titles.id', $season->title_id)
->where('videos.season_num', $season->number);
break;
case Episode::class:
$episode = Episode::with(['title'])->findOrFail($this->modelId);
Gate::authorize('update', $episode->title);
$builder = $episode->plays()->getQuery();
break;
default:
throw new Exception();
}
return $builder;
}
protected function getPlaysMetric(): array
{
if (config('common.site.fake_plays_data')) {
$data = (new GenerateFakePlaysData())->playsTrend(
$this->builder,
$this->dateRange,
);
} else {
$data = (new Trend(
$this->builder,
dateRange: $this->dateRange,
))->count();
}
return [
'granularity' => $this->dateRange->granularity,
'total' => array_sum(Arr::pluck($data, 'value')),
'datasets' => [
[
'label' => __('Plays'),
'data' => $data,
],
],
];
}
protected function getDevicesMetric(): array
{
return $this->getPartitionMetric('device', 5);
}
protected function getBrowsersMetric(): array
{
return $this->getPartitionMetric('browser', 8);
}
protected function getPlatformsMetric(): array
{
return $this->getPartitionMetric('platform', 5);
}
protected function getTitlesMetric(bool $isSeries = null): array
{
if (config('common.site.fake_plays_data')) {
$data = (new GenerateFakePlaysData())->titles($isSeries);
} else {
$data = (new Partition(
$this->builder
->join('videos', 'video_plays.video_id', '=', 'videos.id')
->join('titles', 'videos.title_id', '=', 'titles.id')
->when(
!is_null($isSeries),
fn($query) => $query->where('is_series', $isSeries),
)
->orderBy('aggregate', 'desc'),
groupBy: 'title_id',
dateRange: $this->dateRange,
limit: 30,
))->count();
$titles = Title::whereIn('id', Arr::pluck($data, 'label'))
->compact()
->get();
$data = array_map(function ($item) use ($titles) {
$title = $titles->firstWhere('id', $item['label']);
$item['model'] = $title;
$item['label'] = $title->name;
return $item;
}, $data);
}
return [
'datasets' => [
[
'label' => __('Plays'),
'data' => $data,
],
],
];
}
protected function getSeasonsMetric(): array
{
if (config('common.site.fake_plays_data')) {
$data = (new GenerateFakePlaysData())->seasons($this->modelId);
} else {
$data = (new Partition(
$this->builder
->whereNotNull('season_num')
->orderBy('aggregate', 'desc'),
groupBy: 'season_num',
dateRange: $this->dateRange,
limit: 30,
))->count();
$seasons = Season::where('title_id', $this->modelId)
->whereIn('number', Arr::pluck($data, 'label'))
->with(['title' => fn($query) => $query->compact()])
->get();
$data = array_map(function ($item) use ($seasons) {
$season = $seasons->firstWhere('number', (int) $item['label']);
$item['model'] = $season;
$item['label'] = __('Season :number', [
'number' => $season->number,
]);
return $item;
}, $data);
}
return [
'datasets' => [
[
'label' => __('Plays'),
'data' => $data,
],
],
];
}
protected function getEpisodesMetric(): array
{
if (config('common.site.fake_plays_data')) {
$data = (new GenerateFakePlaysData())->episodes($this->modelId);
} else {
$data = (new Partition(
$this->builder
->whereNotNull('episode_id')
->orderBy('aggregate', 'desc'),
groupBy: 'episode_id',
dateRange: $this->dateRange,
limit: 30,
))->count();
$episodes = Episode::whereIn('id', Arr::pluck($data, 'label'))
->with(['title' => fn($query) => $query->compact()])
->get();
$data = array_map(function ($item) use ($episodes) {
$episode = $episodes->firstWhere('id', (int) $item['label']);
$item['model'] = $episode;
$item['label'] = __('Season :s, Episode :e', [
's' => $episode->season_number,
'e' => $episode->number,
]);
return $item;
}, $data);
}
return [
'datasets' => [
[
'label' => __('Plays'),
'data' => $data,
],
],
];
}
protected function getVideosMetric(): array
{
if (config('common.site.fake_plays_data')) {
$data = (new GenerateFakePlaysData())->videos();
} else {
$data = (new Partition(
$this->builder->orderBy('aggregate', 'desc'),
groupBy: 'video_id',
dateRange: $this->dateRange,
limit: 30,
))->count();
$videos = Video::whereIn('id', Arr::pluck($data, 'label'))
->with(['title' => fn($query) => $query->compact()])
->get();
$data = array_map(function ($item) use ($videos) {
$video = $videos->firstWhere('id', $item['label']);
$item['model'] = $video;
$item['label'] = $video?->name;
return $item;
}, $data);
}
$data = array_values(array_filter($data, fn($item) => $item['label']));
return [
'datasets' => [
[
'label' => __('Plays'),
'data' => $data,
],
],
];
}
protected function getUsersMetric(): array
{
if (config('common.site.fake_plays_data')) {
$data = (new GenerateFakePlaysData())->users();
} else {
$data = (new Partition(
$this->builder->orderBy('aggregate', 'desc'),
groupBy: 'user_id',
dateRange: $this->dateRange,
limit: 30,
))->count();
$userIds = collect($data)
->pluck('label')
->filter()
->unique();
$users = User::whereIn('id', $userIds)->get();
$data = array_map(function ($item) use ($users) {
$user =
$users->firstWhere('id', $item['label']) ??
new User(['first_name' => __('Guest user')]);
$item['model'] = $user;
$item['label'] = $user->display_name;
return $item;
}, $data);
}
return [
'datasets' => [
[
'label' => __('Plays'),
'data' => $data,
],
],
];
}
protected function getLocationsMetric(): array
{
$metric = $this->getPartitionMetric('location');
$countries = app(ValueLists::class)->countries();
$metric['datasets'][0]['data'] = array_map(function ($location) use (
$countries,
$metric,
) {
// only short country code is stored in DB, get and return full country name as well
$location['code'] = strtolower($location['label']);
$location['label'] =
Arr::first(
$countries,
fn($country) => strtolower($country['code']) ===
strtolower($location['code']),
)['name'] ?? $location['label'];
return $location;
}, $metric['datasets'][0]['data']);
return $metric;
}
protected function getPartitionMetric(
string $groupBy,
int $limit = 10,
): array {
if (config('common.site.fake_plays_data')) {
$data = (new GenerateFakePlaysData())->partitionMetric($groupBy);
} else {
$data = (new Partition(
$this->builder,
groupBy: $groupBy,
dateRange: $this->dateRange,
limit: $limit,
))->count();
}
return [
'datasets' => [
[
'label' => __('Plays'),
'data' => $data,
],
],
];
}
}