Skip to content

Commit b748147

Browse files
committed
Fix importing attachments within processes/assemblies
1 parent 3b9ef1c commit b748147

4 files changed

Lines changed: 270 additions & 28 deletions

File tree

decidim-assemblies/app/serializers/decidim/assemblies/assembly_importer.rb

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -84,27 +84,24 @@ def import_folders_and_attachments(attachments)
8484
next
8585
end
8686

87-
begin
88-
file_tmp = URI.parse(url).open
89-
rescue OpenURI::HTTPError, Errno::ENOENT, Errno::ECONNREFUSED, SocketError, Net::OpenTimeout, Net::ReadTimeout => e
90-
@warnings << I18n.t(
91-
"decidim.assemblies.admin.imports.attachment_error",
92-
title: attachment_title(file),
93-
error: format_error(e)
94-
)
95-
next
96-
end
97-
9887
Decidim.traceability.perform_action!("create", Attachment, @user) do
9988
attachment = Attachment.new(
10089
title: file["title"],
10190
description: file["description"],
102-
content_type: file_tmp.content_type,
10391
attached_to: @imported_assembly,
10492
weight: file["weight"],
105-
file: file_tmp, # Define attached_to before this
106-
file_size: file_tmp.size
93+
content_type: fetch_content_type(url)
10794
)
95+
begin
96+
attachment.attached_uploader(:file).remote_url = url
97+
rescue OpenURI::HTTPError, Errno::ENOENT, Errno::ECONNREFUSED, SocketError, Net::OpenTimeout, Net::ReadTimeout => e
98+
@warnings << I18n.t(
99+
"decidim.assemblies.admin.imports.attachment_error",
100+
title: attachment_title(file),
101+
error: format_error(e)
102+
)
103+
next
104+
end
108105
attachment.create_attachment_collection(file["attachment_collection"])
109106
attachment.save!
110107
attachment
@@ -199,6 +196,22 @@ def format_error(error)
199196
message = message.presence || error.message
200197
"#{code} #{message}"
201198
end
199+
200+
def fetch_content_type(url)
201+
return nil if url.blank?
202+
203+
uri = URI.parse(url)
204+
http_connection = Net::HTTP.new(uri.host, uri.port)
205+
http_connection.use_ssl = true if uri.scheme == "https"
206+
http_connection.start do |http|
207+
response = http.head(uri.request_uri)
208+
return nil unless response.is_a?(Net::HTTPSuccess)
209+
210+
response["Content-Type"]&.split(";")&.first
211+
end
212+
rescue StandardError
213+
nil
214+
end
202215
end
203216
end
204217
end

decidim-assemblies/spec/serializers/decidim/assemblies/assembly_importer_spec.rb

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,114 @@ module Decidim::Assemblies
311311
expect(importer.warnings).to include(a_string_matching(/The attachment "Test File" could not be imported \(500 Internal Server Error\)\./i))
312312
end
313313
end
314+
315+
context "when remote file is accessible and downloadable (PDF)" do
316+
let(:remote_file_url) { "http://example.com/document.pdf" }
317+
318+
before do
319+
stub_request(:head, remote_file_url)
320+
.to_return(status: 200, headers: { "Content-Type" => "application/pdf" })
321+
stub_request(:get, remote_file_url)
322+
.to_return(status: 200, body: File.read(Decidim::Dev.asset("Exampledocument.pdf")))
323+
end
324+
325+
it "successfully imports the attachment" do
326+
expect { importer.import_folders_and_attachments(attachments_data) }
327+
.to change(Decidim::Attachment, :count).by(1)
328+
end
329+
330+
it "attaches the file to the assembly" do
331+
importer.import_folders_and_attachments(attachments_data)
332+
attachment = Decidim::Attachment.last
333+
expect(attachment.file).to be_attached
334+
expect(attachment.file.filename.to_s).to eq("document.pdf")
335+
end
336+
337+
it "sets the content type automatically" do
338+
importer.import_folders_and_attachments(attachments_data)
339+
attachment = Decidim::Attachment.last
340+
expect(attachment.content_type).to eq("application/pdf")
341+
end
342+
343+
it "sets the file size automatically" do
344+
importer.import_folders_and_attachments(attachments_data)
345+
attachment = Decidim::Attachment.last
346+
expect(attachment.file_size).to be_present
347+
end
348+
349+
it "has no warnings" do
350+
importer.import_folders_and_attachments(attachments_data)
351+
expect(importer.warnings).to be_empty
352+
end
353+
end
354+
355+
context "when remote file is accessible and downloadable (image)" do
356+
let(:remote_file_url) { "http://example.com/image.jpg" }
357+
358+
before do
359+
stub_request(:head, remote_file_url)
360+
.to_return(status: 200, headers: { "Content-Type" => "image/jpeg" })
361+
stub_request(:get, remote_file_url)
362+
.to_return(status: 200, body: File.read(Decidim::Dev.asset("city.jpeg")))
363+
end
364+
365+
it "successfully imports the attachment" do
366+
expect { importer.import_folders_and_attachments(attachments_data) }
367+
.to change(Decidim::Attachment, :count).by(1)
368+
end
369+
370+
it "attaches the file to the assembly" do
371+
importer.import_folders_and_attachments(attachments_data)
372+
attachment = Decidim::Attachment.last
373+
expect(attachment.file).to be_attached
374+
end
375+
376+
it "sets the content type automatically" do
377+
importer.import_folders_and_attachments(attachments_data)
378+
attachment = Decidim::Attachment.last
379+
expect(attachment.content_type).to eq("image/jpeg")
380+
end
381+
382+
it "has no warnings" do
383+
importer.import_folders_and_attachments(attachments_data)
384+
expect(importer.warnings).to be_empty
385+
end
386+
end
387+
388+
context "when remote file URL is blank" do
389+
let(:attachments_data) do
390+
{
391+
"files" => [
392+
{
393+
"title" => { "en" => "Test File" },
394+
"description" => { "en" => "Test Description" },
395+
"weight" => 1,
396+
"remote_file_url" => ""
397+
}
398+
],
399+
"attachment_collections" => []
400+
}
401+
end
402+
403+
it "does not create any attachments" do
404+
expect { importer.import_folders_and_attachments(attachments_data) }
405+
.not_to change(Decidim::Attachment, :count)
406+
end
407+
end
408+
409+
context "when files array is nil" do
410+
let(:attachments_data) do
411+
{
412+
"files" => nil,
413+
"attachment_collections" => []
414+
}
415+
end
416+
417+
it "does not create any attachments" do
418+
expect { importer.import_folders_and_attachments(attachments_data) }
419+
.not_to change(Decidim::Attachment, :count)
420+
end
421+
end
314422
end
315423
end
316424
end

decidim-participatory_processes/app/serializers/decidim/participatory_processes/participatory_process_importer.rb

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -106,27 +106,24 @@ def import_folders_and_attachments(attachments)
106106
next
107107
end
108108

109-
begin
110-
file_tmp = URI.parse(url).open
111-
rescue OpenURI::HTTPError, Errno::ENOENT, Errno::ECONNREFUSED, SocketError, Net::OpenTimeout, Net::ReadTimeout => e
112-
@warnings << I18n.t(
113-
"decidim.participatory_processes.admin.imports.attachment_error",
114-
title: attachment_title(file),
115-
error: format_error(e)
116-
)
117-
next
118-
end
119-
120109
Decidim.traceability.perform_action!("create", Attachment, @user) do
121110
attachment = Attachment.new(
122111
title: file["title"],
123112
description: file["description"],
124-
content_type: file_tmp.content_type,
125113
attached_to: @imported_process,
126114
weight: file["weight"],
127-
file: file_tmp, # Define attached_to before this
128-
file_size: file_tmp.size
115+
content_type: fetch_content_type(url)
129116
)
117+
begin
118+
attachment.attached_uploader(:file).remote_url = url
119+
rescue OpenURI::HTTPError, Errno::ENOENT, Errno::ECONNREFUSED, SocketError, Net::OpenTimeout, Net::ReadTimeout => e
120+
@warnings << I18n.t(
121+
"decidim.participatory_processes.admin.imports.attachment_error",
122+
title: attachment_title(file),
123+
error: format_error(e)
124+
)
125+
next
126+
end
130127
attachment.create_attachment_collection(file["attachment_collection"])
131128
attachment.save!
132129
attachment
@@ -226,6 +223,22 @@ def format_error(error)
226223
message = message.presence || error.message
227224
"#{code} #{message}"
228225
end
226+
227+
def fetch_content_type(url)
228+
return nil if url.blank?
229+
230+
uri = URI.parse(url)
231+
http_connection = Net::HTTP.new(uri.host, uri.port)
232+
http_connection.use_ssl = true if uri.scheme == "https"
233+
http_connection.start do |http|
234+
response = http.head(uri.request_uri)
235+
return nil unless response.is_a?(Net::HTTPSuccess)
236+
237+
response["Content-Type"]&.split(";")&.first
238+
end
239+
rescue StandardError
240+
nil
241+
end
229242
end
230243
end
231244
end

decidim-participatory_processes/spec/serializers/decidim/participatory_processes/participatory_process_importer_spec.rb

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,114 @@ module Decidim::ParticipatoryProcesses
387387
expect(importer.warnings).to include(a_string_matching(/The attachment "Test File" could not be imported \(500 Internal Server Error\)\./i))
388388
end
389389
end
390+
391+
context "when remote file is accessible and downloadable (PDF)" do
392+
let(:remote_file_url) { "http://example.com/document.pdf" }
393+
394+
before do
395+
stub_request(:head, remote_file_url)
396+
.to_return(status: 200, headers: { "Content-Type" => "application/pdf" })
397+
stub_request(:get, remote_file_url)
398+
.to_return(status: 200, body: File.read(Decidim::Dev.asset("Exampledocument.pdf")))
399+
end
400+
401+
it "successfully imports the attachment" do
402+
expect { importer.import_folders_and_attachments(attachments_data) }
403+
.to change(Decidim::Attachment, :count).by(1)
404+
end
405+
406+
it "attaches the file to the process" do
407+
importer.import_folders_and_attachments(attachments_data)
408+
attachment = Decidim::Attachment.last
409+
expect(attachment.file).to be_attached
410+
expect(attachment.file.filename.to_s).to eq("document.pdf")
411+
end
412+
413+
it "sets the content type automatically" do
414+
importer.import_folders_and_attachments(attachments_data)
415+
attachment = Decidim::Attachment.last
416+
expect(attachment.content_type).to eq("application/pdf")
417+
end
418+
419+
it "sets the file size automatically" do
420+
importer.import_folders_and_attachments(attachments_data)
421+
attachment = Decidim::Attachment.last
422+
expect(attachment.file_size).to be_present
423+
end
424+
425+
it "has no warnings" do
426+
importer.import_folders_and_attachments(attachments_data)
427+
expect(importer.warnings).to be_empty
428+
end
429+
end
430+
431+
context "when remote file is accessible and downloadable (image)" do
432+
let(:remote_file_url) { "http://example.com/image.jpg" }
433+
434+
before do
435+
stub_request(:head, remote_file_url)
436+
.to_return(status: 200, headers: { "Content-Type" => "image/jpeg" })
437+
stub_request(:get, remote_file_url)
438+
.to_return(status: 200, body: File.read(Decidim::Dev.asset("city.jpeg")))
439+
end
440+
441+
it "successfully imports the attachment" do
442+
expect { importer.import_folders_and_attachments(attachments_data) }
443+
.to change(Decidim::Attachment, :count).by(1)
444+
end
445+
446+
it "attaches the file to the process" do
447+
importer.import_folders_and_attachments(attachments_data)
448+
attachment = Decidim::Attachment.last
449+
expect(attachment.file).to be_attached
450+
end
451+
452+
it "sets the content type automatically" do
453+
importer.import_folders_and_attachments(attachments_data)
454+
attachment = Decidim::Attachment.last
455+
expect(attachment.content_type).to eq("image/jpeg")
456+
end
457+
458+
it "has no warnings" do
459+
importer.import_folders_and_attachments(attachments_data)
460+
expect(importer.warnings).to be_empty
461+
end
462+
end
463+
464+
context "when remote file URL is blank" do
465+
let(:attachments_data) do
466+
{
467+
"files" => [
468+
{
469+
"title" => { "en" => "Test File" },
470+
"description" => { "en" => "Test Description" },
471+
"weight" => 1,
472+
"remote_file_url" => ""
473+
}
474+
],
475+
"attachment_collections" => []
476+
}
477+
end
478+
479+
it "does not create any attachments" do
480+
expect { importer.import_folders_and_attachments(attachments_data) }
481+
.not_to change(Decidim::Attachment, :count)
482+
end
483+
end
484+
485+
context "when files array is nil" do
486+
let(:attachments_data) do
487+
{
488+
"files" => nil,
489+
"attachment_collections" => []
490+
}
491+
end
492+
493+
it "does not create any attachments" do
494+
expect { importer.import_folders_and_attachments(attachments_data) }
495+
.not_to change(Decidim::Attachment, :count)
496+
end
497+
end
390498
end
391499
end
392500
end

0 commit comments

Comments
 (0)