optimizations
This commit is contained in:
90
app/Services/NovaCards/NovaCardTrendingService.php
Normal file
90
app/Services/NovaCards/NovaCardTrendingService.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\NovaCards;
|
||||
|
||||
use App\Models\NovaCard;
|
||||
use App\Models\NovaCardChallengeEntry;
|
||||
use App\Models\NovaCardComment;
|
||||
use App\Models\NovaCardCollectionItem;
|
||||
use App\Models\NovaCardReaction;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterface;
|
||||
|
||||
class NovaCardTrendingService
|
||||
{
|
||||
public function refreshCard(NovaCard $card): NovaCard
|
||||
{
|
||||
$likes = NovaCardReaction::query()->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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user