user; $reactionsAgg = []; foreach ($m->reactions->groupBy('emoji') as $emoji => $group) { $reactionsAgg[] = [ 'emoji' => $emoji, 'count' => $group->count(), 'reactedByMe' => $group->contains('user_id', $authId), ]; } $replyTo = null; if ($m->reply_to_id && $m->replyTo) { $replyTo = [ 'id' => $m->replyTo->id, 'message' => $m->replyTo->message, 'user' => [ 'id' => $m->replyTo->user?->id, 'username' => $m->replyTo->user?->username, ], ]; } return [ 'id' => $m->id, 'user_id' => $m->user_id, 'message' => $m->is_deleted ? null : $m->message, 'is_deleted' => (bool) $m->is_deleted, 'deleted_by_user' => $m->is_deleted && $m->deletedByUser ? [ 'id' => $m->deletedByUser->id, 'username' => $m->deletedByUser->username, 'role' => $m->deletedByUser->role ?? 'user', ] : null, 'reply_to_id' => $m->reply_to_id, 'reply_to' => $replyTo, 'created_at' => $m->created_at?->toIso8601String(), 'user' => [ 'id' => $user?->id, 'username' => $user?->username, 'avatar_url' => $user?->avatar_url ?? $user?->avatar ?? null, 'role' => $user?->role ?? 'user', 'vip_level' => (int) ($user?->vip_level ?? 0), 'clan_tag' => $user?->clan_tag ?? null, ], 'reactions_agg' => $reactionsAgg, ]; } /** * List recent chat messages from local DB. */ public function index(Request $request) { $limit = min(max((int) $request->query('limit', 50), 1), 200); $afterId = $request->query('after_id'); $authId = (int) optional($request->user())->id; $query = ChatMessage::with([ 'user:id,username,avatar,avatar_url,role,vip_level,clan_tag', 'reactions', 'replyTo:id,message,user_id', 'replyTo.user:id,username', 'deletedByUser:id,username,role', ])->orderBy('id', 'asc'); if ($afterId !== null) { $query->where('id', '>', (int) $afterId); } else { // Return last N messages $query = ChatMessage::with([ 'user:id,username,avatar,avatar_url,role,vip_level,clan_tag', 'reactions', 'replyTo:id,message,user_id', 'replyTo.user:id,username', ])->orderBy('id', 'desc')->limit($limit); $messages = $query->get()->reverse()->values(); $formatted = $messages->map(fn ($m) => $this->formatMessage($m, $authId))->values()->all(); $lastId = $messages->last()?->id; return response()->json(['data' => $formatted, 'last_id' => $lastId]); } $messages = $query->limit($limit)->get(); $formatted = $messages->map(fn ($m) => $this->formatMessage($m, $authId))->values()->all(); $lastId = $messages->last()?->id; return response()->json(['data' => $formatted, 'last_id' => $lastId]); } /** * Store a new chat message in local DB. */ public function store(Request $request) { $user = $request->user(); if (!$user) return response()->json(['message' => 'Unauthenticated'], 401); $validated = $request->validate([ 'message' => ['required', 'string', 'min:1', 'max:300'], 'reply_to_id' => ['nullable', 'integer', 'exists:chat_messages,id'], ]); // Check local chat ban $chatBan = \App\Models\UserRestriction::where('user_id', $user->id) ->where('type', 'chat_ban') ->where('active', true) ->where(fn($q) => $q->whereNull('ends_at')->orWhere('ends_at', '>', now())) ->first(); if ($chatBan) { return response()->json([ 'message' => 'Du bist vom Chat gebannt.', 'type' => 'chat_ban', 'ends_at' => $chatBan->ends_at?->toIso8601String(), ], 403); } $msg = ChatMessage::create([ 'user_id' => $user->id, 'message' => $validated['message'], 'reply_to_id' => $validated['reply_to_id'] ?? null, ]); $msg->load([ 'user:id,username,avatar,avatar_url,role,vip_level,clan_tag', 'reactions', 'replyTo:id,message,user_id', 'replyTo.user:id,username', 'deletedByUser:id,username,role', ]); return response()->json(['data' => $this->formatMessage($msg, (int) $user->id)], 201); } /** * Soft-delete a chat message (admin/mod only). */ public function destroy(Request $request, int $id) { $user = $request->user(); if (!$user) return response()->json(['message' => 'Unauthenticated'], 401); $role = strtolower((string) $user->role); if (!in_array($role, ['admin', 'moderator', 'mod'])) { return response()->json(['message' => 'Forbidden'], 403); } $msg = ChatMessage::findOrFail($id); $msg->update(['is_deleted' => true, 'deleted_by' => $user->id]); $msg->load([ 'user:id,username,avatar,avatar_url,role,vip_level,clan_tag', 'reactions', 'replyTo:id,message,user_id', 'replyTo.user:id,username', 'deletedByUser:id,username,role', ]); return response()->json(['data' => $this->formatMessage($msg, (int) $user->id)]); } /** * Toggle a reaction on a message. */ public function react(Request $request, int $id) { $user = $request->user(); if (!$user) return response()->json(['message' => 'Unauthenticated'], 401); $validated = $request->validate([ 'emoji' => ['required', 'string', 'max:8'], ]); $msg = ChatMessage::findOrFail($id); $existing = ChatMessageReaction::where([ 'message_id' => $msg->id, 'user_id' => $user->id, 'emoji' => $validated['emoji'], ])->first(); if ($existing) { $existing->delete(); } else { ChatMessageReaction::create([ 'message_id' => $msg->id, 'user_id' => $user->id, 'emoji' => $validated['emoji'], ]); } $msg->load([ 'user:id,username,avatar,avatar_url,role,vip_level,clan_tag', 'reactions', 'replyTo:id,message,user_id', 'replyTo.user:id,username', 'deletedByUser:id,username,role', ]); return response()->json(['data' => $this->formatMessage($msg, (int) $user->id)]); } /** * Report a chat message. */ public function report(Request $request, int $id) { $user = $request->user(); if (!$user) return response()->json(['message' => 'Unauthenticated'], 401); $validated = $request->validate([ 'message_text' => ['required', 'string', 'max:500'], 'sender_id' => ['nullable', 'integer'], 'sender_username' => ['nullable', 'string', 'max:255'], 'reason' => ['nullable', 'string', 'max:255'], 'context_messages' => ['nullable', 'array'], 'context_messages.*.id' => ['nullable'], 'context_messages.*.message' => ['nullable', 'string', 'max:500'], 'context_messages.*.user' => ['nullable', 'array'], 'context_messages.*.created_at' => ['nullable', 'string'], ]); ChatMessageReport::create([ 'reporter_id' => $user->id, 'message_id' => (string) $id, 'message_text' => $validated['message_text'], 'sender_id' => $validated['sender_id'] ?? null, 'sender_username' => $validated['sender_username'] ?? null, 'reason' => $validated['reason'] ?? null, 'context_messages' => $validated['context_messages'] ?? null, ]); return response()->json(['message' => 'Reported.'], 201); } }