listReactions('artwork', $artworkId, $request->user()?->id); } public function toggleArtworkReaction(Request $request, int $artworkId): JsonResponse { $this->validateExists('artworks', $artworkId); $slug = $this->validateReactionSlug($request); return $this->toggle( model: new ArtworkReaction(), where: ['artwork_id' => $artworkId, 'user_id' => $request->user()->id, 'reaction' => $slug], countWhere: ['artwork_id' => $artworkId], entityId: $artworkId, entityType: 'artwork', userId: $request->user()->id, slug: $slug, ); } // ───────────────────────────────────────────────────────────────────────── // Comment reactions // ───────────────────────────────────────────────────────────────────────── public function commentReactions(Request $request, int $commentId): JsonResponse { return $this->listReactions('comment', $commentId, $request->user()?->id); } public function toggleCommentReaction(Request $request, int $commentId): JsonResponse { // Make sure comment exists and belongs to a public artwork $comment = ArtworkComment::with('artwork') ->where('id', $commentId) ->whereHas('artwork', fn ($q) => $q->public()->published()) ->firstOrFail(); $slug = $this->validateReactionSlug($request); return $this->toggle( model: new CommentReaction(), where: ['comment_id' => $commentId, 'user_id' => $request->user()->id, 'reaction' => $slug], countWhere: ['comment_id' => $commentId], entityId: $commentId, entityType: 'comment', userId: $request->user()->id, slug: $slug, ); } // ───────────────────────────────────────────────────────────────────────── // Shared internals // ───────────────────────────────────────────────────────────────────────── private function toggle( \Illuminate\Database\Eloquent\Model $model, array $where, array $countWhere, int $entityId, string $entityType, int $userId, string $slug, ): JsonResponse { $table = $model->getTable(); $existing = DB::table($table)->where($where)->first(); if ($existing) { // Toggle off DB::table($table)->where($where)->delete(); $active = false; } else { // Toggle on DB::table($table)->insertOrIgnore(array_merge($where, [ 'created_at' => now(), ])); $active = true; } // Return fresh totals per reaction type $totals = $this->getTotals($table, $countWhere, $userId); return response()->json([ 'entity_type' => $entityType, 'entity_id' => $entityId, 'reaction' => $slug, 'active' => $active, 'totals' => $totals, ]); } private function listReactions(string $entityType, int $entityId, ?int $userId): JsonResponse { if ($entityType === 'artwork') { $table = 'artwork_reactions'; $where = ['artwork_id' => $entityId]; } else { $table = 'comment_reactions'; $where = ['comment_id' => $entityId]; } $totals = $this->getTotals($table, $where, $userId); return response()->json([ 'entity_type' => $entityType, 'entity_id' => $entityId, 'totals' => $totals, ]); } /** * Return per-slug totals and whether the current user has each reaction. */ private function getTotals(string $table, array $where, ?int $userId): array { $rows = DB::table($table) ->where($where) ->selectRaw('reaction, COUNT(*) as total') ->groupBy('reaction') ->get() ->keyBy('reaction'); $totals = []; foreach (ReactionType::cases() as $type) { $slug = $type->value; $count = (int) ($rows[$slug]->total ?? 0); // Check if current user has this reaction $mine = false; if ($userId && $count > 0) { $mine = DB::table($table) ->where($where) ->where('reaction', $slug) ->where('user_id', $userId) ->exists(); } $totals[$slug] = [ 'emoji' => $type->emoji(), 'label' => $type->label(), 'count' => $count, 'mine' => $mine, ]; } return $totals; } private function validateReactionSlug(Request $request): string { $request->validate([ 'reaction' => ['required', 'string', 'in:' . implode(',', ReactionType::values())], ]); return $request->input('reaction'); } private function validateExists(string $table, int $id): void { if (! DB::table($table)->where('id', $id)->exists()) { throw new ModelNotFoundException("No [{$table}] record found with id [{$id}]."); } } }