Skip to content

Commit 4d526e4

Browse files
authored
Merge pull request #20 from CreatekIO/REW-2045-file-upload-restrictions-csv-uploads-on-the-admin-area
Rew 2045 file upload restrictions csv uploads on the admin area
2 parents 7ab7863 + 6d94812 commit 4d526e4

9 files changed

Lines changed: 184 additions & 22 deletions

File tree

app/controllers/concerns/csv2db/controller_helpers.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def enqueue_csv_import_and_redirect(klass, options = {}, &block)
3636
end
3737

3838
def enqueue_csv_import(klass, options = {})
39-
permitted_params = options.fetch(:params) do
39+
permitted_params = options.fetch(:params) do
4040
params.require(klass.model_name.param_key).permit(
4141
:file,
4242
*options[:extra_params]
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
module Csv2db::ActiveStorageAdapter
2+
require 'active_support/all'
3+
4+
extend ActiveSupport::Concern
5+
FILE_TYPE = 'text/csv'.freeze
6+
LINK_MAX_EXPIRY = 7.days.to_s.freeze
7+
8+
included do
9+
has_one_attached Csv2db.config.file_attachment_name
10+
11+
validate :check_file_extension
12+
13+
alias_method :file_attachment, Csv2db.config.file_attachment_name
14+
end
15+
16+
def file=(file)
17+
# Override Dragonfly setter method
18+
return unless file.present?
19+
20+
filename = file.original_filename
21+
22+
file_attachment.attach(
23+
io: File.open(file),
24+
filename: filename,
25+
content_type: file.content_type
26+
)
27+
28+
self.file_name = filename
29+
end
30+
31+
def expiring_link(expires_in: LINK_MAX_EXPIRY)
32+
return unless file_attachment.present?
33+
34+
set_current_host
35+
36+
file_attachment.service_url(expires_in: expires_in.to_i, disposition: 'attachment')
37+
end
38+
39+
private
40+
41+
def set_current_host
42+
return unless %i[test local].include?(Rails.application.config.active_storage.service)
43+
44+
ActiveStorage::Current.host = ReportGenerator.config.local_storage_host
45+
end
46+
47+
def check_file_extension
48+
# very basic check of file extension
49+
errors.add(:file, I18n.t('shared.file_processor.incorrect_file_type')) unless file_attachment.blob.content_type == FILE_TYPE
50+
end
51+
52+
def file_data
53+
return @file_data if @file_data.present?
54+
55+
file_attachment.blob.open do |blob|
56+
@file_data = str_to_utf8(blob.read)
57+
end
58+
59+
byte_order_mark = Csv2db::Import::BYTE_ORDER_MARK
60+
@file_data.sub!(byte_order_mark, '') if @file_data.starts_with?(byte_order_mark)
61+
62+
@file_data
63+
end
64+
end
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module Csv2db::DragonflyAdapter
2+
extend ActiveSupport::Concern
3+
require 'dragonfly'
4+
5+
included do
6+
extend Dragonfly::Model
7+
8+
dragonfly_accessor :file
9+
10+
validates :file, presence: true
11+
validate :check_file_extension
12+
end
13+
14+
private
15+
16+
def check_file_extension
17+
# very basic check of file extension
18+
errors.add(:file, I18n.t('shared.file_processor.incorrect_file_type')) unless file.ext == 'csv'
19+
end
20+
21+
def file_data
22+
file_data = str_to_utf8(file.data)
23+
byte_order_mark = Csv2db::Import::BYTE_ORDER_MARK
24+
file_data.sub!(byte_order_mark, '') if file_data.starts_with?(byte_order_mark)
25+
file_data
26+
end
27+
end

app/models/concerns/csv2db/import.rb

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,9 @@ def around_process(*args, &block)
2727
end
2828

2929
included do
30-
extend Dragonfly::Model
30+
include Module.const_get("Csv2db::#{Csv2db.config.storage_adapter.camelize.constantize}Adapter")
3131

32-
validates :file, presence: true
3332
validate :required_params_are_present
34-
validate :check_file_extension
35-
36-
dragonfly_accessor :file
3733

3834
after_initialize :set_default_values, :set_required_params
3935

@@ -128,10 +124,14 @@ def method_missing(method, *args, &block)
128124
end
129125
end
130126

127+
def respond_to_missing?(method, include_private = false)
128+
method.to_s.start_with?('param_') || super
129+
end
130+
131131
private
132132

133133
def check_file_contains_data
134-
error(I18n.t('shared.file_processor.insufficient_rows')) unless file.data.present? && csv.count > 0
134+
error(I18n.t('shared.file_processor.insufficient_rows')) unless csv.headers.present? && csv.count.positive?
135135
stop if errors?
136136
end
137137

@@ -165,12 +165,6 @@ def csv
165165
@csv ||= CSV.parse(file_data, headers: true)
166166
end
167167

168-
def file_data
169-
file_data = str_to_utf_8(file.data)
170-
file_data.sub!(BYTE_ORDER_MARK, '') if file_data.starts_with?(BYTE_ORDER_MARK)
171-
file_data
172-
end
173-
174168
def required_params_are_present
175169
return if @required_params.empty?
176170

@@ -183,7 +177,7 @@ def required_params_are_present
183177
end
184178

185179
def log(message, level = :info)
186-
log_messages << { message: str_to_utf_8(message), level: level, time: Time.now }
180+
log_messages << { message: str_to_utf8(message), level: level, time: Time.now }
187181
end
188182

189183
def error(message)
@@ -203,16 +197,16 @@ def set_default_values
203197
self.status ||= Status::PENDING
204198
end
205199

206-
def str_to_utf_8(str)
207-
CharlockHolmes::Converter.convert(str, str.detect_encoding[:encoding], 'UTF-8')
200+
def str_to_utf8(str)
201+
CharlockHolmes::Converter.convert(str, str_encoding(str), 'UTF-8')
208202
end
209203

210-
def set_required_params
211-
@required_params = []
204+
def str_encoding(str)
205+
str.detect_encoding[:encoding]
212206
end
213207

214-
def check_file_extension
215-
errors.add(:file, I18n.t('shared.file_processor.incorrect_file_type')) unless file.ext == 'csv'
208+
def set_required_params
209+
@required_params = []
216210
end
217211
end
218212
end

app/views/csv2db/_csv_import_table.html.haml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
%tr
1616
%td= import.id
1717
%td= import.created_at
18-
%td= link_to import.file.name, import.file.url, target: '_blank'
18+
- if Csv2db.config.storage_adapter.active_storage?
19+
%td= link_to import.file_name, import.expiring_link
20+
- else
21+
%td= link_to import.file.name, import.file.url, target: '_blank'
1922
%td
2023
.badge{ class: "badge-upload-#{import.status}" }= import.status
2124
%td

lib/csv2db.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
require 'csv2db/version'
2+
require 'csv2db/config'
23

34
module Csv2db
45
class Error < StandardError; end
5-
# Your code goes here...
6+
7+
class << self
8+
def config
9+
Config.instance
10+
end
11+
12+
def configure
13+
yield(config)
14+
end
15+
end
616
end
717

818
require 'csv2db/rails' if defined?(Rails)

lib/csv2db/config.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
require 'singleton'
2+
3+
module Csv2db
4+
class Config
5+
include Singleton
6+
7+
attr_writer :storage_adapter, :local_storage_host, :file_attachment_name
8+
9+
def storage_adapter
10+
@storage_adapter ||= :dragonfly
11+
ActiveSupport::StringInquirer.new(@storage_adapter.to_s)
12+
end
13+
14+
def local_storage_host
15+
@local_storage_host ||= ''
16+
end
17+
18+
def file_attachment_name
19+
@file_attachment_name ||= :file_attachment
20+
end
21+
end
22+
end

spec/models/concerns/csv2db/import_spec.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,44 @@ class TestModel < ActiveRecord::Base
104104
expect(subject.errors?).to be_truthy
105105
end
106106
end
107+
108+
context 'ActiveStorageAdapter' do
109+
let(:file) do
110+
Rack::Test::UploadedFile.new(Tempfile.new)
111+
end
112+
113+
let(:attachment_spy) do
114+
spy('file_attachment')
115+
end
116+
117+
subject do
118+
TestModel.new
119+
end
120+
121+
before do
122+
allow(TestModel).to receive(:has_one_attached)
123+
allow(TestModel).to receive(:alias_method)
124+
.with(:file_attachment, :file_attachment).and_return(nil)
125+
TestModel.include(Csv2db::ActiveStorageAdapter)
126+
allow(subject).to receive(:file_attachment).and_return(attachment_spy)
127+
end
128+
129+
it 'calls correct attach methods' do
130+
expect(file).to receive(:original_filename)
131+
expect(file).to receive(:content_type)
132+
expect(attachment_spy).to receive(:attach)
133+
134+
subject.file = file
135+
end
136+
137+
it 'sets the file_name on the model' do
138+
subject.file = file
139+
140+
expect(subject.file_name).to eq(file.original_filename)
141+
end
142+
143+
it 'returns nil if no file passed' do
144+
expect(subject.file = nil).to eq(nil)
145+
end
146+
end
107147
end

spec/spec_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
require 'csv2db'
77
require_relative '../app/models/concerns/csv2db/import'
88
require_relative '../app/workers/csv2db/import_worker'
9+
require_relative '../app/models/concerns/csv2db/dragonfly_adapter'
10+
require_relative '../app/models/concerns/csv2db/active_storage_adapter'
911

1012
ENV['RAILS_ENV'] ||= 'test'
1113

0 commit comments

Comments
 (0)