diff --git a/README.md b/README.md index 2d9e4e0c4..38cd9d9af 100644 --- a/README.md +++ b/README.md @@ -297,6 +297,47 @@ cache: Of course, you can force a refresh at any time. +### Sharing (optional) + +You can generate sharing urls for queries. You can download up-to-date results for each query in CSV directly. + +This is useful for scripts or for automatic importing into spreadsheets. + +There are 2 steps necessary for setting up sharing: + +1. Configuring an API key +2. Make the sharing endpoint accessible in your routes + +First configure an API key in `blazer.yml`: + +```yml +sharing: + api_key: 'secret' +``` + +Alternatively you can set the `BLAZER_DOWNLOAD_API_KEY` ENV var which blazer uses by default. + +Now routes: we assume you have secured blazer so you will need to expose a new route outside of the mount. + +The default path for shares is `/blazer_share`. You can change this in `blazer.yml`: + +```yml +sharing: + path: /another_path +``` + +This config is only so that blazer can generate the correct url. + +Now add this route to your `routes.rb`: + +```ruby + get Blazer.sharing.route_path, to: Blazer.sharing.to_controller if Blazer.sharing.enabled? +``` + +Now restart your server and each query page will have a `share` button which will open up a modal that allows you to copy sharing urls. + +Each url has a unique token based on a hash of the query's id and the API key, so the token can't be reused for other queries. + ## Charts Blazer will automatically generate charts based on the types of the columns returned in your query. diff --git a/app/controllers/blazer/queries_controller.rb b/app/controllers/blazer/queries_controller.rb index ce0130d79..78fb458c3 100644 --- a/app/controllers/blazer/queries_controller.rb +++ b/app/controllers/blazer/queries_controller.rb @@ -84,6 +84,15 @@ def show def edit end + def share + return render_forbidden unless params[:token] && params[:query_id] + + @query = Query.find_by(id: params[:query_id]) if params[:query_id] + return render_forbidden unless @query.correct_token?(params[:token]) + + run + end + def run @query = Query.find_by(id: params[:query_id]) if params[:query_id] @@ -92,7 +101,8 @@ def run data_source ||= params[:data_source] @data_source = Blazer.data_sources[data_source] - @statement = Blazer::Statement.new(params[:statement], @data_source) + sql_statement = params[:statement] || @query.statement + @statement = Blazer::Statement.new(sql_statement, @data_source) # before process_vars @cohort_analysis = @statement.cohort_analysis? diff --git a/app/models/blazer/query.rb b/app/models/blazer/query.rb index b603f2bbb..5a13bcf57 100644 --- a/app/models/blazer/query.rb +++ b/app/models/blazer/query.rb @@ -1,5 +1,7 @@ module Blazer class Query < Record + has_secure_token :secret_token, length: 36 + belongs_to :creator, optional: true, class_name: Blazer.user_class.to_s if Blazer.user_class has_many :checks, dependent: :destroy has_many :dashboard_queries, dependent: :destroy @@ -15,6 +17,10 @@ def to_param [id, name].compact.join("-").gsub("'", "").parameterize end + def correct_token?(token) + ActiveSupport::SecurityUtils.secure_compare(secret_token, token) + end + def friendly_name name.to_s.sub(/\A[#\*]/, "").gsub(/\[.+\]/, "").strip end diff --git a/app/views/blazer/queries/_sharing_modal.html.erb b/app/views/blazer/queries/_sharing_modal.html.erb new file mode 100644 index 000000000..f47641a64 --- /dev/null +++ b/app/views/blazer/queries/_sharing_modal.html.erb @@ -0,0 +1,25 @@ +