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