Guard against non-numeric lat and lons in nodes and notes
[rails.git] / app / controllers / notes_controller.rb
1 class NotesController < ApplicationController
2
3   layout 'site', :only => [:mine]
4
5   before_filter :check_api_readable
6   before_filter :authorize_web, :only => [:mine]
7   before_filter :setup_user_auth, :only => [:create, :comment]
8   before_filter :authorize, :only => [:close, :reopen, :destroy]
9   before_filter :require_moderator, :only => [:destroy]
10   before_filter :check_api_writable, :only => [:create, :comment, :close, :reopen, :destroy]
11   before_filter :require_allow_write_notes, :only => [:create, :comment, :close, :reopen, :destroy]
12   before_filter :set_locale
13   after_filter :compress_output
14   around_filter :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.new("No l was given") unless params[:l]
25       raise OSM::APIBadUserInput.new("No r was given") unless params[:r]
26       raise OSM::APIBadUserInput.new("No b was given") unless params[:b]
27       raise OSM::APIBadUserInput.new("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.scoped)
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(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 arguments are sane
57     raise OSM::APIBadUserInput.new("No lat was given") unless params[:lat]
58     raise OSM::APIBadUserInput.new("No lon was given") unless params[:lon]
59     raise OSM::APIBadUserInput.new("No text was given") if params[:text].blank?
60
61     # Extract the arguments
62     begin
63       lon = Float(params[:lon])
64     rescue
65       raise OSM::APIBadUserInput.new("lon was not a number")
66     end
67     begin
68       lat = Float(params[:lat])
69     rescue
70       raise OSM::APIBadUserInput.new("lat was not a number")
71     end
72     comment = params[:text]
73
74     # Include in a transaction to ensure that there is always a note_comment for every note
75     Note.transaction do
76       # Create the note
77       @note = Note.create(:lat => lat, :lon => lon)
78       raise OSM::APIBadUserInput.new("The note is outside this world") unless @note.in_world?
79
80       # Save the note
81       @note.save!
82
83       # Add a comment to the note
84       add_comment(@note, comment, "opened")
85     end
86
87     # Return a copy of the new note
88     respond_to do |format|
89       format.xml { render :action => :show }
90       format.json { render :action => :show }
91     end
92   end
93
94   ##
95   # Add a comment to an existing note
96   def comment
97     # Check the arguments are sane
98     raise OSM::APIBadUserInput.new("No id was given") unless params[:id]
99     raise OSM::APIBadUserInput.new("No text was given") if params[:text].blank?
100
101     # Extract the arguments
102     id = params[:id].to_i
103     comment = params[:text]
104
105     # Find the note and check it is valid
106     @note = Note.find(id)
107     raise OSM::APINotFoundError unless @note
108     raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
109     raise OSM::APINoteAlreadyClosedError.new(@note) if @note.closed?
110
111     # Add a comment to the note
112     Note.transaction do
113       add_comment(@note, comment, "commented")
114     end
115
116     # Return a copy of the updated note
117     respond_to do |format|
118       format.xml { render :action => :show }
119       format.json { render :action => :show }
120     end
121   end
122
123   ##
124   # Close a note
125   def close
126     # Check the arguments are sane
127     raise OSM::APIBadUserInput.new("No id was given") unless params[:id]
128
129     # Extract the arguments
130     id = params[:id].to_i
131     comment = params[:text]
132
133     # Find the note and check it is valid
134     @note = Note.find_by_id(id)
135     raise OSM::APINotFoundError unless @note
136     raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
137     raise OSM::APINoteAlreadyClosedError.new(@note) if @note.closed?
138
139     # Close the note and add a comment
140     Note.transaction do
141       @note.close
142
143       add_comment(@note, comment, "closed")
144     end
145
146     # Return a copy of the updated note
147     respond_to do |format|
148       format.xml { render :action => :show }
149       format.json { render :action => :show }
150     end
151   end 
152
153   ##
154   # Reopen a note
155   def reopen
156     # Check the arguments are sane
157     raise OSM::APIBadUserInput.new("No id was given") unless params[:id]
158
159     # Extract the arguments
160     id = params[:id].to_i
161     comment = params[:text]
162
163     # Find the note and check it is valid
164     @note = Note.find_by_id(id)
165     raise OSM::APINotFoundError unless @note
166     raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
167     raise OSM::APINoteAlreadyOpenError.new(@note) unless @note.closed?
168
169     # Reopen the note and add a comment
170     Note.transaction do
171       @note.reopen
172
173       add_comment(@note, comment, "reopened")
174     end
175
176     # Return a copy of the updated note
177     respond_to do |format|
178       format.xml { render :action => :show }
179       format.json { render :action => :show }
180     end
181   end 
182
183   ##
184   # Get a feed of recent notes and comments
185   def feed
186     # Get any conditions that need to be applied
187     notes = closed_condition(Note.scoped)
188
189     # Process any bbox
190     if params[:bbox]
191       bbox = BoundingBox.from_bbox_params(params)
192
193       bbox.check_boundaries
194       bbox.check_size(MAX_NOTE_REQUEST_AREA)
195
196       notes = notes.bbox(bbox)
197     end
198
199     # Find the comments we want to return
200     @comments = NoteComment.where(:note_id => notes).order("created_at DESC").limit(result_limit).preload(:note)
201
202     # Render the result
203     respond_to do |format|
204       format.rss
205     end
206   end
207
208   ##
209   # Read a note
210   def show
211     # Check the arguments are sane
212     raise OSM::APIBadUserInput.new("No id was given") unless params[:id]
213
214     # Find the note and check it is valid
215     @note = Note.find(params[:id])
216     raise OSM::APINotFoundError unless @note
217     raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
218
219     # Render the result
220     respond_to do |format|
221       format.xml
222       format.rss
223       format.json
224       format.gpx
225     end
226   end
227
228   ##
229   # Delete (hide) a note
230   def destroy
231     # Check the arguments are sane
232     raise OSM::APIBadUserInput.new("No id was given") unless params[:id]
233
234     # Extract the arguments
235     id = params[:id].to_i
236     comment = params[:text]
237
238     # Find the note and check it is valid
239     @note = Note.find(id)
240     raise OSM::APINotFoundError unless @note
241     raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
242
243     # Mark the note as hidden
244     Note.transaction do
245       @note.status = "hidden"
246       @note.save
247
248       add_comment(@note, comment, "hidden", false)
249     end
250
251     # Return a copy of the updated note
252     respond_to do |format|
253       format.xml { render :action => :show }
254       format.json { render :action => :show }
255     end
256   end
257
258   ##
259   # Return a list of notes matching a given string
260   def search
261     # Check the arguments are sane
262     raise OSM::APIBadUserInput.new("No query string was given") unless params[:q]
263
264     # Get any conditions that need to be applied
265     @notes = closed_condition(Note.scoped)
266     @notes = @notes.joins(:comments).where("note_comments.body ~ ?", params[:q])
267
268     # Find the notes we want to return
269     @notes = @notes.order("updated_at DESC").limit(result_limit).preload(:comments)
270
271     # Render the result
272     respond_to do |format|
273       format.rss { render :action => :index }
274       format.xml { render :action => :index }
275       format.json { render :action => :index }
276       format.gpx { render :action => :index }
277     end
278   end
279
280   ##
281   # Display a list of notes by a specified user
282   def mine
283     if params[:display_name] 
284       if @this_user = User.active.find_by_display_name(params[:display_name])
285         @title =  t 'note.mine.title', :user => @this_user.display_name 
286         @heading =  t 'note.mine.heading', :user => @this_user.display_name 
287         @description = t 'note.mine.subheading', :user => render_to_string(:partial => "user", :object => @this_user)
288         @page = (params[:page] || 1).to_i 
289         @page_size = 10
290         @notes = @this_user.notes.order("updated_at DESC, id").uniq.offset((@page - 1) * @page_size).limit(@page_size).preload(:comments => :author).all
291       else
292         @title = t 'user.no_such_user.title' 
293         @not_found_user = params[:display_name] 
294
295         render :template => 'user/no_such_user', :status => :not_found 
296       end 
297     end
298   end
299
300 private 
301   #------------------------------------------------------------ 
302   # utility functions below. 
303   #------------------------------------------------------------   
304  
305   ##
306   # Render an OK response
307   def render_ok
308     if params[:format] == "js"
309       render :text => "osbResponse();", :content_type => "text/javascript" 
310     else
311       render :text => "ok " + @note.id.to_s + "\n", :content_type => "text/plain" if @note
312       render :text => "ok\n", :content_type => "text/plain" unless @note
313     end
314   end
315
316   ##
317   # Get the maximum number of results to return
318   def result_limit
319     if params[:limit] and params[:limit].to_i > 0 and params[:limit].to_i < 10000
320       params[:limit].to_i
321     else
322       100
323     end
324   end
325
326   ##
327   # Generate a condition to choose which bugs we want based
328   # on their status and the user's request parameters
329   def closed_condition(notes)
330     if params[:closed]
331       closed_since = params[:closed].to_i
332     else
333       closed_since = 7
334     end
335         
336     if closed_since < 0
337       notes = notes.where("status != 'hidden'")
338     elsif closed_since > 0
339       notes = notes.where("(status = 'open' OR (status = 'closed' AND closed_at > '#{Time.now - closed_since.days}'))")
340     else
341       notes = notes.where("status = 'open'")
342     end
343
344     return notes
345   end
346
347   ##
348   # Add a comment to a note
349   def add_comment(note, text, event, notify = true)
350     attributes = { :visible => true, :event => event, :body => text }
351
352     if @user  
353       attributes[:author_id] = @user.id
354     else  
355       attributes[:author_ip] = request.remote_ip
356     end
357
358     comment = note.comments.create(attributes, :without_protection => true)
359
360     note.comments.map { |c| c.author }.uniq.each do |user|
361       if notify and user and user != @user
362         Notifier.note_comment_notification(comment, user).deliver
363       end
364     end
365   end
366 end