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