90 lines
3.5 KiB
PHP
90 lines
3.5 KiB
PHP
<?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();
|
|
}
|
|
} |