Move /key to /panes/legend
[rails.git] / app / controllers / api / changesets_controller.rb
index a3dff4f1cd7e4fc935986a009c71e9b31ac7c411..cadd4ac330599a7619458fd0ed18b6c753af207c 100644 (file)
 
 module Api
   class ChangesetsController < ApiController
-    require "xml/libxml"
+    include QueryMethods
 
-    before_action :check_api_writable, :only => [:create, :update, :upload, :subscribe, :unsubscribe]
-    before_action :check_api_readable, :except => [:create, :update, :upload, :download, :query, :subscribe, :unsubscribe]
-    before_action :authorize, :only => [:create, :update, :upload, :close, :subscribe, :unsubscribe]
+    before_action :check_api_writable, :only => [:create, :update]
+    before_action :setup_user_auth, :only => [:show]
+    before_action :authorize, :only => [:create, :update]
 
     authorize_resource
 
-    before_action :require_public_data, :only => [:create, :update, :upload, :close, :subscribe, :unsubscribe]
-    before_action :set_request_formats, :except => [:create, :close, :upload]
-
-    around_action :api_call_handle_error
-    around_action :api_call_timeout, :except => [:upload]
+    before_action :require_public_data, :only => [:create, :update]
+    before_action :set_request_formats, :except => [:create]
 
     # Helper methods for checking consistency
     include ConsistencyValidations
 
-    ##
-    # Return XML giving the basic info about the changeset. Does not
-    # return anything about the nodes, ways and relations in the changeset.
-    def show
-      @changeset = Changeset.find(params[:id])
-      @include_discussion = params[:include_discussion].presence
-      render "changeset"
-
-      respond_to do |format|
-        format.xml
-        format.json
-      end
-    end
-
-    # Create a changeset from XML.
-    def create
-      assert_method :put
-
-      cs = Changeset.from_xml(request.raw_post, :create => true)
-
-      # Assume that Changeset.from_xml has thrown an exception if there is an error parsing the xml
-      cs.user = current_user
-      cs.save_with_tags!
-
-      # Subscribe user to changeset comments
-      cs.subscribers << current_user
-
-      render :plain => cs.id.to_s
-    end
-
-    ##
-    # marks a changeset as closed. this may be called multiple times
-    # on the same changeset, so is idempotent.
-    def close
-      assert_method :put
-
-      changeset = Changeset.find(params[:id])
-      check_changeset_consistency(changeset, current_user)
-
-      # to close the changeset, we'll just set its closed_at time to
-      # now. this might not be enough if there are concurrency issues,
-      # but we'll have to wait and see.
-      changeset.set_closed_time_now
-
-      changeset.save!
-      head :ok
-    end
-
-    ##
-    # Upload a diff in a single transaction.
-    #
-    # This means that each change within the diff must succeed, i.e: that
-    # each version number mentioned is still current. Otherwise the entire
-    # transaction *must* be rolled back.
-    #
-    # Furthermore, each element in the diff can only reference the current
-    # changeset.
-    #
-    # Returns: a diffResult document, as described in
-    # http://wiki.openstreetmap.org/wiki/OSM_Protocol_Version_0.6
-    def upload
-      # only allow POST requests, as the upload method is most definitely
-      # not idempotent, as several uploads with placeholder IDs will have
-      # different side-effects.
-      # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.2
-      assert_method :post
-
-      changeset = Changeset.find(params[:id])
-      check_changeset_consistency(changeset, current_user)
-
-      diff_reader = DiffReader.new(request.raw_post, changeset)
-      Changeset.transaction do
-        result = diff_reader.commit
-        render :xml => result.to_s
-      end
-    end
-
-    ##
-    # download the changeset as an osmChange document.
-    #
-    # to make it easier to revert diffs it would be better if the osmChange
-    # format were reversible, i.e: contained both old and new versions of
-    # modified elements. but it doesn't at the moment...
-    #
-    # this method cannot order the database changes fully (i.e: timestamp and
-    # version number may be too coarse) so the resulting diff may not apply
-    # to a different database. however since changesets are not atomic this
-    # behaviour cannot be guaranteed anyway and is the result of a design
-    # choice.
-    def download
-      changeset = Changeset.find(params[:id])
-
-      # get all the elements in the changeset which haven't been redacted
-      # and stick them in a big array.
-      elements = [changeset.old_nodes.unredacted,
-                  changeset.old_ways.unredacted,
-                  changeset.old_relations.unredacted].flatten
-
-      # sort the elements by timestamp and version number, as this is the
-      # almost sensible ordering available. this would be much nicer if
-      # global (SVN-style) versioning were used - then that would be
-      # unambiguous.
-      elements.sort! do |a, b|
-        if a.timestamp == b.timestamp
-          a.version <=> b.version
-        else
-          a.timestamp <=> b.timestamp
-        end
-      end
-
-      # generate an output element for each operation. note: we avoid looking
-      # at the history because it is simpler - but it would be more correct to
-      # check these assertions.
-      @created = []
-      @modified = []
-      @deleted = []
-
-      elements.each do |elt|
-        if elt.version == 1
-          # first version, so it must be newly-created.
-          @created << elt
-        elsif elt.visible
-          # must be a modify
-          @modified << elt
-        else
-          # if the element isn't visible then it must have been deleted
-          @deleted << elt
-        end
-      end
-
-      respond_to do |format|
-        format.xml
-      end
-    end
-
     ##
     # query changesets by bounding box, time, user or open/closed status.
-    def query
+    def index
       raise OSM::APIBadUserInput, "cannot use order=oldest with time" if params[:time] && params[:order] == "oldest"
 
       # find any bounding box
@@ -168,7 +30,7 @@ module Api
       changesets = conditions_bbox(changesets, bbox)
       changesets = conditions_user(changesets, params["user"], params["display_name"])
       changesets = conditions_time(changesets, params["time"])
-      changesets = conditions_from_to(changesets, params["from"], params["to"])
+      changesets = query_conditions_time(changesets)
       changesets = conditions_open(changesets, params["open"])
       changesets = conditions_closed(changesets, params["closed"])
       changesets = conditions_ids(changesets, params["changesets"])
@@ -181,11 +43,10 @@ module Api
                    end
 
       # limit the result
-      changesets = changesets.limit(result_limit)
+      changesets = query_limit(changesets)
 
       # preload users, tags and comments, and render result
       @changesets = changesets.preload(:user, :changeset_tags, :comments)
-      render "changesets"
 
       respond_to do |format|
         format.xml
@@ -194,23 +55,15 @@ module Api
     end
 
     ##
-    # updates a changeset's tags. none of the changeset's attributes are
-    # user-modifiable, so they will be ignored.
-    #
-    # changesets are not (yet?) versioned, so we don't have to deal with
-    # history tables here. changesets are locked to a single user, however.
-    #
-    # after succesful update, returns the XML of the changeset.
-    def update
-      # request *must* be a PUT.
-      assert_method :put
-
+    # Return XML giving the basic info about the changeset. Does not
+    # return anything about the nodes, ways and relations in the changeset.
+    def show
       @changeset = Changeset.find(params[:id])
-      new_changeset = Changeset.from_xml(request.raw_post)
-
-      check_changeset_consistency(@changeset, current_user)
-      @changeset.update_from(new_changeset, current_user)
-      render "changeset"
+      if params[:include_discussion].presence
+        @comments = @changeset.comments
+        @comments = @comments.unscope(:where => :visible) if params[:show_hidden_comments].presence && can?(:create, :changeset_comment_visibility)
+        @comments = @comments.includes(:author)
+      end
 
       respond_to do |format|
         format.xml
@@ -218,51 +71,35 @@ module Api
       end
     end
 
-    ##
-    # Adds a subscriber to the changeset
-    def subscribe
-      # Check the arguments are sane
-      raise OSM::APIBadUserInput, "No id was given" unless params[:id]
-
-      # Extract the arguments
-      id = params[:id].to_i
-
-      # Find the changeset and check it is valid
-      changeset = Changeset.find(id)
-      raise OSM::APIChangesetAlreadySubscribedError, changeset if changeset.subscribers.exists?(current_user.id)
+    # Create a changeset from XML.
+    def create
+      cs = Changeset.from_xml(request.raw_post, :create => true)
 
-      # Add the subscriber
-      changeset.subscribers << current_user
+      # Assume that Changeset.from_xml has thrown an exception if there is an error parsing the xml
+      cs.user = current_user
+      cs.save_with_tags!
 
-      # Return a copy of the updated changeset
-      @changeset = changeset
-      render "changeset"
+      # Subscribe user to changeset comments
+      cs.subscribers << current_user
 
-      respond_to do |format|
-        format.xml
-        format.json
-      end
+      render :plain => cs.id.to_s
     end
 
     ##
-    # Removes a subscriber from the changeset
-    def unsubscribe
-      # Check the arguments are sane
-      raise OSM::APIBadUserInput, "No id was given" unless params[:id]
-
-      # Extract the arguments
-      id = params[:id].to_i
-
-      # Find the changeset and check it is valid
-      changeset = Changeset.find(id)
-      raise OSM::APIChangesetNotSubscribedError, changeset unless changeset.subscribers.exists?(current_user.id)
-
-      # Remove the subscriber
-      changeset.subscribers.delete(current_user)
+    # updates a changeset's tags. none of the changeset's attributes are
+    # user-modifiable, so they will be ignored.
+    #
+    # changesets are not (yet?) versioned, so we don't have to deal with
+    # history tables here. changesets are locked to a single user, however.
+    #
+    # after succesful update, returns the XML of the changeset.
+    def update
+      @changeset = Changeset.find(params[:id])
+      new_changeset = Changeset.from_xml(request.raw_post)
 
-      # Return a copy of the updated changeset
-      @changeset = changeset
-      render "changeset"
+      check_changeset_consistency(@changeset, current_user)
+      @changeset.update_from(new_changeset, current_user)
+      render "show"
 
       respond_to do |format|
         format.xml
@@ -279,7 +116,6 @@ module Api
     ##
     # if a bounding box was specified do some sanity checks.
     # restrict changesets to those enclosed by a bounding box
-    # we need to return both the changesets and the bounding box
     def conditions_bbox(changesets, bbox)
       if bbox
         bbox.check_boundaries
@@ -325,7 +161,7 @@ module Api
           raise OSM::APINotFoundError if current_user.nil? || current_user != u
         end
 
-        changesets.where(:user_id => u.id)
+        changesets.where(:user => u)
       end
     end
 
@@ -346,7 +182,7 @@ module Api
         changesets.where("closed_at >= ? and created_at <= ?", from, to)
       else
         # if there is no comma, assume its a lower limit on time
-        changesets.where("closed_at >= ?", Time.parse(time).utc)
+        changesets.where(:closed_at => Time.parse(time).utc..)
       end
       # stupid Time seems to throw both of these for bad parsing, so
       # we have to catch both and ensure the correct code path is taken.
@@ -354,33 +190,6 @@ module Api
       raise OSM::APIBadUserInput, e.message.to_s
     end
 
-    ##
-    # restrict changesets to those opened during a particular time period
-    # works similar to from..to of notes controller, including the requirement of 'from' when specifying 'to'
-    def conditions_from_to(changesets, from, to)
-      if from
-        begin
-          from = Time.parse(from).utc
-        rescue ArgumentError
-          raise OSM::APIBadUserInput, "Date #{from} is in a wrong format"
-        end
-
-        begin
-          to = if to
-                 Time.parse(to).utc
-               else
-                 Time.now.utc
-               end
-        rescue ArgumentError
-          raise OSM::APIBadUserInput, "Date #{to} is in a wrong format"
-        end
-
-        changesets.where(:created_at => from..to)
-      else
-        changesets
-      end
-    end
-
     ##
     # return changesets which are open (haven't been closed yet)
     # we do this by seeing if the 'closed at' time is in the future. Also if we've
@@ -420,19 +229,5 @@ module Api
         changesets.where(:id => ids)
       end
     end
-
-    ##
-    # Get the maximum number of results to return
-    def result_limit
-      if params[:limit]
-        if params[:limit].to_i.positive? && params[:limit].to_i <= Settings.max_changeset_query_limit
-          params[:limit].to_i
-        else
-          raise OSM::APIBadUserInput, "Changeset limit must be between 1 and #{Settings.max_changeset_query_limit}"
-        end
-      else
-        Settings.default_changeset_query_limit
-      end
-    end
   end
 end