mirror of https://github.com/docusealco/docuseal
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
327 lines
11 KiB
327 lines
11 KiB
# frozen_string_literal: true
|
|
|
|
module Embed
|
|
class FormsController < ActionController::API
|
|
include ActionController::Cookies
|
|
include ActiveStorage::SetCurrent
|
|
include EmbedCors
|
|
|
|
before_action :set_embed_cors_headers, only: :preflight
|
|
|
|
def create
|
|
slug = token_payload['slug'] || params[:slug].presence || params[:template_slug].presence
|
|
|
|
raise ActiveRecord::RecordNotFound if slug.blank?
|
|
|
|
submitter = Submitter.find_by(slug: slug)
|
|
|
|
payload =
|
|
if submitter
|
|
@embed_cors_account = submitter.account
|
|
|
|
form_json(submitter)
|
|
else
|
|
template = Template.find_by!(slug: slug)
|
|
@embed_cors_account = template.account
|
|
|
|
form_json_for_template(template)
|
|
end
|
|
|
|
set_embed_cors_headers
|
|
|
|
render json: payload, status: payload[:error].present? ? :unprocessable_content : :ok
|
|
end
|
|
|
|
def preflight
|
|
head :ok
|
|
end
|
|
|
|
private
|
|
|
|
def form_json_for_template(template)
|
|
validate_shared_link_template!(template)
|
|
|
|
attrs = submitter_params
|
|
|
|
return { template: template_json(template), logo: logo_json(template.account) } if attrs.compact_blank.blank?
|
|
|
|
submitter = find_or_initialize_submitter(template, attrs)
|
|
|
|
return shared_link_multiple_parties_error if multiple_parties_shared_link?(template, submitter)
|
|
|
|
prepare_shared_link_submitter(submitter, template)
|
|
assign_shared_link_params(submitter)
|
|
|
|
if shared_link_2fa_required?(template, submitter)
|
|
Submitters.send_shared_link_email_verification_code(submitter, request: request)
|
|
|
|
return unverified_shared_link_email_json(template, submitter)
|
|
end
|
|
|
|
save_shared_link_submitter(submitter)
|
|
end
|
|
|
|
def form_json(submitter)
|
|
raise ActiveRecord::RecordNotFound if submitter.account.archived_at?
|
|
|
|
unavailable_payload = unavailable_submitter_json(submitter)
|
|
|
|
return unavailable_payload if unavailable_payload
|
|
|
|
pending_payload = pending_verification_json(submitter)
|
|
|
|
return pending_payload if pending_payload
|
|
|
|
ready_submitter_json(submitter)
|
|
end
|
|
|
|
def ready_submitter_json(submitter)
|
|
submission = submitter.submission
|
|
|
|
Submissions.preload_with_pages(submission)
|
|
Submitters::MaybeUpdateDefaultValues.call(submitter, nil)
|
|
|
|
{
|
|
template: template_json(submitter.template),
|
|
submission: submission_json(submission, submitter),
|
|
submitter: submitter_json(submitter),
|
|
documents: documents_json(submission),
|
|
attachments: attachments_json(submission),
|
|
values: values_param.presence || {},
|
|
logo: logo_json(submitter.account)
|
|
}
|
|
end
|
|
|
|
def unavailable_submitter_json(submitter)
|
|
return submitter_status_json(submitter, :expired_submitter) if submitter.submission.expired?
|
|
return submitter_status_json(submitter, :completed_submitter) if submitter.completed_at?
|
|
return submitter_status_json(submitter, :expired_submitter) if submitter.declined_at?
|
|
end
|
|
|
|
def submitter_status_json(submitter, key)
|
|
{
|
|
key => submitter_json(submitter),
|
|
submission: submission_json(submitter.submission, submitter),
|
|
template: template_json(submitter.template),
|
|
logo: logo_json(submitter.account)
|
|
}
|
|
end
|
|
|
|
def pending_verification_json(submitter)
|
|
unless Submitters::AuthorizedForForm.pass_email_2fa?(submitter, request)
|
|
return submitter_status_json(submitter, :submitter_email_2fa)
|
|
end
|
|
|
|
return if Submitters::AuthorizedForForm.pass_link_2fa?(submitter, nil, request)
|
|
|
|
{
|
|
unverified_email: submitter.email,
|
|
submission: submission_json(submitter.submission, submitter),
|
|
template: template_json(submitter.template),
|
|
logo: logo_json(submitter.account)
|
|
}
|
|
end
|
|
|
|
def validate_shared_link_template!(template)
|
|
raise ActiveRecord::RecordNotFound if template.archived_at? || template.account.archived_at?
|
|
raise ActiveRecord::RecordNotFound unless template.shared_link?
|
|
end
|
|
|
|
def multiple_parties_shared_link?(template, submitter)
|
|
selected_template_submitter(template).blank? &&
|
|
filter_undefined_submitters(template).size > 1 &&
|
|
submitter.new_record?
|
|
end
|
|
|
|
def shared_link_multiple_parties_error
|
|
{ error: I18n.t('this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link') }
|
|
end
|
|
|
|
def prepare_shared_link_submitter(submitter, template)
|
|
if submitter.new_record?
|
|
assign_submission_attributes(submitter, template)
|
|
Submissions::AssignDefinedSubmitters.call(submitter.submission)
|
|
else
|
|
submitter.assign_attributes(ip: request.remote_ip, ua: request.user_agent)
|
|
end
|
|
end
|
|
|
|
def assign_shared_link_params(submitter)
|
|
submitter.values = values_param.presence || submitter.values
|
|
submitter.metadata = metadata_param.presence || submitter.metadata
|
|
submitter.external_id = params[:external_id].presence || submitter.external_id
|
|
end
|
|
|
|
def shared_link_2fa_required?(template, submitter)
|
|
template.preferences['shared_link_2fa'] == true &&
|
|
!Submitters::AuthorizedForForm.pass_link_2fa?(submitter, nil, request)
|
|
end
|
|
|
|
def unverified_shared_link_email_json(template, submitter)
|
|
{
|
|
template: template_json(template),
|
|
unverified_email: submitter.email,
|
|
logo: logo_json(template.account)
|
|
}
|
|
end
|
|
|
|
def save_shared_link_submitter(submitter)
|
|
if submitter.errors.blank? && submitter.save
|
|
enqueue_new_submitter_jobs(submitter) if submitter.previous_changes.key?('id')
|
|
|
|
form_json(submitter)
|
|
else
|
|
{ error: submitter.errors.full_messages.to_sentence }
|
|
end
|
|
end
|
|
|
|
def template_json(template)
|
|
template.as_json(only: %i[id name slug preferences schema submitters archived_at account_id])
|
|
end
|
|
|
|
def submission_json(submission, submitter = nil)
|
|
submission.as_json(
|
|
only: %i[
|
|
id slug name source submitters_order expire_at archived_at created_at updated_at template_id account_id
|
|
],
|
|
methods: %i[expired?]
|
|
).merge(
|
|
'template_schema' => Submissions.filtered_conditions_schema(submission,
|
|
include_submitter_uuid: submitter&.uuid),
|
|
'template_fields' => submission.template_fields || submission.template.fields,
|
|
'template_submitters' => submission.template_submitters || submission.template.submitters,
|
|
'submitters' => submission.submitters.as_json(
|
|
only: %i[id uuid slug name email phone completed_at declined_at opened_at sent_at]
|
|
)
|
|
)
|
|
end
|
|
|
|
def submitter_json(submitter)
|
|
submitter.as_json(
|
|
only: %i[id uuid slug name email phone values metadata preferences completed_at declined_at opened_at sent_at]
|
|
)
|
|
end
|
|
|
|
def documents_json(submission)
|
|
submission.schema_documents.as_json(
|
|
methods: %i[metadata signed_key],
|
|
include: { preview_images: { methods: %i[url metadata filename] } }
|
|
)
|
|
end
|
|
|
|
def attachments_json(submission)
|
|
ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
|
|
.preload(:blob)
|
|
.as_json(only: %i[uuid created_at], methods: %i[url filename content_type])
|
|
end
|
|
|
|
def logo_json(account)
|
|
return unless account.logo.attached?
|
|
|
|
{ url: account.logo.url }
|
|
end
|
|
|
|
def find_or_initialize_submitter(template, attrs)
|
|
required_fields = template.preferences.fetch('link_form_fields', ['email'])
|
|
required_params = required_fields.index_with { |key| attrs[key] }
|
|
find_params = required_params.except('name')
|
|
|
|
submitter = Submitter.new if find_params.compact_blank.blank?
|
|
|
|
relation =
|
|
Submitter
|
|
.where(submission: template.submissions.where(expire_at: Time.current..)
|
|
.or(template.submissions.where(expire_at: nil)).where(archived_at: nil))
|
|
.order(id: :desc)
|
|
.where(declined_at: nil)
|
|
.where(external_id: nil)
|
|
.where(template.preferences['shared_link_2fa'] == true ? {} : { ip: [nil, request.remote_ip] })
|
|
|
|
if (template_submitter = selected_template_submitter(template))
|
|
relation = relation.where(uuid: template_submitter['uuid'])
|
|
end
|
|
|
|
submitter ||= relation
|
|
.find_or_initialize_by(find_params)
|
|
|
|
submitter.name = required_params['name'] if submitter.new_record?
|
|
|
|
required_params.each do |key, value|
|
|
submitter.errors.add(key.to_sym, :blank) if value.blank?
|
|
end
|
|
|
|
submitter
|
|
end
|
|
|
|
def assign_submission_attributes(submitter, template)
|
|
submitter.assign_attributes(
|
|
uuid: (selected_template_submitter(template) || filter_undefined_submitters(template).first ||
|
|
template.submitters.first)['uuid'],
|
|
ip: request.remote_ip,
|
|
ua: request.user_agent,
|
|
values: {},
|
|
preferences: { 'send_email' => params[:send_email] },
|
|
metadata: {}
|
|
)
|
|
|
|
submitter.submission ||= Submission.new(template: template,
|
|
account_id: template.account_id,
|
|
template_submitters: template.submitters,
|
|
template_fields: template.fields,
|
|
template_schema: template.schema,
|
|
expire_at: Templates.build_default_expire_at(template),
|
|
submitters: [submitter],
|
|
source: :embed)
|
|
|
|
Submissions::CreateFromSubmitters.maybe_set_dynamic_documents(submitter.submission)
|
|
|
|
submitter.account_id = submitter.submission.account_id
|
|
end
|
|
|
|
def enqueue_new_submitter_jobs(submitter)
|
|
WebhookUrls.enqueue_events(submitter.submission, 'submission.created')
|
|
SearchEntries.enqueue_reindex(submitter)
|
|
|
|
return unless submitter.submission.expire_at?
|
|
|
|
ProcessSubmissionExpiredJob.perform_at(submitter.submission.expire_at, 'submission_id' => submitter.submission_id)
|
|
end
|
|
|
|
def filter_undefined_submitters(template)
|
|
Templates.filter_undefined_submitters(template.submitters)
|
|
end
|
|
|
|
def selected_template_submitter(template)
|
|
submitter_name = params[:submitter].presence || params[:role].presence
|
|
|
|
return if submitter_name.blank?
|
|
|
|
template.submitters.find { |item| item['uuid'] == submitter_name || item['name'] == submitter_name }
|
|
end
|
|
|
|
def submitter_params
|
|
{
|
|
'email' => Submissions.normalize_email(params[:email].presence),
|
|
'name' => params[:name].presence,
|
|
'phone' => params[:phone].presence
|
|
}.compact
|
|
end
|
|
|
|
def values_param
|
|
params[:values].respond_to?(:to_unsafe_h) ? params[:values].to_unsafe_h : params[:values]
|
|
end
|
|
|
|
def metadata_param
|
|
params[:metadata].respond_to?(:to_unsafe_h) ? params[:metadata].to_unsafe_h : params[:metadata]
|
|
end
|
|
|
|
def token_payload
|
|
return {} if params[:token].blank?
|
|
|
|
JSON.parse(Base64.urlsafe_decode64(params[:token].split('.')[1])).with_indifferent_access
|
|
rescue JSON::ParserError, ArgumentError
|
|
{}
|
|
end
|
|
end
|
|
end
|