6 changed files with 162 additions and 0 deletions
			
			
		@ -0,0 +1,18 @@
					 | 
				
			||||
class CsvDownloadsController < ApplicationController | 
				
			||||
  before_action :authenticate_user! | 
				
			||||
 | 
				
			||||
  def download | 
				
			||||
    csv_download = CsvDownload.find(params[:id]) | 
				
			||||
    authorize csv_download | 
				
			||||
 | 
				
			||||
    downloader = Csv::Downloader.new(csv_download:) | 
				
			||||
 | 
				
			||||
    if Rails.env.development? | 
				
			||||
      downloader.call | 
				
			||||
      send_file downloader.path, filename: csv_download.filename, type: "text/csv" | 
				
			||||
    else | 
				
			||||
      presigned_url = downloader.presigned_url | 
				
			||||
      redirect_to presigned_url, allow_other_host: true | 
				
			||||
    end | 
				
			||||
  end | 
				
			||||
end | 
				
			||||
@ -0,0 +1,12 @@
					 | 
				
			||||
class CsvDownloadPolicy | 
				
			||||
  attr_reader :current_user, :csv_download | 
				
			||||
 | 
				
			||||
  def initialize(current_user, csv_download) | 
				
			||||
    @current_user = current_user | 
				
			||||
    @csv_download = csv_download | 
				
			||||
  end | 
				
			||||
 | 
				
			||||
  def download? | 
				
			||||
    @current_user == @csv_download.user || @current_user.support? || @current_user.organisation == @csv_download.organisation | 
				
			||||
  end | 
				
			||||
end | 
				
			||||
@ -0,0 +1,50 @@
					 | 
				
			||||
class Csv::Downloader | 
				
			||||
  attr_reader :csv_download | 
				
			||||
 | 
				
			||||
  delegate :path, to: :file | 
				
			||||
 | 
				
			||||
  def initialize(csv_download:) | 
				
			||||
    @csv_download = csv_download | 
				
			||||
  end | 
				
			||||
 | 
				
			||||
  def call | 
				
			||||
    download | 
				
			||||
  end | 
				
			||||
 | 
				
			||||
  def delete_local_file! | 
				
			||||
    file.unlink | 
				
			||||
  end | 
				
			||||
 | 
				
			||||
  def presigned_url | 
				
			||||
    s3_storage_service.get_presigned_url(csv_download.filename, 60, response_content_disposition: "attachment; filename=#{csv_download.filename}") | 
				
			||||
  end | 
				
			||||
 | 
				
			||||
private | 
				
			||||
 | 
				
			||||
  def download | 
				
			||||
    io = storage_service.get_file_io(csv_download.filename) | 
				
			||||
    file.write(io.read) | 
				
			||||
    io.close | 
				
			||||
    file.close | 
				
			||||
  end | 
				
			||||
 | 
				
			||||
  def file | 
				
			||||
    @file ||= Tempfile.new | 
				
			||||
  end | 
				
			||||
 | 
				
			||||
  def storage_service | 
				
			||||
    @storage_service ||= if FeatureToggle.upload_enabled? | 
				
			||||
                           s3_storage_service | 
				
			||||
                         else | 
				
			||||
                           local_disk_storage_service | 
				
			||||
                         end | 
				
			||||
  end | 
				
			||||
 | 
				
			||||
  def s3_storage_service | 
				
			||||
    Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"]) | 
				
			||||
  end | 
				
			||||
 | 
				
			||||
  def local_disk_storage_service | 
				
			||||
    Storage::LocalDiskService.new | 
				
			||||
  end | 
				
			||||
end | 
				
			||||
@ -0,0 +1,8 @@
					 | 
				
			||||
FactoryBot.define do | 
				
			||||
  factory :csv_download do | 
				
			||||
    download_type { "lettings" } | 
				
			||||
    user { create(:user) } | 
				
			||||
    organisation { user.organisation } | 
				
			||||
    filename { "lettings.csv" } | 
				
			||||
  end | 
				
			||||
end | 
				
			||||
@ -0,0 +1,68 @@
					 | 
				
			||||
require "rails_helper" | 
				
			||||
 | 
				
			||||
RSpec.describe CsvDownloadsController, type: :request do | 
				
			||||
  describe "GET #download" do | 
				
			||||
    let(:csv_user) { create(:user) } | 
				
			||||
    let(:csv_download) { create(:csv_download, user: csv_user, organisation: csv_user.organisation) } | 
				
			||||
    let(:get_file_io) do | 
				
			||||
      io = StringIO.new | 
				
			||||
      io.write("hello") | 
				
			||||
      io.rewind | 
				
			||||
      io | 
				
			||||
    end | 
				
			||||
    let(:mock_storage_service) { instance_double(Storage::S3Service, get_file_io:, get_presigned_url: "https://example.com") } | 
				
			||||
 | 
				
			||||
    before do | 
				
			||||
      allow(Storage::S3Service).to receive(:new).and_return(mock_storage_service) | 
				
			||||
    end | 
				
			||||
 | 
				
			||||
    context "when user is not signed in" do | 
				
			||||
      it "redirects to sign in page" do | 
				
			||||
        get "/csv-downloads/#{csv_download.id}/download" | 
				
			||||
        expect(response).to redirect_to("/account/sign-in") | 
				
			||||
      end | 
				
			||||
    end | 
				
			||||
 | 
				
			||||
    context "when user is signed in" do | 
				
			||||
      before do | 
				
			||||
        sign_in user | 
				
			||||
      end | 
				
			||||
 | 
				
			||||
      context "and the user is from a different organisation" do | 
				
			||||
        let(:user) { create(:user) } | 
				
			||||
 | 
				
			||||
        before do | 
				
			||||
          get "/csv-downloads/#{csv_download.id}/download" | 
				
			||||
        end | 
				
			||||
 | 
				
			||||
        it "returns page not found" do | 
				
			||||
          expect(response).to have_http_status(:unauthorized) | 
				
			||||
        end | 
				
			||||
      end | 
				
			||||
 | 
				
			||||
      context "and is the user who generated the csv" do | 
				
			||||
        let(:user) { csv_user } | 
				
			||||
 | 
				
			||||
        before do | 
				
			||||
          get "/csv-downloads/#{csv_download.id}/download" | 
				
			||||
        end | 
				
			||||
 | 
				
			||||
        it "allows downloading the csv" do | 
				
			||||
          expect(response).to have_http_status(:found) | 
				
			||||
        end | 
				
			||||
      end | 
				
			||||
 | 
				
			||||
      context "and is the user is from the same organisation" do | 
				
			||||
        let(:user) { create(:user, organisation: csv_user.organisation) } | 
				
			||||
 | 
				
			||||
        before do | 
				
			||||
          get "/csv-downloads/#{csv_download.id}/download" | 
				
			||||
        end | 
				
			||||
 | 
				
			||||
        it "allows downloading the csv" do | 
				
			||||
          expect(response).to have_http_status(:found) | 
				
			||||
        end | 
				
			||||
      end | 
				
			||||
    end | 
				
			||||
  end | 
				
			||||
end | 
				
			||||
					Loading…
					
					
				
		Reference in new issue