Migrate attestation files to active_storage

This commit is contained in:
Paul Chavard 2019-08-27 17:42:52 +02:00
parent a194ffa392
commit a19b10b118
11 changed files with 289 additions and 96 deletions

View file

@ -48,8 +48,8 @@ class Admin::AttestationTemplatesController < AdminController
# In a case of a preview, when the user does not change its images, # In a case of a preview, when the user does not change its images,
# the images are not uploaded and thus should be retrieved from previous # the images are not uploaded and thus should be retrieved from previous
# attestation_template # attestation_template
@logo = activated_attestation_params[:logo] || @procedure.attestation_template&.logo @logo = activated_attestation_params[:logo_active_storage] || @procedure.attestation_template&.proxy_logo
@signature = activated_attestation_params[:signature] || @procedure.attestation_template&.signature @signature = activated_attestation_params[:signature_active_storage] || @procedure.attestation_template&.proxy_signature
render 'admin/attestation_templates/show', formats: [:pdf] render 'admin/attestation_templates/show', formats: [:pdf]
end end
@ -57,8 +57,11 @@ class Admin::AttestationTemplatesController < AdminController
def delete_logo def delete_logo
attestation_template = @procedure.attestation_template attestation_template = @procedure.attestation_template
attestation_template.remove_logo! if attestation_template.logo.present?
attestation_template.save attestation_template.remove_logo!
attestation_template.save
end
attestation_template.logo_active_storage.purge_later
flash.notice = 'le logo a bien été supprimée' flash.notice = 'le logo a bien été supprimée'
redirect_to edit_admin_procedure_attestation_template_path(@procedure) redirect_to edit_admin_procedure_attestation_template_path(@procedure)
@ -67,8 +70,11 @@ class Admin::AttestationTemplatesController < AdminController
def delete_signature def delete_signature
attestation_template = @procedure.attestation_template attestation_template = @procedure.attestation_template
attestation_template.remove_signature! if attestation_template.signature.present?
attestation_template.save attestation_template.remove_signature!
attestation_template.save
end
attestation_template.signature_active_storage.purge_later
flash.notice = 'la signature a bien été supprimée' flash.notice = 'la signature a bien été supprimée'
redirect_to edit_admin_procedure_attestation_template_path(@procedure) redirect_to edit_admin_procedure_attestation_template_path(@procedure)
@ -80,11 +86,18 @@ class Admin::AttestationTemplatesController < AdminController
# cache result to avoid multiple uninterlaced computations # cache result to avoid multiple uninterlaced computations
if @activated_attestation_params.nil? if @activated_attestation_params.nil?
@activated_attestation_params = params.require(:attestation_template) @activated_attestation_params = params.require(:attestation_template)
.permit(:title, :body, :footer, :signature) .permit(:title, :body, :footer)
.merge(activated: true) .merge(activated: true)
@activated_attestation_params[:logo] = uninterlaced_png(params['attestation_template']['logo']) logo_file = params['attestation_template'].delete('logo')
@activated_attestation_params[:signature] = uninterlaced_png(params['attestation_template']['signature']) signature_file = params['attestation_template'].delete('signature')
if logo_file.present?
@activated_attestation_params[:logo_active_storage] = uninterlaced_png(logo_file)
end
if signature_file.present?
@activated_attestation_params[:signature_active_storage] = uninterlaced_png(signature_file)
end
end end
@activated_attestation_params @activated_attestation_params

View file

@ -3,8 +3,12 @@ class Attestation < ApplicationRecord
mount_uploader :pdf, AttestationUploader mount_uploader :pdf, AttestationUploader
has_one_attached :pdf_active_storage
def pdf_url def pdf_url
if Rails.application.secrets.fog[:enabled] if pdf_active_storage.attached?
Rails.application.routes.url_helpers.url_for(pdf_active_storage)
elsif Rails.application.secrets.fog[:enabled]
RemoteDownloader.new(pdf.path).url RemoteDownloader.new(pdf.path).url
elsif pdf&.url elsif pdf&.url
# FIXME: this is horrible but used only in dev and will be removed after migration # FIXME: this is horrible but used only in dev and will be removed after migration

View file

@ -7,14 +7,23 @@ class AttestationTemplate < ApplicationRecord
mount_uploader :logo, AttestationTemplateLogoUploader mount_uploader :logo, AttestationTemplateLogoUploader
mount_uploader :signature, AttestationTemplateSignatureUploader mount_uploader :signature, AttestationTemplateSignatureUploader
validate :logo_signature_file_size has_one_attached :logo_active_storage
has_one_attached :signature_active_storage
validates :footer, length: { maximum: 190 } validates :footer, length: { maximum: 190 }
FILE_MAX_SIZE_IN_MB = 0.5
DOSSIER_STATE = Dossier.states.fetch(:accepte) DOSSIER_STATE = Dossier.states.fetch(:accepte)
def attestation_for(dossier) def attestation_for(dossier)
Attestation.new(title: replace_tags(title, dossier), pdf: build_pdf(dossier)) attestation = Attestation.new(title: replace_tags(title, dossier))
attestation.pdf_active_storage.attach(
io: build_pdf(dossier),
filename: "attestation-dossier-#{dossier.id}.pdf",
content_type: 'application/pdf',
# we don't want to run virus scanner on this file
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
attestation
end end
def unspecified_champs_for_dossier(dossier) def unspecified_champs_for_dossier(dossier)
@ -34,17 +43,83 @@ class AttestationTemplate < ApplicationRecord
end end
def dup def dup
result = AttestationTemplate.new(title: title, body: body, footer: footer, activated: activated) attestation_template = AttestationTemplate.new(title: title, body: body, footer: footer, activated: activated)
if logo.present? if logo_active_storage.attached?
CopyCarrierwaveFile::CopyFileService.new(self, result, :logo).set_file attestation_template.logo_active_storage.attach(
io: StringIO.new(logo_active_storage.download),
filename: logo_active_storage.filename,
content_type: logo_active_storage.content_type,
# we don't want to run virus scanner on duplicated file
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
elsif logo.present?
CopyCarrierwaveFile::CopyFileService.new(self, attestation_template, :logo).set_file
end end
if signature.present? if signature_active_storage.attached?
CopyCarrierwaveFile::CopyFileService.new(self, result, :signature).set_file attestation_template.signature_active_storage.attach(
io: StringIO.new(signature_active_storage.download),
filename: signature_active_storage.filename,
content_type: signature_active_storage.content_type,
# we don't want to run virus scanner on duplicated file
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
elsif signature.present?
CopyCarrierwaveFile::CopyFileService.new(self, attestation_template, :signature).set_file
end end
result attestation_template
end
def logo?
logo_active_storage.attached? || logo.present?
end
def signature?
signature_active_storage.attached? || signature.present?
end
def logo_url
if logo_active_storage.attached?
Rails.application.routes.url_helpers.url_for(logo_active_storage)
elsif logo.present?
if Rails.application.secrets.fog[:enabled]
RemoteDownloader.new(logo.path).url
elsif logo&.url
# FIXME: this is horrible but used only in dev and will be removed after migration
File.join(LOCAL_DOWNLOAD_URL, logo.url)
end
end
end
def signature_url
if signature_active_storage.attached?
Rails.application.routes.url_helpers.url_for(signature_active_storage)
elsif signature.present?
if Rails.application.secrets.fog[:enabled]
RemoteDownloader.new(signature.path).url
elsif signature&.url
# FIXME: this is horrible but used only in dev and will be removed after migration
File.join(LOCAL_DOWNLOAD_URL, signature.url)
end
end
end
def proxy_logo
if logo_active_storage.attached?
logo_active_storage
else
logo
end
end
def proxy_signature
if signature_active_storage.attached?
signature_active_storage
else
signature
end
end end
private private
@ -60,40 +135,17 @@ class AttestationTemplate < ApplicationRecord
.flatten .flatten
end end
def logo_signature_file_size
[:logo, :signature]
.select { |file_name| send(file_name).present? }
.each { |file_name| file_size_check(file_name) }
end
def file_size_check(file_name)
if send(file_name).file.size.to_f > FILE_MAX_SIZE_IN_MB.megabyte.to_f
errors.add(file_name, " : vous ne pouvez pas charger une image de plus de #{number_with_delimiter(FILE_MAX_SIZE_IN_MB, locale: :fr)} Mo")
end
end
def build_pdf(dossier) def build_pdf(dossier)
action_view = ActionView::Base.new(ActionController::Base.view_paths, action_view = ActionView::Base.new(ActionController::Base.view_paths,
logo: logo, logo: proxy_logo,
title: replace_tags(title, dossier), title: replace_tags(title, dossier),
body: replace_tags(body, dossier), body: replace_tags(body, dossier),
signature: signature, signature: proxy_signature,
footer: footer, footer: footer,
created_at: Time.zone.now) created_at: Time.zone.now)
attestation_view = action_view.render(file: 'admin/attestation_templates/show', attestation_view = action_view.render(file: 'admin/attestation_templates/show', formats: [:pdf])
formats: [:pdf])
view_to_memory_file(attestation_view) StringIO.new(attestation_view)
end
def view_to_memory_file(view)
pdf = StringIO.new(view)
def pdf.original_filename
'attestation'
end
pdf
end end
end end

View file

@ -19,7 +19,9 @@ class AttestationUploader < BaseUploader
end end
def filename def filename
"attestation-#{secure_token}.pdf" if file.present?
"attestation-#{secure_token}.pdf"
end
end end
private private

View file

@ -17,11 +17,11 @@
celle-ci est également disponible au téléchargement depuis lespace personnel de lusager. celle-ci est également disponible au téléchargement depuis lespace personnel de lusager.
.image-upload .image-upload
- if @attestation_template.logo.present? - if @attestation_template.logo?
= image_tag @attestation_template.logo.url, class: 'thumbnail' = image_tag @attestation_template.logo_url, class: 'thumbnail'
.form-group .form-group
= f.label :logo, "Logo de l'attestation" = f.label :logo, "Logo de l'attestation"
- if @attestation_template.logo.present? - if @attestation_template.logo?
= link_to 'Supprimer le logo', admin_procedure_attestation_template_logo_path(@procedure), method: :delete = link_to 'Supprimer le logo', admin_procedure_attestation_template_logo_path(@procedure), method: :delete
= f.file_field :logo, accept: 'image/png, image/jpg, image/jpeg' = f.file_field :logo, accept: 'image/png, image/jpg, image/jpeg'
%p.help-block %p.help-block
@ -54,11 +54,11 @@
= tag[:description] = tag[:description]
.image-upload .image-upload
- if @attestation_template.signature.present? - if @attestation_template.signature?
= image_tag @attestation_template.signature.url, class: 'thumbnail' = image_tag @attestation_template.signature_url, class: 'thumbnail'
.form-group .form-group
= f.label :signature, "Tampon de l'attestation" = f.label :signature, "Tampon de l'attestation"
- if @attestation_template.signature.present? - if @attestation_template.signature?
= link_to 'Supprimer le tampon', admin_procedure_attestation_template_signature_path(@procedure), method: :delete = link_to 'Supprimer le tampon', admin_procedure_attestation_template_signature_path(@procedure), method: :delete
= f.file_field :signature, accept: 'image/png, image/jpg, image/jpeg' = f.file_field :signature, accept: 'image/png, image/jpg, image/jpeg'
%p.help-block %p.help-block

View file

@ -30,7 +30,12 @@ prawn_document(margin: [top_margin, right_margin, bottom_margin, left_margin], p
pdf.bounding_box([0, pdf.cursor], width: body_width, height: body_height) do pdf.bounding_box([0, pdf.cursor], width: body_width, height: body_height) do
if @logo.present? if @logo.present?
pdf.image StringIO.new(@logo.read), fit: [max_logo_width , max_logo_height], position: :center logo_file = if @logo.is_a?(ActiveStorage::Attached::One)
@logo.download
else
@logo.read
end
pdf.image StringIO.new(logo_file), fit: [max_logo_width , max_logo_height], position: :center
end end
pdf.fill_color grey pdf.fill_color grey
@ -44,7 +49,12 @@ prawn_document(margin: [top_margin, right_margin, bottom_margin, left_margin], p
if @signature.present? if @signature.present?
pdf.pad_top(40) do pdf.pad_top(40) do
pdf.image StringIO.new(@signature.read), fit: [max_signature_size , max_signature_size], position: :right signature_file = if @signature.is_a?(ActiveStorage::Attached::One)
@signature.download
else
@signature.read
end
pdf.image StringIO.new(signature_file), fit: [max_signature_size , max_signature_size], position: :right
end end
end end
end end

View file

@ -0,0 +1,97 @@
namespace :'2019_08_22_migrate_attestation_files' do
task migrate_attestation_pdf: :environment do
attestations = Attestation.where
.not(pdf: nil)
.left_joins(:pdf_active_storage_attachment)
.where('active_storage_attachments.id IS NULL')
.order(:created_at)
limit = ENV['LIMIT']
if limit
attestations.limit!(limit.to_i)
end
progress = ProgressReport.new(attestations.count)
attestations.find_each do |attestation|
if attestation.pdf.present?
uri = URI.parse(URI.escape(attestation.pdf_url))
response = Typhoeus.get(uri)
if response.success?
filename = attestation.pdf.filename || attestation.pdf_identifier
attestation.pdf_active_storage.attach(
io: StringIO.new(response.body),
filename: filename,
content_type: attestation.pdf.content_type,
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
end
end
progress.inc
end
progress.finish
end
task migrate_attestation_template_logo: :environment do
attestation_templates = AttestationTemplate.where
.not(logo: nil)
.left_joins(:logo_active_storage_attachment)
.where('active_storage_attachments.id IS NULL')
.order(:created_at)
limit = ENV['LIMIT']
if limit
attestation_templates.limit!(limit.to_i)
end
progress = ProgressReport.new(attestation_templates.count)
attestation_templates.find_each do |attestation_template|
if attestation_template.logo.present?
uri = URI.parse(URI.escape(attestation_template.logo_url))
response = Typhoeus.get(uri)
if response.success?
filename = attestation_template.logo.filename || attestation_template.logo_identifier
attestation_template.logo_active_storage.attach(
io: StringIO.new(response.body),
filename: filename,
content_type: attestation_template.logo.content_type,
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
end
end
progress.inc
end
progress.finish
end
task migrate_attestation_template_signature: :environment do
attestation_templates = AttestationTemplate.where
.not(signature: nil)
.left_joins(:signature_active_storage_attachment)
.where('active_storage_attachments.id IS NULL')
.order(:created_at)
limit = ENV['LIMIT']
if limit
attestation_templates.limit!(limit.to_i)
end
progress = ProgressReport.new(attestation_templates.count)
attestation_templates.find_each do |attestation_template|
if attestation_template.signature.present?
uri = URI.parse(URI.escape(attestation_template.signature_url))
response = Typhoeus.get(uri)
if response.success?
filename = attestation_template.signature.filename || attestation_template.signature_identifier
attestation_template.signature_active_storage.attach(
io: StringIO.new(response.body),
filename: filename,
content_type: attestation_template.signature.content_type,
metadata: { virus_scan_result: ActiveStorage::VirusScanner::SAFE }
)
end
end
progress.inc
end
progress.finish
end
end

View file

@ -87,8 +87,8 @@ describe Admin::AttestationTemplatesController, type: :controller do
it { expect(procedure.attestation_template).to have_attributes(attestation_params) } it { expect(procedure.attestation_template).to have_attributes(attestation_params) }
it { expect(procedure.attestation_template.activated).to be true } it { expect(procedure.attestation_template.activated).to be true }
it { expect(procedure.attestation_template.logo.read).to eq(logo2.read) } it { expect(procedure.attestation_template.logo_active_storage.download).to eq(logo2.read) }
it { expect(procedure.attestation_template.signature.read).to eq(signature2.read) } it { expect(procedure.attestation_template.signature_active_storage.download).to eq(signature2.read) }
it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) } it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) }
it { expect(flash.notice).to eq("L'attestation a bien été sauvegardée") } it { expect(flash.notice).to eq("L'attestation a bien été sauvegardée") }
@ -130,8 +130,8 @@ describe Admin::AttestationTemplatesController, type: :controller do
end end
it { expect(procedure.attestation_template).to have_attributes(attestation_params) } it { expect(procedure.attestation_template).to have_attributes(attestation_params) }
it { expect(procedure.attestation_template.logo.read).to eq(logo2.read) } it { expect(procedure.attestation_template.logo_active_storage.download).to eq(logo2.read) }
it { expect(procedure.attestation_template.signature.read).to eq(signature2.read) } it { expect(procedure.attestation_template.signature_active_storage.download).to eq(signature2.read) }
it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) } it { expect(response).to redirect_to edit_admin_procedure_attestation_template_path(procedure) }
it { expect(flash.notice).to eq("L'attestation a bien été modifiée") } it { expect(flash.notice).to eq("L'attestation a bien été modifiée") }

View file

@ -0,0 +1,10 @@
FactoryBot.define do
factory :attestation do
title { 'title' }
dossier { create(:dossier) }
end
trait :with_legacy_pdf do
pdf { Rack::Test::UploadedFile.new("./spec/fixtures/files/dossierPDF.pdf", 'application/pdf') }
end
end

View file

@ -5,4 +5,14 @@ FactoryBot.define do
footer { 'footer' } footer { 'footer' }
activated { true } activated { true }
end end
trait :with_files do
logo_active_storage { Rack::Test::UploadedFile.new("./spec/fixtures/files/logo_test_procedure.png", 'image/png') }
signature_active_storage { Rack::Test::UploadedFile.new("./spec/fixtures/files/logo_test_procedure.png", 'image/png') }
end
trait :with_legacy_files do
logo { Rack::Test::UploadedFile.new("./spec/fixtures/files/logo_test_procedure.png", 'image/png') }
signature { Rack::Test::UploadedFile.new("./spec/fixtures/files/logo_test_procedure.png", 'image/png') }
end
end end

View file

@ -1,44 +1,44 @@
describe AttestationTemplate, type: :model do describe AttestationTemplate, type: :model do
describe 'validate' do # describe 'validate' do
let(:logo_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte } # let(:logo_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte }
let(:signature_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte } # let(:signature_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte }
let(:fake_logo) { double(AttestationTemplateLogoUploader, file: double(size: logo_size)) } # let(:fake_logo) { double(AttestationTemplateLogoUploader, file: double(size: logo_size)) }
let(:fake_signature) { double(AttestationTemplateSignatureUploader, file: double(size: signature_size)) } # let(:fake_signature) { double(AttestationTemplateSignatureUploader, file: double(size: signature_size)) }
let(:attestation_template) { AttestationTemplate.new } # let(:attestation_template) { AttestationTemplate.new }
before do # before do
allow(attestation_template).to receive(:logo).and_return(fake_logo) # allow(attestation_template).to receive(:logo).and_return(fake_logo)
allow(attestation_template).to receive(:signature).and_return(fake_signature) # allow(attestation_template).to receive(:signature).and_return(fake_signature)
attestation_template.validate # attestation_template.validate
end # end
subject { attestation_template.errors.details } # subject { attestation_template.errors.details }
context 'when no files are present' do # context 'when no files are present' do
let(:fake_logo) { nil } # let(:fake_logo) { nil }
let(:fake_signature) { nil } # let(:fake_signature) { nil }
it { is_expected.to match({}) } # it { is_expected.to match({}) }
end # end
context 'when the logo and the signature have the right size' do # context 'when the logo and the signature have the right size' do
it { is_expected.to match({}) } # it { is_expected.to match({}) }
end # end
context 'when the logo and the signature are too heavy' do # context 'when the logo and the signature are too heavy' do
let(:logo_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte + 1 } # let(:logo_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte + 1 }
let(:signature_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte + 1 } # let(:signature_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte + 1 }
it do # it do
expected = { # expected = {
signature: [{ error: ' : vous ne pouvez pas charger une image de plus de 0,5 Mo' }], # signature: [{ error: ' : vous ne pouvez pas charger une image de plus de 0,5 Mo' }],
logo: [{ error: ' : vous ne pouvez pas charger une image de plus de 0,5 Mo' }] # logo: [{ error: ' : vous ne pouvez pas charger une image de plus de 0,5 Mo' }]
} # }
is_expected.to match(expected) # is_expected.to match(expected)
end # end
end # end
end # end
describe 'validates footer length' do describe 'validates footer length' do
let(:attestation_template) { AttestationTemplate.new(footer: footer) } let(:attestation_template) { AttestationTemplate.new(footer: footer) }
@ -140,11 +140,6 @@ describe AttestationTemplate, type: :model do
let(:attestation) { attestation_template.attestation_for(dossier) } let(:attestation) { attestation_template.attestation_for(dossier) }
it 'provides a pseudo file' do
expect(attestation.pdf.file).to exist
expect(attestation.pdf.filename).to start_with('attestation')
end
context 'when the procedure has a type de champ named libelleA et libelleB' do context 'when the procedure has a type de champ named libelleA et libelleB' do
let(:types_de_champ) do let(:types_de_champ) do
[ [