conversations() ->with(['participants', 'lastMessage.user']) ->orderByDesc('conversations.updated_at') ->get() ->map(function ($conv) use ($user) { $other = $conv->participants->firstWhere('id', '!=', $user->id); return [ 'id' => $conv->id, 'other' => $other, 'last_message' => $conv->lastMessage, 'unread' => $conv->unreadCountFor($user->id), 'updated_at' => $conv->updated_at, ]; }); } catch (\Throwable $e) { $conversations = collect(); } return view('frontend.messages.index', compact('conversations')); } public function show(Conversation $conversation) { $user = Auth::user(); abort_unless( $conversation->participants()->where('user_id', $user->id)->exists(), 403 ); $other = $conversation->participants()->where('user_id', '!=', $user->id)->first(); $messages = $conversation->messages() ->with('user') ->orderBy('created_at') ->get(); // Mark as read $conversation->participants() ->updateExistingPivot($user->id, ['last_read_at' => now()]); return view('frontend.messages.show', compact('conversation', 'messages', 'other')); } public function startOrOpen(User $user) { $me = Auth::user(); if ($me->id === $user->id) abort(422); // Find existing conversation between these two users $conv = Conversation::whereHas('participants', fn($q) => $q->where('user_id', $me->id)) ->whereHas('participants', fn($q) => $q->where('user_id', $user->id)) ->first(); if (!$conv) { $conv = DB::transaction(function () use ($me, $user) { $c = Conversation::create(); $c->participants()->attach([$me->id, $user->id]); return $c; }); } return redirect()->route('messages.show', $conv); } public function send(Request $request, Conversation $conversation) { $user = Auth::user(); abort_unless( $conversation->participants()->where('user_id', $user->id)->exists(), 403 ); $request->validate(['body' => 'required|string|max:5000']); $message = Message::create([ 'conversation_id' => $conversation->id, 'user_id' => $user->id, 'body' => $request->body, ]); $conversation->touch(); // Mark sender as read $conversation->participants() ->updateExistingPivot($user->id, ['last_read_at' => now()]); if ($request->expectsJson()) { return response()->json([ 'id' => $message->id, 'body' => $message->body, 'user_id' => $user->id, 'created_at' => $message->created_at->format('H:i'), 'avatar' => $user->avatar ? \App\Support\MediaUrl::fromStoragePath($user->avatar) : null, 'name' => $user->name, ]); } return back(); } public function poll(Request $request, Conversation $conversation) { $user = Auth::user(); abort_unless( $conversation->participants()->where('user_id', $user->id)->exists(), 403 ); $after = $request->query('after', 0); $messages = $conversation->messages() ->with('user') ->where('id', '>', $after) ->orderBy('created_at') ->get() ->map(fn($m) => [ 'id' => $m->id, 'body' => $m->body, 'user_id' => $m->user_id, 'created_at' => $m->created_at->format('H:i'), 'avatar' => $m->user->avatar ? \App\Support\MediaUrl::fromStoragePath($m->user->avatar) : null, 'name' => $m->user->name, ]); // Update last_read $conversation->participants() ->updateExistingPivot($user->id, ['last_read_at' => now()]); return response()->json(['messages' => $messages]); } public function unreadCount() { $user = Auth::user(); if (!$user) return response()->json(['count' => 0]); $count = 0; foreach ($user->conversations()->with(['messages'])->get() as $conv) { $count += $conv->unreadCountFor($user->id); } return response()->json(['count' => $count]); } public function conversationsJson() { $user = Auth::user(); $convs = $user->conversations() ->with(['participants', 'lastMessage.user']) ->orderByDesc('conversations.updated_at') ->limit(30) ->get() ->map(function ($conv) use ($user) { $other = $conv->participants->firstWhere('id', '!=', $user->id); $last = $conv->lastMessage; $unread = $conv->unreadCountFor($user->id); $preview = null; if ($last) { if (str_starts_with($last->body, 'ANIMESHARE::')) { try { $sd = json_decode(substr($last->body, 12), true); $preview = '🎬 ' . ($sd['title'] ?? 'Anime paylaştı'); } catch(\Throwable) {} } elseif (str_starts_with($last->body, 'IMAGE::')) { $preview = ($last->user_id === $user->id ? 'Sen: ' : '') . '📷 Fotoğraf'; } elseif (str_starts_with($last->body, 'GIF::')) { $preview = ($last->user_id === $user->id ? 'Sen: ' : '') . '🎞 GIF'; } else { $isMine = $last->user_id === $user->id; $preview = ($isMine ? 'Sen: ' : '') . \Illuminate\Support\Str::limit($last->body, 50); } } return [ 'conv_id' => $conv->id, 'id' => $other?->id, 'name' => $other?->name ?? 'Silinmiş', 'avatar' => $other?->avatar ? \App\Support\MediaUrl::fromStoragePath($other->avatar) : null, 'last_preview' => $preview, 'unread' => $unread, 'time' => $conv->updated_at ? $conv->updated_at->diffForHumans(null, true) : null, ]; }); return response()->json($convs); } public function uploadImage(Request $request) { $request->validate([ 'image' => 'required|file|image|max:8192|mimes:jpeg,jpg,png,gif,webp', ]); $path = $request->file('image')->store('chat-images', 'public'); $url = Storage::disk('public')->url($path); return response()->json(['url' => $url]); } public function quickShare(Request $request) { $request->validate([ 'to_user_id' => 'required|integer|exists:users,id', 'body' => 'required|string|max:3000', ]); $me = Auth::user(); $target = User::findOrFail($request->to_user_id); if ($me->id === $target->id) abort(422, 'Kendinize gönderemezsiniz.'); $conv = Conversation::whereHas('participants', fn($q) => $q->where('user_id', $me->id)) ->whereHas('participants', fn($q) => $q->where('user_id', $target->id)) ->first(); if (!$conv) { $conv = DB::transaction(function () use ($me, $target) { $c = Conversation::create(); $c->participants()->attach([$me->id, $target->id]); return $c; }); } $message = Message::create([ 'conversation_id' => $conv->id, 'user_id' => $me->id, 'body' => $request->body, ]); $conv->touch(); $conv->participants()->updateExistingPivot($me->id, ['last_read_at' => now()]); return response()->json([ 'ok' => true, 'conversation_id' => $conv->id, 'message_id' => $message->id, ]); } }