2 class NotesController < ApiController
3 before_action :check_api_writable, :only => [:create, :comment, :close, :reopen, :destroy]
4 before_action :setup_user_auth, :only => [:create, :show]
5 before_action :authorize, :only => [:close, :reopen, :destroy, :comment]
9 before_action :set_locale
10 before_action :set_request_formats, :except => [:feed]
13 # Return a list of notes in a given area
15 # Figure out the bbox - we prefer a bbox argument but also
16 # support the old, deprecated, method with four arguments
18 bbox = BoundingBox.from_bbox_params(params)
19 elsif params[:l] && params[:r] && params[:b] && params[:t]
20 bbox = BoundingBox.from_lrbt_params(params)
22 raise OSM::APIBadUserInput, "The parameter bbox is required"
25 # Get any conditions that need to be applied
26 notes = closed_condition(Note.all)
28 # Check that the boundaries are valid
31 # Check the the bounding box is not too big
32 bbox.check_size(Settings.max_note_request_area)
33 @min_lon = bbox.min_lon
34 @min_lat = bbox.min_lat
35 @max_lon = bbox.max_lon
36 @max_lat = bbox.max_lat
38 # Find the notes we want to return
39 @notes = notes.bbox(bbox).order("updated_at DESC").limit(result_limit).preload(:comments)
42 respond_to do |format|
53 # Check the arguments are sane
54 raise OSM::APIBadUserInput, "No id was given" unless params[:id]
56 # Find the note and check it is valid
57 @note = Note.find(params[:id])
58 raise OSM::APINotFoundError unless @note
59 raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible? || current_user&.moderator?
62 respond_to do |format|
74 raise OSM::APIAccessDenied if current_user.nil? && Acl.no_note_comment(request.remote_ip)
76 # Check the arguments are sane
77 raise OSM::APIBadUserInput, "No lat was given" unless params[:lat]
78 raise OSM::APIBadUserInput, "No lon was given" unless params[:lon]
79 raise OSM::APIBadUserInput, "No text was given" if params[:text].blank?
81 # Extract the arguments
82 lon = OSM.parse_float(params[:lon], OSM::APIBadUserInput, "lon was not a number")
83 lat = OSM.parse_float(params[:lat], OSM::APIBadUserInput, "lat was not a number")
84 description = params[:text]
86 # Get note's author info (for logged in users - user_id, for logged out users - IP address)
87 note_author_info = author_info
89 # Include in a transaction to ensure that there is always a note_comment for every note
92 @note = Note.create(:lat => lat, :lon => lon, :description => description, :user_id => note_author_info[:user_id], :user_ip => note_author_info[:user_ip])
93 raise OSM::APIBadUserInput, "The note is outside this world" unless @note.in_world?
98 # Add opening comment (description) to the note
99 add_comment(@note, description, "opened")
102 # Return a copy of the new note
103 respond_to do |format|
104 format.xml { render :action => :show }
105 format.json { render :action => :show }
110 # Delete (hide) a note
112 # Check the arguments are sane
113 raise OSM::APIBadUserInput, "No id was given" unless params[:id]
115 # Extract the arguments
116 id = params[:id].to_i
117 comment = params[:text]
119 # Find the note and check it is valid
121 @note = Note.lock.find(id)
122 raise OSM::APINotFoundError unless @note
123 raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
125 # Mark the note as hidden
126 @note.status = "hidden"
129 add_comment(@note, comment, "hidden", :notify => false)
132 # Return a copy of the updated note
133 respond_to do |format|
134 format.xml { render :action => :show }
135 format.json { render :action => :show }
140 # Add a comment to an existing note
142 # Check the arguments are sane
143 raise OSM::APIBadUserInput, "No id was given" unless params[:id]
144 raise OSM::APIBadUserInput, "No text was given" if params[:text].blank?
146 # Extract the arguments
147 id = params[:id].to_i
148 comment = params[:text]
150 # Find the note and check it is valid
152 @note = Note.lock.find(id)
153 raise OSM::APINotFoundError unless @note
154 raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
155 raise OSM::APINoteAlreadyClosedError, @note if @note.closed?
157 # Add a comment to the note
158 add_comment(@note, comment, "commented")
161 # Return a copy of the updated note
162 respond_to do |format|
163 format.xml { render :action => :show }
164 format.json { render :action => :show }
171 # Check the arguments are sane
172 raise OSM::APIBadUserInput, "No id was given" unless params[:id]
174 # Extract the arguments
175 id = params[:id].to_i
176 comment = params[:text]
178 # Find the note and check it is valid
180 @note = Note.lock.find_by(:id => id)
181 raise OSM::APINotFoundError unless @note
182 raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
183 raise OSM::APINoteAlreadyClosedError, @note if @note.closed?
185 # Close the note and add a comment
188 add_comment(@note, comment, "closed")
191 # Return a copy of the updated note
192 respond_to do |format|
193 format.xml { render :action => :show }
194 format.json { render :action => :show }
201 # Check the arguments are sane
202 raise OSM::APIBadUserInput, "No id was given" unless params[:id]
204 # Extract the arguments
205 id = params[:id].to_i
206 comment = params[:text]
208 # Find the note and check it is valid
210 @note = Note.lock.find_by(:id => id)
211 raise OSM::APINotFoundError unless @note
212 raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible? || current_user.moderator?
213 raise OSM::APINoteAlreadyOpenError, @note unless @note.closed? || !@note.visible?
215 # Reopen the note and add a comment
218 add_comment(@note, comment, "reopened")
221 # Return a copy of the updated note
222 respond_to do |format|
223 format.xml { render :action => :show }
224 format.json { render :action => :show }
229 # Get a feed of recent notes and comments
231 # Get any conditions that need to be applied
232 notes = closed_condition(Note.all)
233 notes = bbox_condition(notes)
235 # Find the comments we want to return
236 @comments = NoteComment.where(:note => notes)
237 .order(:created_at => :desc).limit(result_limit)
238 .preload(:author, :note => { :comments => :author })
241 respond_to do |format|
247 # Return a list of notes matching a given string
249 # Get the initial set of notes
250 @notes = closed_condition(Note.all)
251 @notes = bbox_condition(@notes)
253 # Add any user filter
254 if params[:display_name] || params[:user]
255 if params[:display_name]
256 @user = User.find_by(:display_name => params[:display_name])
258 raise OSM::APIBadUserInput, "User #{params[:display_name]} not known" unless @user
260 @user = User.find_by(:id => params[:user])
262 raise OSM::APIBadUserInput, "User #{params[:user]} not known" unless @user
265 @notes = @notes.joins(:comments).where(:note_comments => { :author_id => @user })
268 # Add any text filter
269 @notes = @notes.joins(:comments).where("to_tsvector('english', note_comments.body) @@ plainto_tsquery('english', ?)", params[:q]) if params[:q]
271 # Add any date filter
274 from = Time.parse(params[:from]).utc
276 raise OSM::APIBadUserInput, "Date #{params[:from]} is in a wrong format"
281 Time.parse(params[:to]).utc
286 raise OSM::APIBadUserInput, "Date #{params[:to]} is in a wrong format"
289 @notes = if params[:sort] == "updated_at"
290 @notes.where(:updated_at => from..to)
292 @notes.where(:created_at => from..to)
296 # Choose the sort order
297 @notes = if params[:sort] == "created_at"
298 if params[:order] == "oldest"
299 @notes.order("created_at ASC")
301 @notes.order("created_at DESC")
304 if params[:order] == "oldest"
305 @notes.order("updated_at ASC")
307 @notes.order("updated_at DESC")
311 # Find the notes we want to return
312 @notes = @notes.distinct.limit(result_limit).preload(:comments)
315 respond_to do |format|
316 format.rss { render :action => :index }
317 format.xml { render :action => :index }
318 format.json { render :action => :index }
319 format.gpx { render :action => :index }
325 #------------------------------------------------------------
326 # utility functions below.
327 #------------------------------------------------------------
330 # Get the maximum number of results to return
333 if params[:limit].to_i.positive? && params[:limit].to_i <= Settings.max_note_query_limit
336 raise OSM::APIBadUserInput, "Note limit must be between 1 and #{Settings.max_note_query_limit}"
339 Settings.default_note_query_limit
344 # Generate a condition to choose which notes we want based
345 # on their status and the user's request parameters
346 def closed_condition(notes)
347 closed_since = if params[:closed]
348 params[:closed].to_i.days
350 Note::DEFAULT_FRESHLY_CLOSED_LIMIT
353 if closed_since.negative?
354 notes.where.not(:status => "hidden")
355 elsif closed_since.positive?
356 notes.where(:status => "open")
357 .or(notes.where(:status => "closed")
358 .where(notes.arel_table[:closed_at].gt(Time.now.utc - closed_since)))
360 notes.where(:status => "open")
365 # Generate a condition to choose which notes we want based
366 # on the user's bounding box request parameters
367 def bbox_condition(notes)
369 bbox = BoundingBox.from_bbox_params(params)
371 bbox.check_boundaries
372 bbox.check_size(Settings.max_note_request_area)
374 @min_lon = bbox.min_lon
375 @min_lat = bbox.min_lat
376 @max_lon = bbox.max_lon
377 @max_lat = bbox.max_lat
386 # Get author's information (for logged in users - user_id, for logged out users - IP address)
388 if scope_enabled?(:write_notes)
389 { :user_id => current_user.id }
391 { :user_ip => request.remote_ip }
396 # Add a comment to a note
397 def add_comment(note, text, event, notify: true)
398 attributes = { :visible => true, :event => event, :body => text }
400 # Get note comment's author info (for logged in users - user_id, for logged out users - IP address)
401 note_comment_author_info = author_info
403 if note_comment_author_info[:user_ip].nil?
404 attributes[:author_id] = note_comment_author_info[:user_id]
406 attributes[:author_ip] = note_comment_author_info[:user_ip]
409 comment = note.comments.create!(attributes)
412 note.subscribers.visible.each do |user|
413 UserMailer.note_comment_notification(comment, user).deliver_later if current_user != user
417 NoteSubscription.find_or_create_by(:note => note, :user => current_user) if current_user