]> git.openstreetmap.org Git - rails.git/blob - app/controllers/api/notes_controller.rb
Refactor api controllers to inherit from a common ApiController
[rails.git] / app / controllers / api / notes_controller.rb
1 module Api
2   class NotesController < ApiController
3     layout "site", :only => [:mine]
4
5     before_action :check_api_readable
6     before_action :setup_user_auth, :only => [:create, :comment, :show]
7     before_action :authorize, :only => [:close, :reopen, :destroy]
8     before_action :api_deny_access_handler
9
10     authorize_resource
11
12     before_action :check_api_writable, :only => [:create, :comment, :close, :reopen, :destroy]
13     before_action :set_locale
14     around_action :api_call_handle_error, :api_call_timeout
15
16     ##
17     # Return a list of notes in a given area
18     def index
19       # Figure out the bbox - we prefer a bbox argument but also
20       # support the old, deprecated, method with four arguments
21       if params[:bbox]
22         bbox = BoundingBox.from_bbox_params(params)
23       else
24         raise OSM::APIBadUserInput, "No l was given" unless params[:l]
25         raise OSM::APIBadUserInput, "No r was given" unless params[:r]
26         raise OSM::APIBadUserInput, "No b was given" unless params[:b]
27         raise OSM::APIBadUserInput, "No t was given" unless params[:t]
28
29         bbox = BoundingBox.from_lrbt_params(params)
30       end
31
32       # Get any conditions that need to be applied
33       notes = closed_condition(Note.all)
34
35       # Check that the boundaries are valid
36       bbox.check_boundaries
37
38       # Check the the bounding box is not too big
39       bbox.check_size(Settings.max_note_request_area)
40
41       # Find the notes we want to return
42       @notes = notes.bbox(bbox).order("updated_at DESC").limit(result_limit).preload(:comments)
43
44       # Render the result
45       respond_to do |format|
46         format.rss
47         format.xml
48         format.json
49         format.gpx
50       end
51     end
52
53     ##
54     # Create a new note
55     def create
56       # Check the ACLs
57       raise OSM::APIAccessDenied if current_user.nil? && Acl.no_note_comment(request.remote_ip)
58
59       # Check the arguments are sane
60       raise OSM::APIBadUserInput, "No lat was given" unless params[:lat]
61       raise OSM::APIBadUserInput, "No lon was given" unless params[:lon]
62       raise OSM::APIBadUserInput, "No text was given" if params[:text].blank?
63
64       # Extract the arguments
65       lon = OSM.parse_float(params[:lon], OSM::APIBadUserInput, "lon was not a number")
66       lat = OSM.parse_float(params[:lat], OSM::APIBadUserInput, "lat was not a number")
67       comment = params[:text]
68
69       # Include in a transaction to ensure that there is always a note_comment for every note
70       Note.transaction do
71         # Create the note
72         @note = Note.create(:lat => lat, :lon => lon)
73         raise OSM::APIBadUserInput, "The note is outside this world" unless @note.in_world?
74
75         # Save the note
76         @note.save!
77
78         # Add a comment to the note
79         add_comment(@note, comment, "opened")
80       end
81
82       # Return a copy of the new note
83       respond_to do |format|
84         format.xml { render :action => :show }
85         format.json { render :action => :show }
86       end
87     end
88
89     ##
90     # Add a comment to an existing note
91     def comment
92       # Check the ACLs
93       raise OSM::APIAccessDenied if current_user.nil? && Acl.no_note_comment(request.remote_ip)
94
95       # Check the arguments are sane
96       raise OSM::APIBadUserInput, "No id was given" unless params[:id]
97       raise OSM::APIBadUserInput, "No text was given" if params[:text].blank?
98
99       # Extract the arguments
100       id = params[:id].to_i
101       comment = params[:text]
102
103       # Find the note and check it is valid
104       @note = Note.find(id)
105       raise OSM::APINotFoundError unless @note
106       raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
107       raise OSM::APINoteAlreadyClosedError, @note if @note.closed?
108
109       # Add a comment to the note
110       Note.transaction do
111         add_comment(@note, comment, "commented")
112       end
113
114       # Return a copy of the updated note
115       respond_to do |format|
116         format.xml { render :action => :show }
117         format.json { render :action => :show }
118       end
119     end
120
121     ##
122     # Close a note
123     def close
124       # Check the arguments are sane
125       raise OSM::APIBadUserInput, "No id was given" unless params[:id]
126
127       # Extract the arguments
128       id = params[:id].to_i
129       comment = params[:text]
130
131       # Find the note and check it is valid
132       @note = Note.find_by(:id => id)
133       raise OSM::APINotFoundError unless @note
134       raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
135       raise OSM::APINoteAlreadyClosedError, @note if @note.closed?
136
137       # Close the note and add a comment
138       Note.transaction do
139         @note.close
140
141         add_comment(@note, comment, "closed")
142       end
143
144       # Return a copy of the updated note
145       respond_to do |format|
146         format.xml { render :action => :show }
147         format.json { render :action => :show }
148       end
149     end
150
151     ##
152     # Reopen a note
153     def reopen
154       # Check the arguments are sane
155       raise OSM::APIBadUserInput, "No id was given" unless params[:id]
156
157       # Extract the arguments
158       id = params[:id].to_i
159       comment = params[:text]
160
161       # Find the note and check it is valid
162       @note = Note.find_by(:id => id)
163       raise OSM::APINotFoundError unless @note
164       raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible? || current_user.moderator?
165       raise OSM::APINoteAlreadyOpenError, @note unless @note.closed? || !@note.visible?
166
167       # Reopen the note and add a comment
168       Note.transaction do
169         @note.reopen
170
171         add_comment(@note, comment, "reopened")
172       end
173
174       # Return a copy of the updated note
175       respond_to do |format|
176         format.xml { render :action => :show }
177         format.json { render :action => :show }
178       end
179     end
180
181     ##
182     # Get a feed of recent notes and comments
183     def feed
184       # Get any conditions that need to be applied
185       notes = closed_condition(Note.all)
186
187       # Process any bbox
188       if params[:bbox]
189         bbox = BoundingBox.from_bbox_params(params)
190
191         bbox.check_boundaries
192         bbox.check_size(Settings.max_note_request_area)
193
194         notes = notes.bbox(bbox)
195       end
196
197       # Find the comments we want to return
198       @comments = NoteComment.where(:note_id => notes).order("created_at DESC").limit(result_limit).preload(:note)
199
200       # Render the result
201       respond_to do |format|
202         format.rss
203       end
204     end
205
206     ##
207     # Read a note
208     def show
209       # Check the arguments are sane
210       raise OSM::APIBadUserInput, "No id was given" unless params[:id]
211
212       # Find the note and check it is valid
213       @note = Note.find(params[:id])
214       raise OSM::APINotFoundError unless @note
215       raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible? || current_user&.moderator?
216
217       # Render the result
218       respond_to do |format|
219         format.xml
220         format.rss
221         format.json
222         format.gpx
223       end
224     end
225
226     ##
227     # Delete (hide) a note
228     def destroy
229       # Check the arguments are sane
230       raise OSM::APIBadUserInput, "No id was given" unless params[:id]
231
232       # Extract the arguments
233       id = params[:id].to_i
234       comment = params[:text]
235
236       # Find the note and check it is valid
237       @note = Note.find(id)
238       raise OSM::APINotFoundError unless @note
239       raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
240
241       # Mark the note as hidden
242       Note.transaction do
243         @note.status = "hidden"
244         @note.save
245
246         add_comment(@note, comment, "hidden", false)
247       end
248
249       # Return a copy of the updated note
250       respond_to do |format|
251         format.xml { render :action => :show }
252         format.json { render :action => :show }
253       end
254     end
255
256     ##
257     # Return a list of notes matching a given string
258     def search
259       # Get the initial set of notes
260       @notes = closed_condition(Note.all)
261
262       # Add any user filter
263       if params[:display_name] || params[:user]
264         if params[:display_name]
265           @user = User.find_by(:display_name => params[:display_name])
266
267           raise OSM::APIBadUserInput, "User #{params[:display_name]} not known" unless @user
268         else
269           @user = User.find_by(:id => params[:user])
270
271           raise OSM::APIBadUserInput, "User #{params[:user]} not known" unless @user
272         end
273
274         @notes = @notes.joins(:comments).where(:note_comments => { :author_id => @user })
275       end
276
277       # Add any text filter
278       @notes = @notes.joins(:comments).where("to_tsvector('english', note_comments.body) @@ plainto_tsquery('english', ?)", params[:q]) if params[:q]
279
280       # Add any date filter
281       if params[:from]
282         begin
283           from = Time.parse(params[:from])
284         rescue ArgumentError
285           raise OSM::APIBadUserInput, "Date #{params[:from]} is in a wrong format"
286         end
287
288         begin
289           to = if params[:to]
290                  Time.parse(params[:to])
291                else
292                  Time.now
293                end
294         rescue ArgumentError
295           raise OSM::APIBadUserInput, "Date #{params[:to]} is in a wrong format"
296         end
297
298         @notes = @notes.where(:created_at => from..to)
299       end
300
301       # Find the notes we want to return
302       @notes = @notes.order("updated_at DESC").limit(result_limit).preload(:comments)
303
304       # Render the result
305       respond_to do |format|
306         format.rss { render :action => :index }
307         format.xml { render :action => :index }
308         format.json { render :action => :index }
309         format.gpx { render :action => :index }
310       end
311     end
312
313     private
314
315     #------------------------------------------------------------
316     # utility functions below.
317     #------------------------------------------------------------
318
319     ##
320     # Get the maximum number of results to return
321     def result_limit
322       if params[:limit]
323         if params[:limit].to_i.positive? && params[:limit].to_i <= 10000
324           params[:limit].to_i
325         else
326           raise OSM::APIBadUserInput, "Note limit must be between 1 and 10000"
327         end
328       else
329         100
330       end
331     end
332
333     ##
334     # Generate a condition to choose which notes we want based
335     # on their status and the user's request parameters
336     def closed_condition(notes)
337       closed_since = if params[:closed]
338                        params[:closed].to_i
339                      else
340                        7
341                      end
342
343       if closed_since.negative?
344         notes.where.not(:status => "hidden")
345       elsif closed_since.positive?
346         notes.where(:status => "open")
347              .or(notes.where(:status => "closed")
348                       .where(notes.arel_table[:closed_at].gt(Time.now - closed_since.days)))
349       else
350         notes.where(:status => "open")
351       end
352     end
353
354     ##
355     # Add a comment to a note
356     def add_comment(note, text, event, notify = true)
357       attributes = { :visible => true, :event => event, :body => text }
358
359       if current_user
360         attributes[:author_id] = current_user.id
361       else
362         attributes[:author_ip] = request.remote_ip
363       end
364
365       comment = note.comments.create!(attributes)
366
367       note.comments.map(&:author).uniq.each do |user|
368         Notifier.note_comment_notification(comment, user).deliver_later if notify && user && user != current_user && user.visible?
369       end
370     end
371   end
372 end