From 1009053258cea675c4033a1ceca14bf297fb21ad Mon Sep 17 00:00:00 2001 From: Chris Dinger Date: Mon, 11 Jan 2021 14:56:53 -0600 Subject: [PATCH 1/3] Add basic study API This adds two simple study endpoints to allow data integrations to be built outside of StudyFinder: - GET /api/studies/:id will return a JSON representation of a study - PATCH /api/studies/:id will update an existing study Right now, updateable fields are limited to: - contact_override - contact_override_first_name - contact_override_last_name - irb_number - pi_id - pi_name - recruiting - simple_description - brief_title - visible API endpoints use simple token authentication via HTTP headers. Tokens are defined int he new ApiKey model. An admin page has not yet been built for this model. For now, tokens can be created and managed via the console. --- app/controllers/api/studies_controller.rb | 34 ++++++++++++ app/controllers/api_controller.rb | 15 ++++++ app/models/api_key.rb | 7 +++ config/routes.rb | 4 ++ db/migrate/20210108230551_create_api_keys.rb | 10 ++++ db/schema.rb | 9 +++- .../api/studies_controller_spec.rb | 52 +++++++++++++++++++ spec/models/api_key_spec.rb | 12 +++++ 8 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 app/controllers/api/studies_controller.rb create mode 100644 app/controllers/api_controller.rb create mode 100644 app/models/api_key.rb create mode 100644 db/migrate/20210108230551_create_api_keys.rb create mode 100644 spec/controllers/api/studies_controller_spec.rb create mode 100644 spec/models/api_key_spec.rb diff --git a/app/controllers/api/studies_controller.rb b/app/controllers/api/studies_controller.rb new file mode 100644 index 0000000..d1d7db4 --- /dev/null +++ b/app/controllers/api/studies_controller.rb @@ -0,0 +1,34 @@ +class Api::StudiesController < ApiController + def show + @trial = Trial.find_by(system_id: params[:id]) + + render json: @trial + end + + def update + @trial = Trial.find_by(system_id: params[:id]) + + if @trial.update(trial_params) + head 200 + else + render json: { error: @trial.errors }, status: 400 + end + end + + private + + def trial_params + params.permit( + :contact_override, + :contact_override_first_name, + :contact_override_last_name, + :irb_number, + :pi_id, + :pi_name, + :recruiting, + :simple_description, + :brief_title, + :visible + ) + end +end diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb new file mode 100644 index 0000000..98756fc --- /dev/null +++ b/app/controllers/api_controller.rb @@ -0,0 +1,15 @@ +class ApiController < ActionController::API + before_action :restrict_to_authorized_tokens + + private + + def restrict_to_authorized_tokens + unless ApiKey.exists?(token: authorization_token) + head 401 and return + end + end + + def authorization_token + request.headers["Authorization"].to_s.split(" ").last + end +end diff --git a/app/models/api_key.rb b/app/models/api_key.rb new file mode 100644 index 0000000..55dcb0b --- /dev/null +++ b/app/models/api_key.rb @@ -0,0 +1,7 @@ +class ApiKey < ApplicationRecord + validates :name, presence: true + + after_initialize do |api_key| + api_key.token = SecureRandom.base58(32) + end +end diff --git a/config/routes.rb b/config/routes.rb index 183a2a9..6e4407b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -43,6 +43,10 @@ get 'spotlight', controller: 'home', action: 'spotlight', as: :welcome get 'embed', controller: 'search', action: 'embed', as: :embed + namespace :api do + resources :studies, only: [:show, :update] + end + root 'home#index' # Example of regular route: diff --git a/db/migrate/20210108230551_create_api_keys.rb b/db/migrate/20210108230551_create_api_keys.rb new file mode 100644 index 0000000..0999f4c --- /dev/null +++ b/db/migrate/20210108230551_create_api_keys.rb @@ -0,0 +1,10 @@ +class CreateApiKeys < ActiveRecord::Migration[5.2] + def change + create_table :api_keys do |t| + t.string :name + t.string :token + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index cc37b3c..c808498 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_10_06_154045) do +ActiveRecord::Schema.define(version: 2021_01_08_230551) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -36,6 +36,13 @@ t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true end + create_table "api_keys", force: :cascade do |t| + t.string "name" + t.string "token" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "showcase_items", force: :cascade do |t| t.string "name" t.string "title" diff --git a/spec/controllers/api/studies_controller_spec.rb b/spec/controllers/api/studies_controller_spec.rb new file mode 100644 index 0000000..0b58791 --- /dev/null +++ b/spec/controllers/api/studies_controller_spec.rb @@ -0,0 +1,52 @@ +require "rails_helper" + +describe Api::StudiesController do + context "unauthenticated requests" do + it "are rejected" do + get :show, params: { id: "NCT123" } + expect(response).to have_http_status(401) + end + end + + context "authenticated requests" do + before do + api_key = ApiKey.create!(name: "blah") + request.headers["Authorization"] = "bearer #{api_key.token}" + end + + it "can read studies" do + study = Trial.create!(system_id: "NCT123") + + get :show, params: { id: study.system_id } + + expect(response).to have_http_status(200) + end + + it "can update studies" do + study = Trial.create!(system_id: "NCT345") + attributes_to_update = { + contact_override: "blah@example.com", + contact_override_first_name: "Testy", + contact_override_last_name: "McTesterson", + irb_number: "1234567890", + pi_id: "somepi@example.com", + pi_name: "Some PI, M.D.", + recruiting: true, + simple_description: "This is a short description", + brief_title: "This is a brief title", + visible: true + } + + patch :update, params: attributes_to_update.merge(id: "NCT345") + + expect(response).to have_http_status(200) + + study.reload + + attributes_to_update.each do |attribute, value| + expect(study[attribute]).to eq(value) + end + end + end +end + diff --git a/spec/models/api_key_spec.rb b/spec/models/api_key_spec.rb new file mode 100644 index 0000000..0acfbd9 --- /dev/null +++ b/spec/models/api_key_spec.rb @@ -0,0 +1,12 @@ +require 'rails_helper' + +describe ApiKey do + it "should generate a token when created" do + expect(subject.token.length).to eq(32) + end + + it "should require a name" do + subject.save + expect(subject.errors).to have_key(:name) + end +end From a9314a03b8fefc5f48d44d7227ae2494e26ebbfa Mon Sep 17 00:00:00 2001 From: Chris Dinger Date: Mon, 11 Jan 2021 22:26:58 -0600 Subject: [PATCH 2/3] Add all studies API endpoint Most integrations will want to know all of the NCT numbers that currently exist in Study Finder. The common pattern is to pull data from clinicaltrials.gov and then augment those studies with data from other systems. This add an index action the the API studies controller. This is a naive first implementation (some implementations might have too much data for this to be practical without paging or limiting the fields returned). The idea is to try this out and change when/if needed. --- app/controllers/api/studies_controller.rb | 4 ++++ config/routes.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/studies_controller.rb b/app/controllers/api/studies_controller.rb index d1d7db4..f194565 100644 --- a/app/controllers/api/studies_controller.rb +++ b/app/controllers/api/studies_controller.rb @@ -1,4 +1,8 @@ class Api::StudiesController < ApiController + def index + render json: Trial.all + end + def show @trial = Trial.find_by(system_id: params[:id]) diff --git a/config/routes.rb b/config/routes.rb index 6e4407b..7ccf5b7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -44,7 +44,7 @@ get 'embed', controller: 'search', action: 'embed', as: :embed namespace :api do - resources :studies, only: [:show, :update] + resources :studies, only: [:index, :show, :update] end root 'home#index' From 606ea098a6049e9a31f9cf915d7b407b8468d7ed Mon Sep 17 00:00:00 2001 From: Chris Dinger Date: Thu, 21 Jan 2021 10:07:07 -0600 Subject: [PATCH 3/3] Add overall_status to study API --- app/controllers/api/studies_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/studies_controller.rb b/app/controllers/api/studies_controller.rb index f194565..0eed304 100644 --- a/app/controllers/api/studies_controller.rb +++ b/app/controllers/api/studies_controller.rb @@ -23,15 +23,16 @@ def update def trial_params params.permit( + :brief_title, :contact_override, :contact_override_first_name, :contact_override_last_name, :irb_number, + :overall_status, :pi_id, :pi_name, :recruiting, :simple_description, - :brief_title, :visible ) end