app/controllers/openstax/api/v1/api_controller.rb in openstax_api-0.0.1 vs app/controllers/openstax/api/v1/api_controller.rb in openstax_api-0.1.0

- old
+ new

@@ -28,13 +28,25 @@ options[:url_base].to_s "#{url_base}/#{options[:url_end] || ''}" end - def self.json_schema(representer, options={}) - RepresentableSchemaPrinter.json(representer, options) + # TODO doorkeeper users (or rather users who have doorkeeper + # applications) need to agree to API terms of use (need to have agreed + # to it at one time, can't require them to agree when terms change since + # their apps are doing the talking) -- this needs more thought + + def current_user + @current_user ||= doorkeeper_token ? + User.find(doorkeeper_token.resource_owner_id) : + super + # TODO maybe freak out if current user is anonymous (require we know + # who person/app is so we can do things like throttling, API terms + # agreement, etc) end + + protected def rescue_from_exception(exception) # See https://github.com/rack/rack/blob/master/lib/rack/utils.rb#L453 for error names/symbols @@ -52,11 +64,11 @@ error = :not_found notify = false end if notify -# Not yet in OSU + # TODO: Not yet in OSU =begin ExceptionNotifier.notify_exception( exception, env: request.env, data: { message: "An exception occurred" } @@ -64,9 +76,137 @@ =end Rails.logger.error("An exception occurred: #{exception.message}\n\n#{exception.backtrace.join("\n")}") \ end head error + end + + def self.json_schema(representer, options={}) + RepresentableSchemaPrinter.json(representer, options) + end + + # A hack at a conversion from a Representer to a series of Apipie declarations + # Can call it like any Apipie DSL method, + # + # example "blah" + # representer Api::V1::ExerciseRepresenter + # def update ... + # + def self.representer(representer) + representer.representable_attrs.each do |attr| + schema_info = attr.options[:schema_info] || {} + param attr.name, (attr.options[:type] || Object), required: schema_info[:required] + end + end + + def get_representer(represent_with, model=nil) + return nil if represent_with.nil? + if represent_with.is_a? Proc + represent_with.call(model) + else + represent_with + end + end + + def rest_get(model_klass, id, represent_with=nil) + @model = model_klass.find(id) + raise SecurityTransgression unless current_user.can_read?(@model) + respond_with @model, represent_with: get_representer(represent_with, @model) + end + + def rest_update(model_klass, id, represent_with=nil) + @model = model_klass.find(id) + raise SecurityTransgression unless current_user.can_update?(@model) + consume!(@model, represent_with: get_representer(represent_with, @model)) + + if @model.save + head :no_content + else + render json: @model.errors, status: :unprocessable_entity + end + end + + def rest_create(model_klass) + @model = model_klass.new() + + # Unlike the implications of the representable README, "consume!" can + # actually make changes to the database. See http://goo.gl/WVLBqA. + # We do want to consume before checking the permissions so we can know + # what we're dealing with, but if user doesn't have permission we don't + # want to have changed the DB. Wrap in a transaction to protect ourselves. + + model_klass.transaction do + consume!(@model) + raise SecurityTransgression unless current_user.can_create?(@model) + end + + if @model.save + respond_with @model + else + render json: @model.errors, status: :unprocessable_entity + end + end + + def rest_destroy(model_klass, id) + @model = model_klass.find(id) + raise SecurityTransgression unless current_user.can_destroy?(@model) + + if @model.destroy + head :no_content + else + render json: @model.errors, status: :unprocessable_entity + end + end + + def standard_sort(model_klass) + # take array of all IDs or hash of id => position, + # regardless build up an array of all IDs in the right order and pass those to sort + + new_positions = params['newPositions'] + return head :no_content if new_positions.length == 0 + + # Can't have duplicate positions or IDs + unique_ids = new_positions.collect{|np| np['id']}.uniq + unique_positions = new_positions.collect{|np| np['position']}.uniq + + return head :bad_request if unique_ids.length != new_positions.length + return head :bad_request if unique_positions.length != new_positions.length + + first = model_klass.where(:id => new_positions[0]['id']).first + + return head :not_found if first.blank? + + originalOrdered = first.me_and_peers.ordered.all + + originalOrdered.each do |item| + raise SecurityTransgression unless item.send(:container_column) == originalOrdered[0].send(:container_column) \ + if item.respond_to?(:container_column) + raise SecurityTransgression unless current_user.can_sort?(item) + end + + originalOrderedIds = originalOrdered.collect{|sc| sc.id} + + newOrderedIds = Array.new(originalOrderedIds.size) + + new_positions.each do |newPosition| + id = newPosition['id'].to_i + newOrderedIds[newPosition['position']] = id + originalOrderedIds.delete(id) + end + + ptr = 0 + for oldId in originalOrderedIds + while !newOrderedIds[ptr].nil?; ptr += 1; end + newOrderedIds[ptr] = oldId + end + + begin + model_klass.sort!(newOrderedIds) + rescue Exception => e + return head :internal_server_error + end + + head :no_content end end end \ No newline at end of file