118 lines
4.7 KiB
PHP
118 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Collection;
|
|
use App\Models\CollectionDailyStat;
|
|
use App\Services\ThumbnailPresenter;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class CollectionAnalyticsService
|
|
{
|
|
public function snapshot(Collection $collection, ?Carbon $date = null): void
|
|
{
|
|
$bucket = ($date ?? now())->toDateString();
|
|
|
|
DB::table('collection_daily_stats')->updateOrInsert(
|
|
[
|
|
'collection_id' => $collection->id,
|
|
'stat_date' => $bucket,
|
|
],
|
|
[
|
|
'views_count' => (int) $collection->views_count,
|
|
'likes_count' => (int) $collection->likes_count,
|
|
'follows_count' => (int) $collection->followers_count,
|
|
'saves_count' => (int) $collection->saves_count,
|
|
'comments_count' => (int) $collection->comments_count,
|
|
'shares_count' => (int) $collection->shares_count,
|
|
'submissions_count' => (int) $collection->submissions()->count(),
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]
|
|
);
|
|
}
|
|
|
|
public function overview(Collection $collection, int $days = 30): array
|
|
{
|
|
$rows = CollectionDailyStat::query()
|
|
->where('collection_id', $collection->id)
|
|
->where('stat_date', '>=', now()->subDays(max(7, $days - 1))->toDateString())
|
|
->orderBy('stat_date')
|
|
->get();
|
|
|
|
$first = $rows->first();
|
|
$last = $rows->last();
|
|
|
|
$delta = static fn (string $column): int => max(0, (int) ($last?->{$column} ?? 0) - (int) ($first?->{$column} ?? 0));
|
|
|
|
return [
|
|
'totals' => [
|
|
'views' => (int) $collection->views_count,
|
|
'likes' => (int) $collection->likes_count,
|
|
'follows' => (int) $collection->followers_count,
|
|
'saves' => (int) $collection->saves_count,
|
|
'comments' => (int) $collection->comments_count,
|
|
'shares' => (int) $collection->shares_count,
|
|
'submissions' => (int) $collection->submissions()->count(),
|
|
],
|
|
'range' => [
|
|
'days' => $days,
|
|
'views_delta' => $delta('views_count'),
|
|
'likes_delta' => $delta('likes_count'),
|
|
'follows_delta' => $delta('follows_count'),
|
|
'saves_delta' => $delta('saves_count'),
|
|
'comments_delta' => $delta('comments_count'),
|
|
],
|
|
'timeline' => $rows->map(fn (CollectionDailyStat $row) => [
|
|
'date' => $row->stat_date?->toDateString(),
|
|
'views' => (int) $row->views_count,
|
|
'likes' => (int) $row->likes_count,
|
|
'follows' => (int) $row->follows_count,
|
|
'saves' => (int) $row->saves_count,
|
|
'comments' => (int) $row->comments_count,
|
|
'shares' => (int) $row->shares_count,
|
|
'submissions' => (int) $row->submissions_count,
|
|
])->values()->all(),
|
|
'top_artworks' => $this->topArtworks($collection),
|
|
];
|
|
}
|
|
|
|
public function topArtworks(Collection $collection, int $limit = 8): array
|
|
{
|
|
return DB::table('collection_artwork as ca')
|
|
->join('artworks as a', 'a.id', '=', 'ca.artwork_id')
|
|
->leftJoin('artwork_stats as s', 's.artwork_id', '=', 'a.id')
|
|
->where('ca.collection_id', $collection->id)
|
|
->whereNull('a.deleted_at')
|
|
->orderByDesc(DB::raw('COALESCE(s.ranking_score, 0)'))
|
|
->orderByDesc(DB::raw('COALESCE(s.views, 0)'))
|
|
->limit(max(1, min($limit, 12)))
|
|
->get([
|
|
'a.id',
|
|
'a.title',
|
|
'a.slug',
|
|
'a.hash',
|
|
'a.thumb_ext',
|
|
DB::raw('COALESCE(s.views, 0) as views'),
|
|
DB::raw('COALESCE(s.favorites, 0) as favourites'),
|
|
DB::raw('COALESCE(s.shares_count, 0) as shares'),
|
|
DB::raw('COALESCE(s.ranking_score, 0) as ranking_score'),
|
|
])
|
|
->map(fn ($row) => [
|
|
'id' => (int) $row->id,
|
|
'title' => (string) $row->title,
|
|
'slug' => (string) $row->slug,
|
|
'thumb' => $row->hash && $row->thumb_ext ? ThumbnailPresenter::forHash((string) $row->hash, (string) $row->thumb_ext, 'sq') : null,
|
|
'views' => (int) $row->views,
|
|
'favourites' => (int) $row->favourites,
|
|
'shares' => (int) $row->shares,
|
|
'ranking_score' => round((float) $row->ranking_score, 2),
|
|
])
|
|
->values()
|
|
->all();
|
|
}
|
|
}
|