where('card_id', $card->id)->where('type', NovaCardReaction::TYPE_LIKE)->count(); $favorites = NovaCardReaction::query()->where('card_id', $card->id)->where('type', NovaCardReaction::TYPE_FAVORITE)->count(); $saves = NovaCardCollectionItem::query()->where('card_id', $card->id)->count(); $remixes = NovaCard::query()->where('original_card_id', $card->id)->count(); $comments = NovaCardComment::query()->where('card_id', $card->id)->where('status', 'visible')->count(); $challengeEntries = NovaCardChallengeEntry::query()->where('card_id', $card->id)->count(); $lastEngagedAt = $this->lastEngagedAt($card); $card->forceFill([ 'likes_count' => $likes, 'favorites_count' => $favorites, 'saves_count' => $saves, 'remixes_count' => $remixes, 'comments_count' => $comments, 'challenge_entries_count' => $challengeEntries, 'last_engaged_at' => $lastEngagedAt, 'trending_score' => $this->score($card, $likes, $favorites, $saves, $remixes, $comments, $challengeEntries, $lastEngagedAt), ])->save(); return $card->refresh(); } public function rebuildAll(): void { NovaCard::query()->select('id')->orderBy('id')->chunkById(100, function ($cards): void { foreach ($cards as $card) { $this->refreshCard(NovaCard::query()->findOrFail($card->id)); } }); } private function score(NovaCard $card, int $likes, int $favorites, int $saves, int $remixes, int $comments, int $challengeEntries, ?CarbonInterface $lastEngagedAt): float { $base = ($likes * 4.0) + ($favorites * 2.5) + ($saves * 5.0) + ($remixes * 6.0) + ($comments * 2.0) + ($challengeEntries * 4.0) + ($card->shares_count * 3.0) + ($card->downloads_count * 2.0) + ($card->views_count * 0.25); $engagedAt = $lastEngagedAt ?? $card->published_at ?? now(); $ageHours = max(1.0, (float) $engagedAt->diffInHours(now())); $decay = max(0.2, 1 / (1 + ($ageHours / 72))); return round($base * $decay, 4); } private function lastEngagedAt(NovaCard $card): ?CarbonInterface { $timestamps = array_filter([ NovaCardReaction::query()->where('card_id', $card->id)->max('created_at'), NovaCardCollectionItem::query()->where('card_id', $card->id)->max('created_at'), NovaCard::query()->where('original_card_id', $card->id)->max('created_at'), NovaCardComment::query()->where('card_id', $card->id)->max('created_at'), NovaCardChallengeEntry::query()->where('card_id', $card->id)->max('created_at'), $card->updated_at?->toDateTimeString(), $card->published_at?->toDateTimeString(), ]); if ($timestamps === []) { return null; } return collect($timestamps) ->map(fn ($timestamp) => Carbon::parse($timestamp)) ->sortDesc() ->first(); } }