user(); $artwork = Artwork::findOrFail($id); $this->authorize('award', [ArtworkAward::class, $artwork]); $data = $request->validate([ 'medal' => ['required', 'string', 'in:gold,silver,bronze'], ]); $this->service->award($artwork, $user, $data['medal']); // Record activity event try { \App\Models\ActivityEvent::record( actorId: $user->id, type: \App\Models\ActivityEvent::TYPE_AWARD, targetType: \App\Models\ActivityEvent::TARGET_ARTWORK, targetId: $artwork->id, meta: ['medal' => $data['medal']], ); } catch (\Throwable) {} return response()->json( $this->buildPayload($artwork->id, $user->id), 201 ); } public function upsert(Request $request, int $id): JsonResponse { $user = $request->user(); $artwork = Artwork::findOrFail($id); $this->authorize('award', [ArtworkAward::class, $artwork]); $data = $request->validate([ 'medal_type' => ['required', 'string', 'in:gold,silver,bronze'], ]); $existed = ArtworkMedal::query() ->where('artwork_id', $artwork->id) ->where('user_id', $user->id) ->exists(); $this->service->upsert($artwork, $user, $data['medal_type']); return response()->json( array_merge($this->buildPayload($artwork->id, $user->id), [ 'message' => $existed ? 'Medal updated.' : 'Medal added.', ]), $existed ? 200 : 201, ); } /** * PUT /api/artworks/{id}/award * Change an existing award medal. */ public function update(Request $request, int $id): JsonResponse { $user = $request->user(); $artwork = Artwork::findOrFail($id); $existingAward = ArtworkMedal::where('artwork_id', $artwork->id) ->where('user_id', $user->id) ->firstOrFail(); $this->authorize('change', $existingAward); $data = $request->validate([ 'medal' => ['required', 'string', 'in:gold,silver,bronze'], ]); $this->service->changeMedal($artwork, $user, $data['medal']); return response()->json($this->buildPayload($artwork->id, $user->id)); } /** * DELETE /api/artworks/{id}/award * Remove the user's award for this artwork. */ public function destroy(Request $request, int $id): JsonResponse { $user = $request->user(); $artwork = Artwork::findOrFail($id); $existingAward = ArtworkMedal::where('artwork_id', $artwork->id) ->where('user_id', $user->id) ->firstOrFail(); $this->authorize('remove', $existingAward); $this->service->removeMedal($artwork, $user); return response()->json($this->buildPayload($artwork->id, $user->id)); } public function destroyMedal(Request $request, int $id): JsonResponse { $user = $request->user(); $artwork = Artwork::findOrFail($id); $this->service->removeMedal($artwork, $user); return response()->json(array_merge($this->buildPayload($artwork->id, $user->id), [ 'message' => 'Medal removed.', ])); } /** * GET /api/artworks/{id}/awards * Return award stats + viewer's current award. */ public function show(Request $request, int $id): JsonResponse { $artwork = Artwork::findOrFail($id); return response()->json($this->buildPayload($artwork->id, $request->user()?->id)); } // ------------------------------------------------------------------------- // All authorization is delegated to ArtworkAwardPolicy via $this->authorize(). private function buildPayload(int $artworkId, ?int $userId): array { $stat = ArtworkMedalStat::find($artworkId); $userAward = $userId ? ArtworkMedal::where('artwork_id', $artworkId) ->where('user_id', $userId) ->value('medal_type') : null; $medals = [ 'gold' => (int) ($stat?->gold_count ?? 0), 'silver' => (int) ($stat?->silver_count ?? 0), 'bronze' => (int) ($stat?->bronze_count ?? 0), 'score' => (int) ($stat?->score_total ?? 0), 'score_7d' => (int) ($stat?->score_7d ?? 0), 'score_30d' => (int) ($stat?->score_30d ?? 0), 'last_medaled_at' => $stat?->last_medaled_at?->toIsoString(), ]; return [ 'awards' => $medals, 'medals' => $medals, 'viewer_award' => $userAward, 'current_user_medal' => $userAward, ]; } }