From 3ee41975fdefac63e8806c9790b66679024c112a Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 15 Oct 2024 19:16:09 +0300 Subject: [PATCH] add documents preview endpoint --- .../api/submission_documents_controller.rb | 45 +++++++++ app/models/submission.rb | 2 + config/routes.rb | 1 + .../generate_preview_attachments.rb | 96 +++++++++++++++++++ .../generate_result_attachments.rb | 44 +++++---- 5 files changed, 167 insertions(+), 21 deletions(-) create mode 100644 app/controllers/api/submission_documents_controller.rb create mode 100644 lib/submissions/generate_preview_attachments.rb diff --git a/app/controllers/api/submission_documents_controller.rb b/app/controllers/api/submission_documents_controller.rb new file mode 100644 index 00000000..aa3021c5 --- /dev/null +++ b/app/controllers/api/submission_documents_controller.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Api + class SubmissionDocumentsController < ApiBaseController + load_and_authorize_resource :submission + + def index + documents = + if @submission.submitters.all?(&:completed_at?) + last_submitter = @submission.submitters.max_by(&:completed_at) + + if last_submitter.documents_attachments.blank? + last_submitter.documents_attachments = Submissions::EnsureResultGenerated.call(submitter) + end + + last_submitter.documents_attachments + else + values_hash = Submissions::GeneratePreviewAttachments.build_values_hash(@submission) + + if @submission.preview_documents.present? && + @submission.preview_documents.all? { |s| s.metadata['values_hash'] == values_hash } + @submission.preview_documents + else + ApplicationRecord.no_touching do + @submission.preview_documents.each(&:destroy) + end + + Submissions::GeneratePreviewAttachments.call(@submission, values_hash:) + end + end + + ActiveRecord::Associations::Preloader.new( + records: documents, + associations: [:blob] + ).call + + render json: { + id: @submission.id, + documents: documents.map do |attachment| + { name: attachment.filename.base, url: ActiveStorage::Blob.proxy_url(attachment.blob) } + end + } + end + end +end diff --git a/app/models/submission.rb b/app/models/submission.rb index ff6efa8f..3972b6e5 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -55,6 +55,8 @@ class Submission < ApplicationRecord has_one_attached :audit_trail has_one_attached :combined_document + has_many_attached :preview_documents + has_many :template_schema_documents, ->(e) { where(uuid: (e.template_schema.presence || e.template.schema).pluck('attachment_uuid')) }, through: :template, source: :documents_attachments diff --git a/config/routes.rb b/config/routes.rb index a9e383cc..3d10d04d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -34,6 +34,7 @@ Rails.application.routes.draw do resources :submitter_form_views, only: %i[create] resources :submitters, only: %i[index show update] resources :submissions, only: %i[index show create destroy] do + resources :documents, only: %i[index], controller: 'submission_documents' collection do resources :init, only: %i[create], controller: 'submissions' resources :emails, only: %i[create], controller: 'submissions', as: :submissions_emails diff --git a/lib/submissions/generate_preview_attachments.rb b/lib/submissions/generate_preview_attachments.rb new file mode 100644 index 00000000..b2c9ea27 --- /dev/null +++ b/lib/submissions/generate_preview_attachments.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +module Submissions + module GeneratePreviewAttachments + module_function + + # rubocop:disable Metrics + def call(submission, values_hash: nil) + values_hash ||= build_values_hash(submission) + + with_signature_id = submission.account.account_configs + .exists?(key: AccountConfig::WITH_SIGNATURE_ID, value: true) + + is_flatten = + submission.account.account_configs + .find_or_initialize_by(key: AccountConfig::FLATTEN_RESULT_PDF_KEY).value != false + + pdfs_index = GenerateResultAttachments.build_pdfs_index(submission, flatten: is_flatten) + + submission.submitters.where(completed_at: nil).preload(attachments_attachments: :blob).each do |submitter| + GenerateResultAttachments.fill_submitter_fields(submitter, submission.account, pdfs_index, + with_signature_id:, is_flatten:) + end + + template = submission.template + + image_pdfs = [] + original_documents = template.documents.preload(:blob) + + result_attachments = + submission.template_schema.map do |item| + pdf = pdfs_index[item['attachment_uuid']] + + if original_documents.find { |a| a.uuid == item['attachment_uuid'] }.image? + pdf = GenerateResultAttachments.normalize_image_pdf(pdf) + + image_pdfs << pdf + end + + build_pdf_attachment(pdf:, submission:, + uuid: item['attachment_uuid'], + values_hash:, + name: item['name']) + end + + return ApplicationRecord.no_touching { result_attachments.map { |e| e.tap(&:save!) } } if image_pdfs.size < 2 + + images_pdf = + image_pdfs.each_with_object(HexaPDF::Document.new) do |pdf, doc| + pdf.pages.each { |page| doc.pages << doc.import(page) } + end + + images_pdf = GenerateResultAttachments.normalize_image_pdf(images_pdf) + + images_pdf_attachment = + build_pdf_attachment( + pdf: images_pdf, + submission:, + uuid: GenerateResultAttachments.images_pdf_uuid(original_documents.select(&:image?)), + values_hash:, + name: template.name + ) + + ApplicationRecord.no_touching do + (result_attachments + [images_pdf_attachment]).map { |e| e.tap(&:save!) } + end + end + + def build_values_hash(submission) + submission.submitters.reduce({}) { |acc, s| acc.merge(s.values) }.hash + end + + def build_pdf_attachment(pdf:, submission:, uuid:, name:, values_hash:) + io = StringIO.new + + begin + pdf.write(io, incremental: true, validate: false) + rescue HexaPDF::MalformedPDFError => e + Rollbar.error(e) if defined?(Rollbar) + + pdf.write(io, incremental: false, validate: false) + end + + ActiveStorage::Attachment.new( + blob: ActiveStorage::Blob.create_and_upload!(io: io.tap(&:rewind), filename: "#{name}.pdf"), + metadata: { original_uuid: uuid, + values_hash:, + analyzed: true, + sha256: Base64.urlsafe_encode64(Digest::SHA256.digest(io.string)) }, + name: 'preview_documents', + record: submission + ) + end + # rubocop:enable Metrics + end +end diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index c396ba31..1bcd7a53 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -92,19 +92,13 @@ module Submissions end def generate_pdfs(submitter) - cell_layouter = HexaPDF::Layout::TextLayouter.new(text_valign: :center, text_align: :center) - - with_signature_id = submitter.account.account_configs - .exists?(key: AccountConfig::WITH_SIGNATURE_ID, value: true) + configs = submitter.account.account_configs.where(key: [AccountConfig::FLATTEN_RESULT_PDF_KEY, + AccountConfig::WITH_SIGNATURE_ID]) - is_flatten = - submitter.account.account_configs - .find_or_initialize_by(key: AccountConfig::FLATTEN_RESULT_PDF_KEY).value != false - - account = submitter.account - attachments_data_cache = {} + with_signature_id = configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID }&.value == true + is_flatten = configs.find { |c| c.key == AccountConfig::FLATTEN_RESULT_PDF_KEY }&.value != false - pdfs_index = build_pdfs_index(submitter, flatten: is_flatten) + pdfs_index = build_pdfs_index(submitter.submission, submitter:, flatten: is_flatten) if with_signature_id || submitter.account.testing? pdfs_index.each_value do |pdf| @@ -145,6 +139,16 @@ module Submissions end end + fill_submitter_fields(submitter, submitter.account, pdfs_index, with_signature_id:, is_flatten:) + + pdfs_index + end + + def fill_submitter_fields(submitter, account, pdfs_index, with_signature_id:, is_flatten:) + cell_layouter = HexaPDF::Layout::TextLayouter.new(text_valign: :center, text_align: :center) + + attachments_data_cache = {} + submitter.submission.template_fields.each do |field| next if field['submitter_uuid'] != submitter.uuid @@ -442,8 +446,6 @@ module Submissions end end end - - pdfs_index end def build_pdf_attachment(pdf:, submitter:, pkcs:, tsa_url:, uuid:, name:) @@ -514,13 +516,13 @@ module Submissions Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, attachments.map(&:uuid).sort.join(':')) end - def build_pdfs_index(submitter, flatten: true) - latest_submitter = find_last_submitter(submitter) + def build_pdfs_index(submission, submitter: nil, flatten: true) + latest_submitter = find_last_submitter(submission, submitter:) Submissions::EnsureResultGenerated.call(latest_submitter) if latest_submitter documents = latest_submitter&.documents&.preload(:blob).to_a.presence - documents ||= submitter.submission.template_schema_documents.preload(:blob) + documents ||= submission.template_schema_documents.preload(:blob) documents.to_h do |attachment| pdf = @@ -582,11 +584,11 @@ module Submissions font_wrapper.custom_glyph(replace_with, character) end - def find_last_submitter(submitter) - submitter.submission.submitters - .select(&:completed_at?) - .select { |e| e.id != submitter.id && e.completed_at <= submitter.completed_at } - .max_by(&:completed_at) + def find_last_submitter(submission, submitter: nil) + submission.submitters + .select(&:completed_at?) + .select { |e| submitter.nil? ? true : e.id != submitter.id && e.completed_at <= submitter.completed_at } + .max_by(&:completed_at) end def build_pdf_from_image(attachment)