X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/80be122d6b73044f5e3f740b5c38a38d3b3f79d7..c2aeaa729c350a33ec47ae1b012e50a6685ac2d2:/services/api/app/controllers/application_controller.rb diff --git a/services/api/app/controllers/application_controller.rb b/services/api/app/controllers/application_controller.rb index 05d12427e6..4b13fca1de 100644 --- a/services/api/app/controllers/application_controller.rb +++ b/services/api/app/controllers/application_controller.rb @@ -1,5 +1,6 @@ class ApplicationController < ActionController::Base include CurrentApiClient + include ThemesForRails::ActionController respond_to :json protect_from_forgery @@ -10,6 +11,7 @@ class ApplicationController < ActionController::Base before_filter :catch_redirect_hint before_filter :load_where_param, :only => :index + before_filter :load_filters_param, :only => :index before_filter :find_objects_for_index, :only => :index before_filter :find_object_by_uuid, :except => [:index, :create, :render_error, @@ -19,6 +21,8 @@ class ApplicationController < ActionController::Base :render_error, :render_not_found] + theme :select_theme + attr_accessor :resource_attrs def index @@ -35,22 +39,16 @@ class ApplicationController < ActionController::Base def create @object = model_class.new resource_attrs - if @object.save - show - else - render_error "Save failed" - end + @object.save! + show end def update attrs_to_update = resource_attrs.reject { |k,v| [:kind, :etag, :href].index k } - if @object.update_attributes attrs_to_update - show - else - render_error "Update failed" - end + @object.update_attributes! attrs_to_update + show end def destroy @@ -87,7 +85,9 @@ class ApplicationController < ActionController::Base def render_error(e) logger.error e.inspect - logger.error e.backtrace.collect { |x| x + "\n" }.join('') if e.backtrace + if e.respond_to? :backtrace and e.backtrace + logger.error e.backtrace.collect { |x| x + "\n" }.join('') + end if @object and @object.errors and @object.errors.full_messages and not @object.errors.full_messages.empty? errors = @object.errors.full_messages else @@ -111,24 +111,88 @@ class ApplicationController < ActionController::Base @where = params[:where] elsif params[:where].is_a? String begin - @where = Oj.load(params[:where], symbol_keys: true) + @where = Oj.load(params[:where]) + raise unless @where.is_a? Hash rescue raise ArgumentError.new("Could not parse \"where\" param as an object") end end + @where = @where.with_indifferent_access + end + + def load_filters_param + @filters ||= [] + if params[:filters].is_a? Array + @filters += params[:filters] + elsif params[:filters].is_a? String and !params[:filters].empty? + begin + f = Oj.load params[:filters] + raise unless f.is_a? Array + @filters += f + rescue + raise ArgumentError.new("Could not parse \"filters\" param as an array") + end + end end def find_objects_for_index @objects ||= model_class.readable_by(current_user) - if !@where.empty? + apply_where_limit_order_params + end + + def apply_where_limit_order_params + if @filters.is_a? Array and @filters.any? + cond_out = [] + param_out = [] + @filters.each do |attr, operator, operand| + if !model_class.searchable_columns(operator).index attr.to_s + raise ArgumentError.new("Invalid attribute '#{attr}' in condition") + end + case operator.downcase + when '=', '<', '<=', '>', '>=', 'like' + if operand.is_a? String + cond_out << "#{table_name}.#{attr} #{operator} ?" + if (# any operator that operates on value rather than + # representation: + operator.match(/[<=>]/) and + model_class.attribute_column(attr).type == :datetime) + operand = Time.parse operand + end + param_out << operand + end + when 'in' + if operand.is_a? Array + cond_out << "#{table_name}.#{attr} IN (?)" + param_out << operand + end + when 'is_a' + operand = [operand] unless operand.is_a? Array + cond = [] + operand.each do |op| + cl = ArvadosModel::kind_class op + if cl + cond << "#{table_name}.#{attr} like ?" + param_out << cl.uuid_like_pattern + else + cond << "1=0" + end + end + cond_out << cond.join(' OR ') + end + end + if cond_out.any? + @objects = @objects.where(cond_out.join(' AND '), *param_out) + end + end + if @where.is_a? Hash and @where.any? conditions = ['1=1'] @where.each do |attr,value| - if attr == :any + if attr.to_s == 'any' if value.is_a?(Array) and - value[0] == 'contains' and - model_class.columns.collect(&:name).index('name') then + value.length == 2 and + value[0] == 'contains' then ilikes = [] - model_class.searchable_columns.each do |column| + model_class.searchable_columns('ilike').each do |column| ilikes << "#{table_name}.#{column} ilike ?" conditions << "%#{value[1]}%" end @@ -142,8 +206,13 @@ class ApplicationController < ActionController::Base conditions[0] << " and #{table_name}.#{attr} is ?" conditions << nil elsif value.is_a? Array - conditions[0] << " and #{table_name}.#{attr} in (?)" - conditions << value + if value[0] == 'contains' and value.length == 2 + conditions[0] << " and #{table_name}.#{attr} like ?" + conditions << "%#{value[1]}%" + else + conditions[0] << " and #{table_name}.#{attr} in (?)" + conditions << value + end elsif value.is_a? String or value.is_a? Fixnum or value == true or value == false conditions[0] << " and #{table_name}.#{attr}=?" conditions << value @@ -164,15 +233,31 @@ class ApplicationController < ActionController::Base where(*conditions) end end + if params[:limit] begin - @objects = @objects.limit(params[:limit].to_i) + @limit = params[:limit].to_i + rescue + raise ArgumentError.new("Invalid value for limit parameter") + end + else + @limit = 100 + end + @objects = @objects.limit(@limit) + + orders = [] + + if params[:offset] + begin + @objects = @objects.offset(params[:offset].to_i) + @offset = params[:offset].to_i rescue raise ArgumentError.new("Invalid value for limit parameter") end else - @objects = @objects.limit(100) + @offset = 0 end + orders = [] if params[:order] params[:order].split(',').each do |order| @@ -259,7 +344,7 @@ class ApplicationController < ActionController::Base if supplied_token api_client_auth = ApiClientAuthorization. includes(:api_client, :user). - where('api_token=? and (expires_at is null or expires_at > now())', supplied_token). + where('api_token=? and (expires_at is null or expires_at > CURRENT_TIMESTAMP)', supplied_token). first if api_client_auth.andand.user session[:user_id] = api_client_auth.user.id @@ -267,6 +352,9 @@ class ApplicationController < ActionController::Base session[:api_client_authorization_id] = api_client_auth.id user = api_client_auth.user api_client = api_client_auth.api_client + else + # Token seems valid, but points to a non-existent (deleted?) user. + api_client_auth = nil end elsif session[:user_id] user = User.find(session[:user_id]) rescue nil @@ -355,12 +443,14 @@ class ApplicationController < ActionController::Base :kind => "arvados##{(@response_resource_name || resource_name).camelize(:lower)}List", :etag => "", :self_link => "", - :next_page_token => "", - :next_link => "", + :offset => @offset, + :limit => @limit, :items => @objects.as_api_response(nil) } if @objects.respond_to? :except - @object_list[:items_available] = @objects.except(:limit).count + @object_list[:items_available] = @objects. + except(:limit).except(:offset). + count(:id, distinct: true) end render json: @object_list end @@ -378,25 +468,32 @@ class ApplicationController < ActionController::Base def self._index_requires_parameters { + filters: { type: 'array', required: false }, where: { type: 'object', required: false }, order: { type: 'string', required: false } } end - + def client_accepts_plain_text_stream (request.headers['Accept'].split(' ') & ['text/plain', '*/*']).count > 0 end def render *opts - response = opts.first[:json] - if response.is_a?(Hash) && - params[:_profile] && - Thread.current[:request_starttime] - response[:_profile] = { - request_time: Time.now - Thread.current[:request_starttime] - } + if opts.first + response = opts.first[:json] + if response.is_a?(Hash) && + params[:_profile] && + Thread.current[:request_starttime] + response[:_profile] = { + request_time: Time.now - Thread.current[:request_starttime] + } + end end super *opts end + + def select_theme + return Rails.configuration.arvados_theme + end end