get chat maybe working with broadcast many
Bobby Grayson 3 days ago 2 files (+152, -36)
MODIFIED
lib/blog/chat.ex
MODIFIED
lib/blog/chat.ex
@@ -2,6 +2,7 @@ defmodule Blog.Chat do@moduledoc """Module for handling chat functionality and message persistence using ETS."""+ require Logger@table_name :blog_chat_messages@banned_words_table :blog_chat_banned_words@@ -15,8 +16,10 @@ # Initialize message tablecase :ets.info(@table_name) do:undefined ->:ets.new(@table_name, [:ordered_set, :public, :named_table])+ Logger.info("Created new chat message ETS table")initialize_rooms()_ ->+ Logger.debug("Chat message ETS table already exists"):okend@@ -26,7 +29,9 @@ :undefined ->:ets.new(@banned_words_table, [:set, :protected, :named_table])# Add some initial banned words (these should be severe ones)add_banned_word("somedefaultbannedword")+ Logger.info("Created new banned words ETS table")_ ->+ Logger.debug("Banned words ETS table already exists"):okendend@@ -58,8 +63,10 @@ content: Map.get(welcome_messages, room),timestamp: DateTime.utc_now(),room: room})+ Logger.info("Initialized #{room} room with welcome message")end)_ ->+ Logger.debug("Rooms already initialized with welcome messages"):okendend@@ -71,6 +78,7 @@ def add_banned_word(word) when is_binary(word) dolowercase_word = String.downcase(String.trim(word))if lowercase_word != "" do:ets.insert(@banned_words_table, {lowercase_word, true})+ Logger.info("Added new banned word: #{lowercase_word}"){:ok, lowercase_word}else{:error, :empty_word}@@ -103,6 +111,7 @@ String.contains?(lowercase_message, word)end)if found_banned_word do+ Logger.warn("Message contained banned word, rejected"){:error, :contains_banned_words}else{:ok, message}@@ -113,13 +122,24 @@ @doc """Saves a message to ETS storage."""def save_message(message) do- # Use timestamp as part of the key for ordering+ # Use room and timestamp as key for orderingkey = {message.room, message.id}- :ets.insert(@table_name, {key, message})++ # Insert into ETS table+ result = :ets.insert(@table_name, {key, message})++ # Debug the actual key structure to ensure consistency+ Logger.debug("Saved message to ETS with key structure: #{inspect(key)}, result: #{inspect(result)}")+ Logger.debug("Message content: #{inspect(message)}")++ # Debug the current state of the table+ count = :ets.info(@table_name, :size)+ Logger.debug("ETS table now has #{count} total messages")# Trim messages if we have too manytrim_messages(message.room)+ # Return the stored messagemessageend@@ -127,21 +147,27 @@ @doc """Trims messages in a room to keep only the most recent ones."""defp trim_messages(room) do- # Count messages in this room- count = :ets.select_count(@table_name, [{{{"#{room}", :_}, :_}, [], [true]}])+ # Count messages in this room using match_object instead of select_count with fun2ms+ messages = :ets.match_object(@table_name, {{room, :_}, :_})+ count = length(messages)++ Logger.debug("Room #{room} has #{count} messages, max is #{@max_messages_per_room}")if count > @max_messages_per_room do- # Get all messages for this room- messages =- :ets.match_object(@table_name, {{{"#{room}", :_}, :_}})- |> Enum.sort()+ # Sort messages by ID (timestamp)+ sorted_messages =+ messages+ |> Enum.sort_by(fn {{_, id}, _} -> id end)# Delete the oldest messages- to_delete = Enum.take(messages, count - @max_messages_per_room)+ to_delete = Enum.take(sorted_messages, count - @max_messages_per_room)- Enum.each(to_delete, fn {key, _} ->- :ets.delete(@table_name, key)+ Enum.each(to_delete, fn {{r, id}, _} ->+ :ets.delete(@table_name, {r, id})+ Logger.debug("Deleted old message with key {#{r}, #{id}}")end)++ Logger.info("Trimmed #{length(to_delete)} old messages from room #{room}")endend@@ -149,30 +175,85 @@ @doc """Gets messages for a specific room."""def get_messages(room) do- # Create a match pattern for the room- case :ets.match_object(@table_name, {{{room, :_}, :_}}) do- [] -> []- messages ->- messages- |> Enum.sort(fn {{_, id1}, _}, {{_, id2}, _} -> id1 > id2 end)- |> Enum.map(fn {_, message} -> message end)- |> Enum.take(50)+ # Debug the query we're about to run+ Logger.debug("Fetching messages for room '#{room}' from ETS table #{inspect(@table_name)}")++ # Use match_object to get all messages for the room+ all_matching = :ets.match_object(@table_name, {{room, :_}, :_})+ Logger.debug("Found #{length(all_matching)} raw entries for room #{room}")++ # Show raw results for debugging+ if length(all_matching) > 0 do+ Logger.debug("First matched entry: #{inspect(hd(all_matching))}")+ end++ # Extract the messages from the match_object results+ messages = Enum.map(all_matching, fn {_key, msg} -> msg end)++ # Log what we found+ Logger.debug("Retrieved #{length(messages)} message structs for room #{room}")++ # Sort and return the messages+ sorted_messages =+ messages+ |> Enum.sort_by(fn msg -> msg.id end, :desc)+ |> Enum.take(50)++ Logger.debug("Returning #{length(sorted_messages)} sorted messages")+ sorted_messages+ end++ @doc """+ Debug function to list all messages in all rooms.+ """+ def list_all_messages do+ # Get all objects from the table+ all_messages = :ets.tab2list(@table_name)+ Logger.debug("Total messages in ETS: #{length(all_messages)}")++ # Log the raw data+ if length(all_messages) > 0 do+ sample = Enum.take(all_messages, 3)+ Logger.debug("Raw message data sample: #{inspect(sample)}")end++ # Group by room+ result = all_messages+ |> Enum.map(fn {{room, _id}, message} -> {room, message} end)+ |> Enum.group_by(fn {room, _} -> room end, fn {_, message} -> message end)++ # Log the count per room+ Enum.each(result, fn {room, msgs} ->+ Logger.debug("Room #{room} has #{length(msgs)} messages")+ end)++ resultend@doc """Clears all messages from a room."""def clear_room(room) do- # Delete all messages in the room- :ets.match_delete(@table_name, {{{room, :_}, :_}})+ # Get all messages in the room+ messages = :ets.match_object(@table_name, {{room, :_}, :_})++ # Delete them one by one+ deleted_count = Enum.reduce(messages, 0, fn {{r, id}, _}, acc ->+ :ets.delete(@table_name, {r, id})+ acc + 1+ end)++ Logger.info("Cleared #{deleted_count} messages from room #{room}")+ deleted_countend@doc """Clears all messages from all rooms."""def clear_all do+ count = :ets.info(@table_name, :size):ets.delete_all_objects(@table_name)+ Logger.info("Cleared all #{count} chat messages from all rooms")initialize_rooms()endend
MODIFIED
lib/blog_web/live/post_live/index.ex
MODIFIED
lib/blog_web/live/post_live/index.ex
@@ -3,6 +3,7 @@ use BlogWeb, :live_viewalias BlogWeb.Presencealias Blog.Contentalias Blog.Chat+ require Logger@presence_topic "blog_presence"@chat_topic "blog_chat"@@ -31,8 +32,11 @@ display_name: nil,current_room: "general"})+ # Subscribe to presence and chat topicsPhoenix.PubSub.subscribe(Blog.PubSub, @presence_topic)Phoenix.PubSub.subscribe(Blog.PubSub, @chat_topic)++ Logger.debug("User #{id} mounted and subscribed to topics: #{@presence_topic}, #{@chat_topic}")idelsenil@@ -49,8 +53,9 @@ |> Enum.into(%{})total_readers = map_size(visitor_cursors)- # Get messages for the default room- messages = if connected?(socket), do: Chat.get_messages("general"), else: []+ # Get messages for the default room - always fetch from ETS+ messages = Chat.get_messages("general")+ Logger.debug("Loaded #{length(messages)} messages for general room during mount"){:ok,assign(socket,@@ -105,11 +110,16 @@ {:noreply, assign(socket, total_readers: total_readers, visitor_cursors: visitor_cursors, room_users: room_users)}enddef handle_info({:new_chat_message, message}, socket) do+ Logger.debug("Received new chat message: #{inspect(message.id)} in room #{message.room} from #{message.sender_name}")+# Only update messages if we're in the same room as the messageif message.room == socket.assigns.current_room do- updated_messages = [message | socket.assigns.chat_messages] |> Enum.take(50)+ # Get all messages from ETS to ensure we have the latest data+ updated_messages = Chat.get_messages(socket.assigns.current_room)+ Logger.debug("Updated chat messages for room #{socket.assigns.current_room}, now have #{length(updated_messages)} messages"){:noreply, assign(socket, chat_messages: updated_messages)}else+ Logger.debug("Ignoring message for room #{message.room} since user is in room #{socket.assigns.current_room}"){:noreply, socket}endend@@ -187,6 +197,7 @@ end)# Get messages for the new roommessages = Chat.get_messages(room)+ Logger.debug("Changed room to #{room}, loaded #{length(messages)} messages"){:noreply, assign(socket, current_room: room, chat_messages: messages)}else@@ -198,6 +209,8 @@ def handle_event("send_chat_message", %{"message" => message}, socket) doreader_id = socket.assigns.reader_idcurrent_room = socket.assigns.current_roomtrimmed_message = String.trim(message)++ Logger.debug("Handling send_chat_message event for #{reader_id} in room #{current_room}")if reader_id && trimmed_message != "" do# Check for banned words@@ -226,13 +239,22 @@ room: current_room}# Save message to ETS- Chat.save_message(new_message)+ saved_message = Chat.save_message(new_message)+ Logger.debug("Saved new message to ETS with ID: #{inspect(saved_message.id)}")- # Broadcast the message to all clients- Phoenix.PubSub.broadcast(Blog.PubSub, @chat_topic, {:new_chat_message, new_message})+ # Broadcast the message to all clients - use broadcast! to raise errors+ Phoenix.PubSub.broadcast!(+ Blog.PubSub,+ @chat_topic,+ {:new_chat_message, saved_message}+ )+ Logger.debug("Broadcast message to topic #{@chat_topic} succeeded")- # Clear the input field- {:noreply, assign(socket, chat_form: %{"message" => ""})}+ # Get updated messages from ETS to ensure consistency+ updated_messages = Chat.get_messages(current_room)+ Logger.debug("After sending: room #{current_room} has #{length(updated_messages)} messages")++ {:noreply, assign(socket, chat_form: %{"message" => ""}, chat_messages: updated_messages)}{:error, :contains_banned_words} -># Message contains banned words, reject it@@ -274,6 +296,26 @@ # Create the anchor tag with appropriate attributes# Note: We use target="_blank" and rel="noopener noreferrer" for security"<a href=\"#{href}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-blue-600 hover:underline break-all\">#{url}</a>"end)+ end++ # Add a debug function to be used for troubleshooting+ def debug_chat_state(socket) do+ Logger.debug("------- CHAT DEBUG -------")+ Logger.debug("Reader ID: #{socket.assigns.reader_id}")+ Logger.debug("Current room: #{socket.assigns.current_room}")+ Logger.debug("Message count: #{length(socket.assigns.chat_messages)}")++ # Dump the contents of the ETS table for this room+ room_messages = Chat.get_messages(socket.assigns.current_room)+ Logger.debug("ETS messages for room: #{length(room_messages)}")++ if length(room_messages) > 0 do+ sample = Enum.take(room_messages, 2)+ Logger.debug("Sample messages: #{inspect(sample)}")+ end++ Logger.debug("-------------------------")+ socketenddef render(assigns) do@@ -688,12 +730,5 @@ </div></footer></div>"""- end-- # Add a debug function to help troubleshoot- def debug_presence(socket) do- IO.inspect(socket.assigns.reader_id, label: "Current reader_id")- IO.inspect(socket.assigns.visitor_cursors, label: "All visitor cursors")- socketendend