mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-08-16 12:00:42 +09:00
Merge pull request #190 from jpshackelford/feature/bot-read-messages
feat: Add bot API endpoints for reading messages and adding reactions
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
module RawRequestBody
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
private
|
||||
|
||||
def raw_request_body
|
||||
request.body.rewind
|
||||
request.body.read.force_encoding("UTF-8")
|
||||
ensure
|
||||
request.body.rewind
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
class Messages::Boosts::ByBotsController < Messages::BoostsController
|
||||
include RawRequestBody
|
||||
|
||||
allow_bot_access only: :create
|
||||
|
||||
before_action :ensure_content_present
|
||||
|
||||
def create
|
||||
@boost = @message.boosts.create!(boost_params)
|
||||
|
||||
broadcast_create
|
||||
render :show, status: :created
|
||||
end
|
||||
|
||||
private
|
||||
def set_message
|
||||
if room = Current.user.rooms.find_by(id: params[:room_id])
|
||||
@message = room.messages.find_by(id: params[:message_id])
|
||||
end
|
||||
|
||||
head :not_found unless @message
|
||||
end
|
||||
|
||||
def ensure_content_present
|
||||
if raw_request_body.blank?
|
||||
head :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def boost_params
|
||||
{ content: raw_request_body }
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,15 @@
|
||||
class Messages::ByBotsController < MessagesController
|
||||
allow_bot_access only: :create
|
||||
include RawRequestBody
|
||||
|
||||
allow_bot_access only: %i[ index create ]
|
||||
|
||||
before_action :set_room
|
||||
before_action :ensure_body_or_attachment_present, only: :create
|
||||
|
||||
def index
|
||||
@messages = find_paged_messages
|
||||
set_pagination_headers
|
||||
end
|
||||
|
||||
def create
|
||||
super
|
||||
@@ -7,18 +17,41 @@ class Messages::ByBotsController < MessagesController
|
||||
end
|
||||
|
||||
private
|
||||
def set_room
|
||||
@room = Current.user.rooms.find_by(id: params[:room_id])
|
||||
|
||||
head :not_found unless @room
|
||||
end
|
||||
|
||||
def ensure_body_or_attachment_present
|
||||
if params[:attachment].blank? && raw_request_body.blank?
|
||||
head :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
def set_pagination_headers
|
||||
headers["X-Total-Count"] = @room.messages.count.to_s
|
||||
|
||||
if next_page = next_page_params
|
||||
headers["Link"] = %(<#{room_bot_messages_url(@room, params[:bot_key], **next_page)}>; rel="next")
|
||||
end
|
||||
end
|
||||
|
||||
def next_page_params
|
||||
if @messages.any?
|
||||
if params[:after].present?
|
||||
{ after: @messages.last.id } if @room.messages.after(@messages.last).exists?
|
||||
else
|
||||
{ before: @messages.first.id } if @room.messages.before(@messages.first).exists?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def message_params
|
||||
if params[:attachment]
|
||||
params.permit(:attachment)
|
||||
else
|
||||
reading(request.body) { |body| { body: body } }
|
||||
{ body: raw_request_body }
|
||||
end
|
||||
end
|
||||
|
||||
def reading(io)
|
||||
io.rewind
|
||||
yield io.read.force_encoding("UTF-8")
|
||||
ensure
|
||||
io.rewind
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
json.cache! message do
|
||||
json.(message, :id)
|
||||
|
||||
json.created_at message.created_at.utc
|
||||
|
||||
json.body do
|
||||
json.plain_text message.plain_text_body
|
||||
json.html message.body.to_s
|
||||
end
|
||||
|
||||
json.creator message.creator, partial: "users/user", as: :user
|
||||
|
||||
json.room do
|
||||
json.id message.room_id
|
||||
end
|
||||
|
||||
json.url room_message_url(message.room, message)
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
json.cache! boost do
|
||||
json.(boost, :id, :content)
|
||||
|
||||
json.created_at boost.created_at.utc
|
||||
|
||||
json.booster boost.booster, partial: "users/user", as: :user
|
||||
|
||||
json.message do
|
||||
json.id boost.message_id
|
||||
json.url room_message_url(boost.message.room, boost.message)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
json.partial! "messages/boosts/boost", boost: @boost
|
||||
@@ -0,0 +1 @@
|
||||
json.array! @messages, partial: "messages/message", as: :message
|
||||
@@ -0,0 +1,5 @@
|
||||
json.cache! user do
|
||||
json.(user, :id, :name, :role)
|
||||
|
||||
json.avatar_url fresh_user_avatar_url(user)
|
||||
end
|
||||
+7
-1
@@ -62,7 +62,13 @@ Rails.application.routes.draw do
|
||||
resources :rooms do
|
||||
resources :messages
|
||||
|
||||
post ":bot_key/messages", to: "messages/by_bots#create", as: :bot_messages
|
||||
nested do
|
||||
scope path: ":bot_key", as: :bot, defaults: { format: :json } do
|
||||
resources :messages, controller: "messages/by_bots", only: %i[ index create ] do
|
||||
resources :boosts, controller: "messages/boosts/by_bots", only: :create
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scope module: "rooms" do
|
||||
resource :refresh, only: :show
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
require "test_helper"
|
||||
|
||||
class Messages::Boosts::ByBotsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
@room = rooms(:watercooler)
|
||||
@message = messages(:fourth)
|
||||
@bot = users(:bender)
|
||||
end
|
||||
|
||||
test "create adds a boost to the message and returns it" do
|
||||
assert_difference -> { @message.boosts.count }, +1 do
|
||||
post room_bot_message_boosts_url(@room, @bot.bot_key, @message), params: +"👀"
|
||||
assert_response :created
|
||||
end
|
||||
|
||||
boost = @message.boosts.last
|
||||
assert_equal "👀", boost.content
|
||||
assert_equal @bot, boost.booster
|
||||
|
||||
json = JSON.parse(response.body)
|
||||
assert_equal boost.id, json["id"]
|
||||
assert_equal "👀", json["content"]
|
||||
assert_equal @bot.id, json["booster"]["id"]
|
||||
assert_equal @message.id, json["message"]["id"]
|
||||
assert_equal room_message_url(@room, @message), json["message"]["url"]
|
||||
end
|
||||
|
||||
test "create with text content" do
|
||||
assert_difference -> { Boost.count }, +1 do
|
||||
post room_bot_message_boosts_url(@room, @bot.bot_key, @message), params: +"Nice!"
|
||||
assert_response :created
|
||||
end
|
||||
|
||||
assert_equal "Nice!", @message.boosts.last.content
|
||||
end
|
||||
|
||||
test "create broadcasts the boost" do
|
||||
assert_turbo_stream_broadcasts [ @message.room, :messages ], count: 1 do
|
||||
post room_bot_message_boosts_url(@room, @bot.bot_key, @message), params: +"👍"
|
||||
end
|
||||
end
|
||||
|
||||
test "create without content" do
|
||||
assert_no_difference -> { Boost.count } do
|
||||
post room_bot_message_boosts_url(@room, @bot.bot_key, @message)
|
||||
assert_response :unprocessable_content
|
||||
|
||||
post room_bot_message_boosts_url(@room, @bot.bot_key, @message), params: +" "
|
||||
assert_response :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
test "create requires a valid bot key" do
|
||||
assert_no_difference -> { Boost.count } do
|
||||
post room_bot_message_boosts_url(@room, "invalid-bot-key", @message), params: +"👀"
|
||||
end
|
||||
assert_response :redirect
|
||||
end
|
||||
|
||||
test "create is not found for a room the bot is not a member of" do
|
||||
assert_no_difference -> { Boost.count } do
|
||||
post room_bot_message_boosts_url(rooms(:designers), @bot.bot_key, messages(:first)), params: +"👀"
|
||||
end
|
||||
assert_response :not_found
|
||||
end
|
||||
|
||||
test "create is not found for a message outside the room" do
|
||||
assert_no_difference -> { Boost.count } do
|
||||
post room_bot_message_boosts_url(@room, @bot.bot_key, messages(:first)), params: +"👀"
|
||||
end
|
||||
assert_response :not_found
|
||||
end
|
||||
|
||||
test "create can't be abused to post boosts as a regular user" do
|
||||
bot_key = "#{users(:kevin).id}-"
|
||||
|
||||
assert_no_difference -> { Boost.count } do
|
||||
post room_bot_message_boosts_url(@room, bot_key, @message), params: +"👀"
|
||||
end
|
||||
assert_response :redirect
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,6 @@
|
||||
require "test_helper"
|
||||
|
||||
class Messages::ByBotsControlleTest < ActionDispatch::IntegrationTest
|
||||
class Messages::ByBotsControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
@room = rooms(:watercooler)
|
||||
end
|
||||
@@ -40,6 +40,16 @@ class Messages::ByBotsControlleTest < ActionDispatch::IntegrationTest
|
||||
end
|
||||
end
|
||||
|
||||
test "create without a body or attachment" do
|
||||
assert_no_difference -> { Message.count } do
|
||||
post room_bot_messages_url(@room, users(:bender).bot_key)
|
||||
assert_response :unprocessable_content
|
||||
|
||||
post room_bot_messages_url(@room, users(:bender).bot_key), params: +" "
|
||||
assert_response :unprocessable_content
|
||||
end
|
||||
end
|
||||
|
||||
test "create can't be abused to post messages as any user" do
|
||||
user = users(:kevin)
|
||||
bot_key = "#{user.id}-"
|
||||
@@ -51,8 +61,91 @@ 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 the room's messages in the order they were sent" do
|
||||
get room_bot_messages_url(@room, users(:bender).bot_key)
|
||||
assert_response :success
|
||||
|
||||
json = JSON.parse(response.body)
|
||||
assert_equal @room.messages.ordered.map(&:id), json.map { it["id"] }
|
||||
end
|
||||
|
||||
test "index includes message details" do
|
||||
post room_bot_messages_url(@room, users(:bender).bot_key), params: +"Hello from Bender!"
|
||||
|
||||
get room_bot_messages_url(@room, users(:bender).bot_key)
|
||||
assert_response :success
|
||||
|
||||
message = Message.last
|
||||
json_message = JSON.parse(response.body).last
|
||||
assert_equal message.id, json_message["id"]
|
||||
assert_equal "Hello from Bender!", json_message["body"]["plain_text"]
|
||||
assert_includes json_message["body"]["html"], "Hello from Bender!"
|
||||
assert_equal message.created_at.utc.iso8601(3), json_message["created_at"]
|
||||
assert_equal users(:bender).id, json_message["creator"]["id"]
|
||||
assert_equal "Bender Bot", json_message["creator"]["name"]
|
||||
assert_equal "bot", json_message["creator"]["role"]
|
||||
assert_equal @room.id, json_message["room"]["id"]
|
||||
assert_equal room_message_url(@room, message), json_message["url"]
|
||||
end
|
||||
|
||||
test "index pages through older messages with the Link header" do
|
||||
(Message::PAGE_SIZE - @room.messages.count + 1).times do |i|
|
||||
@room.messages.create!(body: "Filler #{i}", creator: users(:jason), client_message_id: "filler-#{i}")
|
||||
end
|
||||
|
||||
get room_bot_messages_url(@room, users(:bender).bot_key)
|
||||
assert_response :success
|
||||
|
||||
json = JSON.parse(response.body)
|
||||
assert_equal Message::PAGE_SIZE, json.size
|
||||
assert_equal "41", response.headers["X-Total-Count"]
|
||||
assert_not_includes json.map { it["id"] }, messages(:fourth).id
|
||||
|
||||
get response.headers["Link"][/<(.*)>/, 1]
|
||||
assert_response :success
|
||||
|
||||
json = JSON.parse(response.body)
|
||||
assert_equal [ messages(:fourth).id ], json.map { it["id"] }
|
||||
assert_nil response.headers["Link"]
|
||||
end
|
||||
|
||||
test "index pages newer messages with after" do
|
||||
get room_bot_messages_url(@room, users(:bender).bot_key, after: messages(:tenth).id)
|
||||
assert_response :success
|
||||
|
||||
json = JSON.parse(response.body)
|
||||
assert_equal %i[ eleventh twelfth thirteenth ].map { messages(it).id }, json.map { it["id"] }
|
||||
assert_nil response.headers["Link"]
|
||||
end
|
||||
|
||||
test "index in a room with no messages" do
|
||||
get room_bot_messages_url(rooms(:bender_and_kevin), users(:bender).bot_key)
|
||||
assert_response :success
|
||||
|
||||
assert_equal [], JSON.parse(response.body)
|
||||
assert_equal "0", response.headers["X-Total-Count"]
|
||||
assert_nil response.headers["Link"]
|
||||
end
|
||||
|
||||
test "index requires a valid bot key" do
|
||||
get room_bot_messages_url(@room, "invalid-bot-key")
|
||||
assert_response :redirect
|
||||
end
|
||||
|
||||
test "index is not found for a room the bot is not a member of" do
|
||||
get room_bot_messages_url(rooms(:designers), users(:bender).bot_key)
|
||||
assert_response :not_found
|
||||
end
|
||||
|
||||
test "create is not found for a room the bot is not a member of" do
|
||||
assert_no_difference -> { Message.count } do
|
||||
post room_bot_messages_url(rooms(:designers), users(:bender).bot_key), params: +"Hello!"
|
||||
end
|
||||
assert_response :not_found
|
||||
end
|
||||
|
||||
test "regular messages index remains denied for bots" do
|
||||
get room_messages_url(@room, bot_key: users(:bender).bot_key)
|
||||
assert_response :forbidden
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user