allow to resent copy email
This commit is contained in:
@@ -10,9 +10,9 @@ module Api
|
|||||||
before_action :set_cors_headers
|
before_action :set_cors_headers
|
||||||
|
|
||||||
def show
|
def show
|
||||||
blob_uuid, purp = ApplicationRecord.signed_id_verifier.verified(params[:signed_uuid])
|
blob_uuid, purp, exp = ApplicationRecord.signed_id_verifier.verified(params[:signed_uuid])
|
||||||
|
|
||||||
if blob_uuid.blank? || purp != 'blob'
|
if blob_uuid.blank? || purp != 'blob' || (exp && exp < Time.current.to_i)
|
||||||
Rollbar.error('Blob not found') if defined?(Rollbar)
|
Rollbar.error('Blob not found') if defined?(Rollbar)
|
||||||
|
|
||||||
return head :not_found
|
return head :not_found
|
||||||
|
|||||||
@@ -12,13 +12,16 @@ class SendSubmissionEmailController < ApplicationController
|
|||||||
def create
|
def create
|
||||||
@submitter =
|
@submitter =
|
||||||
if params[:template_slug]
|
if params[:template_slug]
|
||||||
Submitter.joins(submission: :template).find_by!(email: params[:email],
|
Submitter.joins(submission: :template).find_by!(email: params[:email].to_s.downcase,
|
||||||
template: { slug: params[:template_slug] })
|
template: { slug: params[:template_slug] })
|
||||||
|
elsif params[:submission_slug]
|
||||||
|
Submitter.joins(:submission).find_by!(email: params[:email].to_s.downcase,
|
||||||
|
submission: { slug: params[:submission_slug] })
|
||||||
else
|
else
|
||||||
Submitter.find_by!(slug: params[:submitter_slug])
|
Submitter.find_by!(slug: params[:submitter_slug])
|
||||||
end
|
end
|
||||||
|
|
||||||
SubmitterMailer.documents_copy_email(@submitter).deliver_later!
|
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later!
|
||||||
|
|
||||||
respond_to do |f|
|
respond_to do |f|
|
||||||
f.html { redirect_to success_send_submission_email_index_path }
|
f.html { redirect_to success_send_submission_email_index_path }
|
||||||
|
|||||||
@@ -6,23 +6,8 @@ class SubmissionsController < ApplicationController
|
|||||||
|
|
||||||
load_and_authorize_resource :submission, only: %i[show destroy]
|
load_and_authorize_resource :submission, only: %i[show destroy]
|
||||||
|
|
||||||
PRELOAD_ALL_PAGES_AMOUNT = 200
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
ActiveRecord::Associations::Preloader.new(
|
@submission = Submissions.preload_with_pages(@submission)
|
||||||
records: [@submission],
|
|
||||||
associations: [:template, { template_schema_documents: :blob }]
|
|
||||||
).call
|
|
||||||
|
|
||||||
total_pages =
|
|
||||||
@submission.template_schema_documents.sum { |e| e.metadata.dig('pdf', 'number_of_pages').to_i }
|
|
||||||
|
|
||||||
if total_pages < PRELOAD_ALL_PAGES_AMOUNT
|
|
||||||
ActiveRecord::Associations::Preloader.new(
|
|
||||||
records: @submission.template_schema_documents,
|
|
||||||
associations: [:blob, { preview_images_attachments: :blob }]
|
|
||||||
).call
|
|
||||||
end
|
|
||||||
|
|
||||||
render :show, layout: 'plain'
|
render :show, layout: 'plain'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,10 +4,20 @@ class SubmissionsDownloadController < ApplicationController
|
|||||||
skip_before_action :authenticate_user!
|
skip_before_action :authenticate_user!
|
||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
TTL = 20.minutes
|
TTL = 40.minutes
|
||||||
|
FILES_TTL = 5.minutes
|
||||||
|
|
||||||
def index
|
def index
|
||||||
submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
submitter = Submitter.find_signed(params[:sig], purpose: :download_completed) if params[:sig].present?
|
||||||
|
|
||||||
|
signature_valid =
|
||||||
|
if submitter&.slug == params[:submitter_slug]
|
||||||
|
true
|
||||||
|
else
|
||||||
|
submitter = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
submitter ||= Submitter.find_by!(slug: params[:submitter_slug])
|
||||||
|
|
||||||
Submissions::EnsureResultGenerated.call(submitter)
|
Submissions::EnsureResultGenerated.call(submitter)
|
||||||
|
|
||||||
@@ -17,18 +27,24 @@ class SubmissionsDownloadController < ApplicationController
|
|||||||
|
|
||||||
return head :not_found unless last_submitter.completed_at?
|
return head :not_found unless last_submitter.completed_at?
|
||||||
|
|
||||||
if last_submitter.completed_at < TTL.ago &&
|
if last_submitter.completed_at < TTL.ago && !signature_valid && !current_user_submitter?(last_submitter)
|
||||||
(current_user.nil? || !current_user.account.submitters.exists?(id: last_submitter.id))
|
|
||||||
Rollbar.info("TTL: #{last_submitter.id}") if defined?(Rollbar)
|
Rollbar.info("TTL: #{last_submitter.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
return head :not_found
|
return head :not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
urls =
|
render json: build_urls(last_submitter)
|
||||||
Submitters.select_attachments_for_download(last_submitter).map do |attachment|
|
end
|
||||||
ActiveStorage::Blob.proxy_url(attachment.blob)
|
|
||||||
end
|
|
||||||
|
|
||||||
render json: urls
|
private
|
||||||
|
|
||||||
|
def current_user_submitter?(submitter)
|
||||||
|
current_user && current_user.account.submitters.exists?(id: submitter.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_urls(submitter)
|
||||||
|
Submitters.select_attachments_for_download(submitter).map do |attachment|
|
||||||
|
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.minutes.from_now.to_i)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,37 +4,31 @@ class SubmissionsPreviewController < ApplicationController
|
|||||||
skip_before_action :authenticate_user!
|
skip_before_action :authenticate_user!
|
||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
PRELOAD_ALL_PAGES_AMOUNT = 200
|
TTL = 40.minutes
|
||||||
|
|
||||||
TTL = 20.minutes
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@submission = Submission.find_by!(slug: params[:slug])
|
submitter = Submitter.find_signed(params[:sig], purpose: :download_completed) if params[:sig].present?
|
||||||
|
|
||||||
|
signature_valid =
|
||||||
|
if submitter && submitter.submission.slug == params[:slug]
|
||||||
|
@submission = submitter.submission
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
@submission ||= Submission.find_by!(slug: params[:slug])
|
||||||
|
|
||||||
if !@submission.submitters.all?(&:completed_at?) && current_user.blank?
|
if !@submission.submitters.all?(&:completed_at?) && current_user.blank?
|
||||||
raise ActionController::RoutingError, 'Not Found'
|
raise ActionController::RoutingError, 'Not Found'
|
||||||
end
|
end
|
||||||
|
|
||||||
unless submission_valid_ttl?(@submission)
|
if !submission_valid_ttl?(@submission) && !signature_valid
|
||||||
Rollbar.info("TTL: #{@submission.id}") if defined?(Rollbar)
|
Rollbar.info("TTL: #{@submission.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
return redirect_to submissions_preview_completed_path(@submission.slug)
|
return redirect_to submissions_preview_completed_path(@submission.slug)
|
||||||
end
|
end
|
||||||
|
|
||||||
ActiveRecord::Associations::Preloader.new(
|
@submission = Submissions.preload_with_pages(@submission)
|
||||||
records: [@submission],
|
|
||||||
associations: [:template, { template_schema_documents: :blob }]
|
|
||||||
).call
|
|
||||||
|
|
||||||
total_pages =
|
|
||||||
@submission.template_schema_documents.sum { |e| e.metadata.dig('pdf', 'number_of_pages').to_i }
|
|
||||||
|
|
||||||
if total_pages < PRELOAD_ALL_PAGES_AMOUNT
|
|
||||||
ActiveRecord::Associations::Preloader.new(
|
|
||||||
records: @submission.template_schema_documents,
|
|
||||||
associations: [:blob, { preview_images_attachments: :blob }]
|
|
||||||
).call
|
|
||||||
end
|
|
||||||
|
|
||||||
render 'submissions/show', layout: 'plain'
|
render 'submissions/show', layout: 'plain'
|
||||||
end
|
end
|
||||||
@@ -42,7 +36,7 @@ class SubmissionsPreviewController < ApplicationController
|
|||||||
def completed
|
def completed
|
||||||
@submission = Submission.find_by!(slug: params[:submissions_preview_slug])
|
@submission = Submission.find_by!(slug: params[:submissions_preview_slug])
|
||||||
|
|
||||||
render :completed, layout: 'plain'
|
render :completed, layout: 'form'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
class SubmitterMailer < ApplicationMailer
|
class SubmitterMailer < ApplicationMailer
|
||||||
MAX_ATTACHMENTS_SIZE = 10.megabytes
|
MAX_ATTACHMENTS_SIZE = 10.megabytes
|
||||||
|
SIGN_TTL = 1.hour + 20.minutes
|
||||||
|
|
||||||
DEFAULT_INVITATION_SUBJECT = 'You are invited to submit a form'
|
DEFAULT_INVITATION_SUBJECT = 'You are invited to submit a form'
|
||||||
|
|
||||||
@@ -61,9 +62,10 @@ class SubmitterMailer < ApplicationMailer
|
|||||||
subject:)
|
subject:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def documents_copy_email(submitter, to: nil)
|
def documents_copy_email(submitter, to: nil, sig: false)
|
||||||
@current_account = submitter.submission.template.account
|
@current_account = submitter.submission.template.account
|
||||||
@submitter = submitter
|
@submitter = submitter
|
||||||
|
@sig = submitter.signed_id(expires_in: SIGN_TTL, purpose: :download_completed) if sig
|
||||||
|
|
||||||
Submissions::EnsureResultGenerated.call(@submitter)
|
Submissions::EnsureResultGenerated.call(@submitter)
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<div class="space-x-3 flex items-center">
|
<div class="space-x-3 flex items-center">
|
||||||
<% if @submission.audit_trail.present? %>
|
<% if @submission.audit_trail.present? %>
|
||||||
<a href="<%= ActiveStorage::Blob.proxy_url(@submission.audit_trail.blob) %>" class="white-button" target="_blank">
|
<a href="<%= ActiveStorage::Blob.proxy_url(@submission.audit_trail.blob, expires_at: 4.hours.from_now) %>" class="white-button" target="_blank">
|
||||||
<%= svg_icon('external_link', class: 'w-6 h-6') %>
|
<%= svg_icon('external_link', class: 'w-6 h-6') %>
|
||||||
<span class="hidden md:inline">
|
<span class="hidden md:inline">
|
||||||
Audit Log
|
Audit Log
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if last_submitter = @submission.submitters.to_a.select(&:completed_at?).max_by(&:completed_at) %>
|
<% if last_submitter = @submission.submitters.to_a.select(&:completed_at?).max_by(&:completed_at) %>
|
||||||
<download-button data-src="<%= submitter_download_index_path(last_submitter.slug) %>" class="base-button">
|
<download-button data-src="<%= submitter_download_index_path(last_submitter.slug, { sig: params[:sig] }.compact) %>" class="base-button">
|
||||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||||
<%= svg_icon('download', class: 'w-6 h-6') %>
|
<%= svg_icon('download', class: 'w-6 h-6') %>
|
||||||
<span class="hidden md:inline">Download</span>
|
<span class="hidden md:inline">Download</span>
|
||||||
|
|||||||
@@ -20,6 +20,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<% if Accounts.can_send_emails?(@submission.account) %>
|
||||||
|
<%= form_for '', url: send_submission_email_index_path, method: :post, html: { class: 'space-y-4', onsubmit: 'event.submitter.disabled = true' } do |f| %>
|
||||||
|
<div dir="auto" class="form-control !mt-0">
|
||||||
|
<%= f.hidden_field :submission_slug, value: @submission.slug %>
|
||||||
|
<%= f.label :email, t('email'), class: 'label' %>
|
||||||
|
<%= f.email_field :email, value: current_user&.email || params[:email], required: true, class: 'base-input', placeholder: t('send_copy_to_email') %>
|
||||||
|
</div>
|
||||||
|
<div dir="auto" class="form-control">
|
||||||
|
<%= f.button button_title(title: t('send_copy_to_email'), disabled_with: t('starting')), class: 'base-button' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% if Docuseal.multitenant? %>
|
||||||
|
<div class="divider uppercase"><%= t('or') %></div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
<% if Docuseal.multitenant? %>
|
<% if Docuseal.multitenant? %>
|
||||||
<div>
|
<div>
|
||||||
<%= link_to 'Create free account', registration_path, class: 'white-button w-full' %>
|
<%= link_to 'Create free account', registration_path, class: 'white-button w-full' %>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<div class="py-2"></div>
|
<div class="py-2"></div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if @submitter.completed_at > 15.minutes.ago || (current_user && current_user.account.submitters.exists?(id: @submitter.id)) %>
|
<% if @submitter.completed_at > 30.minutes.ago || (current_user && current_user.account.submitters.exists?(id: @submitter.id)) %>
|
||||||
<download-button data-src="<%= submitter_download_index_path(@submitter.slug) %>" class="base-button w-full">
|
<download-button data-src="<%= submitter_download_index_path(@submitter.slug) %>" class="base-button w-full">
|
||||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||||
<%= svg_icon('download', class: 'w-6 h-6') %>
|
<%= svg_icon('download', class: 'w-6 h-6') %>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<% if @email_config %>
|
<% if @email_config %>
|
||||||
<%= auto_link(simple_format(h(ReplaceEmailVariables.call(@email_config.value['body'], submitter: @submitter)))) %>
|
<%= auto_link(simple_format(h(ReplaceEmailVariables.call(@email_config.value['body'], submitter: @submitter, sig: @sig)))) %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p>Hi there,</p>
|
<p>Hi there,</p>
|
||||||
<p>Please check the copy of your "<%= @submitter.submission.template.name %>" submission in the email attachments.</p>
|
<p>Please check the copy of your "<%= @submitter.submission.template.name %>" submission in the email attachments.</p>
|
||||||
<p>Alternatively, you can review and download your copy using:</p>
|
<p>Alternatively, you can review and download your copy using:</p>
|
||||||
<p>
|
<p>
|
||||||
<%= link_to @submitter.template.name, submissions_preview_url(@submitter.submission.slug) %>
|
<%= link_to @submitter.template.name, submissions_preview_url(@submitter.submission.slug, { sig: @sig }.compact) %>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Thanks,<br><%= @current_account.name %>
|
Thanks,<br><%= @current_account.name %>
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ Rails.application.configure do
|
|||||||
|
|
||||||
config.lograge.custom_payload do |controller|
|
config.lograge.custom_payload do |controller|
|
||||||
{
|
{
|
||||||
fwd: controller.request.ip.to_s[/\A\d+\.(.*)/, 1],
|
fwd: controller.request.ip,
|
||||||
params: controller.request.params&.slice(:id),
|
params: controller.request.params&.slice(:id),
|
||||||
host: controller.request.host,
|
host: controller.request.host,
|
||||||
uid: controller.instance_variable_get(:@current_user).try(:id)
|
uid: controller.instance_variable_get(:@current_user).try(:id)
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ end
|
|||||||
ActiveSupport.on_load(:active_storage_blob) do
|
ActiveSupport.on_load(:active_storage_blob) do
|
||||||
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
attribute :uuid, :string, default: -> { SecureRandom.uuid }
|
||||||
|
|
||||||
def self.proxy_url(blob, expires_in: nil)
|
def self.proxy_url(blob, expires_at: nil)
|
||||||
Rails.application.routes.url_helpers.blobs_proxy_url(
|
Rails.application.routes.url_helpers.blobs_proxy_url(
|
||||||
signed_uuid: blob.signed_uuid(expires_in:), filename: blob.filename,
|
signed_uuid: blob.signed_uuid(expires_at:), filename: blob.filename,
|
||||||
**Docuseal.default_url_options
|
**Docuseal.default_url_options
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@@ -28,8 +28,10 @@ ActiveSupport.on_load(:active_storage_blob) do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def signed_uuid(expires_in: nil)
|
def signed_uuid(expires_at: nil)
|
||||||
ApplicationRecord.signed_id_verifier.generate([uuid, 'blob'], expires_in:)
|
expires_at = expires_at.to_i if expires_at
|
||||||
|
|
||||||
|
ApplicationRecord.signed_id_verifier.generate([uuid, 'blob', expires_at].compact)
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete
|
def delete
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ module ReplaceEmailVariables
|
|||||||
module_function
|
module_function
|
||||||
|
|
||||||
# rubocop:disable Metrics
|
# rubocop:disable Metrics
|
||||||
def call(text, submitter:, tracking_event_type: 'click_email')
|
def call(text, submitter:, tracking_event_type: 'click_email', sig: nil)
|
||||||
submitter_link = build_submitter_link(submitter, tracking_event_type)
|
submitter_link = build_submitter_link(submitter, tracking_event_type)
|
||||||
|
|
||||||
submission_link = build_submission_link(submitter.submission) if submitter.submission
|
submission_link = build_submission_link(submitter.submission) if submitter.submission
|
||||||
@@ -35,8 +35,8 @@ module ReplaceEmailVariables
|
|||||||
if text.include?(SUBMISSION_SUBMITTERS)
|
if text.include?(SUBMISSION_SUBMITTERS)
|
||||||
text = text.gsub(SUBMISSION_SUBMITTERS, build_submission_submitters(submitter.submission))
|
text = text.gsub(SUBMISSION_SUBMITTERS, build_submission_submitters(submitter.submission))
|
||||||
end
|
end
|
||||||
text = text.gsub(DOCUMENTS_LINKS, build_documents_links_text(submitter))
|
text = text.gsub(DOCUMENTS_LINKS, build_documents_links_text(submitter, sig))
|
||||||
text = text.gsub(DOCUMENTS_LINK, build_documents_links_text(submitter))
|
text = text.gsub(DOCUMENTS_LINK, build_documents_links_text(submitter, sig))
|
||||||
|
|
||||||
text = text.gsub(ACCOUNT_NAME, submitter.template.account.name) if submitter.template
|
text = text.gsub(ACCOUNT_NAME, submitter.template.account.name) if submitter.template
|
||||||
|
|
||||||
@@ -44,9 +44,9 @@ module ReplaceEmailVariables
|
|||||||
end
|
end
|
||||||
# rubocop:enable Metrics
|
# rubocop:enable Metrics
|
||||||
|
|
||||||
def build_documents_links_text(submitter)
|
def build_documents_links_text(submitter, sig = nil)
|
||||||
Rails.application.routes.url_helpers.submissions_preview_url(
|
Rails.application.routes.url_helpers.submissions_preview_url(
|
||||||
submitter.submission.slug, **Docuseal.default_url_options
|
submitter.submission.slug, { sig:, **Docuseal.default_url_options }.compact
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
module Submissions
|
module Submissions
|
||||||
DEFAULT_SUBMITTERS_ORDER = 'random'
|
DEFAULT_SUBMITTERS_ORDER = 'random'
|
||||||
|
|
||||||
|
PRELOAD_ALL_PAGES_AMOUNT = 200
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
def search(submissions, keyword)
|
def search(submissions, keyword)
|
||||||
@@ -27,6 +29,25 @@ module Submissions
|
|||||||
submission.save!
|
submission.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def preload_with_pages(submission)
|
||||||
|
ActiveRecord::Associations::Preloader.new(
|
||||||
|
records: [submission],
|
||||||
|
associations: [:template, { template_schema_documents: :blob }]
|
||||||
|
).call
|
||||||
|
|
||||||
|
total_pages =
|
||||||
|
submission.template_schema_documents.sum { |e| e.metadata.dig('pdf', 'number_of_pages').to_i }
|
||||||
|
|
||||||
|
if total_pages < PRELOAD_ALL_PAGES_AMOUNT
|
||||||
|
ActiveRecord::Associations::Preloader.new(
|
||||||
|
records: submission.template_schema_documents,
|
||||||
|
associations: [:blob, { preview_images_attachments: :blob }]
|
||||||
|
).call
|
||||||
|
end
|
||||||
|
|
||||||
|
submission
|
||||||
|
end
|
||||||
|
|
||||||
def create_from_emails(template:, user:, emails:, source:, mark_as_sent: false, params: {})
|
def create_from_emails(template:, user:, emails:, source:, mark_as_sent: false, params: {})
|
||||||
preferences = Submitters.normalize_preferences(user.account, user, params)
|
preferences = Submitters.normalize_preferences(user.account, user, params)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user