feat: ship creator journey v2 and profile updates
This commit is contained in:
176
app/Services/ArtworkMedalService.php
Normal file
176
app/Services/ArtworkMedalService.php
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Jobs\IndexArtworkJob;
|
||||
use App\Models\Artwork;
|
||||
use App\Models\ArtworkAward;
|
||||
use App\Models\ArtworkAwardStat;
|
||||
use App\Models\ArtworkMedal;
|
||||
use App\Models\ArtworkMedalStat;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
final class ArtworkMedalService
|
||||
{
|
||||
public function __construct(private readonly HomepageService $homepage)
|
||||
{
|
||||
}
|
||||
|
||||
public function upsert(Artwork $artwork, User $user, string $medal): ArtworkMedal
|
||||
{
|
||||
$existing = ArtworkMedal::query()
|
||||
->where('artwork_id', $artwork->id)
|
||||
->where('user_id', $user->id)
|
||||
->first();
|
||||
|
||||
return $existing
|
||||
? $this->changeMedal($artwork, $user, $medal)
|
||||
: $this->award($artwork, $user, $medal);
|
||||
}
|
||||
|
||||
public function award(Artwork $artwork, User $user, string $medal): ArtworkMedal
|
||||
{
|
||||
$this->validateMedal($medal);
|
||||
|
||||
$exists = ArtworkMedal::query()
|
||||
->where('artwork_id', $artwork->id)
|
||||
->where('user_id', $user->id)
|
||||
->exists();
|
||||
|
||||
if ($exists) {
|
||||
throw ValidationException::withMessages([
|
||||
'medal' => 'You have already awarded this artwork. Use change to update.',
|
||||
]);
|
||||
}
|
||||
|
||||
return ArtworkMedal::query()->create([
|
||||
'artwork_id' => $artwork->id,
|
||||
'user_id' => $user->id,
|
||||
'medal_type' => $medal,
|
||||
'weight' => ArtworkAward::weightFor($medal),
|
||||
]);
|
||||
}
|
||||
|
||||
public function changeMedal(Artwork $artwork, User $user, string $medal): ArtworkMedal
|
||||
{
|
||||
$this->validateMedal($medal);
|
||||
|
||||
$award = ArtworkMedal::query()
|
||||
->where('artwork_id', $artwork->id)
|
||||
->where('user_id', $user->id)
|
||||
->first();
|
||||
|
||||
if (! $award) {
|
||||
throw ValidationException::withMessages([
|
||||
'medal' => 'No existing medal found for this artwork.',
|
||||
]);
|
||||
}
|
||||
|
||||
$award->update([
|
||||
'medal_type' => $medal,
|
||||
'weight' => ArtworkAward::weightFor($medal),
|
||||
]);
|
||||
|
||||
return $award->fresh();
|
||||
}
|
||||
|
||||
public function removeMedal(Artwork $artwork, User $user): void
|
||||
{
|
||||
$award = ArtworkMedal::query()
|
||||
->where('artwork_id', $artwork->id)
|
||||
->where('user_id', $user->id)
|
||||
->first();
|
||||
|
||||
if ($award) {
|
||||
$award->delete();
|
||||
}
|
||||
}
|
||||
|
||||
public function recalculateStats(int $artworkId): ArtworkMedalStat
|
||||
{
|
||||
$rows = ArtworkMedal::query()
|
||||
->where('artwork_id', $artworkId)
|
||||
->get(['medal_type', 'weight', 'updated_at']);
|
||||
|
||||
$cutoff7d = now()->subDays(7);
|
||||
$cutoff30d = now()->subDays(30);
|
||||
|
||||
$goldCount = 0;
|
||||
$silverCount = 0;
|
||||
$bronzeCount = 0;
|
||||
$scoreTotal = 0;
|
||||
$score7d = 0;
|
||||
$score30d = 0;
|
||||
$lastMedaledAt = null;
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$medal = (string) $row->medal;
|
||||
$weight = (int) ($row->weight ?? ArtworkAward::weightFor($medal));
|
||||
$updatedAt = $row->updated_at instanceof Carbon ? $row->updated_at : Carbon::parse($row->updated_at);
|
||||
|
||||
if ($medal === 'gold') {
|
||||
$goldCount++;
|
||||
} elseif ($medal === 'silver') {
|
||||
$silverCount++;
|
||||
} elseif ($medal === 'bronze') {
|
||||
$bronzeCount++;
|
||||
}
|
||||
|
||||
$scoreTotal += $weight;
|
||||
|
||||
if ($updatedAt->greaterThanOrEqualTo($cutoff7d)) {
|
||||
$score7d += $weight;
|
||||
}
|
||||
|
||||
if ($updatedAt->greaterThanOrEqualTo($cutoff30d)) {
|
||||
$score30d += $weight;
|
||||
}
|
||||
|
||||
if ($lastMedaledAt === null || $updatedAt->greaterThan($lastMedaledAt)) {
|
||||
$lastMedaledAt = $updatedAt;
|
||||
}
|
||||
}
|
||||
|
||||
$stat = ArtworkAwardStat::query()->updateOrCreate(
|
||||
['artwork_id' => $artworkId],
|
||||
[
|
||||
'gold_count' => $goldCount,
|
||||
'silver_count' => $silverCount,
|
||||
'bronze_count' => $bronzeCount,
|
||||
'score_total' => $scoreTotal,
|
||||
'score_7d' => $score7d,
|
||||
'score_30d' => $score30d,
|
||||
'last_medaled_at' => $lastMedaledAt,
|
||||
]
|
||||
);
|
||||
|
||||
return ArtworkMedalStat::query()->findOrFail($stat->artwork_id);
|
||||
}
|
||||
|
||||
public function refreshArtworkMedalState(int $artworkId): ArtworkMedalStat
|
||||
{
|
||||
$stat = $this->recalculateStats($artworkId);
|
||||
$this->syncArtworkToSearch($artworkId);
|
||||
$this->homepage->clearFeaturedAndMedalCaches();
|
||||
|
||||
return $stat;
|
||||
}
|
||||
|
||||
public function syncArtworkToSearch(int $artworkId): void
|
||||
{
|
||||
IndexArtworkJob::dispatch($artworkId);
|
||||
}
|
||||
|
||||
private function validateMedal(string $medal): void
|
||||
{
|
||||
if (! in_array($medal, ArtworkAward::MEDALS, true)) {
|
||||
throw ValidationException::withMessages([
|
||||
'medal' => 'Invalid medal. Must be gold, silver, or bronze.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user