Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Messages API #4605

Merged
merged 1 commit into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Metrics/ClassLength:
# Offense count: 59
# Configuration parameters: AllowedMethods, AllowedPatterns.
Metrics/CyclomaticComplexity:
Max: 29
Max: 31

# Offense count: 753
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
Expand All @@ -86,7 +86,7 @@ Metrics/ParameterLists:
# Offense count: 56
# Configuration parameters: AllowedMethods, AllowedPatterns.
Metrics/PerceivedComplexity:
Max: 30
Max: 32

# Offense count: 2394
# This cop supports safe autocorrection (--autocorrect).
Expand All @@ -95,7 +95,7 @@ Minitest/EmptyLineBeforeAssertionMethods:

# Offense count: 565
Minitest/MultipleAssertions:
Max: 54
Max: 60

# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
Expand Down
2 changes: 2 additions & 0 deletions app/abilities/api_capability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ def initialize(token)
can [:gpx_files], User if scope?(token, :read_gpx)
can [:index, :show], UserPreference if scope?(token, :read_prefs)
can [:update, :update_all, :destroy], UserPreference if scope?(token, :write_prefs)
can [:inbox, :outbox, :show, :update, :destroy], Message if scope?(token, :consume_messages)
can [:create], Message if scope?(token, :send_messages)

if user.terms_agreed?
can [:create, :update, :upload, :close, :subscribe, :unsubscribe], Changeset if scope?(token, :write_api)
Expand Down
149 changes: 149 additions & 0 deletions app/controllers/api/messages_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# The MessagesController is the RESTful interface to Message objects

module Api
class MessagesController < ApiController
before_action :authorize

before_action :check_api_writable, :only => [:create, :update, :destroy]
before_action :check_api_readable, :except => [:create, :update, :destroy]
tomhughes marked this conversation as resolved.
Show resolved Hide resolved

authorize_resource

around_action :api_call_handle_error, :api_call_timeout

before_action :set_request_formats

def inbox
@skip_body = true
@messages = Message.includes(:sender, :recipient).where(:to_user_id => current_user.id)

show_messages
end

def outbox
@skip_body = true
@messages = Message.includes(:sender, :recipient).where(:from_user_id => current_user.id)

show_messages
end

# Dump the details on a message given in params[:id]
def show
@message = Message.includes(:sender, :recipient).find(params[:id])

raise OSM::APIAccessDenied if current_user.id != @message.from_user_id && current_user.id != @message.to_user_id

# Render the result
respond_to do |format|
format.xml
format.json
end
end

# Create a new message from current user
def create
# Check the arguments are sane
raise OSM::APIBadUserInput, "No title was given" if params[:title].blank?
raise OSM::APIBadUserInput, "No body was given" if params[:body].blank?

# Extract the arguments
if params[:recipient_id]
recipient_id = params[:recipient_id].to_i
recipient = User.find(recipient_id)
elsif params[:recipient]
recipient_display_name = params[:recipient]
recipient = User.find_by(:display_name => recipient_display_name)
else
raise OSM::APIBadUserInput, "No recipient was given"
end

raise OSM::APIRateLimitExceeded if current_user.sent_messages.where(:sent_on => Time.now.utc - 1.hour..).count >= current_user.max_messages_per_hour

@message = Message.new(:sender => current_user,
:recipient => recipient,
:sent_on => Time.now.utc,
:title => params[:title],
:body => params[:body],
:body_format => "markdown")
@message.save!

UserMailer.message_notification(@message).deliver_later if @message.notify_recipient?

# Return a copy of the new message
respond_to do |format|
format.xml { render :action => :show }
format.json { render :action => :show }
end
end

# Update read status of a message
def update
@message = Message.find(params[:id])
read_status_idx = %w[true false].index params[:read_status]

raise OSM::APIBadUserInput, "Invalid value of `read_status` was given" if read_status_idx.nil?
raise OSM::APIAccessDenied unless current_user.id == @message.to_user_id

@message.message_read = read_status_idx.zero?
@message.save!

# Return a copy of the message
respond_to do |format|
format.xml { render :action => :show }
format.json { render :action => :show }
end
end

# Delete message by marking it as not visible for the current user
def destroy
@message = Message.find(params[:id])
if current_user.id == @message.from_user_id
@message.from_user_visible = false
elsif current_user.id == @message.to_user_id
@message.to_user_visible = false
else
raise OSM::APIAccessDenied
end

@message.save!

# Return a copy of the message
respond_to do |format|
format.xml { render :action => :show }
format.json { render :action => :show }
end
end

private

def show_messages
tomhughes marked this conversation as resolved.
Show resolved Hide resolved
@messages = @messages.where(:muted => false)
if params[:order].nil? || params[:order] == "newest"
@messages = @messages.where(:id => ..params[:from_id]) unless params[:from_id].nil?
@messages = @messages.order(:id => :desc)
elsif params[:order] == "oldest"
@messages = @messages.where(:id => params[:from_id]..) unless params[:from_id].nil?
@messages = @messages.order(:id => :asc)
else
raise OSM::APIBadUserInput, "Invalid order specified"
end

limit = params[:limit]
if !limit
limit = Settings.default_message_query_limit
elsif !limit.to_i.positive? || limit.to_i > Settings.max_message_query_limit
raise OSM::APIBadUserInput, "Messages limit must be between 1 and #{Settings.max_message_query_limit}"
else
limit = limit.to_i
end

@messages = @messages.limit(limit)

# Render the result
respond_to do |format|
format.xml
format.json
end
end
end
end
17 changes: 17 additions & 0 deletions app/views/api/messages/_message.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
json.id message.id
json.from_user_id message.from_user_id
json.from_display_name message.sender.display_name
json.to_user_id message.to_user_id
tomhughes marked this conversation as resolved.
Show resolved Hide resolved
json.to_display_name message.recipient.display_name
json.title message.title
json.sent_on message.sent_on.xmlschema

if current_user.id == message.from_user_id
json.deleted !message.from_user_visible
elsif current_user.id == message.to_user_id
json.message_read message.message_read
json.deleted !message.to_user_visible
end

json.body_format message.body_format
json.body message.body unless @skip_body
21 changes: 21 additions & 0 deletions app/views/api/messages/_message.xml.builder
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
attrs = {
"id" => message.id,
"from_user_id" => message.from_user_id,
"from_display_name" => message.sender.display_name,
"to_user_id" => message.to_user_id,
"to_display_name" => message.recipient.display_name,
"sent_on" => message.sent_on.xmlschema,
"body_format" => message.body_format
}

if current_user.id == message.from_user_id
attrs["deleted"] = !message.from_user_visible
elsif current_user.id == message.to_user_id
attrs["message_read"] = message.message_read
attrs["deleted"] = !message.to_user_visible
end
milan-cvetkovic marked this conversation as resolved.
Show resolved Hide resolved

xml.message(attrs) do |nd|
nd.title(message.title)
nd.body(message.body) unless @skip_body
end
5 changes: 5 additions & 0 deletions app/views/api/messages/inbox.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
json.partial! "api/root_attributes"

json.messages(@messages) do |message|
json.partial! message
end
7 changes: 7 additions & 0 deletions app/views/api/messages/inbox.xml.builder
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
xml.instruct!

xml.osm(OSM::API.new.xml_root_attributes) do |osm|
xml.tag! "messages" do
osm << (render(@messages) || "")
end
end
5 changes: 5 additions & 0 deletions app/views/api/messages/outbox.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
json.partial! "api/root_attributes"

json.messages(@messages) do |message|
json.partial! message
end
5 changes: 5 additions & 0 deletions app/views/api/messages/outbox.xml.builder
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
xml.instruct!

xml.osm(OSM::API.new.xml_root_attributes) do |osm|
osm << (render(@messages) || "")
end
5 changes: 5 additions & 0 deletions app/views/api/messages/show.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
json.partial! "api/root_attributes"

json.message do
json.partial! @message
end
5 changes: 5 additions & 0 deletions app/views/api/messages/show.xml.builder
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
xml.instruct! :xml, :version => "1.0"

xml.osm(OSM::API.new.xml_root_attributes) do |osm|
osm << render(@message)
end
2 changes: 2 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2641,6 +2641,8 @@ en:
write_notes: Modify notes
write_redactions: Redact map data
read_email: Read user email address
consume_messages: Read, update status and delete user messages
send_messages: Send private messages to other users
skip_authorization: Auto approve application
for_roles:
moderator: This permission is for actions available only to moderators
Expand Down
9 changes: 9 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@
end
end

resources :messages, :path => "user/messages", :constraints => { :id => /\d+/ }, :only => [:create, :show, :destroy], :controller => "messages", :as => :api_messages do
tomhughes marked this conversation as resolved.
Show resolved Hide resolved
collection do
get "inbox"
get "outbox"
end
end

post "/user/messages/:id" => "messages#update", :as => :api_message_update
tomhughes marked this conversation as resolved.
Show resolved Hide resolved

post "gpx/create" => "traces#create"
get "gpx/:id" => "traces#show", :as => :api_trace, :id => /\d+/
put "gpx/:id" => "traces#update", :id => /\d+/
Expand Down
4 changes: 4 additions & 0 deletions config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ user_block_periods: [0, 1, 3, 6, 12, 24, 48, 96, 168, 336, 731, 4383, 8766, 8766
user_account_deletion_delay: null
# Rate limit for message sending
max_messages_per_hour: 60
# Default limit on the number of messages returned by inbox and outbox message api
default_message_query_limit: 100
# Maximum number of messages returned by inbox and outbox message api
max_message_query_limit: 100
# Rate limit for friending
max_friends_per_hour: 60
# Rate limit for changeset comments
Expand Down
2 changes: 1 addition & 1 deletion lib/oauth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Oauth
SCOPES = %w[read_prefs write_prefs write_diary write_api read_gpx write_gpx write_notes].freeze
PRIVILEGED_SCOPES = %w[read_email skip_authorization].freeze
MODERATOR_SCOPES = %w[write_redactions].freeze
OAUTH2_SCOPES = %w[write_redactions openid].freeze
OAUTH2_SCOPES = %w[write_redactions consume_messages send_messages openid].freeze

class Scope
attr_reader :name
Expand Down
Loading