From 6a41f82e5b7164bb3c6b32bfbbcea089f4a1fa13 Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Sun, 28 May 2023 23:20:48 +0300 Subject: [PATCH] add pdf generation --- Gemfile | 2 +- Gemfile.lock | 13 +- app/controllers/api/attachments_controller.rb | 5 +- app/controllers/esign_settings_controller.rb | 26 ++++ app/controllers/setup_controller.rb | 3 + .../submissions_debug_controller.rb | 27 ++++ app/javascript/application.js | 2 + app/javascript/elements/file_dropzone.js | 82 +++++++++++ app/javascript/flow_builder/page.vue | 2 +- app/javascript/flow_form/area.vue | 4 +- app/javascript/flow_form/signature_step.vue | 2 +- app/models/encrypted_config.rb | 1 + app/models/submission.rb | 2 - app/views/dashboard/index.html.erb | 1 + app/views/esign_settings/index.html.erb | 10 ++ config/routes.rb | 2 + lib/generate_certificate.rb | 90 +++++++++++++ lib/pdf_icons.rb | 15 +++ lib/pdf_icons/check.png | Bin 0 -> 1077 bytes lib/pdf_icons/paperclip.png | Bin 0 -> 4383 bytes .../generate_result_attachments.rb | 127 ++++++++++++++---- 21 files changed, 375 insertions(+), 41 deletions(-) create mode 100644 app/controllers/esign_settings_controller.rb create mode 100644 app/controllers/submissions_debug_controller.rb create mode 100644 app/javascript/elements/file_dropzone.js create mode 100644 app/views/esign_settings/index.html.erb create mode 100644 lib/generate_certificate.rb create mode 100644 lib/pdf_icons.rb create mode 100644 lib/pdf_icons/check.png create mode 100644 lib/pdf_icons/paperclip.png diff --git a/Gemfile b/Gemfile index 20482287..bf3e17eb 100644 --- a/Gemfile +++ b/Gemfile @@ -8,11 +8,11 @@ gem 'audited' gem 'aws-sdk-s3' gem 'azure-storage-blob' gem 'bootsnap', require: false -gem 'combine_pdf' gem 'devise' gem 'faraday' gem 'geoip' gem 'google-cloud-storage' +gem 'hexapdf' gem 'image_processing' gem 'lograge' gem 'oj' diff --git a/Gemfile.lock b/Gemfile.lock index 11f0cb24..293e9ada 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -123,10 +123,8 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) + cmdparse (3.0.7) coderay (1.1.3) - combine_pdf (1.0.23) - matrix - ruby-rc4 (>= 0.1.5) concurrent-ruby (1.2.2) connection_pool (2.4.0) crack (0.4.5) @@ -199,6 +197,7 @@ GEM websocket-driver (>= 0.6, < 0.8) ffi (1.15.5) geoip (1.6.4) + geom2d (0.3.1) globalid (1.1.0) activesupport (>= 5.0) google-apis-core (0.11.0) @@ -236,6 +235,10 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) hashdiff (1.0.1) + hexapdf (0.32.2) + cmdparse (~> 3.0, >= 3.0.3) + geom2d (~> 0.3) + openssl (>= 2.2.1) htmlentities (4.3.4) httpclient (2.8.3) i18n (1.13.0) @@ -296,6 +299,7 @@ GEM nokogiri (1.15.0-arm64-darwin) racc (~> 1.4) oj (3.14.3) + openssl (3.1.0) orm_adapter (0.5.0) os (1.1.4) pagy (6.0.4) @@ -412,7 +416,6 @@ GEM rubocop-capybara (~> 2.17) rubocop-factory_bot (~> 2.22) ruby-progressbar (1.13.0) - ruby-rc4 (0.1.5) ruby-vips (2.1.4) ffi (~> 1.12) ruby2_keywords (0.0.5) @@ -483,7 +486,6 @@ DEPENDENCIES bootsnap bullet capybara - combine_pdf cuprite debug devise @@ -493,6 +495,7 @@ DEPENDENCIES faraday geoip google-cloud-storage + hexapdf image_processing letter_opener_web lograge diff --git a/app/controllers/api/attachments_controller.rb b/app/controllers/api/attachments_controller.rb index 1386220e..12c6a606 100644 --- a/app/controllers/api/attachments_controller.rb +++ b/app/controllers/api/attachments_controller.rb @@ -5,13 +5,14 @@ module Api skip_before_action :authenticate_user! def create - submission = Submission.find_by!(slug: params[:submission_slug]) + submission = Submission.find_by!(slug: params[:submission_slug]) unless current_account + blob = ActiveStorage::Blob.find_signed(params[:blob_signed_id]) attachment = ActiveStorage::Attachment.create!( blob:, name: params[:name], - record: submission + record: submission || current_account ) render json: attachment.as_json(only: %i[uuid], methods: %i[url filename content_type]) diff --git a/app/controllers/esign_settings_controller.rb b/app/controllers/esign_settings_controller.rb new file mode 100644 index 00000000..d0d307f7 --- /dev/null +++ b/app/controllers/esign_settings_controller.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class EsignSettingsController < ApplicationController + before_action :load_encrypted_config + + def create + attachment = ActiveStorage::Attachment.find_by!(uuid: params[:attachment_uuid]) + + pdf = HexaPDF::Document.new(io: StringIO.new(attachment.download)) + + pdf.signatures + end + + private + + def load_encrypted_config + @encrypted_config = + EncryptedConfig.find_or_initialize_by(account: current_account, key: EncryptedConfig::ESIGN_CERTS_KEY) + end + + def storage_configs + params.require(:encrypted_config).permit(value: {}).tap do |e| + e[:value].compact_blank! + end + end +end diff --git a/app/controllers/setup_controller.rb b/app/controllers/setup_controller.rb index 90b6424a..43817ef2 100644 --- a/app/controllers/setup_controller.rb +++ b/app/controllers/setup_controller.rb @@ -17,6 +17,9 @@ class SetupController < ApplicationController @user = @account.users.new(user_params) if @user.save + @account.encrypted_configs.create!(key: EncryptedConfig::ESIGN_CERTS_KEY, + value: GenerateCertificate.call) + sign_in(@user) redirect_to root_path diff --git a/app/controllers/submissions_debug_controller.rb b/app/controllers/submissions_debug_controller.rb new file mode 100644 index 00000000..6f681a78 --- /dev/null +++ b/app/controllers/submissions_debug_controller.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class SubmissionsDebugController < ApplicationController + layout 'flow' + + skip_before_action :authenticate_user! + + def index + @submission = Submission.preload({ attachments_attachments: :blob }, + flow: { documents_attachments: :blob }) + .find_by(slug: params[:submission_slug]) + + respond_to do |f| + f.html do + render 'submit_flow/show' + end + f.pdf do + Submissions::GenerateResultAttachments.call(@submission) + + send_data ActiveStorage::Attachment.where(name: :documents).last.download, + filename: 'debug.pdf', + disposition: 'inline', + type: 'application/pdf' + end + end + end +end diff --git a/app/javascript/application.js b/app/javascript/application.js index d4748616..226850d3 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -5,12 +5,14 @@ import { createApp, reactive } from 'vue' import ToggleVisible from './elements/toggle_visible' import DisableHidden from './elements/disable_hidden' import TurboModal from './elements/turbo_modal' +import FileDropzone from './elements/file_dropzone' import FlowBuilder from './flow_builder/builder' window.customElements.define('toggle-visible', ToggleVisible) window.customElements.define('disable-hidden', DisableHidden) window.customElements.define('turbo-modal', TurboModal) +window.customElements.define('file-dropzone', FileDropzone) window.customElements.define('flow-builder', class extends HTMLElement { connectedCallback () { diff --git a/app/javascript/elements/file_dropzone.js b/app/javascript/elements/file_dropzone.js new file mode 100644 index 00000000..b4c4128a --- /dev/null +++ b/app/javascript/elements/file_dropzone.js @@ -0,0 +1,82 @@ +import { DirectUpload } from '@rails/activestorage' + +import { actionable } from '@github/catalyst/lib/actionable' +import { target, targetable } from '@github/catalyst/lib/targetable' + +export default actionable(targetable(class extends HTMLElement { + static [target.static] = [ + 'loading', + 'input', + 'valueField' + ] + + connectedCallback () { + this.addEventListener('drop', this.onDrop) + + this.addEventListener('dragover', (e) => e.preventDefault()) + } + + onDrop (e) { + e.preventDefault() + + this.uploadFiles(e.dataTransfer.files) + } + + onSelectFiles (e) { + e.preventDefault() + + this.uploadFiles(this.input.files).then(() => { + this.input.value = '' + }) + } + + async uploadFiles (files) { + console.log( files ) + const blobs = await Promise.all( + Array.from(files).map(async (file) => { + const upload = new DirectUpload( + file, + '/direct_uploads', + this.input + ) + + return new Promise((resolve, reject) => { + upload.create((error, blob) => { + if (error) { + console.error(error) + + return reject(error) + } else { + return resolve(blob) + } + }) + }).catch((error) => { + console.error(error) + }) + }) + ) + + await Promise.all( + blobs.map((blob) => { + return fetch('/api/attachments', { + method: 'POST', + body: JSON.stringify({ + name: this.dataset.name, + blob_signed_id: blob.signed_id, + submission_slug: this.dataset.submissionSlug + }), + headers: { 'Content-Type': 'application/json' } + }).then(resp => resp.json()).then((data) => { + return data + }) + })).then((result) => { + result.forEach((attachment) => { + if (this.valueField) { + this.valueField.value = attachment.uuid + } + + this.dispatchEvent(new CustomEvent('upload', { detail: attachment })) + }) + }) + } +})) diff --git a/app/javascript/flow_builder/page.vue b/app/javascript/flow_builder/page.vue index 70d56ec7..58722b24 100644 --- a/app/javascript/flow_builder/page.vue +++ b/app/javascript/flow_builder/page.vue @@ -91,7 +91,7 @@ export default { onDrop (e) { this.$emit('drop-field', { x: e.layerX / this.$refs.mask.clientWidth, - y: e.layerY / this.$refs.mask.clientHeight, + y: e.layerY / this.$refs.mask.clientHeight - (this.$refs.mask.clientWidth / 30 / this.$refs.mask.clientWidth) / 2, w: this.$refs.mask.clientWidth / 5 / this.$refs.mask.clientWidth, h: this.$refs.mask.clientWidth / 30 / this.$refs.mask.clientWidth, page: this.number diff --git a/app/javascript/flow_form/area.vue b/app/javascript/flow_form/area.vue index 3b298de2..feb03cf1 100644 --- a/app/javascript/flow_form/area.vue +++ b/app/javascript/flow_form/area.vue @@ -1,14 +1,16 @@