106 lines
3.3 KiB
PHP
106 lines
3.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api\Messaging;
|
|
|
|
use App\Events\TypingStarted;
|
|
use App\Events\TypingStopped;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Conversation;
|
|
use App\Models\ConversationParticipant;
|
|
use Illuminate\Cache\Repository;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
class TypingController extends Controller
|
|
{
|
|
public function start(Request $request, int $conversationId): JsonResponse
|
|
{
|
|
$this->findConversationOrFail($conversationId);
|
|
|
|
$ttl = max(5, (int) config('messaging.typing.ttl_seconds', 8));
|
|
$this->store()->put($this->key($conversationId, (int) $request->user()->id), 1, now()->addSeconds($ttl));
|
|
|
|
if ((bool) config('messaging.realtime', false)) {
|
|
event(new TypingStarted($conversationId, $request->user()));
|
|
}
|
|
|
|
return response()->json(['ok' => true]);
|
|
}
|
|
|
|
public function stop(Request $request, int $conversationId): JsonResponse
|
|
{
|
|
$this->findConversationOrFail($conversationId);
|
|
$this->store()->forget($this->key($conversationId, (int) $request->user()->id));
|
|
|
|
if ((bool) config('messaging.realtime', false)) {
|
|
event(new TypingStopped($conversationId, $request->user()));
|
|
}
|
|
|
|
return response()->json(['ok' => true]);
|
|
}
|
|
|
|
public function index(Request $request, int $conversationId): JsonResponse
|
|
{
|
|
$this->findConversationOrFail($conversationId);
|
|
$userId = (int) $request->user()->id;
|
|
|
|
$participants = ConversationParticipant::query()
|
|
->where('conversation_id', $conversationId)
|
|
->whereNull('left_at')
|
|
->where('user_id', '!=', $userId)
|
|
->with('user:id,username')
|
|
->get();
|
|
|
|
$typing = $participants
|
|
->filter(fn ($p) => $this->store()->has($this->key($conversationId, (int) $p->user_id)))
|
|
->map(fn ($p) => [
|
|
'user_id' => (int) $p->user_id,
|
|
'username' => (string) ($p->user->username ?? ''),
|
|
])
|
|
->values();
|
|
|
|
return response()->json(['typing' => $typing]);
|
|
}
|
|
|
|
private function assertParticipant(Request $request, int $conversationId): void
|
|
{
|
|
abort_unless(
|
|
ConversationParticipant::query()
|
|
->where('conversation_id', $conversationId)
|
|
->where('user_id', $request->user()->id)
|
|
->whereNull('left_at')
|
|
->exists(),
|
|
403,
|
|
'You are not a participant of this conversation.'
|
|
);
|
|
}
|
|
|
|
private function key(int $conversationId, int $userId): string
|
|
{
|
|
return "typing:{$conversationId}:{$userId}";
|
|
}
|
|
|
|
private function store(): Repository
|
|
{
|
|
$store = (string) config('messaging.typing.cache_store', 'redis');
|
|
if ($store === 'redis' && ! class_exists('Redis')) {
|
|
return Cache::store();
|
|
}
|
|
|
|
try {
|
|
return Cache::store($store);
|
|
} catch (\Throwable) {
|
|
return Cache::store();
|
|
}
|
|
}
|
|
|
|
private function findConversationOrFail(int $conversationId): Conversation
|
|
{
|
|
$conversation = Conversation::query()->findOrFail($conversationId);
|
|
$this->authorize('view', $conversation);
|
|
|
|
return $conversation;
|
|
}
|
|
}
|