From d4a56784e0318e6a77f34fbe6185b3b5181548eb Mon Sep 17 00:00:00 2001 From: John-Mason Shackelford Date: Wed, 8 Apr 2026 13:53:17 -0400 Subject: [PATCH] feat: Add bot API endpoint for reading room messages Adds a new GET endpoint at /rooms/:room_id/:bot_key/messages that allows bots to read messages from rooms they are members of. The endpoint returns JSON with: - Room info (id, name) - Messages array with body (plain/html), created_at, and creator info - Pagination info (oldest_id, newest_id, has_more) Supports pagination via ?before=:id and ?after=:id query parameters, consistent with the existing pagination in the messages controller. This enables AI bots and other automated agents to understand conversation context when responding to messages, rather than only receiving the single message that triggered the webhook. Co-authored-by: openhands --- .../messages/by_bots_controller.rb | 44 ++++++++++++++++- config/routes.rb | 2 + .../messages/by_bots_controller_test.rb | 47 ++++++++++++++++++- 3 files changed, 90 insertions(+), 3 deletions(-) diff --git a/app/controllers/messages/by_bots_controller.rb b/app/controllers/messages/by_bots_controller.rb index 3c09473..1d0c08a 100644 --- a/app/controllers/messages/by_bots_controller.rb +++ b/app/controllers/messages/by_bots_controller.rb @@ -1,5 +1,11 @@ class Messages::ByBotsController < MessagesController - allow_bot_access only: :create + allow_bot_access only: %i[ index create ] + + def index + set_room + @messages = find_paged_messages + render json: messages_as_json(@messages) + end def create super @@ -7,6 +13,42 @@ class Messages::ByBotsController < MessagesController end private + def messages_as_json(messages) + { + room: { + id: @room.id, + name: @room.name + }, + messages: messages.map { |m| message_as_json(m) }, + pagination: pagination_info(messages) + } + end + + def message_as_json(message) + { + id: message.id, + body: { + plain: message.plain_text_body, + html: message.body&.body&.to_s + }, + created_at: message.created_at.iso8601, + creator: { + id: message.creator.id, + name: message.creator.name, + is_bot: message.creator.role == "bot" + } + } + end + + def pagination_info(messages) + return {} if messages.empty? + { + oldest_id: messages.last.id, + newest_id: messages.first.id, + has_more: messages.size == Message::PAGE_SIZE + } + end + def message_params if params[:attachment] params.permit(:attachment) diff --git a/config/routes.rb b/config/routes.rb index e55fd7e..2cc6676 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -62,6 +62,8 @@ Rails.application.routes.draw do resources :rooms do resources :messages + # Bot API endpoints - authenticated via bot_key in URL + get ":bot_key/messages", to: "messages/by_bots#index", as: :bot_messages_index post ":bot_key/messages", to: "messages/by_bots#create", as: :bot_messages scope module: "rooms" do diff --git a/test/controllers/messages/by_bots_controller_test.rb b/test/controllers/messages/by_bots_controller_test.rb index 89f81da..e98207e 100644 --- a/test/controllers/messages/by_bots_controller_test.rb +++ b/test/controllers/messages/by_bots_controller_test.rb @@ -51,8 +51,51 @@ class Messages::ByBotsControlleTest < ActionDispatch::IntegrationTest assert_response :redirect end - test "denied index" do - get room_messages_url(@room, bot_key: users(:bender).bot_key, format: :json) + test "index returns messages as JSON" do + get room_bot_messages_index_url(@room, users(:bender).bot_key) + assert_response :success + + json = JSON.parse(response.body) + assert json["room"]["id"].present? + assert json["room"]["name"].present? + assert json["messages"].is_a?(Array) + assert json["pagination"].present? + end + + test "index includes message details" do + # Create a message in the room first + post room_bot_messages_url(@room, users(:bender).bot_key), params: +"Test message for index" + + get room_bot_messages_index_url(@room, users(:bender).bot_key) + assert_response :success + + json = JSON.parse(response.body) + message = json["messages"].find { |m| m["body"]["plain"] == "Test message for index" } + assert message.present?, "Expected to find the test message" + assert message["id"].present? + assert message["created_at"].present? + assert message["creator"]["id"].present? + assert message["creator"]["name"].present? + end + + test "index supports pagination with before parameter" do + get room_bot_messages_index_url(@room, users(:bender).bot_key, before: Message.last.id) + assert_response :success + end + + test "index supports pagination with after parameter" do + get room_bot_messages_index_url(@room, users(:bender).bot_key, after: Message.first.id) + assert_response :success + end + + test "index requires valid bot key" do + get room_bot_messages_index_url(@room, "invalid-bot-key") + assert_response :redirect # Redirects to login + end + + test "regular messages index still denied for bots" do + # The standard messages endpoint (not the bot-specific one) should still be forbidden + get room_messages_url(@room, bot_key: users(:bender).bot_key) assert_response :forbidden end end