diff --git a/app/controllers/submissions_download_controller.rb b/app/controllers/submissions_download_controller.rb index 26ebdf51..93f90027 100644 --- a/app/controllers/submissions_download_controller.rb +++ b/app/controllers/submissions_download_controller.rb @@ -8,6 +8,15 @@ class SubmissionsDownloadController < ApplicationController Submissions::GenerateResultAttachments.call(submitter) if submitter.documents.blank? - render json: submitter.documents.map { |e| helpers.rails_blob_url(e) } + original_documents = submitter.submission.template.documents.preload(:blob) + is_more_than_two_images = original_documents.count(&:image?) > 1 + + urls = submitter.documents.preload(:blob).filter_map do |attachment| + next if is_more_than_two_images && original_documents.find { |a| a.uuid == attachment.uuid }&.image? + + helpers.rails_blob_url(attachment) + end + + render json: urls end end diff --git a/app/javascript/elements/download_button.js b/app/javascript/elements/download_button.js index a51138e7..2e81a176 100644 --- a/app/javascript/elements/download_button.js +++ b/app/javascript/elements/download_button.js @@ -18,20 +18,27 @@ export default targetable(class extends HTMLElement { this.toggleState() fetch(this.dataset.src).then((response) => response.json()).then((urls) => { - urls.forEach((url) => { - fetch(url).then(async (resp) => { - const blobUrl = URL.createObjectURL(await resp.blob()) - const link = document.createElement('a') + const fileRequests = urls.map((url) => { + return () => { + return fetch(url).then(async (resp) => { + const blobUrl = URL.createObjectURL(await resp.blob()) + const link = document.createElement('a') - link.href = blobUrl - link.setAttribute('download', resp.headers.get('content-disposition').split('"')[1]) + link.href = blobUrl + link.setAttribute('download', decodeURI(resp.headers.get('content-disposition').split('"')[1])) - link.click() + link.click() - URL.revokeObjectURL(url) - }) + URL.revokeObjectURL(url) + }) + } }) - }).finally(() => { + + fileRequests.reduce( + (prevPromise, request) => prevPromise.then(() => request()), + Promise.resolve() + ) + this.toggleState() }) } diff --git a/app/javascript/submission_form/completed.vue b/app/javascript/submission_form/completed.vue index a619efb6..1969be30 100644 --- a/app/javascript/submission_form/completed.vue +++ b/app/javascript/submission_form/completed.vue @@ -90,20 +90,27 @@ export default { this.isDownloading = true fetch(`/submitters/${this.submitterSlug}/download`).then((response) => response.json()).then((urls) => { - urls.forEach((url) => { - fetch(url).then(async (resp) => { - const blobUrl = URL.createObjectURL(await resp.blob()) - const link = document.createElement('a') + const fileRequests = urls.map((url) => { + return () => { + return fetch(url).then(async (resp) => { + const blobUrl = URL.createObjectURL(await resp.blob()) + const link = document.createElement('a') - link.href = blobUrl - link.setAttribute('download', resp.headers.get('content-disposition').split('"')[1]) + link.href = blobUrl + link.setAttribute('download', decodeURI(resp.headers.get('content-disposition').split('"')[1])) - link.click() + link.click() - URL.revokeObjectURL(url) - }) + URL.revokeObjectURL(url) + }) + } }) - }).finally(() => { + + fileRequests.reduce( + (prevPromise, request) => prevPromise.then(() => request()), + Promise.resolve() + ) + this.isDownloading = false }) } diff --git a/app/javascript/submission_form/form.vue b/app/javascript/submission_form/form.vue index e1b666d7..1851d7aa 100644 --- a/app/javascript/submission_form/form.vue +++ b/app/javascript/submission_form/form.vue @@ -191,7 +191,7 @@ :field="currentField" :attachments-index="attachmentsIndex" :submitter-slug="submitterSlug" - @attached="attachments.push($event)" + @attached="[attachments.push($event), $refs.areas.scrollIntoField(currentField)]" />
diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index c4cfe3b3..f0a4cfa8 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -5,6 +5,8 @@ module Submissions FONT_SIZE = 11 FONT_NAME = 'Helvetica' + A4_SIZE = [595, 842].freeze + module_function # rubocop:disable Metrics @@ -126,42 +128,101 @@ module Submissions end end - template.schema.map do |item| - template.documents.find { |a| a.uuid == item['attachment_uuid'] } + image_pdfs = [] + original_documents = template.documents.preload(:blob) + + results = + template.schema.map do |item| + pdf = pdfs_index[item['attachment_uuid']] + + attachment = save_signed_pdf(pdf:, submitter:, cert:, uuid: item['attachment_uuid'], name: item['name']) - io = StringIO.new + image_pdfs << pdf if original_documents.find { |a| a.uuid == item['attachment_uuid'] }.image? - pdf = pdfs_index[item['attachment_uuid']] + attachment + end + + return results if image_pdfs.size < 2 - pdf.sign(io, reason: "Signed by #{submitter.email}", - certificate: OpenSSL::X509::Certificate.new(cert['cert']), - key: OpenSSL::PKey::RSA.new(cert['key']), - certificate_chain: [OpenSSL::X509::Certificate.new(cert['sub_ca']), - OpenSSL::X509::Certificate.new(cert['root_ca'])]) + images_pdf = + image_pdfs.each_with_object(HexaPDF::Document.new) do |pdf, doc| + pdf.pages.each { |page| doc.pages << doc.import(page) } + end - ActiveStorage::Attachment.create!( - uuid: item['attachment_uuid'], - blob: ActiveStorage::Blob.create_and_upload!( - io: StringIO.new(io.string), filename: "#{item['name']}.pdf" - ), - name: 'documents', - record: submitter + images_pdf_result = + save_signed_pdf( + pdf: images_pdf, + submitter:, + cert:, + uuid: images_pdf_uuid(original_documents.select(&:image?)), + name: template.name ) - end + + results + [images_pdf_result] end # rubocop:enable Metrics + def save_signed_pdf(pdf:, submitter:, cert:, uuid:, name:) + io = StringIO.new + + pdf.sign(io, reason: "Signed by #{submitter.email}", + certificate: OpenSSL::X509::Certificate.new(cert['cert']), + key: OpenSSL::PKey::RSA.new(cert['key']), + certificate_chain: [OpenSSL::X509::Certificate.new(cert['sub_ca']), + OpenSSL::X509::Certificate.new(cert['root_ca'])]) + + ActiveStorage::Attachment.create!( + uuid:, + blob: ActiveStorage::Blob.create_and_upload!( + io: StringIO.new(io.string), filename: "#{name}.pdf" + ), + name: 'documents', + record: submitter + ) + end + + def images_pdf_uuid(attachments) + Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, attachments.map(&:uuid).sort.join(':')) + end + def build_pdfs_index(submitter) latest_submitter = submitter.submission.submitters .select { |e| e.id != submitter.id && e.completed_at? } .max_by(&:completed_at) - documents = latest_submitter&.documents.to_a.presence - documents ||= submitter.submission.template.documents + documents = latest_submitter&.documents&.preload(:blob).to_a.presence + documents ||= submitter.submission.template.documents.preload(:blob) documents.to_h do |attachment| - [attachment.uuid, HexaPDF::Document.new(io: StringIO.new(attachment.download))] + pdf = + if attachment.image? + build_pdf_from_image(attachment) + else + HexaPDF::Document.new(io: StringIO.new(attachment.download)) + end + + [attachment.uuid, pdf] end end + + def build_pdf_from_image(attachment) + pdf = HexaPDF::Document.new + page = pdf.pages.add + + scale = [A4_SIZE.first / attachment.metadata['width'].to_f, + A4_SIZE.last / attachment.metadata['height'].to_f].min + + page.box.width = attachment.metadata['width'] * scale + page.box.height = attachment.metadata['height'] * scale + + page.canvas.image( + StringIO.new(attachment.download), + at: [0, 0], + width: page.box.width, + height: page.box.height + ) + + pdf + end end end