Save workspace changes
This commit is contained in:
@@ -12,10 +12,13 @@ use App\Models\Story;
|
||||
use App\Models\StoryLike;
|
||||
use App\Models\StoryView;
|
||||
use App\Models\User;
|
||||
use App\Models\World;
|
||||
use App\Models\WorldSubmission;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class LeaderboardService
|
||||
{
|
||||
@@ -68,6 +71,16 @@ class LeaderboardService
|
||||
return $this->persistRows(Leaderboard::TYPE_GROUP, $normalizedPeriod, $rows, self::ENTITY_STORE_LIMIT);
|
||||
}
|
||||
|
||||
public function calculateWorldLeaderboard(string $period): int
|
||||
{
|
||||
$normalizedPeriod = $this->normalizePeriod($period);
|
||||
$rows = $normalizedPeriod === Leaderboard::PERIOD_ALL_TIME
|
||||
? $this->allTimeWorldRows()
|
||||
: $this->windowedWorldRows($this->periodStart($normalizedPeriod));
|
||||
|
||||
return $this->persistRows(Leaderboard::TYPE_WORLD, $normalizedPeriod, $rows, self::ENTITY_STORE_LIMIT);
|
||||
}
|
||||
|
||||
public function refreshAll(): array
|
||||
{
|
||||
$results = [];
|
||||
@@ -77,6 +90,7 @@ class LeaderboardService
|
||||
Leaderboard::TYPE_ARTWORK,
|
||||
Leaderboard::TYPE_GROUP,
|
||||
Leaderboard::TYPE_STORY,
|
||||
Leaderboard::TYPE_WORLD,
|
||||
] as $type) {
|
||||
foreach ($this->periods() as $period) {
|
||||
$results[$type][$period] = match ($type) {
|
||||
@@ -84,6 +98,7 @@ class LeaderboardService
|
||||
Leaderboard::TYPE_ARTWORK => $this->calculateArtworkLeaderboard($period),
|
||||
Leaderboard::TYPE_GROUP => $this->calculateGroupLeaderboard($period),
|
||||
Leaderboard::TYPE_STORY => $this->calculateStoryLeaderboard($period),
|
||||
Leaderboard::TYPE_WORLD => $this->calculateWorldLeaderboard($period),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -121,6 +136,7 @@ class LeaderboardService
|
||||
Leaderboard::TYPE_ARTWORK => $this->artworkEntities($items->pluck('entity_id')->all()),
|
||||
Leaderboard::TYPE_GROUP => $this->groupEntities($items->pluck('entity_id')->all()),
|
||||
Leaderboard::TYPE_STORY => $this->storyEntities($items->pluck('entity_id')->all()),
|
||||
Leaderboard::TYPE_WORLD => $this->worldEntities($items->pluck('entity_id')->all()),
|
||||
};
|
||||
|
||||
return [
|
||||
@@ -205,6 +221,7 @@ class LeaderboardService
|
||||
'artwork', 'artworks' => Leaderboard::TYPE_ARTWORK,
|
||||
'group', 'groups' => Leaderboard::TYPE_GROUP,
|
||||
'story', 'stories' => Leaderboard::TYPE_STORY,
|
||||
'world', 'worlds' => Leaderboard::TYPE_WORLD,
|
||||
default => Leaderboard::TYPE_CREATOR,
|
||||
};
|
||||
}
|
||||
@@ -228,6 +245,7 @@ class LeaderboardService
|
||||
Leaderboard::TYPE_ARTWORK => $this->calculateArtworkLeaderboard($period),
|
||||
Leaderboard::TYPE_GROUP => $this->calculateGroupLeaderboard($period),
|
||||
Leaderboard::TYPE_STORY => $this->calculateStoryLeaderboard($period),
|
||||
Leaderboard::TYPE_WORLD => $this->calculateWorldLeaderboard($period),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -585,6 +603,128 @@ class LeaderboardService
|
||||
->values();
|
||||
}
|
||||
|
||||
private function allTimeWorldRows(): Collection
|
||||
{
|
||||
if (! $this->worldTablesExist()) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return World::query()
|
||||
->from('worlds')
|
||||
->withCount([
|
||||
'worldRelations as relations_count',
|
||||
'worldRelations as featured_relations_count' => fn ($query) => $query->where('is_featured', true),
|
||||
'worldSubmissions as approved_submissions_count' => fn ($query) => $query->where('status', WorldSubmission::STATUS_LIVE),
|
||||
'worldSubmissions as featured_submissions_count' => fn ($query) => $query->where('status', WorldSubmission::STATUS_LIVE)->where('is_featured', true),
|
||||
])
|
||||
->publiclyVisible()
|
||||
->get()
|
||||
->map(fn (World $world): array => [
|
||||
'entity_id' => (int) $world->id,
|
||||
'score' => $this->scoreWorld(
|
||||
(int) ($world->relations_count ?? 0),
|
||||
(int) ($world->featured_relations_count ?? 0),
|
||||
(int) ($world->approved_submissions_count ?? 0),
|
||||
(int) ($world->featured_submissions_count ?? 0),
|
||||
$world->isCurrent(),
|
||||
(bool) $world->is_featured,
|
||||
false,
|
||||
),
|
||||
])
|
||||
->filter(fn (array $row): bool => $row['score'] > 0)
|
||||
->values();
|
||||
}
|
||||
|
||||
private function windowedWorldRows(CarbonImmutable $start): Collection
|
||||
{
|
||||
if (! $this->worldTablesExist()) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$relations = DB::table('world_relations')
|
||||
->select('world_id', DB::raw('COUNT(*) as relations_count'))
|
||||
->where('created_at', '>=', $start)
|
||||
->groupBy('world_id');
|
||||
|
||||
$featuredRelations = DB::table('world_relations')
|
||||
->select('world_id', DB::raw('COUNT(*) as featured_relations_count'))
|
||||
->where('created_at', '>=', $start)
|
||||
->where('is_featured', true)
|
||||
->groupBy('world_id');
|
||||
|
||||
$approvedSubmissions = DB::table('world_submissions')
|
||||
->select('world_id', DB::raw('COUNT(*) as approved_submissions_count'))
|
||||
->where('status', WorldSubmission::STATUS_LIVE)
|
||||
->where(function ($query) use ($start): void {
|
||||
$query->where('reviewed_at', '>=', $start)
|
||||
->orWhere(function ($fallback) use ($start): void {
|
||||
$fallback->whereNull('reviewed_at')
|
||||
->where('created_at', '>=', $start);
|
||||
});
|
||||
})
|
||||
->groupBy('world_id');
|
||||
|
||||
$featuredSubmissions = DB::table('world_submissions')
|
||||
->select('world_id', DB::raw('COUNT(*) as featured_submissions_count'))
|
||||
->where('status', WorldSubmission::STATUS_LIVE)
|
||||
->where('is_featured', true)
|
||||
->where(function ($query) use ($start): void {
|
||||
$query->where('reviewed_at', '>=', $start)
|
||||
->orWhere(function ($fallback) use ($start): void {
|
||||
$fallback->whereNull('reviewed_at')
|
||||
->where('created_at', '>=', $start);
|
||||
});
|
||||
})
|
||||
->groupBy('world_id');
|
||||
|
||||
return World::query()
|
||||
->from('worlds')
|
||||
->leftJoinSub($relations, 'relations', 'relations.world_id', '=', 'worlds.id')
|
||||
->leftJoinSub($featuredRelations, 'featured_relations', 'featured_relations.world_id', '=', 'worlds.id')
|
||||
->leftJoinSub($approvedSubmissions, 'approved_submissions', 'approved_submissions.world_id', '=', 'worlds.id')
|
||||
->leftJoinSub($featuredSubmissions, 'featured_submissions', 'featured_submissions.world_id', '=', 'worlds.id')
|
||||
->publiclyVisible()
|
||||
->select([
|
||||
'worlds.id',
|
||||
'worlds.status',
|
||||
'worlds.starts_at',
|
||||
'worlds.ends_at',
|
||||
'worlds.published_at',
|
||||
'worlds.is_featured',
|
||||
DB::raw('COALESCE(relations.relations_count, 0) as relations_count'),
|
||||
DB::raw('COALESCE(featured_relations.featured_relations_count, 0) as featured_relations_count'),
|
||||
DB::raw('COALESCE(approved_submissions.approved_submissions_count, 0) as approved_submissions_count'),
|
||||
DB::raw('COALESCE(featured_submissions.featured_submissions_count, 0) as featured_submissions_count'),
|
||||
DB::raw("CASE WHEN worlds.published_at >= '" . $start->toDateTimeString() . "' OR worlds.starts_at >= '" . $start->toDateTimeString() . "' THEN 1 ELSE 0 END as recent_launch_bonus"),
|
||||
])
|
||||
->get()
|
||||
->map(function ($row): array {
|
||||
$world = new World();
|
||||
$world->forceFill([
|
||||
'status' => (string) $row->status,
|
||||
'starts_at' => $row->starts_at,
|
||||
'ends_at' => $row->ends_at,
|
||||
'published_at' => $row->published_at,
|
||||
'is_featured' => (bool) $row->is_featured,
|
||||
]);
|
||||
|
||||
return [
|
||||
'entity_id' => (int) $row->id,
|
||||
'score' => $this->scoreWorld(
|
||||
(int) $row->relations_count,
|
||||
(int) $row->featured_relations_count,
|
||||
(int) $row->approved_submissions_count,
|
||||
(int) $row->featured_submissions_count,
|
||||
$world->isCurrent(),
|
||||
(bool) $row->is_featured,
|
||||
(bool) $row->recent_launch_bonus,
|
||||
),
|
||||
];
|
||||
})
|
||||
->filter(fn (array $row): bool => $row['score'] > 0)
|
||||
->values();
|
||||
}
|
||||
|
||||
private function windowedGroupRows(CarbonImmutable $start): Collection
|
||||
{
|
||||
$follows = DB::table('group_follows')
|
||||
@@ -819,4 +959,95 @@ class LeaderboardService
|
||||
})
|
||||
->all();
|
||||
}
|
||||
|
||||
private function worldEntities(array $ids): array
|
||||
{
|
||||
if (! $this->worldTablesExist()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return World::query()
|
||||
->withCount([
|
||||
'worldRelations as relations_count',
|
||||
'worldSubmissions as approved_submissions_count' => fn ($query) => $query->where('status', WorldSubmission::STATUS_LIVE),
|
||||
'worldSubmissions as featured_submissions_count' => fn ($query) => $query->where('status', WorldSubmission::STATUS_LIVE)->where('is_featured', true),
|
||||
])
|
||||
->whereIn('id', $ids)
|
||||
->publiclyVisible()
|
||||
->get()
|
||||
->mapWithKeys(function (World $world): array {
|
||||
return [
|
||||
(int) $world->id => [
|
||||
'id' => (int) $world->id,
|
||||
'type' => Leaderboard::TYPE_WORLD,
|
||||
'name' => (string) $world->title,
|
||||
'summary' => (string) ($world->summary ?: $world->tagline ?: ''),
|
||||
'url' => $world->publicUrl(),
|
||||
'image' => $world->coverUrl(),
|
||||
'timeframe_label' => $this->worldTimeframeLabel($world),
|
||||
'badge_label' => (string) ($world->badge_label ?? ''),
|
||||
'phase' => $world->isCurrent() ? 'active' : ((string) $world->status === World::STATUS_ARCHIVED ? 'archive' : 'published'),
|
||||
'icon_name' => (string) ($world->icon_name ?: 'fa-solid fa-globe'),
|
||||
'theme_label' => $this->worldThemeLabel($world),
|
||||
'relations_count' => (int) ($world->relations_count ?? 0),
|
||||
'approved_submissions_count' => (int) ($world->approved_submissions_count ?? 0),
|
||||
'featured_submissions_count' => (int) ($world->featured_submissions_count ?? 0),
|
||||
'is_featured' => (bool) $world->is_featured,
|
||||
],
|
||||
];
|
||||
})
|
||||
->all();
|
||||
}
|
||||
|
||||
private function scoreWorld(
|
||||
int $relationsCount,
|
||||
int $featuredRelationsCount,
|
||||
int $approvedSubmissionsCount,
|
||||
int $featuredSubmissionsCount,
|
||||
bool $isCurrent,
|
||||
bool $isFeatured,
|
||||
bool $isRecentLaunch,
|
||||
): int {
|
||||
return ($relationsCount * 22)
|
||||
+ ($featuredRelationsCount * 10)
|
||||
+ ($approvedSubmissionsCount * 16)
|
||||
+ ($featuredSubmissionsCount * 26)
|
||||
+ ($isCurrent ? 48 : 0)
|
||||
+ ($isFeatured ? 70 : 0)
|
||||
+ ($isRecentLaunch ? 24 : 0);
|
||||
}
|
||||
|
||||
private function worldTablesExist(): bool
|
||||
{
|
||||
return Schema::hasTable('worlds')
|
||||
&& Schema::hasTable('world_relations')
|
||||
&& Schema::hasTable('world_submissions');
|
||||
}
|
||||
|
||||
private function worldThemeLabel(World $world): string
|
||||
{
|
||||
return match ((string) $world->type) {
|
||||
World::TYPE_EVENT => 'Event',
|
||||
World::TYPE_CAMPAIGN => 'Campaign',
|
||||
World::TYPE_TRIBUTE => 'Tribute',
|
||||
default => 'Seasonal',
|
||||
};
|
||||
}
|
||||
|
||||
private function worldTimeframeLabel(World $world): ?string
|
||||
{
|
||||
if ($world->starts_at && $world->ends_at) {
|
||||
return $world->starts_at->format('M j, Y') . ' - ' . $world->ends_at->format('M j, Y');
|
||||
}
|
||||
|
||||
if ($world->starts_at) {
|
||||
return 'Starts ' . $world->starts_at->format('M j, Y');
|
||||
}
|
||||
|
||||
if ($world->ends_at) {
|
||||
return 'Ends ' . $world->ends_at->format('M j, Y');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user