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(); } }