193 lines
7.1 KiB
PHP
193 lines
7.1 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Enums\ReactionType;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\ArtworkComment;
|
|
use App\Models\ArtworkReaction;
|
|
use App\Models\CommentReaction;
|
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
/**
|
|
* Handles reaction toggling for artworks and comments.
|
|
*
|
|
* POST /api/artworks/{id}/reactions → toggle artwork reaction
|
|
* POST /api/comments/{id}/reactions → toggle comment reaction
|
|
* GET /api/artworks/{id}/reactions → list artwork reactions
|
|
* GET /api/comments/{id}/reactions → list comment reactions
|
|
*/
|
|
class ReactionController extends Controller
|
|
{
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// Artwork reactions
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
public function artworkReactions(Request $request, int $artworkId): JsonResponse
|
|
{
|
|
return $this->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}].");
|
|
}
|
|
}
|
|
}
|