optimizations
This commit is contained in:
81
app/Services/NovaCards/NovaCardRisingService.php
Normal file
81
app/Services/NovaCards/NovaCardRisingService.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\NovaCards;
|
||||
|
||||
use App\Models\NovaCard;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Surfaces "rising" cards — recently published cards that are gaining
|
||||
* engagement faster than average, weighted to balance novelty creators.
|
||||
*/
|
||||
class NovaCardRisingService
|
||||
{
|
||||
/** Number of hours a card is eligible for "rising" feed. */
|
||||
private const WINDOW_HOURS = 96;
|
||||
|
||||
/** Cache TTL in seconds. */
|
||||
private const CACHE_TTL = 300;
|
||||
|
||||
public function risingCards(int $limit = 18, bool $cached = true): Collection
|
||||
{
|
||||
if ($cached) {
|
||||
return Cache::remember(
|
||||
'nova_cards.rising.' . $limit,
|
||||
self::CACHE_TTL,
|
||||
fn () => $this->queryRising($limit),
|
||||
);
|
||||
}
|
||||
|
||||
return $this->queryRising($limit);
|
||||
}
|
||||
|
||||
public function invalidateCache(): void
|
||||
{
|
||||
foreach ([6, 18, 24, 36] as $limit) {
|
||||
Cache::forget('nova_cards.rising.' . $limit);
|
||||
}
|
||||
}
|
||||
|
||||
private function queryRising(int $limit): Collection
|
||||
{
|
||||
$cutoff = Carbon::now()->subHours(self::WINDOW_HOURS);
|
||||
$isSqlite = DB::connection()->getDriverName() === 'sqlite';
|
||||
$ageHoursExpression = $isSqlite
|
||||
? "CASE WHEN ((julianday('now') - julianday(published_at)) * 24.0) < 1 THEN 1 ELSE ((julianday('now') - julianday(published_at)) * 24.0) END"
|
||||
: 'GREATEST(1, TIMESTAMPDIFF(HOUR, published_at, NOW()))';
|
||||
$decayExpression = $isSqlite
|
||||
? $ageHoursExpression
|
||||
: 'POWER(' . $ageHoursExpression . ', 0.7)';
|
||||
$risingMomentumExpression = '(
|
||||
(saves_count * 5.0 + remixes_count * 6.0 + likes_count * 4.0 + favorites_count * 2.5 + comments_count * 2.0 + challenge_entries_count * 4.0)
|
||||
/ ' . $decayExpression . '
|
||||
) AS rising_momentum';
|
||||
|
||||
return NovaCard::query()
|
||||
->publiclyVisible()
|
||||
->where('published_at', '>=', $cutoff)
|
||||
// Must have at least one meaningful engagement signal.
|
||||
->where(function (Builder $q): void {
|
||||
$q->where('saves_count', '>', 0)
|
||||
->orWhere('remixes_count', '>', 0)
|
||||
->orWhere('likes_count', '>', 1);
|
||||
})
|
||||
->select([
|
||||
'nova_cards.*',
|
||||
// Rising score: weight recent engagement, penalise by sqrt(age hours) to let novelty show
|
||||
DB::raw($risingMomentumExpression),
|
||||
])
|
||||
->orderByDesc('rising_momentum')
|
||||
->orderByDesc('published_at')
|
||||
->limit($limit)
|
||||
->with(['user.profile', 'category', 'template', 'backgroundImage', 'tags'])
|
||||
->get();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user