From 97b3adf91f0d89d8c0db8261f86abf9c4cf94230 Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Tue, 4 Jul 2023 01:58:43 +0300 Subject: [PATCH] make public storage and direct upload optional --- app/controllers/api/attachments_controller.rb | 9 +- .../api/templates_documents_controller.rb | 18 ++- app/controllers/esign_settings_controller.rb | 12 +- app/javascript/application.js | 3 +- app/javascript/elements/file_dropzone.js | 96 +++++++++------- app/javascript/form.js | 1 + .../submission_form/attachment_step.vue | 7 ++ app/javascript/submission_form/dropzone.vue | 105 +++++++++++------- app/javascript/submission_form/form.vue | 8 ++ app/javascript/submission_form/image_step.vue | 6 + .../submission_form/signature_step.vue | 58 +++++++--- app/javascript/template_builder/builder.vue | 18 ++- app/javascript/template_builder/dropzone.vue | 24 ++-- app/javascript/template_builder/upload.vue | 104 ++++++++++------- app/views/esign_settings/_result.html.erb | 6 +- app/views/esign_settings/index.html.erb | 6 +- app/views/submit_form/show.html.erb | 2 +- app/views/templates/edit.html.erb | 2 +- config/environments/development.rb | 4 +- config/environments/production.rb | 4 +- config/storage.yml | 11 +- lib/docuseal.rb | 4 + .../generate_result_attachments.rb | 7 +- 23 files changed, 335 insertions(+), 180 deletions(-) diff --git a/app/controllers/api/attachments_controller.rb b/app/controllers/api/attachments_controller.rb index 9a297da7..d255d5da 100644 --- a/app/controllers/api/attachments_controller.rb +++ b/app/controllers/api/attachments_controller.rb @@ -7,7 +7,14 @@ module Api def create submitter = Submitter.find_by!(slug: params[:submitter_slug]) - blob = ActiveStorage::Blob.find_signed(params[:blob_signed_id]) + blob = + if (file = params[:file]) + ActiveStorage::Blob.create_and_upload!(io: file.open, + filename: file.original_filename, + content_type: file.content_type) + else + ActiveStorage::Blob.find_signed(params[:blob_signed_id]) + end attachment = ActiveStorage::Attachment.create!( blob:, diff --git a/app/controllers/api/templates_documents_controller.rb b/app/controllers/api/templates_documents_controller.rb index f39b6102..bfe46cab 100644 --- a/app/controllers/api/templates_documents_controller.rb +++ b/app/controllers/api/templates_documents_controller.rb @@ -6,9 +6,7 @@ module Api @template = current_account.templates.find(params[:template_id]) documents = - params[:blobs].map do |blob| - blob = ActiveStorage::Blob.find_signed(blob[:signed_id]) - + find_or_create_blobs.map do |blob| document = @template.documents.create!(blob:) Templates::ProcessDocument.call(document) @@ -27,5 +25,19 @@ module Api ) } end + + private + + def find_or_create_blobs + blobs = params[:blobs]&.map do |attrs| + ActiveStorage::Blob.find_signed(attrs[:signed_id]) + end + + blobs || params[:files].map do |file| + ActiveStorage::Blob.create_and_upload!(io: file.open, + filename: file.original_filename, + content_type: file.content_type) + end + end end end diff --git a/app/controllers/esign_settings_controller.rb b/app/controllers/esign_settings_controller.rb index b9138383..a8a62f02 100644 --- a/app/controllers/esign_settings_controller.rb +++ b/app/controllers/esign_settings_controller.rb @@ -2,21 +2,17 @@ class EsignSettingsController < ApplicationController def create - blobs = - params[:blob_signed_ids].map do |sid| - ActiveStorage::Blob.find_signed(sid) - end - pdfs = - blobs.map do |blob| - HexaPDF::Document.new(io: StringIO.new(blob.download)) + params[:files].map do |file| + HexaPDF::Document.new(io: file.open) end certs = Accounts.load_signing_certs(current_account) trusted_certs = [certs[:cert], certs[:sub_ca], certs[:root_ca]] - render turbo_stream: turbo_stream.replace('result', partial: 'result', locals: { pdfs:, blobs:, trusted_certs: }) + render turbo_stream: turbo_stream.replace('result', partial: 'result', + locals: { pdfs:, files: params[:files], trusted_certs: }) rescue HexaPDF::MalformedPDFError render turbo_stream: turbo_stream.replace('result', html: helpers.tag.div('Invalid PDF', id: 'result')) end diff --git a/app/javascript/application.js b/app/javascript/application.js index 82d6ea4a..169f3089 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -51,7 +51,8 @@ window.customElements.define('template-builder', class extends HTMLElement { this.appElem = document.createElement('div') this.app = createApp(TemplateBuilder, { - template: reactive(JSON.parse(this.dataset.template)) + template: reactive(JSON.parse(this.dataset.template)), + isDirectUpload: this.dataset.isDirectUpload === 'true' }) this.app.mount(this.appElem) diff --git a/app/javascript/elements/file_dropzone.js b/app/javascript/elements/file_dropzone.js index bdba05ce..f4f5c163 100644 --- a/app/javascript/elements/file_dropzone.js +++ b/app/javascript/elements/file_dropzone.js @@ -9,28 +9,36 @@ export default actionable(targetable(class extends HTMLElement { ] connectedCallback () { - import('@rails/activestorage') + if (this.dataset.isDirectUpload === 'true') { + import('@rails/activestorage') + } this.addEventListener('drop', this.onDrop) this.addEventListener('dragover', (e) => e.preventDefault()) + + document.addEventListener('turbo:submit-end', this.toggleLoading) + } + + disconnectedCallback () { + document.removeEventListener('turbo:submit-end', this.toggleLoading) } onDrop (e) { e.preventDefault() + this.input.files = e.dataTransfer.files + this.uploadFiles(e.dataTransfer.files) } onSelectFiles (e) { e.preventDefault() - this.uploadFiles(this.input.files).then(() => { - this.input.value = '' - }) + this.uploadFiles(this.input.files) } - toggleLoading () { + toggleLoading = () => { this.loading.classList.toggle('hidden') this.icon.classList.toggle('hidden') this.classList.toggle('opacity-50') @@ -39,50 +47,56 @@ export default actionable(targetable(class extends HTMLElement { async uploadFiles (files) { this.toggleLoading() - const { DirectUpload } = await import('@rails/activestorage') - - 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) - } + if (this.dataset.isDirectUpload === 'true') { + const { DirectUpload } = await import('@rails/activestorage') + + 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) }) - }).catch((error) => { - console.error(error) }) - }) - ).then((blobs) => { - if (this.dataset.submitOnUpload) { - this.querySelectorAll('[name="blob_signed_ids[]"]').forEach((e) => e.remove()) - } + ).then((blobs) => { + if (this.dataset.submitOnUpload) { + this.querySelectorAll('[name="blob_signed_ids[]"]').forEach((e) => e.remove()) + } - blobs.forEach((blob) => { - const input = document.createElement('input') + blobs.forEach((blob) => { + const input = document.createElement('input') - input.type = 'hidden' - input.name = 'blob_signed_ids[]' - input.value = blob.signed_id + input.type = 'hidden' + input.name = 'blob_signed_ids[]' + input.value = blob.signed_id - this.append(input) - }) + this.append(input) + }) + if (this.dataset.submitOnUpload) { + this.closest('form').querySelector('button[type="submit"]').click() + } + }).finally(() => { + this.toggleLoading() + }) + } else { if (this.dataset.submitOnUpload) { this.closest('form').querySelector('button[type="submit"]').click() } - }).finally(() => { - this.toggleLoading() - }) + } } })) diff --git a/app/javascript/form.js b/app/javascript/form.js index 2c9453c6..d69e1f31 100644 --- a/app/javascript/form.js +++ b/app/javascript/form.js @@ -13,6 +13,7 @@ window.customElements.define('submission-form', class extends HTMLElement { submitterUuid: this.dataset.submitterUuid, authenticityToken: this.dataset.authenticityToken, canSendEmail: this.dataset.canSendEmail === 'true', + isDirectUpload: this.dataset.isDirectUpload === 'true', values: reactive(JSON.parse(this.dataset.values)), attachments: reactive(JSON.parse(this.dataset.attachments)), fields: JSON.parse(this.dataset.fields) diff --git a/app/javascript/submission_form/attachment_step.vue b/app/javascript/submission_form/attachment_step.vue index 2da33867..9cb6c05c 100644 --- a/app/javascript/submission_form/attachment_step.vue +++ b/app/javascript/submission_form/attachment_step.vue @@ -47,6 +47,8 @@ @@ -77,6 +79,11 @@ export default { required: false, default: () => ({}) }, + isDirectUpload: { + type: Boolean, + required: true, + default: false + }, modelValue: { type: Array, required: false, diff --git a/app/javascript/submission_form/dropzone.vue b/app/javascript/submission_form/dropzone.vue index 272dcf41..9734bace 100644 --- a/app/javascript/submission_form/dropzone.vue +++ b/app/javascript/submission_form/dropzone.vue @@ -69,6 +69,11 @@ export default { required: false, default: '*/*' }, + isDirectUpload: { + type: Boolean, + required: true, + default: false + }, multiple: { type: Boolean, required: false, @@ -87,7 +92,9 @@ export default { } }, mounted () { - import('@rails/activestorage') + if (this.isDirectUpload) { + import('@rails/activestorage') + } }, methods: { onDropFiles (e) { @@ -105,50 +112,72 @@ export default { async uploadFiles (files) { this.isLoading = true - const { DirectUpload } = await import('@rails/activestorage') + if (this.isDirectUpload) { + const { DirectUpload } = await import('@rails/activestorage') - const blobs = await Promise.all( - Array.from(files).map(async (file) => { - const upload = new DirectUpload( - file, - '/direct_uploads', - this.$refs.input - ) + const blobs = await Promise.all( + Array.from(files).map(async (file) => { + const upload = new DirectUpload( + file, + '/direct_uploads', + this.$refs.input + ) - return new Promise((resolve, reject) => { - upload.create((error, blob) => { - if (error) { - console.error(error) + return new Promise((resolve, reject) => { + upload.create((error, blob) => { + if (error) { + console.error(error) - return reject(error) - } else { - return resolve(blob) - } + return reject(error) + } else { + return resolve(blob) + } + }) + }).catch((error) => { + console.error(error) }) - }).catch((error) => { - console.error(error) }) + ) + + return await Promise.all( + blobs.map((blob) => { + return fetch('/api/attachments', { + method: 'POST', + body: JSON.stringify({ + name: 'attachments', + blob_signed_id: blob.signed_id, + submitter_slug: this.submitterSlug + }), + headers: { 'Content-Type': 'application/json' } + }).then(resp => resp.json()).then((data) => { + return data + }) + })).then((result) => { + this.$emit('upload', result) + }).finally(() => { + this.isLoading = false }) - ) + } else { + return await Promise.all( + Array.from(files).map((file) => { + const formData = new FormData() - return await Promise.all( - blobs.map((blob) => { - return fetch('/api/attachments', { - method: 'POST', - body: JSON.stringify({ - name: 'attachments', - blob_signed_id: blob.signed_id, - submitter_slug: this.submitterSlug - }), - headers: { 'Content-Type': 'application/json' } - }).then(resp => resp.json()).then((data) => { - return data - }) - })).then((result) => { - this.$emit('upload', result) - }).finally(() => { - this.isLoading = false - }) + formData.append('file', file) + formData.append('submitter_slug', this.submitterSlug) + formData.append('name', 'attachments') + + return fetch('/api/attachments', { + method: 'POST', + body: formData + }).then(resp => resp.json()).then((data) => { + return data + }) + })).then((result) => { + this.$emit('upload', result) + }).finally(() => { + this.isLoading = false + }) + } } } } diff --git a/app/javascript/submission_form/form.vue b/app/javascript/submission_form/form.vue index 93f6f8c3..8557f047 100644 --- a/app/javascript/submission_form/form.vue +++ b/app/javascript/submission_form/form.vue @@ -201,6 +201,7 @@ v-else-if="currentField.type === 'image'" v-model="values[currentField.uuid]" :field="currentField" + :is-direct-upload="isDirectUpload" :attachments-index="attachmentsIndex" :submitter-slug="submitterSlug" @attached="[attachments.push($event), $refs.areas.scrollIntoField(currentField)]" @@ -210,6 +211,7 @@ ref="currentStep" v-model="values[currentField.uuid]" :field="currentField" + :is-direct-upload="isDirectUpload" :attachments-index="attachmentsIndex" :submitter-slug="submitterSlug" @attached="attachments.push($event)" @@ -217,6 +219,7 @@ @@ -50,6 +51,11 @@ export default { type: Object, required: true }, + isDirectUpload: { + type: Boolean, + required: true, + default: false + }, submitterSlug: { type: String, required: true diff --git a/app/javascript/submission_form/signature_step.vue b/app/javascript/submission_form/signature_step.vue index 33bf9099..77c12676 100644 --- a/app/javascript/submission_form/signature_step.vue +++ b/app/javascript/submission_form/signature_step.vue @@ -56,6 +56,11 @@ export default { type: String, required: true }, + isDirectUpload: { + type: Boolean, + required: true, + default: false + }, attachmentsIndex: { type: Object, required: false, @@ -79,7 +84,10 @@ export default { this.$refs.canvas.height = this.$refs.canvas.parentNode.clientWidth / 3 }) - import('@rails/activestorage') + if (this.isDirectUpload) { + import('@rails/activestorage') + } + const { default: SignaturePad } = await import('signature_pad') this.pad = new SignaturePad(this.$refs.canvas) @@ -102,31 +110,49 @@ export default { return Promise.resolve({}) } - const { DirectUpload } = await import('@rails/activestorage') - return new Promise((resolve) => { - this.$refs.canvas.toBlob((blob) => { + this.$refs.canvas.toBlob(async (blob) => { const file = new File([blob], 'signature.png', { type: 'image/png' }) - new DirectUpload( - file, - '/direct_uploads' - ).create((_error, data) => { - fetch('/api/attachments', { + if (this.isDirectUpload) { + const { DirectUpload } = await import('@rails/activestorage') + + new DirectUpload( + file, + '/direct_uploads' + ).create((_error, data) => { + fetch('/api/attachments', { + method: 'POST', + body: JSON.stringify({ + submitter_slug: this.submitterSlug, + blob_signed_id: data.signed_id, + name: 'attachments' + }), + headers: { 'Content-Type': 'application/json' } + }).then((resp) => resp.json()).then((attachment) => { + this.$emit('update:model-value', attachment.uuid) + this.$emit('attached', attachment) + + return resolve(attachment) + }) + }) + } else { + const formData = new FormData() + + formData.append('file', file) + formData.append('submitter_slug', this.submitterSlug) + formData.append('name', 'attachments') + + return fetch('/api/attachments', { method: 'POST', - body: JSON.stringify({ - submitter_slug: this.submitterSlug, - blob_signed_id: data.signed_id, - name: 'attachments' - }), - headers: { 'Content-Type': 'application/json' } + body: formData }).then((resp) => resp.json()).then((attachment) => { this.$emit('update:model-value', attachment.uuid) this.$emit('attached', attachment) return resolve(attachment) }) - }) + } }, 'image/png') }) } diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index 42673175..af98a659 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -74,6 +74,7 @@ @@ -83,12 +84,12 @@ ref="documents" class="pr-3.5 pl-0.5" > - + @@ -60,6 +65,11 @@ export default { templateId: { type: [Number, String], required: true + }, + isDirectUpload: { + type: Boolean, + required: true, + default: false } }, emits: ['success'], diff --git a/app/javascript/template_builder/upload.vue b/app/javascript/template_builder/upload.vue index 11fcd127..9d6bd5a6 100644 --- a/app/javascript/template_builder/upload.vue +++ b/app/javascript/template_builder/upload.vue @@ -24,15 +24,20 @@ Add Document - + + @@ -49,6 +54,11 @@ export default { templateId: { type: [Number, String], required: true + }, + isDirectUpload: { + type: Boolean, + required: true, + default: false } }, emits: ['success'], @@ -64,52 +74,66 @@ export default { } }, mounted () { - import('@rails/activestorage') + if (this.isDirectUpload) { + import('@rails/activestorage') + } }, methods: { async upload () { this.isLoading = true - const { DirectUpload } = await import('@rails/activestorage') + if (this.isDirectUpload) { + const { DirectUpload } = await import('@rails/activestorage') - const blobs = await Promise.all( - Array.from(this.$refs.input.files).map(async (file) => { - const upload = new DirectUpload( - file, - '/direct_uploads', - this.$refs.input - ) + const blobs = await Promise.all( + Array.from(this.$refs.input.files).map(async (file) => { + const upload = new DirectUpload( + file, + '/direct_uploads', + this.$refs.input + ) - return new Promise((resolve, reject) => { - upload.create((error, blob) => { - if (error) { - console.error(error) + return new Promise((resolve, reject) => { + upload.create((error, blob) => { + if (error) { + console.error(error) - return reject(error) - } else { - return resolve(blob) - } + return reject(error) + } else { + return resolve(blob) + } + }) + }).catch((error) => { + console.error(error) }) - }).catch((error) => { - console.error(error) }) + ).finally(() => { + this.isLoading = false }) - ).finally(() => { - this.isLoading = false - }) - this.isProcessing = true + this.isProcessing = true - fetch(`/api/templates/${this.templateId}/documents`, { - method: 'POST', - body: JSON.stringify({ blobs }), - headers: { 'Content-Type': 'application/json' } - }).then(resp => resp.json()).then((data) => { - this.$emit('success', data) - this.$refs.input.value = '' - }).finally(() => { - this.isProcessing = false - }) + fetch(`/api/templates/${this.templateId}/documents`, { + method: 'POST', + body: JSON.stringify({ blobs }), + headers: { 'Content-Type': 'application/json' } + }).then(resp => resp.json()).then((data) => { + this.$emit('success', data) + this.$refs.input.value = '' + }).finally(() => { + this.isProcessing = false + }) + } else { + fetch(`/api/templates/${this.templateId}/documents`, { + method: 'POST', + body: new FormData(this.$refs.form) + }).then(resp => resp.json()).then((data) => { + this.$emit('success', data) + this.$refs.input.value = '' + }).finally(() => { + this.isLoading = false + }) + } } } } diff --git a/app/views/esign_settings/_result.html.erb b/app/views/esign_settings/_result.html.erb index 10118a72..2cfbec7a 100644 --- a/app/views/esign_settings/_result.html.erb +++ b/app/views/esign_settings/_result.html.erb @@ -1,9 +1,9 @@
- <% blobs.zip(pdfs).each do |blob, pdf| %> + <% files.zip(pdfs).each do |file, pdf| %>
<% if pdf.signatures.to_a.size == 0 %>
- <%= blob.filename %> + <%= file.original_filename %>

There are no signatures... @@ -11,7 +11,7 @@ <% else %>

<%= svg_icon('file_text', class: 'w-5 h-5 inline') %> - <%= blob.filename %> - <%= pluralize(pdf.signatures.to_a.size, 'Signature') %> + <%= file.original_filename %> - <%= pluralize(pdf.signatures.to_a.size, 'Signature') %>
<% pdf.signatures.to_a.each do |signature| %>
diff --git a/app/views/esign_settings/index.html.erb b/app/views/esign_settings/index.html.erb index c7071c47..3dd42c54 100644 --- a/app/views/esign_settings/index.html.erb +++ b/app/views/esign_settings/index.html.erb @@ -7,14 +7,14 @@ Upload signed PDF file to validate its signature:

- <%= form_for '', url: settings_esign_index_path, method: :post do |f| %> + <%= form_for '', url: settings_esign_index_path, method: :post, html: { enctype: 'multipart/form-data' } do |f| %> <%= f.button type: 'submit', class: 'flex' do %>
<%= svg_icon('loader', class: 'w-5 h-5 animate-spin inline') %> Analyzing...
<% end %> - +
diff --git a/app/views/submit_form/show.html.erb b/app/views/submit_form/show.html.erb index 1cbbec9f..884b70ac 100644 --- a/app/views/submit_form/show.html.erb +++ b/app/views/submit_form/show.html.erb @@ -33,7 +33,7 @@
- +
diff --git a/app/views/templates/edit.html.erb b/app/views/templates/edit.html.erb index f1b8892c..0867a976 100644 --- a/app/views/templates/edit.html.erb +++ b/app/views/templates/edit.html.erb @@ -1 +1 @@ - + diff --git a/config/environments/development.rb b/config/environments/development.rb index 11b3069c..79c1486c 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -21,6 +21,7 @@ Rails.application.configure do # Do not eager load code on boot. config.eager_load = false + config.hosts = nil # Show full error reports. config.consider_all_requests_local = true @@ -45,7 +46,8 @@ Rails.application.configure do end # Store uploaded files on the local file system (see config/storage.yml for options). - config.active_storage.service = :local + config.active_storage.service = :disk + config.active_storage.resolve_model_to_route = :rails_storage_proxy # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = true diff --git a/config/environments/production.rb b/config/environments/production.rb index 0ad1f2c8..24d0deeb 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -46,9 +46,11 @@ Rails.application.configure do elsif ENV['AZURE_CONTAINER'].present? :azure else - :local + :disk end + config.active_storage.resolve_model_to_route = :rails_storage_proxy if ENV['ACTIVE_STORAGE_PUBLIC'] != 'true' + # Mount Action Cable outside main process or domain. # config.action_cable.mount_path = nil # config.action_cable.url = "wss://example.com/cable" diff --git a/config/storage.yml b/config/storage.yml index f704e8fa..d48d61f0 100644 --- a/config/storage.yml +++ b/config/storage.yml @@ -1,8 +1,3 @@ -local: - service: Disk - root: <%= ENV['WORKDIR'] || '.' %>/attachments - public: true - disk: service: Disk root: <%= ENV['WORKDIR'] || '.' %>/attachments @@ -14,7 +9,7 @@ aws_s3: secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %> region: <%= ENV['AWS_REGION'] || 'us-east-1' %> bucket: <%= ENV['S3_ATTACHMENTS_BUCKET'] %> - public: true + public: <%= ENV['ACTIVE_STORAGE_PUBLIC'] == 'true' %> upload: cache_control: 'public, max-age=31536000' @@ -23,7 +18,7 @@ google: credentials: <%= JSON.parse(ENV['GCS_CREDENTIALS'] || '{}') %> project: <%= ENV['GCS_PROJECT'] %> bucket: <%= ENV['GCS_BUCKET'] %> - public: true + public: <%= ENV['ACTIVE_STORAGE_PUBLIC'] == 'true' %> cache_control: "public, max-age=31536000" azure: @@ -31,4 +26,4 @@ azure: storage_account_name: <%= ENV['AZURE_STORAGE_ACCOUNT_NAME'] %> storage_access_key: <%= ENV['AZURE_STORAGE_ACCESS_KEY'] %> container: <%= ENV['AZURE_CONTAINER'] %> - public: true + public: <%= ENV['ACTIVE_STORAGE_PUBLIC'] == 'true' %> diff --git a/lib/docuseal.rb b/lib/docuseal.rb index 3cd5c170..13fb6ca8 100644 --- a/lib/docuseal.rb +++ b/lib/docuseal.rb @@ -19,6 +19,10 @@ module Docuseal ENV['MULTITENANT'] == 'true' end + def active_storage_public? + ENV['ACTIVE_STORAGE_PUBLIC'] == 'true' + end + def default_url_options return DEFAULT_URL_OPTIONS if multitenant? diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index 2e551a10..60f58f99 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -98,7 +98,8 @@ module Submissions (area['x'] * width) + (area['w'] * width) + TEXT_LEFT_MARGIN, height - (area['y'] * height) - lines[..next_index].sum(&:height) + height_diff ], - A: { Type: :Action, S: :URI, URI: attachment.url } + A: { Type: :Action, S: :URI, + URI: h.rails_blob_url(attachment, **Docuseal.default_url_options) } } ) @@ -260,5 +261,9 @@ module Submissions .write_to_buffer('.png') end end + + def h + Rails.application.routes.url_helpers + end end end