Merge pull request #4223 from tchak/migrate-attestation-files

Migrate attestation files to active_storage
This commit is contained in:
Paul Chavard 2019-08-27 17:56:39 +02:00 committed by GitHub
commit 6dc5657ba3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 435 additions and 99 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,
# the images are not uploaded and thus should be retrieved from previous
# attestation_template
@logo = activated_attestation_params[:logo] || @procedure.attestation_template&.logo
@signature = activated_attestation_params[:signature] || @procedure.attestation_template&.signature
@logo = activated_attestation_params[:logo_active_storage] || @procedure.attestation_template&.proxy_logo
@signature = activated_attestation_params[:signature_active_storage] || @procedure.attestation_template&.proxy_signature
render 'admin/attestation_templates/show', formats: [:pdf]
end
@ -57,8 +57,11 @@ class Admin::AttestationTemplatesController < AdminController
def delete_logo
attestation_template = @procedure.attestation_template
attestation_template.remove_logo!
attestation_template.save
if attestation_template.logo.present?
attestation_template.remove_logo!
attestation_template.save
end
attestation_template.logo_active_storage.purge_later
flash.notice = 'le logo a bien été supprimée'
redirect_to edit_admin_procedure_attestation_template_path(@procedure)
@ -67,8 +70,11 @@ class Admin::AttestationTemplatesController < AdminController
def delete_signature
attestation_template = @procedure.attestation_template
attestation_template.remove_signature!
attestation_template.save
if attestation_template.signature.present?
attestation_template.remove_signature!
attestation_template.save
end
attestation_template.signature_active_storage.purge_later
flash.notice = 'la signature a bien été supprimée'
redirect_to edit_admin_procedure_attestation_template_path(@procedure)
@ -80,11 +86,18 @@ class Admin::AttestationTemplatesController < AdminController
# cache result to avoid multiple uninterlaced computations
if @activated_attestation_params.nil?
@activated_attestation_params = params.require(:attestation_template)
.permit(:title, :body, :footer, :signature)
.permit(:title, :body, :footer)
.merge(activated: true)
@activated_attestation_params[:logo] = uninterlaced_png(params['attestation_template']['logo'])
@activated_attestation_params[:signature] = uninterlaced_png(params['attestation_template']['signature'])
logo_file = params['attestation_template'].delete('logo')
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
@activated_attestation_params

View file

@ -14,11 +14,22 @@ module Instructeurs
after_action :mark_annotations_privees_as_read, only: [:annotations_privees, :update_annotations]
def attestation
send_data(dossier.attestation.pdf.read, filename: 'attestation.pdf', type: 'application/pdf')
if dossier.attestation.pdf_active_storage.attached?
redirect_to url_for(dossier.attestation.pdf_active_storage)
else
send_data(dossier.attestation.pdf.read, filename: 'attestation.pdf', type: 'application/pdf')
end
end
def apercu_attestation
send_data(dossier.build_attestation.pdf.read, filename: 'apercu_attestation.pdf', disposition: 'inline', type: 'application/pdf')
@title = dossier.procedure.attestation_template.title
@body = dossier.procedure.attestation_template.body
@footer = dossier.procedure.attestation_template.footer
@created_at = Time.zone.now
@logo = dossier.procedure.attestation_template&.proxy_logo
@signature = dossier.procedure.attestation_template&.proxy_signature
render 'admin/attestation_templates/show', formats: [:pdf]
end
def show

View file

@ -48,7 +48,11 @@ module Users
end
def attestation
send_data(dossier.attestation.pdf.read, filename: 'attestation.pdf', type: 'application/pdf')
if dossier.attestation.pdf_active_storage.attached?
redirect_to url_for(dossier.attestation.pdf_active_storage)
else
send_data(dossier.attestation.pdf.read, filename: 'attestation.pdf', type: 'application/pdf')
end
end
def identite

View file

@ -3,8 +3,12 @@ class Attestation < ApplicationRecord
mount_uploader :pdf, AttestationUploader
has_one_attached :pdf_active_storage
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
elsif pdf&.url
# 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 :signature, AttestationTemplateSignatureUploader
validate :logo_signature_file_size
has_one_attached :logo_active_storage
has_one_attached :signature_active_storage
validates :footer, length: { maximum: 190 }
FILE_MAX_SIZE_IN_MB = 0.5
DOSSIER_STATE = Dossier.states.fetch(:accepte)
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
def unspecified_champs_for_dossier(dossier)
@ -34,17 +43,83 @@ class AttestationTemplate < ApplicationRecord
end
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?
CopyCarrierwaveFile::CopyFileService.new(self, result, :logo).set_file
if logo_active_storage.attached?
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
if signature.present?
CopyCarrierwaveFile::CopyFileService.new(self, result, :signature).set_file
if signature_active_storage.attached?
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
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
private
@ -60,40 +135,17 @@ class AttestationTemplate < ApplicationRecord
.flatten
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)
action_view = ActionView::Base.new(ActionController::Base.view_paths,
logo: logo,
logo: proxy_logo,
title: replace_tags(title, dossier),
body: replace_tags(body, dossier),
signature: signature,
signature: proxy_signature,
footer: footer,
created_at: Time.zone.now)
attestation_view = action_view.render(file: 'admin/attestation_templates/show',
formats: [:pdf])
attestation_view = action_view.render(file: 'admin/attestation_templates/show', formats: [:pdf])
view_to_memory_file(attestation_view)
end
def view_to_memory_file(view)
pdf = StringIO.new(view)
def pdf.original_filename
'attestation'
end
pdf
StringIO.new(attestation_view)
end
end

View file

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

View file

@ -17,11 +17,11 @@
celle-ci est également disponible au téléchargement depuis lespace personnel de lusager.
.image-upload
- if @attestation_template.logo.present?
= image_tag @attestation_template.logo.url, class: 'thumbnail'
- if @attestation_template.logo?
= image_tag @attestation_template.logo_url, class: 'thumbnail'
.form-group
= 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
= f.file_field :logo, accept: 'image/png, image/jpg, image/jpeg'
%p.help-block
@ -54,11 +54,11 @@
= tag[:description]
.image-upload
- if @attestation_template.signature.present?
= image_tag @attestation_template.signature.url, class: 'thumbnail'
- if @attestation_template.signature?
= image_tag @attestation_template.signature_url, class: 'thumbnail'
.form-group
= 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
= f.file_field :signature, accept: 'image/png, image/jpg, image/jpeg'
%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
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
pdf.fill_color grey
@ -44,7 +49,12 @@ prawn_document(margin: [top_margin, right_margin, bottom_margin, left_margin], p
if @signature.present?
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

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.activated).to be true }
it { expect(procedure.attestation_template.logo.read).to eq(logo2.read) }
it { expect(procedure.attestation_template.signature.read).to eq(signature2.read) }
it { expect(procedure.attestation_template.logo_active_storage.download).to eq(logo2.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(flash.notice).to eq("L'attestation a bien été sauvegardée") }
@ -130,8 +130,8 @@ describe Admin::AttestationTemplatesController, type: :controller do
end
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.signature.read).to eq(signature2.read) }
it { expect(procedure.attestation_template.logo_active_storage.download).to eq(logo2.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(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' }
activated { true }
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

View file

@ -0,0 +1,128 @@
describe '2019_08_22_migrate_attestation_files.rake' do
let(:rake_task) { Rake::Task["2019_08_22_migrate_attestation_files:#{sub_task}"] }
let(:run_task) do
rake_task.invoke
models.each(&:reload)
end
after do
ENV['LIMIT'] = nil
rake_task.reenable
end
context 'attestation' do
let(:models) do
[
create(:attestation, created_at: 3.days.ago),
create(:attestation, :with_legacy_pdf, created_at: 2.days.ago),
create(:attestation, :with_legacy_pdf, created_at: 1.day.ago)
]
end
before do
first_attestation = models[0]
expect(first_attestation.pdf.present?).to be_falsey
expect(first_attestation.read_attribute(:pdf)).to be_nil
models.each do |attestation|
if attestation.pdf.present?
stub_request(:get, attestation.pdf_url)
.to_return(status: 200, body: File.read(attestation.pdf.path))
end
end
end
context 'pdf' do
let(:sub_task) { 'migrate_attestation_pdf' }
it 'should migrate pdf' do
expect(models.map(&:pdf_active_storage).map(&:attached?)).to eq([false, false, false])
run_task
expect(Attestation.where(pdf: nil).count).to eq(1)
expect(models.map(&:pdf_active_storage).map(&:attached?)).to eq([false, true, true])
end
it 'should migrate pdf within limit' do
expect(models.map(&:pdf_active_storage).map(&:attached?)).to eq([false, false, false])
ENV['LIMIT'] = '1'
run_task
expect(Attestation.where(pdf: nil).count).to eq(1)
expect(models.map(&:pdf_active_storage).map(&:attached?)).to eq([false, true, false])
end
end
end
context 'attestation_templates' do
let(:models) do
[
create(:attestation_template),
create(:attestation_template, :with_legacy_files),
create(:attestation_template, :with_legacy_files)
]
end
before do
models.each do |attestation_template|
if attestation_template.logo.present?
stub_request(:get, attestation_template.logo_url)
.to_return(status: 200, body: File.read(attestation_template.logo.path))
end
if attestation_template.signature.present?
stub_request(:get, attestation_template.signature_url)
.to_return(status: 200, body: File.read(attestation_template.signature.path))
end
end
end
context 'logo' do
let(:sub_task) { 'migrate_attestation_template_logo' }
it 'should migrate logo' do
expect(models.map(&:logo_active_storage).map(&:attached?)).to eq([false, false, false])
run_task
expect(AttestationTemplate.where(logo: nil).count).to eq(1)
expect(models.map(&:logo_active_storage).map(&:attached?)).to eq([false, true, true])
end
it 'should migrate logo within limit' do
expect(models.map(&:logo_active_storage).map(&:attached?)).to eq([false, false, false])
ENV['LIMIT'] = '1'
run_task
expect(AttestationTemplate.where(logo: nil).count).to eq(1)
expect(models.map(&:logo_active_storage).map(&:attached?)).to eq([false, true, false])
end
end
context 'signature' do
let(:sub_task) { 'migrate_attestation_template_signature' }
it 'should migrate signature' do
expect(models.map(&:signature_active_storage).map(&:attached?)).to eq([false, false, false])
run_task
expect(AttestationTemplate.where(signature: nil).count).to eq(1)
expect(models.map(&:signature_active_storage).map(&:attached?)).to eq([false, true, true])
end
it 'should migrate signature within limit' do
expect(models.map(&:signature_active_storage).map(&:attached?)).to eq([false, false, false])
ENV['LIMIT'] = '1'
run_task
expect(AttestationTemplate.where(signature: nil).count).to eq(1)
expect(models.map(&:signature_active_storage).map(&:attached?)).to eq([false, true, false])
end
end
end
end

View file

@ -1,44 +1,44 @@
describe AttestationTemplate, type: :model do
describe 'validate' do
let(:logo_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_signature) { double(AttestationTemplateSignatureUploader, file: double(size: signature_size)) }
let(:attestation_template) { AttestationTemplate.new }
# describe 'validate' do
# let(:logo_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_signature) { double(AttestationTemplateSignatureUploader, file: double(size: signature_size)) }
# let(:attestation_template) { AttestationTemplate.new }
before do
allow(attestation_template).to receive(:logo).and_return(fake_logo)
allow(attestation_template).to receive(:signature).and_return(fake_signature)
attestation_template.validate
end
# before do
# allow(attestation_template).to receive(:logo).and_return(fake_logo)
# allow(attestation_template).to receive(:signature).and_return(fake_signature)
# attestation_template.validate
# end
subject { attestation_template.errors.details }
# subject { attestation_template.errors.details }
context 'when no files are present' do
let(:fake_logo) { nil }
let(:fake_signature) { nil }
# context 'when no files are present' do
# let(:fake_logo) { nil }
# let(:fake_signature) { nil }
it { is_expected.to match({}) }
end
# it { is_expected.to match({}) }
# end
context 'when the logo and the signature have the right size' do
it { is_expected.to match({}) }
end
# context 'when the logo and the signature have the right size' do
# it { is_expected.to match({}) }
# end
context 'when the logo and the signature are too heavy' do
let(:logo_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte + 1 }
let(:signature_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte + 1 }
# context 'when the logo and the signature are too heavy' do
# let(:logo_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte + 1 }
# let(:signature_size) { AttestationTemplate::FILE_MAX_SIZE_IN_MB.megabyte + 1 }
it do
expected = {
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' }]
}
# it do
# expected = {
# 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' }]
# }
is_expected.to match(expected)
end
end
end
# is_expected.to match(expected)
# end
# end
# end
describe 'validates footer length' do
let(:attestation_template) { AttestationTemplate.new(footer: footer) }
@ -140,11 +140,6 @@ describe AttestationTemplate, type: :model do
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
let(:types_de_champ) do
[