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 around_action :api_call_handle_error, :api_call_timeout
11 before_action :set_request_formats, :except => [:feed]
14 # Return a list of notes in a given area
16 # Figure out the bbox - we prefer a bbox argument but also
17 # support the old, deprecated, method with four arguments
19 bbox = BoundingBox.from_bbox_params(params)
20 elsif params[:l] && params[:r] && params[:b] && params[:t]
21 bbox = BoundingBox.from_lrbt_params(params)
23 raise OSM::APIBadUserInput, "The parameter bbox is required"
26 # Get any conditions that need to be applied
27 notes = closed_condition(Note.all)
29 # Check that the boundaries are valid
32 # Check the the bounding box is not too big
33 bbox.check_size(Settings.max_note_request_area)
34 @min_lon = bbox.min_lon
35 @min_lat = bbox.min_lat
36 @max_lon = bbox.max_lon
37 @max_lat = bbox.max_lat
39 # Find the notes we want to return
40 @notes = notes.bbox(bbox).order("updated_at DESC").limit(result_limit).preload(:comments)
43 respond_to do |format|
54 # Check the arguments are sane
55 raise OSM::APIBadUserInput, "No id was given" unless params[:id]
57 # Find the note and check it is valid
58 @note = Note.find(params[:id])
59 raise OSM::APINotFoundError unless @note
60 raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible? || current_user&.moderator?
63 respond_to do |format|
75 raise OSM::APIAccessDenied if current_user.nil? && Acl.no_note_comment(request.remote_ip)
77 # Check the arguments are sane
78 raise OSM::APIBadUserInput, "No lat was given" unless params[:lat]
79 raise OSM::APIBadUserInput, "No lon was given" unless params[:lon]
80 raise OSM::APIBadUserInput, "No text was given" if params[:text].blank?
82 # Extract the arguments
83 lon = OSM.parse_float(params[:lon], OSM::APIBadUserInput, "lon was not a number")
84 lat = OSM.parse_float(params[:lat], OSM::APIBadUserInput, "lat was not a number")
85 comment = params[:text]
87 # Include in a transaction to ensure that there is always a note_comment for every note
90 @note = Note.create(:lat => lat, :lon => lon)
91 raise OSM::APIBadUserInput, "The note is outside this world" unless @note.in_world?
96 # Add a comment to the note
97 add_comment(@note, comment, "opened")
100 # Return a copy of the new note
101 respond_to do |format|
102 format.xml { render :action => :show }
103 format.json { render :action => :show }
108 # Delete (hide) a note
110 # Check the arguments are sane
111 raise OSM::APIBadUserInput, "No id was given" unless params[:id]
113 # Extract the arguments
114 id = params[:id].to_i
115 comment = params[:text]
117 # Find the note and check it is valid
119 @note = Note.lock.find(id)
120 raise OSM::APINotFoundError unless @note
121 raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
123 # Mark the note as hidden
124 @note.status = "hidden"
127 add_comment(@note, comment, "hidden", :notify => false)
130 # Return a copy of the updated note
131 respond_to do |format|
132 format.xml { render :action => :show }
133 format.json { render :action => :show }
138 # Add a comment to an existing note
140 # Check the arguments are sane
141 raise OSM::APIBadUserInput, "No id was given" unless params[:id]
142 raise OSM::APIBadUserInput, "No text was given" if params[:text].blank?
144 # Extract the arguments
145 id = params[:id].to_i
146 comment = params[:text]
148 # Find the note and check it is valid
150 @note = Note.lock.find(id)
151 raise OSM::APINotFoundError unless @note
152 raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
153 raise OSM::APINoteAlreadyClosedError, @note if @note.closed?
155 # Add a comment to the note
156 add_comment(@note, comment, "commented")
159 # Return a copy of the updated note
160 respond_to do |format|
161 format.xml { render :action => :show }
162 format.json { render :action => :show }
169 # Check the arguments are sane
170 raise OSM::APIBadUserInput, "No id was given" unless params[:id]
172 # Extract the arguments
173 id = params[:id].to_i
174 comment = params[:text]
176 # Find the note and check it is valid
178 @note = Note.lock.find_by(:id => id)
179 raise OSM::APINotFoundError unless @note
180 raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
181 raise OSM::APINoteAlreadyClosedError, @note if @note.closed?
183 # Close the note and add a comment
186 add_comment(@note, comment, "closed")
189 # Return a copy of the updated note
190 respond_to do |format|
191 format.xml { render :action => :show }
192 format.json { render :action => :show }
199 # Check the arguments are sane
200 raise OSM::APIBadUserInput, "No id was given" unless params[:id]
202 # Extract the arguments
203 id = params[:id].to_i
204 comment = params[:text]
206 # Find the note and check it is valid
208 @note = Note.lock.find_by(:id => id)
209 raise OSM::APINotFoundError unless @note
210 raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible? || current_user.moderator?
211 raise OSM::APINoteAlreadyOpenError, @note unless @note.closed? || !@note.visible?
213 # Reopen the note and add a comment
216 add_comment(@note, comment, "reopened")
219 # Return a copy of the updated note
220 respond_to do |format|
221 format.xml { render :action => :show }
222 format.json { render :action => :show }
227 # Get a feed of recent notes and comments
229 # Get any conditions that need to be applied
230 notes = closed_condition(Note.all)
231 notes = bbox_condition(notes)
233 # Find the comments we want to return
234 @comments = NoteComment.where(:note => notes)
235 .order(:created_at => :desc).limit(result_limit)
236 .preload(:author, :note => { :comments => :author })
239 respond_to do |format|
245 # Return a list of notes matching a given string
247 # Get the initial set of notes
248 @notes = closed_condition(Note.all)
249 @notes = bbox_condition(@notes)
251 # Add any user filter
252 if params[:display_name] || params[:user]
253 if params[:display_name]
254 @user = User.find_by(:display_name => params[:display_name])
256 raise OSM::APIBadUserInput, "User #{params[:display_name]} not known" unless @user
258 @user = User.find_by(:id => params[:user])
260 raise OSM::APIBadUserInput, "User #{params[:user]} not known" unless @user
263 @notes = @notes.joins(:comments).where(:note_comments => { :author_id => @user })
266 # Add any text filter
267 @notes = @notes.joins(:comments).where("to_tsvector('english', note_comments.body) @@ plainto_tsquery('english', ?)", params[:q]) if params[:q]
269 # Add any date filter
272 from = Time.parse(params[:from]).utc
274 raise OSM::APIBadUserInput, "Date #{params[:from]} is in a wrong format"
279 Time.parse(params[:to]).utc
284 raise OSM::APIBadUserInput, "Date #{params[:to]} is in a wrong format"
287 @notes = if params[:sort] == "updated_at"
288 @notes.where(:updated_at => from..to)
290 @notes.where(:created_at => from..to)
294 # Choose the sort order
295 @notes = if params[:sort] == "created_at"
296 if params[:order] == "oldest"
297 @notes.order("created_at ASC")
299 @notes.order("created_at DESC")
302 if params[:order] == "oldest"
303 @notes.order("updated_at ASC")
305 @notes.order("updated_at DESC")
309 # Find the notes we want to return
310 @notes = @notes.distinct.limit(result_limit).preload(:comments)
313 respond_to do |format|
314 format.rss { render :action => :index }
315 format.xml { render :action => :index }
316 format.json { render :action => :index }
317 format.gpx { render :action => :index }
323 #------------------------------------------------------------
324 # utility functions below.
325 #------------------------------------------------------------
328 # Get the maximum number of results to return
331 if params[:limit].to_i.positive? && params[:limit].to_i <= Settings.max_note_query_limit
334 raise OSM::APIBadUserInput, "Note limit must be between 1 and #{Settings.max_note_query_limit}"
337 Settings.default_note_query_limit
342 # Generate a condition to choose which notes we want based
343 # on their status and the user's request parameters
344 def closed_condition(notes)
345 closed_since = if params[:closed]
346 params[:closed].to_i.days
348 Note::DEFAULT_FRESHLY_CLOSED_LIMIT
351 if closed_since.negative?
352 notes.where.not(:status => "hidden")
353 elsif closed_since.positive?
354 notes.where(:status => "open")
355 .or(notes.where(:status => "closed")
356 .where(notes.arel_table[:closed_at].gt(Time.now.utc - closed_since)))
358 notes.where(:status => "open")
363 # Generate a condition to choose which notes we want based
364 # on the user's bounding box request parameters
365 def bbox_condition(notes)
367 bbox = BoundingBox.from_bbox_params(params)
369 bbox.check_boundaries
370 bbox.check_size(Settings.max_note_request_area)
372 @min_lon = bbox.min_lon
373 @min_lat = bbox.min_lat
374 @max_lon = bbox.max_lon
375 @max_lat = bbox.max_lat
384 # Add a comment to a note
385 def add_comment(note, text, event, notify: true)
386 attributes = { :visible => true, :event => event, :body => text }
389 author = current_user if scope_enabled?(:write_notes)
391 author = current_user
395 attributes[:author_id] = author.id
397 attributes[:author_ip] = request.remote_ip
400 comment = note.comments.create!(attributes)
402 note.comments.map(&:author).uniq.each do |user|
403 UserMailer.note_comment_notification(comment, user).deliver_later if notify && user && user != current_user && user.visible?