Build world campaigns rewards and recaps
This commit is contained in:
569
app/Services/Worlds/WorldRewardService.php
Normal file
569
app/Services/Worlds/WorldRewardService.php
Normal file
@@ -0,0 +1,569 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Worlds;
|
||||
|
||||
use App\Enums\WorldRewardType;
|
||||
use App\Models\Artwork;
|
||||
use App\Models\GroupChallenge;
|
||||
use App\Models\GroupChallengeOutcome;
|
||||
use App\Models\User;
|
||||
use App\Models\World;
|
||||
use App\Models\WorldRelation;
|
||||
use App\Models\WorldRewardGrant;
|
||||
use App\Models\WorldSubmission;
|
||||
use App\Notifications\WorldRewardGrantedNotification;
|
||||
use App\Services\Activity\UserActivityService;
|
||||
use App\Services\XPService;
|
||||
use App\Support\AvatarUrl;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
final class WorldRewardService
|
||||
{
|
||||
private const CHALLENGE_GRANT_SOURCE = 'challenge';
|
||||
|
||||
public function __construct(
|
||||
private readonly UserActivityService $activities,
|
||||
private readonly XPService $xp,
|
||||
private readonly WorldAnalyticsService $analytics,
|
||||
) {
|
||||
}
|
||||
|
||||
public function syncAutomaticRewardsForSubmission(WorldSubmission $submission): void
|
||||
{
|
||||
$submission->loadMissing(['world', 'artwork.user.profile']);
|
||||
|
||||
$world = $submission->world;
|
||||
$creator = $submission->artwork?->user;
|
||||
|
||||
if (! $world || ! $creator) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->syncAutomaticReward($world, $creator, WorldRewardType::Participant);
|
||||
$this->syncAutomaticReward($world, $creator, WorldRewardType::Featured);
|
||||
}
|
||||
|
||||
public function grantManualReward(WorldSubmission $submission, User $editor, WorldRewardType $rewardType, ?string $note = null): WorldRewardGrant
|
||||
{
|
||||
if ($rewardType->isAutomatic()) {
|
||||
throw new \InvalidArgumentException('Automatic world rewards cannot be granted manually.');
|
||||
}
|
||||
|
||||
if ((string) $submission->status !== WorldSubmission::STATUS_LIVE) {
|
||||
throw ValidationException::withMessages([
|
||||
'submission' => 'Only live world submissions can receive manual rewards.',
|
||||
]);
|
||||
}
|
||||
|
||||
$submission->loadMissing(['world', 'artwork.user.profile']);
|
||||
|
||||
$world = $submission->world;
|
||||
$artwork = $submission->artwork;
|
||||
$creator = $artwork?->user;
|
||||
|
||||
if (! $world || ! $artwork || ! $creator) {
|
||||
throw new \RuntimeException('Submission is missing world, artwork, or creator context.');
|
||||
}
|
||||
|
||||
$grant = WorldRewardGrant::query()->firstOrNew([
|
||||
'user_id' => (int) $creator->id,
|
||||
'world_id' => (int) $world->id,
|
||||
'reward_type' => $rewardType->value,
|
||||
]);
|
||||
|
||||
$wasRecentlyCreated = ! $grant->exists;
|
||||
|
||||
$grant->forceFill([
|
||||
'artwork_id' => (int) $artwork->id,
|
||||
'world_submission_id' => (int) $submission->id,
|
||||
'granted_by_user_id' => (int) $editor->id,
|
||||
'grant_source' => $rewardType->source(),
|
||||
'note' => $this->nullableText($note),
|
||||
'granted_at' => $grant->granted_at ?? now(),
|
||||
])->save();
|
||||
|
||||
$grant->loadMissing(['world', 'artwork', 'user.profile']);
|
||||
|
||||
if ($wasRecentlyCreated) {
|
||||
$this->dispatchGrantSideEffects($grant);
|
||||
}
|
||||
|
||||
return $grant;
|
||||
}
|
||||
|
||||
public function revokeManualReward(WorldSubmission $submission, WorldRewardType $rewardType): void
|
||||
{
|
||||
if ($rewardType->isAutomatic()) {
|
||||
throw new \InvalidArgumentException('Automatic world rewards are revoked through submission state changes.');
|
||||
}
|
||||
|
||||
$submission->loadMissing(['world', 'artwork.user']);
|
||||
|
||||
$world = $submission->world;
|
||||
$creator = $submission->artwork?->user;
|
||||
|
||||
if (! $world || ! $creator) {
|
||||
return;
|
||||
}
|
||||
|
||||
WorldRewardGrant::query()
|
||||
->where('user_id', (int) $creator->id)
|
||||
->where('world_id', (int) $world->id)
|
||||
->where('reward_type', $rewardType->value)
|
||||
->where('grant_source', $rewardType->source())
|
||||
->delete();
|
||||
|
||||
$this->activities->invalidateUserFeed((int) $creator->id);
|
||||
}
|
||||
|
||||
public function summaryForUser(User $user, int $limit = 12): array
|
||||
{
|
||||
$recentGrants = WorldRewardGrant::query()
|
||||
->with(['world', 'artwork', 'user.profile'])
|
||||
->where('user_id', (int) $user->id)
|
||||
->orderByDesc('granted_at')
|
||||
->orderByDesc('id')
|
||||
->get();
|
||||
|
||||
$grants = $recentGrants->sortBy([
|
||||
fn (WorldRewardGrant $grant): int => $this->sortPriority($grant->reward_type),
|
||||
fn (WorldRewardGrant $grant): int => -1 * ($grant->granted_at?->getTimestamp() ?? 0),
|
||||
fn (WorldRewardGrant $grant): int => -1 * (int) $grant->id,
|
||||
])->values();
|
||||
|
||||
return [
|
||||
'count' => $grants->count(),
|
||||
'counts' => [
|
||||
'participant' => $grants->where('reward_type', WorldRewardType::Participant)->count(),
|
||||
'featured' => $grants->where('reward_type', WorldRewardType::Featured)->count(),
|
||||
'finalist' => $grants->where('reward_type', WorldRewardType::Finalist)->count(),
|
||||
'winner' => $grants->where('reward_type', WorldRewardType::Winner)->count(),
|
||||
'spotlight' => $grants->where('reward_type', WorldRewardType::Spotlight)->count(),
|
||||
],
|
||||
'recent' => $recentGrants->take($limit)->map(fn (WorldRewardGrant $grant): array => $this->mapGrant($grant))->all(),
|
||||
'items' => $grants->map(fn (WorldRewardGrant $grant): array => $this->mapGrant($grant))->all(),
|
||||
];
|
||||
}
|
||||
|
||||
public function rewardedContributorsForWorld(World $world, int $limit = 24): array
|
||||
{
|
||||
$baseQuery = WorldRewardGrant::query()
|
||||
->where('world_id', (int) $world->id);
|
||||
|
||||
$allGrants = (clone $baseQuery)
|
||||
->get(['user_id', 'reward_type']);
|
||||
|
||||
$grants = (clone $baseQuery)
|
||||
->with(['user.profile', 'artwork'])
|
||||
->orderByRaw($this->sortCaseSql())
|
||||
->orderByDesc('granted_at')
|
||||
->orderByDesc('id')
|
||||
->limit($limit)
|
||||
->get();
|
||||
|
||||
return [
|
||||
'count' => $allGrants->count(),
|
||||
'creator_count' => $allGrants->pluck('user_id')->filter()->unique()->count(),
|
||||
'counts' => [
|
||||
'participant' => $allGrants->where('reward_type', WorldRewardType::Participant->value)->count(),
|
||||
'featured' => $allGrants->where('reward_type', WorldRewardType::Featured->value)->count(),
|
||||
'finalist' => $allGrants->where('reward_type', WorldRewardType::Finalist->value)->count(),
|
||||
'winner' => $allGrants->where('reward_type', WorldRewardType::Winner->value)->count(),
|
||||
'spotlight' => $allGrants->where('reward_type', WorldRewardType::Spotlight->value)->count(),
|
||||
],
|
||||
'items' => $grants->map(fn (WorldRewardGrant $grant): array => $this->mapGrant($grant))->all(),
|
||||
];
|
||||
}
|
||||
|
||||
public function syncLinkedChallengeRewardsForWorld(World $world): void
|
||||
{
|
||||
$world->loadMissing(['worldRelations', 'linkedChallenge.group', 'linkedChallenge.outcomes']);
|
||||
|
||||
if (! (bool) ($world->auto_grant_challenge_world_rewards ?? true)) {
|
||||
$this->deleteChallengeOutcomeGrantsForWorld($world);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$challengeIds = $world->worldRelations
|
||||
->where('related_type', WorldRelation::TYPE_CHALLENGE)
|
||||
->pluck('related_id')
|
||||
->map(fn ($id): int => (int) $id)
|
||||
->filter(fn (int $id): bool => $id > 0)
|
||||
->prepend((int) ($world->linked_challenge_id ?? 0))
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
if ($challengeIds->isEmpty()) {
|
||||
$this->deleteChallengeOutcomeGrantsForWorld($world);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$challenges = GroupChallenge::query()
|
||||
->with(['group', 'featuredArtwork.user.profile', 'outcomes.artwork.user.profile'])
|
||||
->whereIn('id', $challengeIds->all())
|
||||
->get()
|
||||
->filter(fn (GroupChallenge $challenge): bool => $this->challengeCanGrantWorldOutcomeReward($challenge))
|
||||
->values();
|
||||
|
||||
$this->syncChallengeOutcomeRewardTypeForWorld($world, $challenges, WorldRewardType::Winner);
|
||||
$this->syncChallengeOutcomeRewardTypeForWorld($world, $challenges, WorldRewardType::Finalist);
|
||||
}
|
||||
|
||||
private function syncChallengeOutcomeRewardTypeForWorld(World $world, Collection $challenges, WorldRewardType $rewardType): void
|
||||
{
|
||||
$artworkIds = $challenges
|
||||
->flatMap(fn (GroupChallenge $challenge): array => $this->challengeOutcomeArtworkIds($challenge, $rewardType)->all())
|
||||
->unique()
|
||||
->values();
|
||||
|
||||
$submissionsByArtwork = $artworkIds->isEmpty()
|
||||
? collect()
|
||||
: WorldSubmission::query()
|
||||
->with(['artwork.user.profile'])
|
||||
->where('world_id', (int) $world->id)
|
||||
->where('status', WorldSubmission::STATUS_LIVE)
|
||||
->whereIn('artwork_id', $artworkIds->all())
|
||||
->get()
|
||||
->keyBy(fn (WorldSubmission $submission): int => (int) $submission->artwork_id);
|
||||
|
||||
$expected = collect();
|
||||
|
||||
foreach ($challenges as $challenge) {
|
||||
foreach ($this->challengeOutcomeArtworkIds($challenge, $rewardType) as $artworkId) {
|
||||
$submission = $submissionsByArtwork->get((int) $artworkId);
|
||||
$creator = $submission?->artwork?->user;
|
||||
|
||||
if (! $submission || ! $creator || $expected->has((int) $creator->id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$expected->put((int) $creator->id, [
|
||||
'submission' => $submission,
|
||||
'challenge' => $challenge,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$existing = WorldRewardGrant::query()
|
||||
->where('world_id', (int) $world->id)
|
||||
->where('reward_type', $rewardType->value)
|
||||
->get()
|
||||
->keyBy(fn (WorldRewardGrant $grant): int => (int) $grant->user_id);
|
||||
|
||||
foreach ($expected as $userId => $payload) {
|
||||
/** @var WorldSubmission $submission */
|
||||
$submission = $payload['submission'];
|
||||
/** @var GroupChallenge $challenge */
|
||||
$challenge = $payload['challenge'];
|
||||
$current = $existing->get((int) $userId);
|
||||
|
||||
if ($current && (string) $current->grant_source !== self::CHALLENGE_GRANT_SOURCE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$grant = $current ?? new WorldRewardGrant();
|
||||
$wasRecentlyCreated = ! $grant->exists;
|
||||
|
||||
$grant->forceFill([
|
||||
'user_id' => (int) $userId,
|
||||
'world_id' => (int) $world->id,
|
||||
'artwork_id' => (int) $submission->artwork_id,
|
||||
'world_submission_id' => (int) $submission->id,
|
||||
'granted_by_user_id' => null,
|
||||
'reward_type' => $rewardType->value,
|
||||
'grant_source' => self::CHALLENGE_GRANT_SOURCE,
|
||||
'note' => sprintf('Synced from linked challenge %s: %s.', $rewardType->label(), $challenge->title),
|
||||
'granted_at' => $grant->granted_at ?? now(),
|
||||
])->save();
|
||||
|
||||
$grant->loadMissing(['world', 'artwork', 'user.profile']);
|
||||
|
||||
if ($wasRecentlyCreated) {
|
||||
$this->dispatchGrantSideEffects($grant);
|
||||
}
|
||||
}
|
||||
|
||||
$expectedUserIds = $expected->keys()->map(fn ($id): int => (int) $id)->all();
|
||||
|
||||
$existing
|
||||
->filter(fn (WorldRewardGrant $grant): bool => (string) $grant->grant_source === self::CHALLENGE_GRANT_SOURCE)
|
||||
->reject(fn (WorldRewardGrant $grant): bool => in_array((int) $grant->user_id, $expectedUserIds, true))
|
||||
->each(function (WorldRewardGrant $grant): void {
|
||||
$grant->delete();
|
||||
$this->activities->invalidateUserFeed((int) $grant->user_id);
|
||||
});
|
||||
}
|
||||
|
||||
public function syncLinkedChallengeRewardsForChallenge(GroupChallenge $challenge): void
|
||||
{
|
||||
$worldIds = WorldRelation::query()
|
||||
->where('related_type', WorldRelation::TYPE_CHALLENGE)
|
||||
->where('related_id', (int) $challenge->id)
|
||||
->pluck('world_id')
|
||||
->map(fn ($id): int => (int) $id)
|
||||
->filter(fn (int $id): bool => $id > 0)
|
||||
->merge(
|
||||
World::query()
|
||||
->where('linked_challenge_id', (int) $challenge->id)
|
||||
->pluck('id')
|
||||
->map(fn ($id): int => (int) $id)
|
||||
)
|
||||
->unique()
|
||||
->all();
|
||||
|
||||
if ($worldIds === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
World::query()
|
||||
->with('worldRelations')
|
||||
->whereIn('id', $worldIds)
|
||||
->get()
|
||||
->each(fn (World $world): bool => tap(true, fn () => $this->syncLinkedChallengeRewardsForWorld($world)));
|
||||
}
|
||||
|
||||
public function creatorRewardMapForWorld(World $world): Collection
|
||||
{
|
||||
return WorldRewardGrant::query()
|
||||
->with(['artwork'])
|
||||
->where('world_id', (int) $world->id)
|
||||
->orderByRaw($this->sortCaseSql())
|
||||
->orderByDesc('granted_at')
|
||||
->get()
|
||||
->groupBy('user_id')
|
||||
->map(fn (Collection $items): array => $items->map(fn (WorldRewardGrant $grant): array => $this->mapGrant($grant, false))->all());
|
||||
}
|
||||
|
||||
public function artworkRewardBadges(Artwork $artwork): array
|
||||
{
|
||||
return WorldRewardGrant::query()
|
||||
->with('world')
|
||||
->where('artwork_id', (int) $artwork->id)
|
||||
->orderByRaw($this->sortCaseSql())
|
||||
->orderByDesc('granted_at')
|
||||
->get()
|
||||
->map(function (WorldRewardGrant $grant): array {
|
||||
$world = $grant->world;
|
||||
$rewardType = $grant->reward_type;
|
||||
|
||||
return [
|
||||
'world_id' => (int) ($world?->id ?? 0),
|
||||
'world_title' => (string) ($world?->title ?? 'World'),
|
||||
'world_slug' => (string) ($world?->slug ?? ''),
|
||||
'world_url' => $world?->publicUrl(),
|
||||
'badge_label' => $this->worldRewardLabel($world, $rewardType),
|
||||
'status' => $rewardType->value,
|
||||
'status_label' => $rewardType->label(),
|
||||
'tone' => $rewardType->tone(),
|
||||
'sort_priority' => $this->sortPriority($rewardType),
|
||||
];
|
||||
})
|
||||
->all();
|
||||
}
|
||||
|
||||
private function syncAutomaticReward(World $world, User $creator, WorldRewardType $rewardType): void
|
||||
{
|
||||
$qualifyingSubmission = $this->qualifyingSubmission($world, $creator, $rewardType);
|
||||
|
||||
$existing = WorldRewardGrant::query()
|
||||
->where('user_id', (int) $creator->id)
|
||||
->where('world_id', (int) $world->id)
|
||||
->where('reward_type', $rewardType->value)
|
||||
->first();
|
||||
|
||||
if (! $qualifyingSubmission) {
|
||||
if ($rewardType === WorldRewardType::Participant) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($existing && (string) $existing->grant_source === $rewardType->source()) {
|
||||
$existing->delete();
|
||||
$this->activities->invalidateUserFeed((int) $creator->id);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($existing) {
|
||||
$existing->forceFill([
|
||||
'artwork_id' => (int) $qualifyingSubmission->artwork_id,
|
||||
'world_submission_id' => (int) $qualifyingSubmission->id,
|
||||
'grant_source' => $rewardType->source(),
|
||||
])->save();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$grant = WorldRewardGrant::query()->create([
|
||||
'user_id' => (int) $creator->id,
|
||||
'world_id' => (int) $world->id,
|
||||
'artwork_id' => (int) $qualifyingSubmission->artwork_id,
|
||||
'world_submission_id' => (int) $qualifyingSubmission->id,
|
||||
'reward_type' => $rewardType->value,
|
||||
'grant_source' => $rewardType->source(),
|
||||
'granted_at' => now(),
|
||||
]);
|
||||
|
||||
$grant->loadMissing(['world', 'artwork', 'user.profile']);
|
||||
|
||||
$this->dispatchGrantSideEffects($grant);
|
||||
}
|
||||
|
||||
private function qualifyingSubmission(World $world, User $creator, WorldRewardType $rewardType): ?WorldSubmission
|
||||
{
|
||||
$query = WorldSubmission::query()
|
||||
->with(['world', 'artwork.user.profile'])
|
||||
->where('world_id', (int) $world->id)
|
||||
->where('status', WorldSubmission::STATUS_LIVE)
|
||||
->whereHas('artwork', fn (Builder $builder) => $builder->where('user_id', (int) $creator->id));
|
||||
|
||||
if ($rewardType === WorldRewardType::Featured) {
|
||||
$query->where('is_featured', true)->orderByDesc('featured_at');
|
||||
} else {
|
||||
$query->orderByDesc('reviewed_at');
|
||||
}
|
||||
|
||||
return $query->orderByDesc('id')->first();
|
||||
}
|
||||
|
||||
private function dispatchGrantSideEffects(WorldRewardGrant $grant): void
|
||||
{
|
||||
$this->analytics->recordRewardGrant($grant);
|
||||
$grant->user?->notify(new WorldRewardGrantedNotification($grant));
|
||||
$this->activities->logWorldReward((int) $grant->user_id, (int) $grant->id, [
|
||||
'reward_type' => $grant->reward_type->value,
|
||||
'world_id' => (int) $grant->world_id,
|
||||
]);
|
||||
$this->xp->awardWorldReward((int) $grant->user_id, $grant->reward_type, (int) $grant->world_id);
|
||||
}
|
||||
|
||||
private function mapGrant(WorldRewardGrant $grant, bool $includeCreator = true): array
|
||||
{
|
||||
$grant->loadMissing(['world', 'artwork', 'user.profile']);
|
||||
|
||||
$payload = [
|
||||
'id' => (int) $grant->id,
|
||||
'reward_type' => $grant->reward_type->value,
|
||||
'reward_label' => $grant->reward_type->label(),
|
||||
'badge_label' => $this->worldRewardLabel($grant->world, $grant->reward_type),
|
||||
'tone' => $grant->reward_type->tone(),
|
||||
'grant_source' => (string) $grant->grant_source,
|
||||
'note' => (string) ($grant->note ?? ''),
|
||||
'granted_at' => $grant->granted_at?->toIso8601String(),
|
||||
'world' => $grant->world ? [
|
||||
'id' => (int) $grant->world->id,
|
||||
'title' => (string) $grant->world->title,
|
||||
'slug' => (string) $grant->world->slug,
|
||||
'url' => $grant->world->publicUrl(),
|
||||
'edition_year' => $grant->world->edition_year,
|
||||
] : null,
|
||||
'artwork' => $grant->artwork ? [
|
||||
'id' => (int) $grant->artwork->id,
|
||||
'title' => (string) ($grant->artwork->title ?: 'Untitled artwork'),
|
||||
'url' => route('art.show', ['id' => (int) $grant->artwork->id, 'slug' => $grant->artwork->slug ?: Str::slug((string) $grant->artwork->title)]),
|
||||
] : null,
|
||||
];
|
||||
|
||||
if (! $includeCreator) {
|
||||
return $payload;
|
||||
}
|
||||
|
||||
return [
|
||||
...$payload,
|
||||
'creator' => $grant->user ? [
|
||||
'id' => (int) $grant->user->id,
|
||||
'name' => (string) ($grant->user->name ?: $grant->user->username ?: 'Creator'),
|
||||
'username' => (string) ($grant->user->username ?? ''),
|
||||
'profile_url' => $grant->user->username ? route('profile.show', ['username' => strtolower((string) $grant->user->username)]) : null,
|
||||
'avatar_url' => AvatarUrl::forUser((int) $grant->user->id, $grant->user->profile?->avatar_hash, 96),
|
||||
] : null,
|
||||
];
|
||||
}
|
||||
|
||||
private function worldRewardLabel(?World $world, WorldRewardType $rewardType): string
|
||||
{
|
||||
return trim(($world?->title ?? 'World') . ' ' . $rewardType->label());
|
||||
}
|
||||
|
||||
private function sortCaseSql(): string
|
||||
{
|
||||
return "CASE reward_type WHEN 'winner' THEN 0 WHEN 'finalist' THEN 1 WHEN 'spotlight' THEN 2 WHEN 'featured' THEN 3 ELSE 4 END";
|
||||
}
|
||||
|
||||
private function challengeCanGrantWorldOutcomeReward(GroupChallenge $challenge): bool
|
||||
{
|
||||
if ((string) $challenge->status === GroupChallenge::STATUS_DRAFT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $challenge->canBeViewedBy(null);
|
||||
}
|
||||
|
||||
private function challengeOutcomeArtworkIds(GroupChallenge $challenge, WorldRewardType $rewardType): Collection
|
||||
{
|
||||
$challenge->loadMissing('outcomes');
|
||||
|
||||
$type = match ($rewardType) {
|
||||
WorldRewardType::Winner => GroupChallengeOutcome::TYPE_WINNER,
|
||||
WorldRewardType::Finalist => GroupChallengeOutcome::TYPE_FINALIST,
|
||||
default => null,
|
||||
};
|
||||
|
||||
if ($type === null) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$ids = $challenge->outcomes
|
||||
->where('outcome_type', $type)
|
||||
->pluck('artwork_id')
|
||||
->map(fn ($id): int => (int) $id)
|
||||
->filter(fn (int $id): bool => $id > 0)
|
||||
->values();
|
||||
|
||||
if ($rewardType === WorldRewardType::Winner && $ids->isEmpty() && (int) ($challenge->featured_artwork_id ?? 0) > 0) {
|
||||
return collect([(int) $challenge->featured_artwork_id]);
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
private function deleteChallengeOutcomeGrantsForWorld(World $world, ?WorldRewardType $rewardType = null): void
|
||||
{
|
||||
$query = WorldRewardGrant::query()
|
||||
->where('world_id', (int) $world->id)
|
||||
->where('grant_source', self::CHALLENGE_GRANT_SOURCE)
|
||||
->when($rewardType !== null, fn ($builder) => $builder->where('reward_type', $rewardType->value));
|
||||
|
||||
$query
|
||||
->get()
|
||||
->each(function (WorldRewardGrant $grant): void {
|
||||
$grant->delete();
|
||||
$this->activities->invalidateUserFeed((int) $grant->user_id);
|
||||
});
|
||||
}
|
||||
|
||||
private function sortPriority(WorldRewardType $rewardType): int
|
||||
{
|
||||
return match ($rewardType) {
|
||||
WorldRewardType::Winner => 0,
|
||||
WorldRewardType::Finalist => 1,
|
||||
WorldRewardType::Spotlight => 2,
|
||||
WorldRewardType::Featured => 3,
|
||||
WorldRewardType::Participant => 4,
|
||||
};
|
||||
}
|
||||
|
||||
private function nullableText(?string $value): ?string
|
||||
{
|
||||
$trimmed = trim((string) $value);
|
||||
|
||||
return $trimmed !== '' ? $trimmed : null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user