From 33e7e4145f126d99dd86eafcc3513a0f66c09346 Mon Sep 17 00:00:00 2001 From: Tim Cowlishaw Date: Sat, 31 Aug 2024 14:36:05 +0200 Subject: [PATCH] Beginnings of API v1 Move shared code into top level application controller, add Devices#show and Devices#index using new presentations --- app/controllers/application_api_controller.rb | 72 ++++++++++++++++++ app/controllers/v0/application_controller.rb | 73 +------------------ app/controllers/v1/application_controller.rb | 4 + app/controllers/v1/devices_controller.rb | 35 +++++++++ app/views/v001/devices/_device.jbuilder | 37 ---------- .../v001/devices/current_user_index.jbuilder | 1 - app/views/v001/devices/index.jbuilder | 3 - app/views/v001/users/show.jbuilder | 42 ----------- config/routes.rb | 4 + 9 files changed, 116 insertions(+), 155 deletions(-) create mode 100644 app/controllers/application_api_controller.rb create mode 100644 app/controllers/v1/application_controller.rb create mode 100644 app/controllers/v1/devices_controller.rb delete mode 100644 app/views/v001/devices/_device.jbuilder delete mode 100644 app/views/v001/devices/current_user_index.jbuilder delete mode 100644 app/views/v001/devices/index.jbuilder delete mode 100644 app/views/v001/users/show.jbuilder diff --git a/app/controllers/application_api_controller.rb b/app/controllers/application_api_controller.rb new file mode 100644 index 00000000..56cf7c43 --- /dev/null +++ b/app/controllers/application_api_controller.rb @@ -0,0 +1,72 @@ +class ApplicationAPIController < ActionController::API + + include ActionController::ImplicitRender + include ActionController::Helpers + include ActionController::HttpAuthentication::Basic::ControllerMethods + include ActionController::HttpAuthentication::Token::ControllerMethods + include ActionController::Caching + + include Pundit::Authorization + include PrettyJSON + include ErrorHandlers + + include SharedControllerMethods + + include ::UserHelper + helper ::UserHelper + + include ::PresentationHelper + helper ::PresentationHelper + + before_action :set_rate_limit_whitelist + after_action :verify_authorized, except: :index + respond_to :json + + private + + def set_rate_limit_whitelist + if current_user(false)&.is_admin_or_researcher? + Rack::Attack.cache.write("throttle_whitelist_#{request.remote_ip}", true, 5.minutes) + end + end + + def check_if_authorized! + if current_user.nil? + if params[:access_token] + raise Smartcitizen::Unauthorized.new("Invalid OAuth2 Params") + else + raise Smartcitizen::Unauthorized.new("Authorization required") + end + end + end + + def raise_ransack_errors_as_bad_request(&block) + begin + block.call + rescue ArgumentError => e + render json: { message: e.message, status: 400 }, status: 400 + end + end + + def check_missing_params *params_list + missing_params = [] + params_list.each do |param| + individual_params = param.split("||") + missing_params << individual_params.join(" OR ") unless (params.keys & individual_params).any? + end + raise ActionController::ParameterMissing.new(missing_params.to_sentence) if missing_params.any? + end + + def check_date_param_format(param_name) + return true if !params[param_name] + return true if params[param_name] =~ /^\d+$/ + begin + Time.parse(params[param_name]) + return true + rescue + message = "The #{param_name} parameter must be an ISO8601 format date or datetime or an integer number of seconds since the start of the UNIX epoch." + render json: { message: message, status: 400 }, status: 400 + return false + end + end +end diff --git a/app/controllers/v0/application_controller.rb b/app/controllers/v0/application_controller.rb index 34753a1a..16f505a4 100644 --- a/app/controllers/v0/application_controller.rb +++ b/app/controllers/v0/application_controller.rb @@ -1,83 +1,12 @@ -require_relative '../../helpers/user_helper' module V0 - class ApplicationController < ActionController::API - - include ActionController::HttpAuthentication::Basic::ControllerMethods - include ActionController::HttpAuthentication::Token::ControllerMethods - include ActionController::Helpers - include ActionController::ImplicitRender - include ActionController::Caching - - include PrettyJSON - include ErrorHandlers - - helper ::UserHelper - include ::UserHelper - - include SharedControllerMethods - - helper ::PresentationHelper - include ::PresentationHelper - - respond_to :json - + class ApplicationController < ::ApplicationAPIController before_action :prepend_view_paths - before_action :set_rate_limit_whitelist - after_action :verify_authorized, except: :index - - protected - - def check_missing_params *params_list - missing_params = [] - params_list.each do |param| - individual_params = param.split("||") - missing_params << individual_params.join(" OR ") unless (params.keys & individual_params).any? - end - raise ActionController::ParameterMissing.new(missing_params.to_sentence) if missing_params.any? - end - - def check_date_param_format(param_name) - return true if !params[param_name] - return true if params[param_name] =~ /^\d+$/ - begin - Time.parse(params[param_name]) - return true - rescue - message = "The #{param_name} parameter must be an ISO8601 format date or datetime or an integer number of seconds since the start of the UNIX epoch." - render json: { message: message, status: 400 }, status: 400 - return false - end - end private - def raise_ransack_errors_as_bad_request(&block) - begin - block.call - rescue ArgumentError => e - render json: { message: e.message, status: 400 }, status: 400 - end - end - def prepend_view_paths # is this still necessary? prepend_view_path "app/views/v0" end - - def set_rate_limit_whitelist - if current_user(false)&.is_admin_or_researcher? - Rack::Attack.cache.write("throttle_whitelist_#{request.remote_ip}", true, 5.minutes) - end - end - - def check_if_authorized! - if current_user.nil? - if params[:access_token] - raise Smartcitizen::Unauthorized.new("Invalid OAuth2 Params") - else - raise Smartcitizen::Unauthorized.new("Authorization required") - end - end - end end end diff --git a/app/controllers/v1/application_controller.rb b/app/controllers/v1/application_controller.rb new file mode 100644 index 00000000..579dbfe2 --- /dev/null +++ b/app/controllers/v1/application_controller.rb @@ -0,0 +1,4 @@ +module V1 + class ApplicationController < ::ApplicationAPIController + end +end diff --git a/app/controllers/v1/devices_controller.rb b/app/controllers/v1/devices_controller.rb new file mode 100644 index 00000000..0377fe7f --- /dev/null +++ b/app/controllers/v1/devices_controller.rb @@ -0,0 +1,35 @@ +module V1 + class DevicesController < ApplicationController + def show + @device = Device.includes( + :owner,:tags, {sensors: :measurement}).find(params[:id]) + authorize @device + render json: present(@device) + end + + #TODO Document breaking API change as detailed in https://github.com/fablabbcn/smartcitizen-api/issues/186 + def index + raise_ransack_errors_as_bad_request do + @q = policy_scope(Device) + .includes(:owner, :tags, :components, {sensors: :measurement}) + .ransack(params[:q], auth_object: (current_user&.is_admin? ? :admin : nil)) + + @devices = @q.result(distinct: true) + + if params[:near] + if params[:near] =~ /\A(\-?\d+(\.\d+)?),\s*(\-?\d+(\.\d+)?)\z/ + @devices = @devices.near( + params[:near].split(','), (params[:within] || 1000)) + else + return render json: { id: "bad_request", + message: "Malformed near parameter", + url: 'https://developer.smartcitizen.me/#get-all-devices', + errors: nil }, status: :bad_request + end + end + @devices = paginate(@devices) + render json: present(@devices) + end + end + end +end diff --git a/app/views/v001/devices/_device.jbuilder b/app/views/v001/devices/_device.jbuilder deleted file mode 100644 index b1ac8807..00000000 --- a/app/views/v001/devices/_device.jbuilder +++ /dev/null @@ -1,37 +0,0 @@ -json.(device, - :id, - :description, - :city, - :country, - :exposure -) - -json.merge! elevation: device.elevation.try(:to_f) -json.merge! title: device.name -json.merge! location: device.city -json.merge! geo_lat: device.latitude -json.merge! geo_lng: device.longitude -json.merge! created: device.created_at -json.merge! last_insert_datetime: device.last_insert_datetime #Device.find(device.id).last_reading_at - - -# last_insert_datetime - -# { -# "devices": [ -# { -# "id": "24", -# "title": "Pral2a", -# "description": "Test", -# "location": "Barcelona", -# "city": "Barcelona", -# "country": "Spain", -# "exposure": "outdoor", -# "elevation": "100.0", -# "geo_lat": "41.383180", -# "geo_long": "2.157960", -# "created": "2013-04-24 18:09:05", -# "last_insert_datetime": "2013-05-16 11:44:56" -# } -# ] -# } \ No newline at end of file diff --git a/app/views/v001/devices/current_user_index.jbuilder b/app/views/v001/devices/current_user_index.jbuilder deleted file mode 100644 index 4dfd8558..00000000 --- a/app/views/v001/devices/current_user_index.jbuilder +++ /dev/null @@ -1 +0,0 @@ -json.array! @devices, partial: 'device', as: :device diff --git a/app/views/v001/devices/index.jbuilder b/app/views/v001/devices/index.jbuilder deleted file mode 100644 index 64af761e..00000000 --- a/app/views/v001/devices/index.jbuilder +++ /dev/null @@ -1,3 +0,0 @@ -json.devices do - json.array! @devices, partial: 'device', as: :device -end diff --git a/app/views/v001/users/show.jbuilder b/app/views/v001/users/show.jbuilder deleted file mode 100644 index d916dfa6..00000000 --- a/app/views/v001/users/show.jbuilder +++ /dev/null @@ -1,42 +0,0 @@ -json.me do - json.(@user, - :id, - :username, - :city, - :email, - :location - ) - - json.merge! country: @user.country_name - json.merge! website: nil - json.merge! created: @user.created_at - - json.devices @user.devices, partial: 'v001/devices/device', as: :device - -end - -# "me": { -# "id": "5", -# "username": "Guillem", -# "city": "Barcelona", -# "country": "Spain", -# "website": "", -# "email": "g8i113m@gmail.com", -# "created": "2013-04-23 00:34:13", -# "devices": [ -# { -# "id": "24", -# "title": "Pral2a", -# "description": "Test", -# "location": "Barcelona", -# "city": "Barcelona", -# "country": "Spain", -# "exposure": "outdoor", -# "elevation": "100.0", -# "geo_lat": "41.383180", -# "geo_long": "2.157960", -# "created": "2013-04-24 18:09:05", -# "last_insert_datetime": "2013-05-16 11:44:56" -# } -# ] -# } \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index e84ea258..2bb48487 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -21,6 +21,10 @@ get 'password_reset/:token', to: 'sessions#password_reset_landing', as: 'password_reset' end + api_version(module: "V1", path: { value: "v1" }, header: {name: "Accept", value: "application/vnd.smartcitizen; version=1"}, defaults: { format: :json }) do + resources :devices + end + api_version(module: "V0", path: {value: "v0"}, header: {name: "Accept", value: "application/vnd.smartcitizen; version=0"}, default: true, defaults: { format: :json }) do # devices resources :devices do