Files
SkinbaseNova/app/Services/Studio/CreatorStudioChallengeService.php

200 lines
8.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Studio;
use App\Models\NovaCard;
use App\Models\NovaCardChallenge;
use App\Models\NovaCardChallengeEntry;
use App\Models\User;
use Illuminate\Support\Collection;
final class CreatorStudioChallengeService
{
public function build(User $user): array
{
$entryCounts = NovaCardChallengeEntry::query()
->where('user_id', $user->id)
->whereNotIn('status', [NovaCardChallengeEntry::STATUS_HIDDEN, NovaCardChallengeEntry::STATUS_REJECTED])
->selectRaw('challenge_id, COUNT(*) as aggregate')
->groupBy('challenge_id')
->pluck('aggregate', 'challenge_id');
$recentEntriesQuery = NovaCardChallengeEntry::query()
->with(['challenge', 'card'])
->where('user_id', $user->id)
->whereNotIn('status', [NovaCardChallengeEntry::STATUS_HIDDEN, NovaCardChallengeEntry::STATUS_REJECTED])
->whereHas('challenge')
->whereHas('card');
$recentEntries = $recentEntriesQuery
->latest('created_at')
->limit(8)
->get()
->map(fn (NovaCardChallengeEntry $entry): array => $this->mapEntry($entry))
->values()
->all();
$activeChallenges = NovaCardChallenge::query()
->whereIn('status', [NovaCardChallenge::STATUS_ACTIVE, NovaCardChallenge::STATUS_COMPLETED])
->orderByRaw('CASE WHEN featured = 1 THEN 0 ELSE 1 END')
->orderByRaw("CASE WHEN status = 'active' THEN 0 ELSE 1 END")
->orderBy('ends_at')
->orderByDesc('starts_at')
->limit(10)
->get()
->map(fn (NovaCardChallenge $challenge): array => $this->mapChallenge($challenge, $entryCounts))
->values();
$spotlight = $activeChallenges->first();
$cardLeaders = NovaCard::query()
->where('user_id', $user->id)
->whereNull('deleted_at')
->where('challenge_entries_count', '>', 0)
->orderByDesc('challenge_entries_count')
->orderByDesc('published_at')
->limit(6)
->get()
->map(fn (NovaCard $card): array => [
'id' => (int) $card->id,
'title' => (string) $card->title,
'status' => (string) $card->status,
'challenge_entries_count' => (int) $card->challenge_entries_count,
'views_count' => (int) $card->views_count,
'comments_count' => (int) $card->comments_count,
'preview_url' => route('studio.cards.preview', ['id' => $card->id]),
'edit_url' => route('studio.cards.edit', ['id' => $card->id]),
'analytics_url' => route('studio.cards.analytics', ['id' => $card->id]),
])
->values()
->all();
$cardsAvailable = (int) NovaCard::query()
->where('user_id', $user->id)
->whereNull('deleted_at')
->whereNotIn('status', [NovaCard::STATUS_HIDDEN, NovaCard::STATUS_REJECTED])
->count();
$entryCount = (int) $entryCounts->sum();
$featuredEntries = (int) (clone $recentEntriesQuery)
->whereIn('status', [NovaCardChallengeEntry::STATUS_FEATURED, NovaCardChallengeEntry::STATUS_WINNER])
->count();
$winnerEntries = (int) (clone $recentEntriesQuery)
->where('status', NovaCardChallengeEntry::STATUS_WINNER)
->count();
return [
'summary' => [
'active_challenges' => (int) $activeChallenges->where('status', NovaCardChallenge::STATUS_ACTIVE)->count(),
'joined_challenges' => (int) $entryCounts->keys()->count(),
'entries_submitted' => $entryCount,
'featured_entries' => $featuredEntries,
'winner_entries' => $winnerEntries,
'cards_available' => $cardsAvailable,
],
'spotlight' => $spotlight,
'active_challenges' => $activeChallenges->all(),
'recent_entries' => $recentEntries,
'card_leaders' => $cardLeaders,
'reminders' => $this->reminders($cardsAvailable, $entryCount, $activeChallenges, $featuredEntries, $winnerEntries),
];
}
private function mapChallenge(NovaCardChallenge $challenge, Collection $entryCounts): array
{
return [
'id' => (int) $challenge->id,
'slug' => (string) $challenge->slug,
'title' => (string) $challenge->title,
'description' => $challenge->description,
'prompt' => $challenge->prompt,
'status' => (string) $challenge->status,
'official' => (bool) $challenge->official,
'featured' => (bool) $challenge->featured,
'entries_count' => (int) $challenge->entries_count,
'starts_at' => $challenge->starts_at?->toIso8601String(),
'ends_at' => $challenge->ends_at?->toIso8601String(),
'is_joined' => $entryCounts->has($challenge->id),
'submission_count' => (int) ($entryCounts->get($challenge->id) ?? 0),
'url' => route('cards.challenges.show', ['slug' => $challenge->slug]),
];
}
private function mapEntry(NovaCardChallengeEntry $entry): array
{
return [
'id' => (int) $entry->id,
'status' => (string) $entry->status,
'submitted_at' => $entry->created_at?->toIso8601String(),
'note' => $entry->note,
'challenge' => [
'id' => (int) $entry->challenge_id,
'title' => (string) ($entry->challenge?->title ?? 'Challenge'),
'status' => (string) ($entry->challenge?->status ?? ''),
'official' => (bool) ($entry->challenge?->official ?? false),
'url' => $entry->challenge ? route('cards.challenges.show', ['slug' => $entry->challenge->slug]) : route('cards.challenges'),
],
'card' => [
'id' => (int) $entry->card_id,
'title' => (string) ($entry->card?->title ?? 'Card'),
'preview_url' => $entry->card ? route('studio.cards.preview', ['id' => $entry->card->id]) : route('studio.cards.index'),
'edit_url' => $entry->card ? route('studio.cards.edit', ['id' => $entry->card->id]) : route('studio.cards.create'),
'analytics_url' => $entry->card ? route('studio.cards.analytics', ['id' => $entry->card->id]) : route('studio.cards.index'),
],
];
}
private function reminders(int $cardsAvailable, int $entryCount, Collection $activeChallenges, int $featuredEntries, int $winnerEntries): array
{
$items = [];
if ($cardsAvailable === 0) {
$items[] = [
'title' => 'Create a card to join challenges',
'body' => 'Challenge participation starts from published or ready-to-share cards inside Studio.',
'href' => route('studio.cards.create'),
'cta' => 'Create card',
];
}
if ($cardsAvailable > 0 && $entryCount === 0 && $activeChallenges->where('status', NovaCardChallenge::STATUS_ACTIVE)->isNotEmpty()) {
$items[] = [
'title' => 'You have active challenge windows open',
'body' => 'Submit an existing card to the current prompt lineup before the next window closes.',
'href' => route('studio.cards.index'),
'cta' => 'Open cards',
];
}
if ($featuredEntries > 0) {
$items[] = [
'title' => 'Featured challenge entries are live',
'body' => 'Review promoted submissions and keep those cards ready for profile, editorial, or follow-up pushes.',
'href' => route('studio.featured'),
'cta' => 'Manage featured',
];
}
if ($winnerEntries > 0) {
$items[] = [
'title' => 'Winning challenge work deserves a spotlight',
'body' => 'Use featured content and profile curation to extend the reach of cards that already placed well.',
'href' => route('studio.profile'),
'cta' => 'Open profile',
];
}
if ($activeChallenges->isNotEmpty()) {
$items[] = [
'title' => 'Public challenge archive stays one click away',
'body' => 'Use the public challenge directory to review prompts, reference past winners, and see how new runs are framed.',
'href' => route('cards.challenges'),
'cta' => 'Browse challenges',
];
}
return collect($items)->take(4)->values()->all();
}
}