mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 22:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50acff2e81 | ||
|
|
cb6d24dfca | ||
|
|
ab4a22d8ba | ||
|
|
bc40a15de1 | ||
|
|
2542f26d96 | ||
|
|
84edb6dbda | ||
|
|
88776e7316 | ||
|
|
f669045362 | ||
|
|
68d3efbdba | ||
|
|
22d4833496 | ||
|
|
ff14caa9f7 | ||
|
|
05647135bc | ||
|
|
ae76c2a4a8 | ||
|
|
fb2b3d44fe | ||
|
|
3868557efd | ||
|
|
9bef28c8ba | ||
|
|
f25fd3b03d | ||
|
|
d1d7571ee4 | ||
|
|
6a298a068c | ||
|
|
d4aef79b68 |
No files matched your search
+1
-1
@@ -437,7 +437,7 @@ GEM
|
|||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
rexml (3.3.8)
|
rexml (3.3.9)
|
||||||
rotp (6.3.0)
|
rotp (6.3.0)
|
||||||
rqrcode (2.2.0)
|
rqrcode (2.2.0)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<h1 align="center" style="border-bottom: none">
|
<h1 align="center" style="border-bottom: none">
|
||||||
<div>
|
<div>
|
||||||
<a href="https://www.docuseal.co">
|
<a href="https://www.docuseal.com">
|
||||||
<img alt="DocuSeal" src="https://github.com/docusealco/docuseal/assets/5418788/c12cd051-81cd-4402-bc3a-92f2cfdc1b06" width="80" />
|
<img alt="DocuSeal" src="https://github.com/docusealco/docuseal/assets/5418788/c12cd051-81cd-4402-bc3a-92f2cfdc1b06" width="80" />
|
||||||
<br>
|
<br>
|
||||||
</a>
|
</a>
|
||||||
@@ -54,11 +54,11 @@ DocuSeal is an open source platform that provides secure and efficient digital d
|
|||||||
- Conditional fields and formulas
|
- Conditional fields and formulas
|
||||||
- Bulk send with CSV, XLSX spreadsheet import
|
- Bulk send with CSV, XLSX spreadsheet import
|
||||||
- SSO / SAML
|
- SSO / SAML
|
||||||
- Template creation with HTML API ([Guide](https://www.docuseal.co/guides/create-pdf-document-fillable-form-with-html-api))
|
- Template creation with HTML API ([Guide](https://www.docuseal.com/guides/create-pdf-document-fillable-form-with-html-api))
|
||||||
- Template creation with PDF or DOCX and field tags API ([Guide](https://www.docuseal.co/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form))
|
- Template creation with PDF or DOCX and field tags API ([Guide](https://www.docuseal.com/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form))
|
||||||
- Embedded signing form ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.co/docs/embedded))
|
- Embedded signing form ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.com/docs/embedded))
|
||||||
- Embedded document form builder ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.co/docs/embedded))
|
- Embedded document form builder ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.com/docs/embedded))
|
||||||
- [Learn more](https://www.docuseal.co/pricing)
|
- [Learn more](https://www.docuseal.com/pricing)
|
||||||
|
|
||||||
## Deploy
|
## Deploy
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ sudo HOST=your-domain-name.com docker compose up
|
|||||||
|
|
||||||
At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, Real Estate, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws.
|
At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, Real Estate, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws.
|
||||||
|
|
||||||
[Book a Meeting](https://www.docuseal.co/contact)
|
[Book a Meeting](https://www.docuseal.com/contact)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -102,6 +102,6 @@ Unless otherwise noted, all files © 2023 DocuSeal LLC.
|
|||||||
|
|
||||||
## Tools
|
## Tools
|
||||||
|
|
||||||
- [Signature Maker](https://www.docuseal.co/online-signature)
|
- [Signature Maker](https://www.docuseal.com/online-signature)
|
||||||
- [Sign Document Online](https://www.docuseal.co/sign-documents-online)
|
- [Sign Document Online](https://www.docuseal.com/sign-documents-online)
|
||||||
- [Fill PDF Online](https://www.docuseal.co/fill-pdf)
|
- [Fill PDF Online](https://www.docuseal.com/fill-pdf)
|
||||||
@@ -67,15 +67,18 @@ module Api
|
|||||||
|
|
||||||
submissions = create_submissions(@template, params)
|
submissions = create_submissions(@template, params)
|
||||||
|
|
||||||
submissions.each do |submission|
|
WebhookUrls.for_account_id(@template.account_id, 'submission.created').each do |webhook_url|
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => submission.id })
|
submissions.each do |submission|
|
||||||
|
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Submissions.send_signature_requests(submissions)
|
Submissions.send_signature_requests(submissions)
|
||||||
|
|
||||||
submissions.each do |submission|
|
submissions.each do |submission|
|
||||||
submission.submitters.each do |submitter|
|
submission.submitters.each do |submitter|
|
||||||
ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => submitter.id }) if submitter.completed_at?
|
ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id) if submitter.completed_at?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -93,7 +96,10 @@ module Api
|
|||||||
else
|
else
|
||||||
@submission.update!(archived_at: Time.current)
|
@submission.update!(archived_at: Time.current)
|
||||||
|
|
||||||
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id)
|
WebhookUrls.for_account_id(@submission.account_id, 'submission.archived').each do |webhook_url|
|
||||||
|
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: @submission.as_json(only: %i[id archived_at])
|
render json: @submission.as_json(only: %i[id archived_at])
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ module Api
|
|||||||
|
|
||||||
SubmissionEvents.create_with_tracking_data(submitter, 'view_form', request)
|
SubmissionEvents.create_with_tracking_data(submitter, 'view_form', request)
|
||||||
|
|
||||||
SendFormViewedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id })
|
WebhookUrls.for_account_id(submitter.account_id, 'form.viewed').each do |webhook_url|
|
||||||
|
SendFormViewedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
|
||||||
render json: {}
|
render json: {}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ module Api
|
|||||||
end
|
end
|
||||||
|
|
||||||
if @submitter.completed_at?
|
if @submitter.completed_at?
|
||||||
ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => @submitter.id })
|
ProcessSubmitterCompletionJob.perform_async('submitter_id' => @submitter.id)
|
||||||
elsif normalized_params[:send_email] || normalized_params[:send_sms]
|
elsif normalized_params[:send_email] || normalized_params[:send_sms]
|
||||||
Submitters.send_signature_requests([@submitter])
|
Submitters.send_signature_requests([@submitter])
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ module Api
|
|||||||
|
|
||||||
schema_documents = Templates::CloneAttachments.call(template: cloned_template, original_template: @template)
|
schema_documents = Templates::CloneAttachments.call(template: cloned_template, original_template: @template)
|
||||||
|
|
||||||
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => cloned_template.id)
|
WebhookUrls.for_account_id(cloned_template.account_id, 'template.created').each do |webhook_url|
|
||||||
|
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => cloned_template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
|
||||||
render json: Templates::SerializeForApi.call(cloned_template, schema_documents)
|
render json: Templates::SerializeForApi.call(cloned_template, schema_documents)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -65,7 +65,10 @@ module Api
|
|||||||
|
|
||||||
@template.update!(template_params)
|
@template.update!(template_params)
|
||||||
|
|
||||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
WebhookUrls.for_account_id(@template.account_id, 'template.updated').each do |webhook_url|
|
||||||
|
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
|
||||||
render json: @template.as_json(only: %i[id updated_at])
|
render json: @template.as_json(only: %i[id updated_at])
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -104,4 +104,10 @@ class ApplicationController < ActionController::Base
|
|||||||
def svg_icon(icon_name, class: '')
|
def svg_icon(icon_name, class: '')
|
||||||
render_to_string(partial: "icons/#{icon_name}", locals: { class: })
|
render_to_string(partial: "icons/#{icon_name}", locals: { class: })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def maybe_redirect_com
|
||||||
|
return if request.domain != 'docuseal.co'
|
||||||
|
|
||||||
|
redirect_to request.url.gsub('.co/', '.com/'), allow_other_host: true, status: :moved_permanently
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class ErrorsController < ActionController::Base
|
class ErrorsController < ActionController::Base
|
||||||
ENTERPRISE_FEATURE_MESSAGE =
|
ENTERPRISE_FEATURE_MESSAGE =
|
||||||
'This feature is available in Pro Edition: https://www.docuseal.co/pricing'
|
'This feature is available in Pro Edition: https://www.docuseal.com/pricing'
|
||||||
|
|
||||||
ENTERPRISE_PATHS = [
|
ENTERPRISE_PATHS = [
|
||||||
'/templates/html',
|
'/templates/html',
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class StartFormController < ApplicationController
|
|||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
around_action :with_browser_locale, only: %i[show completed]
|
around_action :with_browser_locale, only: %i[show completed]
|
||||||
|
before_action :maybe_redirect_com, only: %i[show completed]
|
||||||
before_action :load_template
|
before_action :load_template
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@@ -37,7 +38,10 @@ class StartFormController < ApplicationController
|
|||||||
|
|
||||||
if @submitter.save
|
if @submitter.save
|
||||||
if is_new_record
|
if is_new_record
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => @submitter.submission.id })
|
WebhookUrls.for_account_id(@submitter.account_id, 'submission.created').each do |webhook_url|
|
||||||
|
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => @submitter.submission_id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to submit_form_path(@submitter.slug)
|
redirect_to submit_form_path(@submitter.slug)
|
||||||
|
|||||||
@@ -50,9 +50,7 @@ class SubmissionsController < ApplicationController
|
|||||||
params: params.merge('send_completed_email' => true))
|
params: params.merge('send_completed_email' => true))
|
||||||
end
|
end
|
||||||
|
|
||||||
submissions.each do |submission|
|
enqueue_submission_created_webhooks(@template, submissions)
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_async({ 'submission_id' => submission.id })
|
|
||||||
end
|
|
||||||
|
|
||||||
Submissions.send_signature_requests(submissions)
|
Submissions.send_signature_requests(submissions)
|
||||||
|
|
||||||
@@ -68,7 +66,10 @@ class SubmissionsController < ApplicationController
|
|||||||
else
|
else
|
||||||
@submission.update!(archived_at: Time.current)
|
@submission.update!(archived_at: Time.current)
|
||||||
|
|
||||||
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id)
|
WebhookUrls.for_account_id(@submission.account_id, 'submission.archived').each do |webhook_url|
|
||||||
|
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
|
||||||
I18n.t('submission_has_been_archived')
|
I18n.t('submission_has_been_archived')
|
||||||
end
|
end
|
||||||
@@ -85,6 +86,15 @@ class SubmissionsController < ApplicationController
|
|||||||
template.save!
|
template.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def enqueue_submission_created_webhooks(template, submissions)
|
||||||
|
WebhookUrls.for_account_id(template.account_id, 'submission.created').each do |webhook_url|
|
||||||
|
submissions.each do |submission|
|
||||||
|
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def submissions_params
|
def submissions_params
|
||||||
params.permit(submission: { submitters: [%i[uuid email phone name]] })
|
params.permit(submission: { submitters: [%i[uuid email phone name]] })
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ class SubmissionsPreviewController < ApplicationController
|
|||||||
skip_before_action :authenticate_user!
|
skip_before_action :authenticate_user!
|
||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
|
before_action :maybe_redirect_com, only: %i[show completed]
|
||||||
|
|
||||||
TTL = 40.minutes
|
TTL = 40.minutes
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SubmissionsUnarchiveController < ApplicationController
|
||||||
|
load_and_authorize_resource :submission
|
||||||
|
|
||||||
|
def create
|
||||||
|
@submission.update!(archived_at: nil)
|
||||||
|
|
||||||
|
redirect_to submission_path(@submission), notice: I18n.t('submission_has_been_unarchived')
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -7,6 +7,8 @@ class SubmitFormController < ApplicationController
|
|||||||
skip_before_action :authenticate_user!
|
skip_before_action :authenticate_user!
|
||||||
skip_authorization_check
|
skip_authorization_check
|
||||||
|
|
||||||
|
before_action :maybe_redirect_com, only: %i[show completed]
|
||||||
|
|
||||||
CONFIG_KEYS = [].freeze
|
CONFIG_KEYS = [].freeze
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|||||||
@@ -21,8 +21,14 @@ class SubmitFormDeclineController < ApplicationController
|
|||||||
|
|
||||||
user = submitter.submission.created_by_user || submitter.template.author
|
user = submitter.submission.created_by_user || submitter.template.author
|
||||||
|
|
||||||
SubmitterMailer.declined_email(submitter, user).deliver_later!
|
if user.user_configs.find_by(key: UserConfig::RECEIVE_DECLINED_EMAIL)&.value != false
|
||||||
SendFormDeclinedWebhookRequestJob.perform_async('submitter_id' => submitter.id)
|
SubmitterMailer.declined_email(submitter, user).deliver_later!
|
||||||
|
end
|
||||||
|
|
||||||
|
WebhookUrls.for_account_id(submitter.account_id, 'form.declined').each do |webhook_url|
|
||||||
|
SendFormDeclinedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
|
||||||
redirect_to submit_form_path(submitter.slug)
|
redirect_to submit_form_path(submitter.slug)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class TemplatesController < ApplicationController
|
|||||||
if @template.save
|
if @template.save
|
||||||
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
|
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
|
||||||
|
|
||||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
enqueue_template_created_webhooks(@template)
|
||||||
|
|
||||||
maybe_redirect_to_template(@template)
|
maybe_redirect_to_template(@template)
|
||||||
else
|
else
|
||||||
@@ -77,7 +77,7 @@ class TemplatesController < ApplicationController
|
|||||||
def update
|
def update
|
||||||
@template.update!(template_params)
|
@template.update!(template_params)
|
||||||
|
|
||||||
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
enqueue_template_updated_webhooks(@template)
|
||||||
|
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
@@ -128,6 +128,20 @@ class TemplatesController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def enqueue_template_created_webhooks(template)
|
||||||
|
WebhookUrls.for_account_id(template.account_id, 'template.created').each do |webhook_url|
|
||||||
|
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def enqueue_template_updated_webhooks(template)
|
||||||
|
WebhookUrls.for_account_id(template.account_id, 'template.updated').each do |webhook_url|
|
||||||
|
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def load_base_template
|
def load_base_template
|
||||||
return if params[:base_template_id].blank?
|
return if params[:base_template_id].blank?
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class TemplatesUploadsController < ApplicationController
|
|||||||
|
|
||||||
@template.update!(schema:)
|
@template.update!(schema:)
|
||||||
|
|
||||||
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => @template.id)
|
enqueue_template_created_webhooks(@template)
|
||||||
|
|
||||||
redirect_to edit_template_path(@template)
|
redirect_to edit_template_path(@template)
|
||||||
rescue Templates::CreateAttachments::PdfEncrypted
|
rescue Templates::CreateAttachments::PdfEncrypted
|
||||||
@@ -65,4 +65,11 @@ class TemplatesUploadsController < ApplicationController
|
|||||||
|
|
||||||
{ files: [file] }
|
{ files: [file] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def enqueue_template_created_webhooks(template)
|
||||||
|
WebhookUrls.for_account_id(template.account_id, 'template.created').each do |webhook_url|
|
||||||
|
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -4,9 +4,8 @@ class TestingApiSettingsController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
authorize!(:manage, current_user.access_token)
|
authorize!(:manage, current_user.access_token)
|
||||||
|
|
||||||
@webhook_config =
|
@webhook_url = current_account.webhook_urls.first_or_initialize
|
||||||
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
|
||||||
|
|
||||||
authorize!(:manage, @webhook_config)
|
authorize!(:manage, @webhook_url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,37 +1,22 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class WebhookPreferencesController < ApplicationController
|
class WebhookPreferencesController < ApplicationController
|
||||||
EVENTS = %w[
|
load_and_authorize_resource :webhook_url, parent: false
|
||||||
form.viewed
|
|
||||||
form.started
|
|
||||||
form.completed
|
|
||||||
form.declined
|
|
||||||
template.created
|
|
||||||
template.updated
|
|
||||||
submission.created
|
|
||||||
submission.archived
|
|
||||||
].freeze
|
|
||||||
|
|
||||||
before_action :load_account_config
|
def update
|
||||||
authorize_resource :account_config, parent: false
|
webhook_preferences_params[:events].each do |event, val|
|
||||||
|
@webhook_url.events.delete(event) if val == '0'
|
||||||
|
@webhook_url.events.push(event) if val == '1' && @webhook_url.events.exclude?(event)
|
||||||
|
end
|
||||||
|
|
||||||
def create
|
@webhook_url.save!
|
||||||
@account_config.value[account_config_params[:event]] = account_config_params[:value] == '1'
|
|
||||||
|
|
||||||
@account_config.save!
|
|
||||||
|
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_account_config
|
def webhook_preferences_params
|
||||||
@account_config =
|
params.require(:webhook_url).permit(events: {})
|
||||||
current_account.account_configs.find_or_initialize_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)
|
|
||||||
@account_config.value ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_config_params
|
|
||||||
params.permit(:event, :value)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,29 +1,21 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class WebhookSecretController < ApplicationController
|
class WebhookSecretController < ApplicationController
|
||||||
before_action :load_encrypted_config
|
load_and_authorize_resource :webhook_url, parent: false
|
||||||
authorize_resource :encrypted_config, parent: false
|
|
||||||
|
|
||||||
def index; end
|
def show; end
|
||||||
|
|
||||||
def create
|
def update
|
||||||
@encrypted_config.assign_attributes(value: {
|
@webhook_url.update!(secret: {
|
||||||
encrypted_config_params[:key] => encrypted_config_params[:value]
|
webhook_secret_params[:key] => webhook_secret_params[:value]
|
||||||
}.compact_blank)
|
}.compact_blank)
|
||||||
|
|
||||||
@encrypted_config.value.present? ? @encrypted_config.save! : @encrypted_config.delete
|
|
||||||
|
|
||||||
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_secret_has_been_saved'))
|
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_secret_has_been_saved'))
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_encrypted_config
|
def webhook_secret_params
|
||||||
@encrypted_config =
|
params.require(:webhook_url).permit(secret: %i[key value]).fetch(:secret, {})
|
||||||
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_SECRET_KEY)
|
|
||||||
end
|
|
||||||
|
|
||||||
def encrypted_config_params
|
|
||||||
params.require(:encrypted_config).permit(value: %i[key value]).fetch(:value, {})
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class WebhookSettingsController < ApplicationController
|
class WebhookSettingsController < ApplicationController
|
||||||
before_action :load_encrypted_config
|
before_action :load_webhook_url
|
||||||
authorize_resource :encrypted_config, parent: false
|
authorize_resource :webhook_url, parent: false
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@encrypted_config.assign_attributes(encrypted_config_params)
|
@webhook_url.assign_attributes(webhook_params)
|
||||||
|
|
||||||
@encrypted_config.value.present? ? @encrypted_config.save! : @encrypted_config.delete
|
@webhook_url.url.present? ? @webhook_url.save! : @webhook_url.delete
|
||||||
|
|
||||||
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_url_has_been_saved'))
|
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_url_has_been_saved'))
|
||||||
end
|
end
|
||||||
@@ -17,20 +17,19 @@ class WebhookSettingsController < ApplicationController
|
|||||||
def update
|
def update
|
||||||
submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last
|
submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last
|
||||||
|
|
||||||
SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
|
SendFormCompletedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
||||||
'encrypted_config_id' => @encrypted_config.id })
|
'webhook_url_id' => @webhook_url.id)
|
||||||
|
|
||||||
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_request_has_been_sent'))
|
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_request_has_been_sent'))
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_encrypted_config
|
def load_webhook_url
|
||||||
@encrypted_config =
|
@webhook_url = current_account.webhook_urls.first_or_initialize
|
||||||
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def encrypted_config_params
|
def webhook_params
|
||||||
params.require(:encrypted_config).permit(:value)
|
params.require(:webhook_url).permit(:url)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
>
|
>
|
||||||
{{ t('powered_by') }}
|
{{ t('powered_by') }}
|
||||||
<a
|
<a
|
||||||
href="https://www.docuseal.co/start"
|
href="https://www.docuseal.com/start"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="underline"
|
class="underline"
|
||||||
>DocuSeal</a> - {{ t('open_source_documents_software') }}
|
>DocuSeal</a> - {{ t('open_source_documents_software') }}
|
||||||
|
|||||||
@@ -145,6 +145,10 @@
|
|||||||
{{ t('clear') }}
|
{{ t('clear') }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="isTextSignature"
|
||||||
|
class="absolute top-0 right-0 left-0 bottom-0"
|
||||||
|
/>
|
||||||
<canvas
|
<canvas
|
||||||
v-show="!modelValue && !computedPreviousValue"
|
v-show="!modelValue && !computedPreviousValue"
|
||||||
ref="canvas"
|
ref="canvas"
|
||||||
@@ -255,7 +259,7 @@
|
|||||||
class="text-base-content/60 text-xs text-center w-full mt-1"
|
class="text-base-content/60 text-xs text-center w-full mt-1"
|
||||||
>
|
>
|
||||||
{{ t('by_clicking_you_agree_to_the').replace('{button}', buttonText.charAt(0).toUpperCase() + buttonText.slice(1)) }} <a
|
{{ t('by_clicking_you_agree_to_the').replace('{button}', buttonText.charAt(0).toUpperCase() + buttonText.slice(1)) }} <a
|
||||||
href="https://www.docuseal.co/esign-disclosure"
|
href="https://www.docuseal.com/esign-disclosure"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<span class="inline md:hidden">
|
<span class="inline md:hidden">
|
||||||
|
|||||||
@@ -415,6 +415,7 @@ import Contenteditable from './contenteditable'
|
|||||||
import DocumentPreview from './preview'
|
import DocumentPreview from './preview'
|
||||||
import DocumentControls from './controls'
|
import DocumentControls from './controls'
|
||||||
import MobileFields from './mobile_fields'
|
import MobileFields from './mobile_fields'
|
||||||
|
import FieldSubmitter from './field_submitter'
|
||||||
import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments } from '@tabler/icons-vue'
|
import { IconPlus, IconUsersPlus, IconDeviceFloppy, IconChevronDown, IconEye, IconWritingSign, IconInnerShadowTop, IconInfoCircle, IconAdjustments } from '@tabler/icons-vue'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
@@ -558,6 +559,11 @@ export default {
|
|||||||
required: false,
|
required: false,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
|
defineSubmitters: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
acceptFileTypes: {
|
acceptFileTypes: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -677,6 +683,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
submitterDefaultNames: FieldSubmitter.computed.names,
|
||||||
selectedAreaRef: () => ref(),
|
selectedAreaRef: () => ref(),
|
||||||
fieldsDragFieldRef: () => ref(),
|
fieldsDragFieldRef: () => ref(),
|
||||||
language () {
|
language () {
|
||||||
@@ -749,6 +756,18 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.defineSubmitters.forEach((name, index) => {
|
||||||
|
const submitter = (this.template.submitters[index] ||= {})
|
||||||
|
|
||||||
|
submitter.name = name || this.submitterDefaultNames[index]
|
||||||
|
|
||||||
|
if (existingSubmittersUuids.filter(Boolean).length) {
|
||||||
|
submitter.uuid = existingSubmittersUuids[index] || submitter.uuid || v4()
|
||||||
|
} else {
|
||||||
|
submitter.uuid ||= v4()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
this.selectedSubmitter = this.template.submitters[0]
|
this.selectedSubmitter = this.template.submitters[0]
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
class="bg-base-300 rounded-xl py-2 px-3 text-center"
|
class="bg-base-300 rounded-xl py-2 px-3 text-center"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="https://www.docuseal.co/pricing"
|
href="https://www.docuseal.com/pricing"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="link"
|
class="link"
|
||||||
>{{ t('available_in_pro') }}</a>
|
>{{ t('available_in_pro') }}</a>
|
||||||
|
|||||||
@@ -140,7 +140,7 @@
|
|||||||
:data-tip="t('unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification')"
|
:data-tip="t('unlock_sms_verified_phone_number_field_with_paid_plan_use_text_field_for_phone_numbers_without_verification')"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="https://www.docuseal.co/pricing"
|
href="https://www.docuseal.com/pricing"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
|
class="opacity-50 flex items-center justify-center border border-dashed border-base-300 w-full rounded relative"
|
||||||
:style="{ backgroundColor }"
|
:style="{ backgroundColor }"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
class="bg-base-300 rounded-xl py-2 px-3 text-center"
|
class="bg-base-300 rounded-xl py-2 px-3 text-center"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href="https://www.docuseal.co/pricing"
|
href="https://www.docuseal.com/pricing"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="link"
|
class="link"
|
||||||
>{{ t('available_in_pro') }}</a>
|
>{{ t('available_in_pro') }}</a>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
<div class="w-full relative">
|
<div class="w-full relative">
|
||||||
<select
|
<select
|
||||||
class="base-select !select-sm !h-10"
|
class="base-select !select-sm !h-10"
|
||||||
:class="{ '!text-gray-300': !mapping.column_index }"
|
:class="{ '!text-gray-300': !mapping.column_index && mapping.column_index != 0 }"
|
||||||
required
|
required
|
||||||
@change="mapping.column_index = parseInt($event.target.value)"
|
@change="mapping.column_index = parseInt($event.target.value)"
|
||||||
>
|
>
|
||||||
@@ -285,7 +285,7 @@ export default {
|
|||||||
submittersIndex[mapping.submitter_uuid][mapping.field_name.toLowerCase()] = row[mapping.column_index]
|
submittersIndex[mapping.submitter_uuid][mapping.field_name.toLowerCase()] = row[mapping.column_index]
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldType = this.fieldTypesIndex[mapping.submitter_uuid][mapping.field_name]
|
const fieldType = this.fieldTypesIndex[mapping.submitter_uuid]?.[mapping.field_name]
|
||||||
|
|
||||||
if (fieldType && fieldType !== 'phone') {
|
if (fieldType && fieldType !== 'phone') {
|
||||||
submittersIndex[mapping.submitter_uuid].fields.push({
|
submittersIndex[mapping.submitter_uuid].fields.push({
|
||||||
|
|||||||
@@ -178,7 +178,7 @@
|
|||||||
<a
|
<a
|
||||||
v-if="!isConnected"
|
v-if="!isConnected"
|
||||||
class="block link text-center mt-1"
|
class="block link text-center mt-1"
|
||||||
href="https://www.docuseal.co/blog/accept-payments-and-request-signatures-with-ease"
|
href="https://www.docuseal.com/blog/accept-payments-and-request-signatures-with-ease"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
data-turbo="false"
|
data-turbo="false"
|
||||||
>{{ t('learn_more') }}</a>
|
>{{ t('learn_more') }}</a>
|
||||||
|
|||||||
@@ -63,32 +63,15 @@ class ProcessSubmitterCompletionJob
|
|||||||
end
|
end
|
||||||
|
|
||||||
def enqueue_completed_webhooks(submitter, is_all_completed: false)
|
def enqueue_completed_webhooks(submitter, is_all_completed: false)
|
||||||
webhook_config = Accounts.load_webhook_config(submitter.account)
|
WebhookUrls.for_account_id(submitter.account_id, %w[form.completed submission.completed]).each do |webhook|
|
||||||
|
|
||||||
if webhook_config
|
|
||||||
SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
|
|
||||||
'encrypted_config_id' => webhook_config.id })
|
|
||||||
end
|
|
||||||
|
|
||||||
webhook_urls = submitter.account.webhook_urls
|
|
||||||
|
|
||||||
webhook_urls = webhook_urls.where(
|
|
||||||
Arel::Table.new(:webhook_urls)[:events].matches('%"form.completed"%')
|
|
||||||
).or(
|
|
||||||
webhook_urls.where(
|
|
||||||
Arel::Table.new(:webhook_urls)[:events].matches('%"submission.completed"%')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
webhook_urls.each do |webhook|
|
|
||||||
if webhook.events.include?('form.completed')
|
if webhook.events.include?('form.completed')
|
||||||
SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
|
SendFormCompletedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
||||||
'webhook_url_id' => webhook.id })
|
'webhook_url_id' => webhook.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
if webhook.events.include?('submission.completed') && is_all_completed
|
if webhook.events.include?('submission.completed') && is_all_completed
|
||||||
SendSubmissionCompletedWebhookRequestJob.perform_async({ 'submission_id' => submitter.submission_id,
|
SendSubmissionCompletedWebhookRequestJob.perform_async('submission_id' => submitter.submission_id,
|
||||||
'webhook_url_id' => webhook.id })
|
'webhook_url_id' => webhook.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,31 +5,30 @@ class SendFormCompletedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submitter = Submitter.find(params['submitter_id'])
|
submitter = Submitter.find(params['submitter_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
url, secret = load_url_and_secret(submitter, params)
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('form.completed')
|
||||||
|
|
||||||
return if url.blank?
|
|
||||||
|
|
||||||
Submissions::EnsureResultGenerated.call(submitter)
|
Submissions::EnsureResultGenerated.call(submitter)
|
||||||
|
|
||||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'form.completed',
|
event_type: 'form.completed',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**secret.to_h,
|
**webhook_url.secret.to_h,
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -45,27 +44,4 @@ class SendFormCompletedWebhookRequestJob
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_url_and_secret(submitter, params)
|
|
||||||
if params['encrypted_config_id']
|
|
||||||
config = EncryptedConfig.find(params['encrypted_config_id'])
|
|
||||||
|
|
||||||
url = config.value
|
|
||||||
|
|
||||||
return if url.blank?
|
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(submitter.submission.account)
|
|
||||||
|
|
||||||
return if preferences['form.completed'] == false
|
|
||||||
|
|
||||||
secret = EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h
|
|
||||||
|
|
||||||
[url, secret]
|
|
||||||
elsif params['webhook_url_id']
|
|
||||||
webhook_url = submitter.account.webhook_urls.find(params['webhook_url_id'])
|
|
||||||
|
|
||||||
webhook_url.url if webhook_url.events.include?('form.completed')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@@ -5,34 +5,28 @@ class SendFormDeclinedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submitter = Submitter.find(params['submitter_id'])
|
submitter = Submitter.find(params['submitter_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
config = Accounts.load_webhook_config(submitter.submission.account)
|
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('form.declined')
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(submitter.submission.account)
|
|
||||||
|
|
||||||
return if preferences['form.declined'] == false
|
|
||||||
|
|
||||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'form.declined',
|
event_type: 'form.declined',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -43,6 +37,7 @@ class SendFormDeclinedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||||
SendFormDeclinedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendFormDeclinedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'submitter_id' => submitter.id,
|
'submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,34 +5,28 @@ class SendFormStartedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submitter = Submitter.find(params['submitter_id'])
|
submitter = Submitter.find(params['submitter_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
config = Accounts.load_webhook_config(submitter.submission.account)
|
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('form.started')
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(submitter.submission.account)
|
|
||||||
|
|
||||||
return if preferences['form.started'] == false
|
|
||||||
|
|
||||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'form.started',
|
event_type: 'form.started',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -43,6 +37,7 @@ class SendFormStartedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||||
SendFormStartedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendFormStartedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'submitter_id' => submitter.id,
|
'submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,34 +5,28 @@ class SendFormViewedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submitter = Submitter.find(params['submitter_id'])
|
submitter = Submitter.find(params['submitter_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
config = Accounts.load_webhook_config(submitter.submission.account)
|
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('form.viewed')
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(submitter.submission.account)
|
|
||||||
|
|
||||||
return if preferences['form.viewed'] == false
|
|
||||||
|
|
||||||
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
ActiveStorage::Current.url_options = Docuseal.default_url_options
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'form.viewed',
|
event_type: 'form.viewed',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submitters::SerializeForWebhook.call(submitter)
|
data: Submitters::SerializeForWebhook.call(submitter)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -43,6 +37,7 @@ class SendFormViewedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || submitter.account.account_configs.exists?(key: :plan))
|
||||||
SendFormViewedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendFormViewedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'submitter_id' => submitter.id,
|
'submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,33 +5,26 @@ class SendSubmissionArchivedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submission = Submission.find(params['submission_id'])
|
submission = Submission.find(params['submission_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
config = Accounts.load_webhook_config(submission.account)
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.archived')
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(submission.account)
|
|
||||||
|
|
||||||
return if preferences['submission.archived'].blank?
|
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'submission.archived',
|
event_type: 'submission.archived',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: submission.as_json(only: %i[id archived_at])
|
data: submission.as_json(only: %i[id archived_at])
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -42,6 +35,7 @@ class SendSubmissionArchivedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
||||||
SendSubmissionArchivedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendSubmissionArchivedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'submission_id' => submission.id,
|
'submission_id' => submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,30 +5,26 @@ class SendSubmissionCompletedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submission = Submission.find(params['submission_id'])
|
submission = Submission.find(params['submission_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
webhook_url = submission.account.webhook_urls.find(params['webhook_url_id'])
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.completed')
|
||||||
|
|
||||||
url = webhook_url.url if webhook_url.events.include?('submission.completed')
|
|
||||||
|
|
||||||
return if url.blank?
|
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'submission.completed',
|
event_type: 'submission.completed',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submissions::SerializeForApi.call(submission)
|
data: Submissions::SerializeForApi.call(submission)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: submission.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
|
|||||||
@@ -5,33 +5,26 @@ class SendSubmissionCreatedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submission = Submission.find(params['submission_id'])
|
submission = Submission.find(params['submission_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
config = Accounts.load_webhook_config(submission.account)
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('submission.created')
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(submission.account)
|
|
||||||
|
|
||||||
return if preferences['submission.created'].blank?
|
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'submission.created',
|
event_type: 'submission.created',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Submissions::SerializeForApi.call(submission)
|
data: Submissions::SerializeForApi.call(submission)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -42,6 +35,7 @@ class SendSubmissionCreatedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || submission.account.account_configs.exists?(key: :plan))
|
||||||
SendSubmissionCreatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendSubmissionCreatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'submission_id' => submission.id,
|
'submission_id' => submission.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,33 +5,26 @@ class SendTemplateCreatedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
template = Template.find(params['template_id'])
|
template = Template.find(params['template_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
config = Accounts.load_webhook_config(template.account)
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('template.created')
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(template.account)
|
|
||||||
|
|
||||||
return if preferences['template.created'].blank?
|
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'template.created',
|
event_type: 'template.created',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Templates::SerializeForApi.call(template)
|
data: Templates::SerializeForApi.call(template)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -42,6 +35,7 @@ class SendTemplateCreatedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
||||||
SendTemplateCreatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendTemplateCreatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'template_id' => template.id,
|
'template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,33 +5,26 @@ class SendTemplateUpdatedWebhookRequestJob
|
|||||||
|
|
||||||
sidekiq_options queue: :webhooks
|
sidekiq_options queue: :webhooks
|
||||||
|
|
||||||
USER_AGENT = 'DocuSeal.co Webhook'
|
USER_AGENT = 'DocuSeal.com Webhook'
|
||||||
|
|
||||||
MAX_ATTEMPTS = 10
|
MAX_ATTEMPTS = 10
|
||||||
|
|
||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
template = Template.find(params['template_id'])
|
template = Template.find(params['template_id'])
|
||||||
|
webhook_url = WebhookUrl.find(params['webhook_url_id'])
|
||||||
|
|
||||||
attempt = params['attempt'].to_i
|
attempt = params['attempt'].to_i
|
||||||
|
|
||||||
config = Accounts.load_webhook_config(template.account)
|
return if webhook_url.url.blank? || webhook_url.events.exclude?('template.updated')
|
||||||
url = config&.value.presence
|
|
||||||
|
|
||||||
return if url.blank?
|
|
||||||
|
|
||||||
preferences = Accounts.load_webhook_preferences(template.account)
|
|
||||||
|
|
||||||
return if preferences['template.updated'].blank?
|
|
||||||
|
|
||||||
resp = begin
|
resp = begin
|
||||||
Faraday.post(url,
|
Faraday.post(webhook_url.url,
|
||||||
{
|
{
|
||||||
event_type: 'template.updated',
|
event_type: 'template.updated',
|
||||||
timestamp: Time.current,
|
timestamp: Time.current,
|
||||||
data: Templates::SerializeForApi.call(template)
|
data: Templates::SerializeForApi.call(template)
|
||||||
}.to_json,
|
}.to_json,
|
||||||
**EncryptedConfig.find_or_initialize_by(account_id: config.account_id,
|
**webhook_url.secret.to_h,
|
||||||
key: EncryptedConfig::WEBHOOK_SECRET_KEY)&.value.to_h,
|
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'User-Agent' => USER_AGENT)
|
'User-Agent' => USER_AGENT)
|
||||||
rescue Faraday::Error
|
rescue Faraday::Error
|
||||||
@@ -42,6 +35,7 @@ class SendTemplateUpdatedWebhookRequestJob
|
|||||||
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
||||||
SendTemplateUpdatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
SendTemplateUpdatedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||||
'template_id' => template.id,
|
'template_id' => template.id,
|
||||||
|
'webhook_url_id' => webhook_url.id,
|
||||||
'attempt' => attempt + 1,
|
'attempt' => attempt + 1,
|
||||||
'last_status' => resp&.status.to_i
|
'last_status' => resp&.status.to_i
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ class AccountConfig < ApplicationRecord
|
|||||||
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
||||||
FORM_PREFILL_SIGNATURE_KEY = 'form_prefill_signature'
|
FORM_PREFILL_SIGNATURE_KEY = 'form_prefill_signature'
|
||||||
ESIGNING_PREFERENCE_KEY = 'esigning_preference'
|
ESIGNING_PREFERENCE_KEY = 'esigning_preference'
|
||||||
WEBHOOK_PREFERENCES_KEY = 'webhook_preferences'
|
|
||||||
DOWNLOAD_LINKS_AUTH_KEY = 'download_links_auth'
|
DOWNLOAD_LINKS_AUTH_KEY = 'download_links_auth'
|
||||||
FORCE_SSO_AUTH_KEY = 'force_sso_auth'
|
FORCE_SSO_AUTH_KEY = 'force_sso_auth'
|
||||||
FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf'
|
FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf'
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ class EncryptedConfig < ApplicationRecord
|
|||||||
EMAIL_SMTP_KEY = 'action_mailer_smtp',
|
EMAIL_SMTP_KEY = 'action_mailer_smtp',
|
||||||
ESIGN_CERTS_KEY = 'esign_certs',
|
ESIGN_CERTS_KEY = 'esign_certs',
|
||||||
TIMESTAMP_SERVER_URL_KEY = 'timestamp_server_url',
|
TIMESTAMP_SERVER_URL_KEY = 'timestamp_server_url',
|
||||||
APP_URL_KEY = 'app_url',
|
APP_URL_KEY = 'app_url'
|
||||||
WEBHOOK_URL_KEY = 'webhook_url',
|
|
||||||
WEBHOOK_SECRET_KEY = 'webhook_secret'
|
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class UserConfig < ApplicationRecord
|
|||||||
SIGNATURE_KEY = 'signature'
|
SIGNATURE_KEY = 'signature'
|
||||||
INITIALS_KEY = 'initials'
|
INITIALS_KEY = 'initials'
|
||||||
RECEIVE_COMPLETED_EMAIL = 'receive_completed_email'
|
RECEIVE_COMPLETED_EMAIL = 'receive_completed_email'
|
||||||
|
RECEIVE_DECLINED_EMAIL = 'receive_declined_email'
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# events :text not null
|
# events :text not null
|
||||||
|
# secret :text not null
|
||||||
# sha1 :string not null
|
# sha1 :string not null
|
||||||
# url :text not null
|
# url :text not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
@@ -22,15 +23,28 @@
|
|||||||
# fk_rails_... (account_id => accounts.id)
|
# fk_rails_... (account_id => accounts.id)
|
||||||
#
|
#
|
||||||
class WebhookUrl < ApplicationRecord
|
class WebhookUrl < ApplicationRecord
|
||||||
|
EVENTS = %w[
|
||||||
|
form.viewed
|
||||||
|
form.started
|
||||||
|
form.completed
|
||||||
|
form.declined
|
||||||
|
template.created
|
||||||
|
template.updated
|
||||||
|
submission.created
|
||||||
|
submission.archived
|
||||||
|
].freeze
|
||||||
|
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
|
|
||||||
attribute :events, :string, default: -> { [] }
|
attribute :events, :string, default: -> { %w[form.viewed form.started form.completed form.declined] }
|
||||||
|
attribute :secret, :string, default: -> { {} }
|
||||||
|
|
||||||
serialize :events, coder: JSON
|
serialize :events, coder: JSON
|
||||||
|
serialize :secret, coder: JSON
|
||||||
|
|
||||||
before_validation :set_sha1
|
before_validation :set_sha1
|
||||||
|
|
||||||
encrypts :url
|
encrypts :url, :secret
|
||||||
|
|
||||||
def set_sha1
|
def set_sha1
|
||||||
self.sha1 = Digest::SHA1.hexdigest(url)
|
self.sha1 = Digest::SHA1.hexdigest(url)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<%= content_for(:canonical_url, new_user_session_url) %>
|
||||||
<div class="max-w-lg mx-auto px-2">
|
<div class="max-w-lg mx-auto px-2">
|
||||||
<%= render 'devise/shared/select_server' if Docuseal.multitenant? %>
|
<%= render 'devise/shared/select_server' if Docuseal.multitenant? %>
|
||||||
<h1 class="text-4xl font-bold text-center mt-8"><%= t('sign_in') %></h1>
|
<h1 class="text-4xl font-bold text-center mt-8"><%= t('sign_in') %></h1>
|
||||||
|
|||||||
@@ -11,6 +11,9 @@
|
|||||||
<% else %>
|
<% else %>
|
||||||
<%= javascript_pack_tag 'application', defer: true %>
|
<%= javascript_pack_tag 'application', defer: true %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if canonical_url = content_for(:canonical_url) %>
|
||||||
|
<link href="<%= canonical_url %>" rel="canonical">
|
||||||
|
<% end %>
|
||||||
<%= stylesheet_pack_tag 'application', media: 'all' %>
|
<%= stylesheet_pack_tag 'application', media: 'all' %>
|
||||||
<%= render 'shared/posthog' if ENV['POSTHOG_TOKEN'] %>
|
<%= render 'shared/posthog' if ENV['POSTHOG_TOKEN'] %>
|
||||||
<%= render 'shared/plausible' if !signed_in? && ENV['PLAUSIBLE_DOMAIN'] %>
|
<%= render 'shared/plausible' if !signed_in? && ENV['PLAUSIBLE_DOMAIN'] %>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<h3 class="mb-4 text-2xl font-semibold">Easy to Start</h3>
|
<h3 class="mb-4 text-2xl font-semibold">Easy to Start</h3>
|
||||||
<p class="text-base text-gray-500">
|
<p class="text-base text-gray-500">
|
||||||
Run on your own host using Docker container, or deploy on your favorite managed PaaS with a single <a href="https://www.docuseal.co/install" class="link link-neutral font-bold">click</a>.
|
Run on your own host using Docker container, or deploy on your favorite managed PaaS with a single <a href="https://www.docuseal.com/install" class="link link-neutral font-bold">click</a>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<a href="<%= Docuseal::GITHUB_URL %>" target="_blank" class="inline">
|
<a href="<%= Docuseal::GITHUB_URL %>" target="_blank" class="inline">
|
||||||
<img alt="GitHub Repo stars" src="https://www.docuseal.co/github-badge.svg" style="height: 22px">
|
<img alt="GitHub Repo stars" src="https://www.docuseal.com/github-badge.svg" style="height: 22px">
|
||||||
</a>
|
</a>
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
<%= link_to 'API', settings_api_index_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'API', settings_api_index_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:read, EncryptedConfig.new(key: EncryptedConfig::WEBHOOK_URL_KEY, account: current_account)) %>
|
<% if can?(:read, WebhookUrl) %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to 'Webhooks', settings_webhooks_path, class: 'text-base hover:bg-base-300' %>
|
<%= link_to 'Webhooks', settings_webhooks_path, class: 'text-base hover:bg-base-300' %>
|
||||||
</li>
|
</li>
|
||||||
@@ -72,13 +72,13 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user != true_user || !current_account.testing?) %>
|
<% if !Docuseal.demo? && can?(:manage, EncryptedConfig) && (current_user != true_user || !current_account.testing?) %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/api") : "#{Docuseal::CONSOLE_URL}/on_premise", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
<%= link_to Docuseal.multitenant? ? console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/api") : "#{Docuseal::CONSOLE_URL}/on_premise", class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
||||||
<% if Docuseal.multitenant? %> API <% else %> <%= t('console') %> <% end %>
|
<% if Docuseal.multitenant? %> API <% else %> <%= t('console') %> <% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
<% if Docuseal.multitenant? %>
|
<% if Docuseal.multitenant? %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}/embedding/form"), class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
<%= link_to console_redirect_index_path(redir: "#{Docuseal::CONSOLE_URL}#{'/test' if current_account.testing?}/embedding/form"), class: 'text-base hover:bg-base-300', data: { prefetch: false } do %>
|
||||||
<%= t('embedding') %>
|
<%= t('embedding') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -9,14 +9,18 @@
|
|||||||
<span class="text-xl md:text-3xl font-semibold focus:text-clip" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;"><% @submission.template.name.split(/(_)/).each do |item| %><%= item %><wbr><% end %></span>
|
<span class="text-xl md:text-3xl font-semibold focus:text-clip" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;"><% @submission.template.name.split(/(_)/).each do |item| %><%= item %><wbr><% end %></span>
|
||||||
</a>
|
</a>
|
||||||
<div class="space-x-3 flex items-center">
|
<div class="space-x-3 flex items-center">
|
||||||
|
<% last_submitter = @submission.submitters.to_a.select(&:completed_at?).max_by(&:completed_at) %>
|
||||||
|
<% is_all_completed = @submission.submitters.to_a.all?(&:completed_at?) %>
|
||||||
|
<% if signed_in? && can?(:create, @submission) && @submission.archived_at? && !is_all_completed %>
|
||||||
|
<%= button_to button_title(title: t('unarchive'), disabled_with: t('unarchive')[0..-2], icon: svg_icon('rotate', class: 'w-6 h-6')), submission_unarchive_index_path(@submission), class: 'btn btn-primary btn-ghost text-base hidden md:flex' %>
|
||||||
|
<% end %>
|
||||||
<% if @submission.audit_trail.present? %>
|
<% if @submission.audit_trail.present? %>
|
||||||
<a href="<%= ActiveStorage::Blob.proxy_url(@submission.audit_trail.blob, expires_at: 4.hours.from_now) %>" 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"><%= t('audit_log') %></span>
|
<span class="hidden md:inline"><%= t('audit_log') %></span>
|
||||||
</a>
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if last_submitter = @submission.submitters.to_a.select(&:completed_at?).max_by(&:completed_at) %>
|
<% if last_submitter %>
|
||||||
<% is_all_completed = @submission.submitters.to_a.all?(&:completed_at?) %>
|
|
||||||
<% if is_all_completed || !is_combined_enabled %>
|
<% if is_all_completed || !is_combined_enabled %>
|
||||||
<div class="join relative">
|
<div class="join relative">
|
||||||
<download-button data-src="<%= submitter_download_index_path(last_submitter.slug, { sig: params[:sig], combined: is_combined_enabled }.compact) %>" class="base-button <%= '!rounded-r-none !pr-2' if is_all_completed && !is_combined_enabled %>">
|
<download-button data-src="<%= submitter_download_index_path(last_submitter.slug, { sig: params[:sig], combined: is_combined_enabled }.compact) %>" class="base-button <%= '!rounded-r-none !pr-2' if is_all_completed && !is_combined_enabled %>">
|
||||||
@@ -54,7 +58,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% elsif @submission.submitters.to_a.size == 1 && !@submission.expired? && !@submission.submitters.to_a.first.declined_at? %>
|
<% elsif @submission.submitters.to_a.size == 1 && !@submission.expired? && !@submission.submitters.to_a.first.declined_at? && !@submission.archived_at? %>
|
||||||
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: @submission.submitters.to_a.first.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy_share_link'), copied_title: t('copied_to_clipboard') %>
|
<%= render 'shared/clipboard_copy', text: submit_form_url(slug: @submission.submitters.to_a.first.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy_share_link'), copied_title: t('copied_to_clipboard') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
<a href="<%= submission_path(submission) %>" class="text-lg break-all peer">
|
<a href="<%= submission_path(submission) %>" class="text-lg break-all peer">
|
||||||
<%= submitter.name || submitter.email || submitter.phone %>
|
<%= submitter.name || submitter.email || submitter.phone %>
|
||||||
</a>
|
</a>
|
||||||
<% if can?(:update, submitter) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? %>
|
<% if can?(:update, submitter) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
||||||
<span class="pl-0.5 tooltip tooltip-top md:opacity-0 md:hover:opacity-100 md:peer-hover:opacity-100" data-tip="<%= t('edit') %>">
|
<span class="pl-0.5 tooltip tooltip-top md:opacity-0 md:hover:opacity-100 md:peer-hover:opacity-100" data-tip="<%= t('edit') %>">
|
||||||
<%= link_to edit_submitter_path(submitter), class: 'shrink-0', data: { turbo_frame: 'modal' } do %>
|
<%= link_to edit_submitter_path(submitter), class: 'shrink-0', data: { turbo_frame: 'modal' } do %>
|
||||||
<%= svg_icon('pencil', class: 'w-5 h-5') %>
|
<%= svg_icon('pencil', class: 'w-5 h-5') %>
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
<a href="<%= submission_path(submission) %>" class="text-lg break-all peer">
|
<a href="<%= submission_path(submission) %>" class="text-lg break-all peer">
|
||||||
<%= submitter.name || submitter.email || submitter.phone %>
|
<%= submitter.name || submitter.email || submitter.phone %>
|
||||||
</a>
|
</a>
|
||||||
<% if can?(:update, submitter) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? %>
|
<% if can?(:update, submitter) && !submitter.start_form_submission_events.any? && !submission.archived_at? && !submission.expired? && !submitter.declined_at? %>
|
||||||
<span class="pl-0.5 tooltip tooltip-top md:opacity-0 md:hover:opacity-100 md:peer-hover:opacity-100" data-tip="<%= t('edit') %>">
|
<span class="pl-0.5 tooltip tooltip-top md:opacity-0 md:hover:opacity-100 md:peer-hover:opacity-100" data-tip="<%= t('edit') %>">
|
||||||
<%= link_to edit_submitter_path(submitter), class: 'shrink-0', data: { turbo_frame: 'modal' } do %>
|
<%= link_to edit_submitter_path(submitter), class: 'shrink-0', data: { turbo_frame: 'modal' } do %>
|
||||||
<%= svg_icon('pencil', class: 'w-5 h-5') %>
|
<%= svg_icon('pencil', class: 'w-5 h-5') %>
|
||||||
|
|||||||
@@ -8,10 +8,10 @@
|
|||||||
<%= render 'shared/clipboard_copy', icon: 'copy', text: current_user.access_token.token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
|
<%= render 'shared/clipboard_copy', icon: 'copy', text: current_user.access_token.token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<%= form_for @webhook_config, url: settings_webhooks_path, method: :post, html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
<%= form_for @webhook_url, url: settings_webhooks_path, method: :post, html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
|
||||||
<%= f.label :value, 'Webhook URL', class: 'text-sm font-semibold' %>
|
<%= f.label :url, 'Webhook URL', class: 'text-sm font-semibold' %>
|
||||||
<div class="space-y-2 md:flex-nowrap mt-2">
|
<div class="space-y-2 md:flex-nowrap mt-2">
|
||||||
<%= f.url_field :value, class: 'base-input w-full', placeholder: 'https://example.com/hook' %>
|
<%= f.url_field :url, class: 'base-input w-full', placeholder: 'https://example.com/hook' %>
|
||||||
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button w-full' %>
|
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button w-full' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<%= render 'shared/turbo_modal', title: 'Webhook Secret' do %>
|
<%= render 'shared/turbo_modal', title: t('webhook_secret') do %>
|
||||||
<%= form_for @encrypted_config, url: webhook_secret_index_path, method: :post, html: { class: 'space-y-4' }, data: { turbo_frame: :_top } do |f| %>
|
<%= form_for @webhook_url, url: webhook_secret_path, method: :patch, html: { class: 'space-y-4' }, data: { turbo_frame: :_top } do |f| %>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<%= f.fields_for :value, Struct.new(:key, :value).new(*@encrypted_config.value.to_a.first) do |ff| %>
|
<%= f.fields_for :secret, Struct.new(:key, :value).new(*@webhook_url.secret.to_a.first) do |ff| %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :key, class: 'label' %>
|
<%= ff.label :key, t('key'), class: 'label' %>
|
||||||
<%= ff.text_field :key, class: 'base-input', placeholder: 'X-Example-Header' %>
|
<%= ff.text_field :key, class: 'base-input', placeholder: 'X-Example-Header' %>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :value, class: 'label' %>
|
<%= ff.label :value, t('value'), class: 'label' %>
|
||||||
<%= ff.text_field :value, class: 'base-input' %>
|
<%= ff.text_field :value, class: 'base-input' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -7,35 +7,36 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card bg-base-200">
|
<div class="card bg-base-200">
|
||||||
<div class="card-body p-6">
|
<div class="card-body p-6">
|
||||||
<%= form_for @encrypted_config, url: settings_webhooks_path, method: :post, html: { autocomplete: 'off' } do |f| %>
|
<%= form_for @webhook_url, url: settings_webhooks_path, method: :post, html: { autocomplete: 'off' } do |f| %>
|
||||||
<%= f.label :value, 'Webhook URL', class: 'text-sm font-semibold' %>
|
<%= f.label :url, 'Webhook URL', class: 'text-sm font-semibold' %>
|
||||||
<div class="flex flex-row flex-wrap space-y-2 md:space-y-0 md:flex-nowrap md:space-x-2 mt-2">
|
<div class="flex flex-row flex-wrap space-y-2 md:space-y-0 md:flex-nowrap md:space-x-2 mt-2">
|
||||||
<%= f.url_field :value, class: 'input font-mono input-bordered w-full', placeholder: 'https://example.com/hook' %>
|
<%= f.url_field :url, class: 'input font-mono input-bordered w-full', placeholder: 'https://example.com/hook' %>
|
||||||
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button w-full md:w-32' %>
|
<%= f.button button_title(title: t('save'), disabled_with: t('saving')), class: 'base-button w-full md:w-32' %>
|
||||||
<a href="<%= webhook_secret_index_path %>" data-turbo-frame="modal" class="white-button w-full md:w-auto">
|
<% if @webhook_url.persisted? %>
|
||||||
<%= t('add_secret') %>
|
<a href="<%= webhook_secret_path(@webhook_url) %>" data-turbo-frame="modal" class="white-button w-full md:w-auto">
|
||||||
</a>
|
<%= @webhook_url.secret.present? ? t('edit_secret') : t('add_secret') %>
|
||||||
</div>
|
</a>
|
||||||
<% end %>
|
|
||||||
<% preference = current_account.account_configs.find_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)&.value || {} %>
|
|
||||||
<% WebhookPreferencesController::EVENTS.group_by { |e| e.include?('form') }.each do |_, events| %>
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 mt-2 gap-y-2">
|
|
||||||
<% events.each do |event| %>
|
|
||||||
<%= form_for '', url: webhook_preferences_path, method: :post do |f| %>
|
|
||||||
<%= f.hidden_field :event, value: event %>
|
|
||||||
<% uuid = SecureRandom.uuid %>
|
|
||||||
<div class="flex">
|
|
||||||
<label for="<%= uuid %>" class="flex items-center space-x-2">
|
|
||||||
<%= f.check_box :value, class: 'base-checkbox', checked: preference[event] || (!preference.key?(event) && event.starts_with?('form.')), onchange: 'this.form.requestSubmit()', id: uuid %>
|
|
||||||
<span>
|
|
||||||
<%= event %>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<%= form_for @webhook_url, url: @webhook_url.url.present? ? webhook_preference_path(@webhook_url) : '', method: :put, html: { autocomplete: 'off' } do |f| %>
|
||||||
|
<% WebhookUrl::EVENTS.group_by { |e| e.include?('form') }.each do |_, events| %>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 mt-4 gap-y-2">
|
||||||
|
<% events.each do |event| %>
|
||||||
|
<%= f.fields_for :events do |ff| %>
|
||||||
|
<div class="flex">
|
||||||
|
<label class="flex items-center space-x-2 cursor-pointer">
|
||||||
|
<%= ff.check_box event, class: 'base-checkbox', checked: @webhook_url.events.include?(event), onchange: 'this.form.requestSubmit()', disabled: @webhook_url.url.blank? %>
|
||||||
|
<span>
|
||||||
|
<%= event %>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last %>
|
<% submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last %>
|
||||||
@@ -47,7 +48,7 @@
|
|||||||
<span>
|
<span>
|
||||||
<%= t('submission_example_payload') %>
|
<%= t('submission_example_payload') %>
|
||||||
</span>
|
</span>
|
||||||
<% if @encrypted_config.value.present? %>
|
<% if @webhook_url.url.present? && @webhook_url.events.include?('form.completed') %>
|
||||||
<%= button_to button_title(title: 'Test Webhook', disabled_with: t('sending'), icon_disabled: svg_icon('loader', class: 'w-4 h-4 animate-spin')), settings_webhooks_path, class: 'btn btn-neutral btn-outline btn-sm', method: :put %>
|
<%= button_to button_title(title: 'Test Webhook', disabled_with: t('sending'), icon_disabled: svg_icon('loader', class: 'w-4 h-4 animate-spin')), settings_webhooks_path, class: 'btn btn-neutral btn-outline btn-sm', method: :put %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+49
-14
@@ -20,6 +20,7 @@ en: &en
|
|||||||
language_ko: 한국어
|
language_ko: 한국어
|
||||||
hi_there: Hi there
|
hi_there: Hi there
|
||||||
thanks: Thanks
|
thanks: Thanks
|
||||||
|
unarchive: Unarchive
|
||||||
first_party: 'First Party'
|
first_party: 'First Party'
|
||||||
docuseal_trusted_signature: DocuSeal Trusted Signature
|
docuseal_trusted_signature: DocuSeal Trusted Signature
|
||||||
hello_name: Hello %{name}
|
hello_name: Hello %{name}
|
||||||
@@ -119,7 +120,7 @@ en: &en
|
|||||||
profile_details: Profile Details
|
profile_details: Profile Details
|
||||||
sign_up_with_google: Sign up with Google
|
sign_up_with_google: Sign up with Google
|
||||||
sign_up_with_microsoft: Sign up with Microsoft
|
sign_up_with_microsoft: Sign up with Microsoft
|
||||||
by_creating_an_account_you_agree_to_our_html: 'By creating an account, you agree to our <a target="_blank" href="https://www.docuseal.co/privacy">Privacy Policy</a> and <a target="_blank" href="https://www.docuseal.co/terms">Terms of Service</a>.'
|
by_creating_an_account_you_agree_to_our_html: 'By creating an account, you agree to our <a target="_blank" href="https://www.docuseal.com/privacy">Privacy Policy</a> and <a target="_blank" href="https://www.docuseal.com/terms">Terms of Service</a>.'
|
||||||
enter_email_to_continue: Enter email to continue
|
enter_email_to_continue: Enter email to continue
|
||||||
account: Account
|
account: Account
|
||||||
preferences: Preferences
|
preferences: Preferences
|
||||||
@@ -473,6 +474,7 @@ en: &en
|
|||||||
logo: Logo
|
logo: Logo
|
||||||
back: Back
|
back: Back
|
||||||
add_secret: Add Secret
|
add_secret: Add Secret
|
||||||
|
edit_secret: Edit Secret
|
||||||
submission_example_payload: Submission example payload
|
submission_example_payload: Submission example payload
|
||||||
there_are_no_signatures: There are no signatures
|
there_are_no_signatures: There are no signatures
|
||||||
signed_with_trusted_certificate: Signed with trusted certificate
|
signed_with_trusted_certificate: Signed with trusted certificate
|
||||||
@@ -498,6 +500,7 @@ en: &en
|
|||||||
document_template_has_been_moved: Document template has been moved.
|
document_template_has_been_moved: Document template has been moved.
|
||||||
unable_to_move_template_into_folder: Unable to move template into folder.
|
unable_to_move_template_into_folder: Unable to move template into folder.
|
||||||
template_has_been_unarchived: Template has been unarchived.
|
template_has_been_unarchived: Template has been unarchived.
|
||||||
|
submission_has_been_unarchived: Submission has been unarchived.
|
||||||
unable_to_update_file: Unable to upload file.
|
unable_to_update_file: Unable to upload file.
|
||||||
user_has_been_invited: User has been invited.
|
user_has_been_invited: User has been invited.
|
||||||
unable_to_update_user: Unable to update user.
|
unable_to_update_user: Unable to update user.
|
||||||
@@ -621,7 +624,9 @@ en: &en
|
|||||||
archived_users: Archived Users
|
archived_users: Archived Users
|
||||||
embedding_users: Embedding Users
|
embedding_users: Embedding Users
|
||||||
view_embedding_users: View Embedding Users
|
view_embedding_users: View Embedding Users
|
||||||
view_users: View Users
|
key: Key
|
||||||
|
value: Value
|
||||||
|
webhook_secret: Webhook Secret
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>Email sent</b> to %{submitter_name}'
|
send_email_to_html: '<b>Email sent</b> to %{submitter_name}'
|
||||||
send_reminder_email_to_html: '<b>Reminder email sent</b> to %{submitter_name}'
|
send_reminder_email_to_html: '<b>Reminder email sent</b> to %{submitter_name}'
|
||||||
@@ -658,6 +663,7 @@ en: &en
|
|||||||
read: Read your data
|
read: Read your data
|
||||||
|
|
||||||
es: &es
|
es: &es
|
||||||
|
unarchive: Desarchivar
|
||||||
awaiting_completion_by_the_other_party: "Esperando la finalización por la otra parte"
|
awaiting_completion_by_the_other_party: "Esperando la finalización por la otra parte"
|
||||||
enforce_recipients_order: 'Hacer cumplir el orden de los destinatarios'
|
enforce_recipients_order: 'Hacer cumplir el orden de los destinatarios'
|
||||||
first_party: 'Primera Parte'
|
first_party: 'Primera Parte'
|
||||||
@@ -759,7 +765,7 @@ es: &es
|
|||||||
profile_details: Detalles del perfil
|
profile_details: Detalles del perfil
|
||||||
sign_up_with_google: Registrarse con Google
|
sign_up_with_google: Registrarse con Google
|
||||||
sign_up_with_microsoft: Registrarse con Microsoft
|
sign_up_with_microsoft: Registrarse con Microsoft
|
||||||
by_creating_an_account_you_agree_to_our_html: 'Al crear una cuenta, aceptas nuestra <a target="_blank" href="https://www.docuseal.co/privacy">Política de Privacidad</a> y <a target="_blank" href="https://www.docuseal.co/terms">Términos de Servicio</a>.'
|
by_creating_an_account_you_agree_to_our_html: 'Al crear una cuenta, aceptas nuestra <a target="_blank" href="https://www.docuseal.com/privacy">Política de Privacidad</a> y <a target="_blank" href="https://www.docuseal.com/terms">Términos de Servicio</a>.'
|
||||||
enter_email_to_continue: Ingresa tu correo electrónico para continuar
|
enter_email_to_continue: Ingresa tu correo electrónico para continuar
|
||||||
account: Cuenta
|
account: Cuenta
|
||||||
preferences: Preferencias
|
preferences: Preferencias
|
||||||
@@ -1113,6 +1119,7 @@ es: &es
|
|||||||
logo: Logotipo
|
logo: Logotipo
|
||||||
back: Atrás
|
back: Atrás
|
||||||
add_secret: Agregar secreto
|
add_secret: Agregar secreto
|
||||||
|
edit_secret: Editar secreto
|
||||||
submission_example_payload: Ejemplo de payload de envío
|
submission_example_payload: Ejemplo de payload de envío
|
||||||
there_are_no_signatures: No hay firmas
|
there_are_no_signatures: No hay firmas
|
||||||
signed_with_trusted_certificate: Firmado con certificado de confianza
|
signed_with_trusted_certificate: Firmado con certificado de confianza
|
||||||
@@ -1138,6 +1145,7 @@ es: &es
|
|||||||
document_template_has_been_moved: La plantilla de documento ha sido movida.
|
document_template_has_been_moved: La plantilla de documento ha sido movida.
|
||||||
unable_to_move_template_into_folder: No se pudo mover la plantilla a la carpeta.
|
unable_to_move_template_into_folder: No se pudo mover la plantilla a la carpeta.
|
||||||
template_has_been_unarchived: La plantilla ha sido desarchivada.
|
template_has_been_unarchived: La plantilla ha sido desarchivada.
|
||||||
|
submission_has_been_unarchived: La presentación ha sido desarchivada.
|
||||||
unable_to_update_file: No se pudo subir el archivo.
|
unable_to_update_file: No se pudo subir el archivo.
|
||||||
user_has_been_invited: El usuario ha sido invitado.
|
user_has_been_invited: El usuario ha sido invitado.
|
||||||
unable_to_update_user: No se pudo actualizar el usuario.
|
unable_to_update_user: No se pudo actualizar el usuario.
|
||||||
@@ -1261,6 +1269,9 @@ es: &es
|
|||||||
archived_users: Usuarios Archivados
|
archived_users: Usuarios Archivados
|
||||||
embedding_users: Usuarios Integrados
|
embedding_users: Usuarios Integrados
|
||||||
view_embedding_users: Ver Usuarios Integrado
|
view_embedding_users: Ver Usuarios Integrado
|
||||||
|
key: Clave
|
||||||
|
value: Valor
|
||||||
|
webhook_secret: Secreto del Webhook
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>Correo electrónico enviado</b> a %{submitter_name}'
|
send_email_to_html: '<b>Correo electrónico enviado</b> a %{submitter_name}'
|
||||||
send_reminder_email_to_html: '<b>Correo de recordatorio enviado</b> a %{submitter_name}'
|
send_reminder_email_to_html: '<b>Correo de recordatorio enviado</b> a %{submitter_name}'
|
||||||
@@ -1297,6 +1308,7 @@ es: &es
|
|||||||
read: Leer tus datos
|
read: Leer tus datos
|
||||||
|
|
||||||
it: &it
|
it: &it
|
||||||
|
unarchive: Ripristina
|
||||||
awaiting_completion_by_the_other_party: "In attesa di completamento da parte dell'altra parte"
|
awaiting_completion_by_the_other_party: "In attesa di completamento da parte dell'altra parte"
|
||||||
enforce_recipients_order: Aplicar el orden de los destinatarios
|
enforce_recipients_order: Aplicar el orden de los destinatarios
|
||||||
first_party: 'Prima parte'
|
first_party: 'Prima parte'
|
||||||
@@ -1398,7 +1410,7 @@ it: &it
|
|||||||
profile_details: Dettagli del profilo
|
profile_details: Dettagli del profilo
|
||||||
sign_up_with_google: Registrati con Google
|
sign_up_with_google: Registrati con Google
|
||||||
sign_up_with_microsoft: Registrati con Microsoft
|
sign_up_with_microsoft: Registrati con Microsoft
|
||||||
by_creating_an_account_you_agree_to_our_html: 'Creando un account, accetti la nostra <a target="_blank" href="https://www.docuseal.co/privacy">Privacy Policy</a> e i nostri <a target="_blank" href="https://www.docuseal.co/terms">Termini di servizio</a>.'
|
by_creating_an_account_you_agree_to_our_html: 'Creando un account, accetti la nostra <a target="_blank" href="https://www.docuseal.com/privacy">Privacy Policy</a> e i nostri <a target="_blank" href="https://www.docuseal.com/terms">Termini di servizio</a>.'
|
||||||
enter_email_to_continue: "Inserisci l'email per continuare"
|
enter_email_to_continue: "Inserisci l'email per continuare"
|
||||||
account: Account
|
account: Account
|
||||||
preferences: Preferenze
|
preferences: Preferenze
|
||||||
@@ -1752,6 +1764,7 @@ it: &it
|
|||||||
logo: Logo
|
logo: Logo
|
||||||
back: Indietro
|
back: Indietro
|
||||||
add_secret: Aggiungi segreto
|
add_secret: Aggiungi segreto
|
||||||
|
edit_secret: Modifica segreto
|
||||||
submission_example_payload: Esempio di payload di invio
|
submission_example_payload: Esempio di payload di invio
|
||||||
there_are_no_signatures: Non ci sono firme
|
there_are_no_signatures: Non ci sono firme
|
||||||
signed_with_trusted_certificate: Firmato con certificato affidabile
|
signed_with_trusted_certificate: Firmato con certificato affidabile
|
||||||
@@ -1777,6 +1790,7 @@ it: &it
|
|||||||
document_template_has_been_moved: Il modello di documento è stato spostato.
|
document_template_has_been_moved: Il modello di documento è stato spostato.
|
||||||
unable_to_move_template_into_folder: Impossibile spostare il modello nella cartella.
|
unable_to_move_template_into_folder: Impossibile spostare il modello nella cartella.
|
||||||
template_has_been_unarchived: Il modello è stato disarchiviato.
|
template_has_been_unarchived: Il modello è stato disarchiviato.
|
||||||
|
submission_has_been_unarchived: La sottomissione è stata dearchiviata.
|
||||||
unable_to_update_file: Impossibile caricare il file.
|
unable_to_update_file: Impossibile caricare il file.
|
||||||
user_has_been_invited: "L'utente è stato invitato."
|
user_has_been_invited: "L'utente è stato invitato."
|
||||||
unable_to_update_user: Impossibile aggiornare l'utente.
|
unable_to_update_user: Impossibile aggiornare l'utente.
|
||||||
@@ -1900,6 +1914,9 @@ it: &it
|
|||||||
archived_users: Utenti Archiviati
|
archived_users: Utenti Archiviati
|
||||||
embedding_users: Utenti Incorporati
|
embedding_users: Utenti Incorporati
|
||||||
view_embedding_users: Visualizza Utenti Incorporati
|
view_embedding_users: Visualizza Utenti Incorporati
|
||||||
|
key: Chiave
|
||||||
|
value: Valore
|
||||||
|
webhook_secret: Segreto del Webhook
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>E-mail inviato</b> a %{submitter_name}'
|
send_email_to_html: '<b>E-mail inviato</b> a %{submitter_name}'
|
||||||
send_reminder_email_to_html: '<b>E-mail di promemoria inviato</b> a %{submitter_name}'
|
send_reminder_email_to_html: '<b>E-mail di promemoria inviato</b> a %{submitter_name}'
|
||||||
@@ -1936,6 +1953,7 @@ it: &it
|
|||||||
read: Leggi i tuoi dati
|
read: Leggi i tuoi dati
|
||||||
|
|
||||||
fr: &fr
|
fr: &fr
|
||||||
|
unarchive: Désarchiver
|
||||||
awaiting_completion_by_the_other_party: "En attente de la complétion par l'autre partie"
|
awaiting_completion_by_the_other_party: "En attente de la complétion par l'autre partie"
|
||||||
enforce_recipients_order: "Respecter l'ordre des destinataires"
|
enforce_recipients_order: "Respecter l'ordre des destinataires"
|
||||||
first_party: 'Première partie'
|
first_party: 'Première partie'
|
||||||
@@ -2038,7 +2056,7 @@ fr: &fr
|
|||||||
sign_up_with_google: S'inscrire avec Google
|
sign_up_with_google: S'inscrire avec Google
|
||||||
sign_up_with_microsoft: S'inscrire avec Microsoft
|
sign_up_with_microsoft: S'inscrire avec Microsoft
|
||||||
by_creating_an_account_you_agree_to_our_html: |
|
by_creating_an_account_you_agree_to_our_html: |
|
||||||
En créant un compte, vous acceptez notre <a target="_blank" href="https://www.docuseal.co/privacy">Politique de Confidentialité</a> et nos <a target="_blank" href="https://www.docuseal.co/terms">Conditions d'Utilisation</a>.
|
En créant un compte, vous acceptez notre <a target="_blank" href="https://www.docuseal.com/privacy">Politique de Confidentialité</a> et nos <a target="_blank" href="https://www.docuseal.com/terms">Conditions d'Utilisation</a>.
|
||||||
enter_email_to_continue: Entrez votre e-mail pour continuer
|
enter_email_to_continue: Entrez votre e-mail pour continuer
|
||||||
account: Compte
|
account: Compte
|
||||||
preferences: Préférences
|
preferences: Préférences
|
||||||
@@ -2392,6 +2410,7 @@ fr: &fr
|
|||||||
logo: Logo
|
logo: Logo
|
||||||
back: Retour
|
back: Retour
|
||||||
add_secret: Ajouter un secret
|
add_secret: Ajouter un secret
|
||||||
|
edit_secret: Modifier le Secret
|
||||||
submission_example_payload: Exemple de payload de soumission
|
submission_example_payload: Exemple de payload de soumission
|
||||||
there_are_no_signatures: "Il n'y a pas de signatures"
|
there_are_no_signatures: "Il n'y a pas de signatures"
|
||||||
signed_with_trusted_certificate: Signé avec un certificat de confiance
|
signed_with_trusted_certificate: Signé avec un certificat de confiance
|
||||||
@@ -2417,6 +2436,7 @@ fr: &fr
|
|||||||
document_template_has_been_moved: Le modèle de document a été déplacé.
|
document_template_has_been_moved: Le modèle de document a été déplacé.
|
||||||
unable_to_move_template_into_folder: Impossible de déplacer le modèle dans le dossier.
|
unable_to_move_template_into_folder: Impossible de déplacer le modèle dans le dossier.
|
||||||
template_has_been_unarchived: Le modèle a été désarchivé.
|
template_has_been_unarchived: Le modèle a été désarchivé.
|
||||||
|
submission_has_been_unarchived: La soumission a été désarchivée.
|
||||||
unable_to_update_file: Impossible de télécharger le fichier.
|
unable_to_update_file: Impossible de télécharger le fichier.
|
||||||
user_has_been_updated: "L'utilisateur a été mis à jour."
|
user_has_been_updated: "L'utilisateur a été mis à jour."
|
||||||
unable_to_remove_user: Impossible de supprimer l'utilisateur.
|
unable_to_remove_user: Impossible de supprimer l'utilisateur.
|
||||||
@@ -2540,6 +2560,9 @@ fr: &fr
|
|||||||
archived_users: Utilisateurs Archivés
|
archived_users: Utilisateurs Archivés
|
||||||
embedding_users: Utilisateurs Intégrés
|
embedding_users: Utilisateurs Intégrés
|
||||||
view_embedding_users: Voir les Utilisateurs Intégrés
|
view_embedding_users: Voir les Utilisateurs Intégrés
|
||||||
|
key: Clé
|
||||||
|
value: Valeur
|
||||||
|
webhook_secret: Secret du Webhook
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>E-mail envoyé</b> à %{submitter_name}'
|
send_email_to_html: '<b>E-mail envoyé</b> à %{submitter_name}'
|
||||||
send_reminder_email_to_html: '<b>E-mail de rappel envoyé</b> à %{submitter_name}'
|
send_reminder_email_to_html: '<b>E-mail de rappel envoyé</b> à %{submitter_name}'
|
||||||
@@ -2576,6 +2599,7 @@ fr: &fr
|
|||||||
read: Lire vos données
|
read: Lire vos données
|
||||||
|
|
||||||
pt: &pt
|
pt: &pt
|
||||||
|
unarchive: Desarquivar
|
||||||
awaiting_completion_by_the_other_party: "Aguardando a conclusão pela outra parte"
|
awaiting_completion_by_the_other_party: "Aguardando a conclusão pela outra parte"
|
||||||
enforce_recipients_order: 'Forçar a ordem dos recipientes'
|
enforce_recipients_order: 'Forçar a ordem dos recipientes'
|
||||||
first_party: 'Primeira Parte'
|
first_party: 'Primeira Parte'
|
||||||
@@ -2677,7 +2701,7 @@ pt: &pt
|
|||||||
profile_details: Detalhes do perfil
|
profile_details: Detalhes do perfil
|
||||||
sign_up_with_google: Inscrever-se com Google
|
sign_up_with_google: Inscrever-se com Google
|
||||||
sign_up_with_microsoft: Inscrever-se com Microsoft
|
sign_up_with_microsoft: Inscrever-se com Microsoft
|
||||||
by_creating_an_account_you_agree_to_our_html: 'Ao criar uma conta, você concorda com nossa <a target="_blank" href="https://www.docuseal.co/privacy">Política de Privacidade</a> e <a target="_blank" href="https://www.docuseal.co/terms">Termos de Serviço</a>.'
|
by_creating_an_account_you_agree_to_our_html: 'Ao criar uma conta, você concorda com nossa <a target="_blank" href="https://www.docuseal.com/privacy">Política de Privacidade</a> e <a target="_blank" href="https://www.docuseal.com/terms">Termos de Serviço</a>.'
|
||||||
enter_email_to_continue: Insira o e-mail para continuar
|
enter_email_to_continue: Insira o e-mail para continuar
|
||||||
account: Conta
|
account: Conta
|
||||||
preferences: Preferências
|
preferences: Preferências
|
||||||
@@ -3031,6 +3055,7 @@ pt: &pt
|
|||||||
logo: Logotipo
|
logo: Logotipo
|
||||||
back: Voltar
|
back: Voltar
|
||||||
add_secret: Adicionar segredo
|
add_secret: Adicionar segredo
|
||||||
|
edit_secret: Editar Segredo
|
||||||
submission_example_payload: Exemplo de payload de submissão
|
submission_example_payload: Exemplo de payload de submissão
|
||||||
there_are_no_signatures: Não há assinaturas
|
there_are_no_signatures: Não há assinaturas
|
||||||
signed_with_trusted_certificate: Assinado com certificado confiável
|
signed_with_trusted_certificate: Assinado com certificado confiável
|
||||||
@@ -3056,6 +3081,7 @@ pt: &pt
|
|||||||
document_template_has_been_moved: O modelo de documento foi movido.
|
document_template_has_been_moved: O modelo de documento foi movido.
|
||||||
unable_to_move_template_into_folder: Não foi possível mover o modelo para a pasta.
|
unable_to_move_template_into_folder: Não foi possível mover o modelo para a pasta.
|
||||||
template_has_been_unarchived: O modelo foi desarquivado.
|
template_has_been_unarchived: O modelo foi desarquivado.
|
||||||
|
submission_has_been_unarchived: A submissao foi desarquivada.
|
||||||
unable_to_update_file: Não foi possível atualizar o arquivo.
|
unable_to_update_file: Não foi possível atualizar o arquivo.
|
||||||
user_has_been_updated: O usuário foi atualizado.
|
user_has_been_updated: O usuário foi atualizado.
|
||||||
unable_to_remove_user: Não foi possível remover o usuário.
|
unable_to_remove_user: Não foi possível remover o usuário.
|
||||||
@@ -3179,6 +3205,9 @@ pt: &pt
|
|||||||
archived_users: Usuários Arquivados
|
archived_users: Usuários Arquivados
|
||||||
embedding_users: Usuários Incorporados
|
embedding_users: Usuários Incorporados
|
||||||
view_embedding_users: Ver Usuários Incorporados
|
view_embedding_users: Ver Usuários Incorporados
|
||||||
|
key: Chave
|
||||||
|
value: Valor
|
||||||
|
webhook_secret: Segredo do Webhook
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>E-mail enviado</b> para %{submitter_name}'
|
send_email_to_html: '<b>E-mail enviado</b> para %{submitter_name}'
|
||||||
send_reminder_email_to_html: '<b>E-mail de lembrete enviado</b> para %{submitter_name}'
|
send_reminder_email_to_html: '<b>E-mail de lembrete enviado</b> para %{submitter_name}'
|
||||||
@@ -3215,6 +3244,7 @@ pt: &pt
|
|||||||
read: Ler seus dados
|
read: Ler seus dados
|
||||||
|
|
||||||
de: &de
|
de: &de
|
||||||
|
unarchive: Wiederherstellen
|
||||||
awaiting_completion_by_the_other_party: "Warten auf die Fertigstellung durch die andere Partei"
|
awaiting_completion_by_the_other_party: "Warten auf die Fertigstellung durch die andere Partei"
|
||||||
enforce_recipients_order: 'Empfängerreihenfolge durchsetzen'
|
enforce_recipients_order: 'Empfängerreihenfolge durchsetzen'
|
||||||
first_party: 'Erste Partei'
|
first_party: 'Erste Partei'
|
||||||
@@ -3316,7 +3346,7 @@ de: &de
|
|||||||
profile_details: Profildetails
|
profile_details: Profildetails
|
||||||
sign_up_with_google: Mit Google registrieren
|
sign_up_with_google: Mit Google registrieren
|
||||||
sign_up_with_microsoft: Mit Microsoft registrieren
|
sign_up_with_microsoft: Mit Microsoft registrieren
|
||||||
by_creating_an_account_you_agree_to_our_html: 'Durch die Erstellung eines Kontos stimmst du unseren <a target="_blank" href="https://www.docuseal.co/privacy">Datenschutzrichtlinien</a> und <a target="_blank" href="https://www.docuseal.co/terms">Nutzungsbedingungen</a> zu.'
|
by_creating_an_account_you_agree_to_our_html: 'Durch die Erstellung eines Kontos stimmst du unseren <a target="_blank" href="https://www.docuseal.com/privacy">Datenschutzrichtlinien</a> und <a target="_blank" href="https://www.docuseal.com/terms">Nutzungsbedingungen</a> zu.'
|
||||||
enter_email_to_continue: E-Mail eingeben, um fortzufahren
|
enter_email_to_continue: E-Mail eingeben, um fortzufahren
|
||||||
account: Konto
|
account: Konto
|
||||||
preferences: Einstellungen
|
preferences: Einstellungen
|
||||||
@@ -3670,6 +3700,7 @@ de: &de
|
|||||||
logo: Logo
|
logo: Logo
|
||||||
back: Zurück
|
back: Zurück
|
||||||
add_secret: Geheimnis hinzufügen
|
add_secret: Geheimnis hinzufügen
|
||||||
|
edit_secret: Geheimnis bearbeiten
|
||||||
submission_example_payload: Beispiel-Payload für Einreichung
|
submission_example_payload: Beispiel-Payload für Einreichung
|
||||||
there_are_no_signatures: Es gibt keine Unterschriften
|
there_are_no_signatures: Es gibt keine Unterschriften
|
||||||
signed_with_trusted_certificate: Signiert mit vertrauenswürdigem Zertifikat
|
signed_with_trusted_certificate: Signiert mit vertrauenswürdigem Zertifikat
|
||||||
@@ -3695,6 +3726,7 @@ de: &de
|
|||||||
document_template_has_been_moved: Die Dokumentvorlage wurde verschoben.
|
document_template_has_been_moved: Die Dokumentvorlage wurde verschoben.
|
||||||
unable_to_move_template_into_folder: Vorlage konnte nicht in den Ordner verschoben werden.
|
unable_to_move_template_into_folder: Vorlage konnte nicht in den Ordner verschoben werden.
|
||||||
template_has_been_unarchived: Die Vorlage wurde entarchiviert.
|
template_has_been_unarchived: Die Vorlage wurde entarchiviert.
|
||||||
|
submission_has_been_unarchived: Die Einreichung wurde wiederhergestellt.
|
||||||
unable_to_update_file: Datei konnte nicht hochgeladen werden.
|
unable_to_update_file: Datei konnte nicht hochgeladen werden.
|
||||||
user_has_been_updated: Der Benutzer wurde aktualisiert.
|
user_has_been_updated: Der Benutzer wurde aktualisiert.
|
||||||
unable_to_remove_user: Benutzer konnte nicht entfernt werden.
|
unable_to_remove_user: Benutzer konnte nicht entfernt werden.
|
||||||
@@ -3818,6 +3850,9 @@ de: &de
|
|||||||
archived_users: Archivierte Benutzer
|
archived_users: Archivierte Benutzer
|
||||||
embedding_users: Einbettende Benutzer
|
embedding_users: Einbettende Benutzer
|
||||||
view_embedding_users: Einbettende Benutzer anzeigen
|
view_embedding_users: Einbettende Benutzer anzeigen
|
||||||
|
key: Schlüssel
|
||||||
|
value: Wert
|
||||||
|
webhook_secret: Webhook-Geheimnis
|
||||||
submission_event_names:
|
submission_event_names:
|
||||||
send_email_to_html: '<b>E-Mail gesendet</b> an %{submitter_name}'
|
send_email_to_html: '<b>E-Mail gesendet</b> an %{submitter_name}'
|
||||||
send_reminder_email_to_html: '<b>Erinnerungs-E-Mail gesendet</b> an %{submitter_name}'
|
send_reminder_email_to_html: '<b>Erinnerungs-E-Mail gesendet</b> an %{submitter_name}'
|
||||||
@@ -3904,7 +3939,7 @@ pl:
|
|||||||
profile_details: Szczegóły profilu
|
profile_details: Szczegóły profilu
|
||||||
sign_up_with_google: Zarejestruj się przez Google
|
sign_up_with_google: Zarejestruj się przez Google
|
||||||
sign_up_with_microsoft: Zarejestruj się przez Microsoft
|
sign_up_with_microsoft: Zarejestruj się przez Microsoft
|
||||||
by_creating_an_account_you_agree_to_our_html: 'Tworząc konto, akceptujesz naszą <a target="_blank" href="https://www.docuseal.co/privacy">Politykę Prywatności</a> i <a target="_blank" href="https://www.docuseal.co/terms">Regulamin</a>.'
|
by_creating_an_account_you_agree_to_our_html: 'Tworząc konto, akceptujesz naszą <a target="_blank" href="https://www.docuseal.com/privacy">Politykę Prywatności</a> i <a target="_blank" href="https://www.docuseal.com/terms">Regulamin</a>.'
|
||||||
enter_email_to_continue: Wprowadź e-mail, aby kontynuować
|
enter_email_to_continue: Wprowadź e-mail, aby kontynuować
|
||||||
|
|
||||||
uk:
|
uk:
|
||||||
@@ -3958,7 +3993,7 @@ uk:
|
|||||||
profile_details: Дані профілю
|
profile_details: Дані профілю
|
||||||
sign_up_with_google: Зареєструватися через Google
|
sign_up_with_google: Зареєструватися через Google
|
||||||
sign_up_with_microsoft: Зареєструватися через Microsoft
|
sign_up_with_microsoft: Зареєструватися через Microsoft
|
||||||
by_creating_an_account_you_agree_to_our_html: 'Створюючи акаунт, ви погоджуєтесь з нашою <a target="_blank" href="https://www.docuseal.co/privacy">Політикою конфіденційності</a> і <a target="_blank" href="https://www.docuseal.co/terms">Умовами надання послуг</a>.'
|
by_creating_an_account_you_agree_to_our_html: 'Створюючи акаунт, ви погоджуєтесь з нашою <a target="_blank" href="https://www.docuseal.com/privacy">Політикою конфіденційності</a> і <a target="_blank" href="https://www.docuseal.com/terms">Умовами надання послуг</a>.'
|
||||||
enter_email_to_continue: Введіть електронну пошту, щоб продовжити
|
enter_email_to_continue: Введіть електронну пошту, щоб продовжити
|
||||||
|
|
||||||
cs:
|
cs:
|
||||||
@@ -4012,7 +4047,7 @@ cs:
|
|||||||
profile_details: Detaily profilu
|
profile_details: Detaily profilu
|
||||||
sign_up_with_google: Zaregistrovat se pomocí Googlu
|
sign_up_with_google: Zaregistrovat se pomocí Googlu
|
||||||
sign_up_with_microsoft: Zaregistrovat se pomocí Microsoftu
|
sign_up_with_microsoft: Zaregistrovat se pomocí Microsoftu
|
||||||
by_creating_an_account_you_agree_to_our_html: 'Vytvořením účtu souhlasíte s našimi <a target="_blank" href="https://www.docuseal.co/privacy">Zásadami ochrany osobních údajů</a> a <a target="_blank" href="https://www.docuseal.co/terms">Podmínkami služby</a>.'
|
by_creating_an_account_you_agree_to_our_html: 'Vytvořením účtu souhlasíte s našimi <a target="_blank" href="https://www.docuseal.com/privacy">Zásadami ochrany osobních údajů</a> a <a target="_blank" href="https://www.docuseal.com/terms">Podmínkami služby</a>.'
|
||||||
enter_email_to_continue: Zadejte e-mail pro pokračování
|
enter_email_to_continue: Zadejte e-mail pro pokračování
|
||||||
|
|
||||||
he:
|
he:
|
||||||
@@ -4066,7 +4101,7 @@ he:
|
|||||||
profile_details: פרטי הפרופיל
|
profile_details: פרטי הפרופיל
|
||||||
sign_up_with_google: הירשם עם גוגל
|
sign_up_with_google: הירשם עם גוגל
|
||||||
sign_up_with_microsoft: הירשם עם מיקרוסופט
|
sign_up_with_microsoft: הירשם עם מיקרוסופט
|
||||||
by_creating_an_account_you_agree_to_our_html: 'על ידי יצירת חשבון, אתה מסכים ל<a target="_blank" href="https://www.docuseal.co/privacy">מדיניות הפרטיות</a> ול<a target="_blank" href="https://www.docuseal.co/terms">תנאי השירות</a> שלנו.'
|
by_creating_an_account_you_agree_to_our_html: 'על ידי יצירת חשבון, אתה מסכים ל<a target="_blank" href="https://www.docuseal.com/privacy">מדיניות הפרטיות</a> ול<a target="_blank" href="https://www.docuseal.com/terms">תנאי השירות</a> שלנו.'
|
||||||
enter_email_to_continue: הכנס דוא"ל כדי להמשיך
|
enter_email_to_continue: הכנס דוא"ל כדי להמשיך
|
||||||
|
|
||||||
nl:
|
nl:
|
||||||
@@ -4120,7 +4155,7 @@ nl:
|
|||||||
profile_details: Profielgegevens
|
profile_details: Profielgegevens
|
||||||
sign_up_with_google: Aanmelden met Google
|
sign_up_with_google: Aanmelden met Google
|
||||||
sign_up_with_microsoft: Aanmelden met Microsoft
|
sign_up_with_microsoft: Aanmelden met Microsoft
|
||||||
by_creating_an_account_you_agree_to_our_html: 'Door een account aan te maken, ga je akkoord met ons <a target="_blank" href="https://www.docuseal.co/privacy">Privacybeleid</a> en onze <a target="_blank" href="https://www.docuseal.co/terms">Gebruiksvoorwaarden</a>.'
|
by_creating_an_account_you_agree_to_our_html: 'Door een account aan te maken, ga je akkoord met ons <a target="_blank" href="https://www.docuseal.com/privacy">Privacybeleid</a> en onze <a target="_blank" href="https://www.docuseal.com/terms">Gebruiksvoorwaarden</a>.'
|
||||||
enter_email_to_continue: Voer e-mail in om door te gaan
|
enter_email_to_continue: Voer e-mail in om door te gaan
|
||||||
|
|
||||||
ar:
|
ar:
|
||||||
@@ -4174,7 +4209,7 @@ ar:
|
|||||||
profile_details: تفاصيل الملف الشخصي
|
profile_details: تفاصيل الملف الشخصي
|
||||||
sign_up_with_google: الاشتراك باستخدام جوجل
|
sign_up_with_google: الاشتراك باستخدام جوجل
|
||||||
sign_up_with_microsoft: الاشتراك باستخدام مايكروسوفت
|
sign_up_with_microsoft: الاشتراك باستخدام مايكروسوفت
|
||||||
by_creating_an_account_you_agree_to_our_html: 'من خلال إنشاء حساب، فإنك توافق على <a target="_blank" href="https://www.docuseal.co/privacy">سياسة الخصوصية</a> و<a target="_blank" href="https://www.docuseal.co/terms">شروط الخدمة</a> الخاصة بنا.'
|
by_creating_an_account_you_agree_to_our_html: 'من خلال إنشاء حساب، فإنك توافق على <a target="_blank" href="https://www.docuseal.com/privacy">سياسة الخصوصية</a> و<a target="_blank" href="https://www.docuseal.com/terms">شروط الخدمة</a> الخاصة بنا.'
|
||||||
enter_email_to_continue: أدخل البريد الإلكتروني للمتابعة
|
enter_email_to_continue: أدخل البريد الإلكتروني للمتابعة
|
||||||
|
|
||||||
ko:
|
ko:
|
||||||
@@ -4228,7 +4263,7 @@ ko:
|
|||||||
profile_details: 프로필 세부 정보
|
profile_details: 프로필 세부 정보
|
||||||
sign_up_with_google: Google로 가입
|
sign_up_with_google: Google로 가입
|
||||||
sign_up_with_microsoft: Microsoft로 가입
|
sign_up_with_microsoft: Microsoft로 가입
|
||||||
by_creating_an_account_you_agree_to_our_html: '계정을 생성함으로써, 귀하는 우리의 <a target="_blank" href="https://www.docuseal.co/privacy">개인정보 보호정책</a> 및 <a target="_blank" href="https://www.docuseal.co/terms">서비스 약관</a>에 동의하는 것입니다.'
|
by_creating_an_account_you_agree_to_our_html: '계정을 생성함으로써, 귀하는 우리의 <a target="_blank" href="https://www.docuseal.com/privacy">개인정보 보호정책</a> 및 <a target="_blank" href="https://www.docuseal.com/terms">서비스 약관</a>에 동의하는 것입니다.'
|
||||||
enter_email_to_continue: 계속하려면 이메일을 입력하세요
|
enter_email_to_continue: 계속하려면 이메일을 입력하세요
|
||||||
|
|
||||||
en-US:
|
en-US:
|
||||||
|
|||||||
+5
-3
@@ -69,7 +69,9 @@ Rails.application.routes.draw do
|
|||||||
resource :user_initials, only: %i[edit update destroy]
|
resource :user_initials, only: %i[edit update destroy]
|
||||||
resources :submissions_archived, only: %i[index], path: 'submissions/archived'
|
resources :submissions_archived, only: %i[index], path: 'submissions/archived'
|
||||||
resources :submissions, only: %i[index], controller: 'submissions_dashboard'
|
resources :submissions, only: %i[index], controller: 'submissions_dashboard'
|
||||||
resources :submissions, only: %i[show destroy]
|
resources :submissions, only: %i[show destroy] do
|
||||||
|
resources :unarchive, only: %i[create], controller: 'submissions_unarchive'
|
||||||
|
end
|
||||||
resources :submitters, only: %i[edit update]
|
resources :submitters, only: %i[edit update]
|
||||||
resources :console_redirect, only: %i[index]
|
resources :console_redirect, only: %i[index]
|
||||||
resources :upgrade, only: %i[index], controller: 'console_redirect'
|
resources :upgrade, only: %i[index], controller: 'console_redirect'
|
||||||
@@ -78,8 +80,8 @@ Rails.application.routes.draw do
|
|||||||
resources :testing_api_settings, only: %i[index]
|
resources :testing_api_settings, only: %i[index]
|
||||||
resources :submitters_autocomplete, only: %i[index]
|
resources :submitters_autocomplete, only: %i[index]
|
||||||
resources :template_folders_autocomplete, only: %i[index]
|
resources :template_folders_autocomplete, only: %i[index]
|
||||||
resources :webhook_preferences, only: %i[create]
|
resources :webhook_secret, only: %i[show update]
|
||||||
resources :webhook_secret, only: %i[index create]
|
resources :webhook_preferences, only: %i[update]
|
||||||
resource :templates_upload, only: %i[create]
|
resource :templates_upload, only: %i[create]
|
||||||
authenticated do
|
authenticated do
|
||||||
resource :templates_upload, only: %i[show], path: 'new'
|
resource :templates_upload, only: %i[show], path: 'new'
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddSecretToWebhookUrls < ActiveRecord::Migration[7.2]
|
||||||
|
class MigrationWebhookUrl < ApplicationRecord
|
||||||
|
self.table_name = 'webhook_urls'
|
||||||
|
|
||||||
|
serialize :secret, coder: JSON
|
||||||
|
|
||||||
|
encrypts :url, :secret
|
||||||
|
end
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_column :webhook_urls, :secret, :text
|
||||||
|
|
||||||
|
MigrationWebhookUrl.all.each do |url|
|
||||||
|
url.update_columns(secret: {})
|
||||||
|
end
|
||||||
|
|
||||||
|
change_column_null :webhook_urls, :secret, false
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class PopulateWebhookUrls < ActiveRecord::Migration[7.2]
|
||||||
|
disable_ddl_transaction
|
||||||
|
|
||||||
|
class MigrationWebhookUrl < ActiveRecord::Base
|
||||||
|
self.table_name = 'webhook_urls'
|
||||||
|
|
||||||
|
serialize :events, coder: JSON
|
||||||
|
serialize :secret, coder: JSON
|
||||||
|
|
||||||
|
encrypts :url, :secret
|
||||||
|
end
|
||||||
|
|
||||||
|
class MigrationEncryptedConfig < ActiveRecord::Base
|
||||||
|
self.table_name = 'encrypted_configs'
|
||||||
|
|
||||||
|
encrypts :value
|
||||||
|
serialize :value, coder: JSON
|
||||||
|
end
|
||||||
|
|
||||||
|
class MigrationAccountConfig < ActiveRecord::Base
|
||||||
|
self.table_name = 'account_configs'
|
||||||
|
|
||||||
|
serialize :value, coder: JSON
|
||||||
|
end
|
||||||
|
|
||||||
|
def up
|
||||||
|
MigrationEncryptedConfig.where(key: 'webhook_url').find_each do |config|
|
||||||
|
webhook_url = MigrationWebhookUrl.find_or_initialize_by(account_id: config.account_id,
|
||||||
|
sha1: Digest::SHA1.hexdigest(config.value))
|
||||||
|
|
||||||
|
webhook_url.secret =
|
||||||
|
MigrationEncryptedConfig.find_by(account_id: config.account_id, key: 'webhook_secret')&.value.to_h
|
||||||
|
|
||||||
|
preferences =
|
||||||
|
MigrationAccountConfig.find_by(account_id: config.account_id, key: 'webhook_preferences')&.value.to_h
|
||||||
|
|
||||||
|
events = %w[form.viewed form.started form.completed form.declined].reject { |event| preferences[event] == false }
|
||||||
|
|
||||||
|
events += preferences.compact_blank.keys
|
||||||
|
|
||||||
|
webhook_url.events = events.uniq
|
||||||
|
webhook_url.url = config.value
|
||||||
|
|
||||||
|
webhook_url.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
+2
-1
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.2].define(version: 2024_10_26_161207) do
|
ActiveRecord::Schema[7.2].define(version: 2024_10_29_192232) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
@@ -367,6 +367,7 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_26_161207) do
|
|||||||
t.string "sha1", null: false
|
t.string "sha1", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.text "secret", null: false
|
||||||
t.index ["account_id"], name: "index_webhook_urls_on_account_id"
|
t.index ["account_id"], name: "index_webhook_urls_on_account_id"
|
||||||
t.index ["sha1"], name: "index_webhook_urls_on_sha1"
|
t.index ["sha1"], name: "index_webhook_urls_on_sha1"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -22,5 +22,6 @@ class Ability
|
|||||||
can :manage, UserConfig, user_id: user.id
|
can :manage, UserConfig, user_id: user.id
|
||||||
can :manage, Account, id: user.account_id
|
can :manage, Account, id: user.account_id
|
||||||
can :manage, AccessToken, user_id: user.id
|
can :manage, AccessToken, user_id: user.id
|
||||||
|
can :manage, WebhookUrl, account_id: user.account_id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -78,30 +78,6 @@ module Accounts
|
|||||||
new_template
|
new_template
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_webhook_url(account)
|
|
||||||
load_webhook_config(account)&.value.presence
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_webhook_config(account)
|
|
||||||
configs = account.encrypted_configs.find_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
|
||||||
|
|
||||||
if !configs && !Docuseal.multitenant? && !account.testing?
|
|
||||||
configs = Account.order(:id).first.encrypted_configs.find_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
|
|
||||||
end
|
|
||||||
|
|
||||||
configs
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_webhook_preferences(account)
|
|
||||||
configs = account.account_configs.find_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)
|
|
||||||
|
|
||||||
unless Docuseal.multitenant?
|
|
||||||
configs ||= Account.order(:id).first.account_configs.find_by(key: AccountConfig::WEBHOOK_PREFERENCES_KEY)
|
|
||||||
end
|
|
||||||
|
|
||||||
configs&.value.presence || {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_signing_pkcs(account)
|
def load_signing_pkcs(account)
|
||||||
cert_data =
|
cert_data =
|
||||||
if Docuseal.multitenant?
|
if Docuseal.multitenant?
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
module Docuseal
|
module Docuseal
|
||||||
URL_CACHE = ActiveSupport::Cache::MemoryStore.new
|
URL_CACHE = ActiveSupport::Cache::MemoryStore.new
|
||||||
PRODUCT_URL = 'https://www.docuseal.co'
|
PRODUCT_URL = 'https://www.docuseal.com'
|
||||||
NEWSLETTER_URL = "#{PRODUCT_URL}/newsletters".freeze
|
NEWSLETTER_URL = "#{PRODUCT_URL}/newsletters".freeze
|
||||||
ENQUIRIES_URL = "#{PRODUCT_URL}/enquiries".freeze
|
ENQUIRIES_URL = "#{PRODUCT_URL}/enquiries".freeze
|
||||||
PRODUCT_NAME = 'DocuSeal'
|
PRODUCT_NAME = 'DocuSeal'
|
||||||
|
|||||||
@@ -291,7 +291,16 @@ module Submissions
|
|||||||
),
|
),
|
||||||
if field['type'].in?(%w[image signature initials stamp])
|
if field['type'].in?(%w[image signature initials stamp])
|
||||||
attachment = submitter.attachments.find { |a| a.uuid == value }
|
attachment = submitter.attachments.find { |a| a.uuid == value }
|
||||||
image = Vips::Image.new_from_buffer(attachment.download, '').autorot
|
|
||||||
|
image =
|
||||||
|
begin
|
||||||
|
Vips::Image.new_from_buffer(attachment.download, '').autorot
|
||||||
|
rescue Vips::Error
|
||||||
|
next unless attachment.content_type.starts_with?('image/')
|
||||||
|
next if attachment.byte_size.zero?
|
||||||
|
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
|
||||||
scale = [600.0 / image.width, 600.0 / image.height].min
|
scale = [600.0 / image.width, 600.0 / image.height].min
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,10 @@ module Submissions
|
|||||||
name: column_name(I18n.t('phone'), submitter_name, submitters_count),
|
name: column_name(I18n.t('phone'), submitter_name, submitters_count),
|
||||||
value: submitter.phone
|
value: submitter.phone
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: column_name(I18n.t('status'), submitter_name, submitters_count),
|
||||||
|
value: submitter.status
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: column_name(I18n.t('completed_at'), submitter_name, submitters_count),
|
name: column_name(I18n.t('completed_at'), submitter_name, submitters_count),
|
||||||
value: submitter.completed_at.to_s
|
value: submitter.completed_at.to_s
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ module Submissions
|
|||||||
'Helvetica'
|
'Helvetica'
|
||||||
end
|
end
|
||||||
|
|
||||||
SIGN_REASON = 'Signed by %<name>s with DocuSeal.co'
|
SIGN_REASON = 'Signed by %<name>s with DocuSeal.com'
|
||||||
|
|
||||||
RTL_REGEXP = TextUtils::RTL_REGEXP
|
RTL_REGEXP = TextUtils::RTL_REGEXP
|
||||||
|
|
||||||
@@ -19,7 +19,6 @@ module Submissions
|
|||||||
MAX_PAGE_ROTATE = 20
|
MAX_PAGE_ROTATE = 20
|
||||||
|
|
||||||
A4_SIZE = [595, 842].freeze
|
A4_SIZE = [595, 842].freeze
|
||||||
SUPPORTED_IMAGE_TYPES = ['image/png', 'image/jpeg'].freeze
|
|
||||||
|
|
||||||
TESTING_FOOTER = 'Testing Document - NOT LEGALLY BINDING'
|
TESTING_FOOTER = 'Testing Document - NOT LEGALLY BINDING'
|
||||||
|
|
||||||
@@ -273,7 +272,15 @@ module Submissions
|
|||||||
|
|
||||||
attachments_data_cache[attachment.uuid] ||= attachment.download
|
attachments_data_cache[attachment.uuid] ||= attachment.download
|
||||||
|
|
||||||
image = Vips::Image.new_from_buffer(attachments_data_cache[attachment.uuid], '').autorot
|
image =
|
||||||
|
begin
|
||||||
|
Vips::Image.new_from_buffer(attachments_data_cache[attachment.uuid], '').autorot
|
||||||
|
rescue Vips::Error
|
||||||
|
next unless attachment.content_type.starts_with?('image/')
|
||||||
|
next if attachment.byte_size.zero?
|
||||||
|
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
|
||||||
scale = [(area['w'] * width) / image.width,
|
scale = [(area['w'] * width) / image.width,
|
||||||
(area['h'] * height) / image.height].min
|
(area['h'] * height) / image.height].min
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ module Submissions
|
|||||||
|
|
||||||
TimestampError = Class.new(StandardError)
|
TimestampError = Class.new(StandardError)
|
||||||
|
|
||||||
attr_reader :tsa_url
|
attr_reader :tsa_url, :tsa_fallback_url
|
||||||
|
|
||||||
def initialize(tsa_url:)
|
def initialize(tsa_url:)
|
||||||
@tsa_url = tsa_url
|
@tsa_url, @tsa_fallback_url = tsa_url.split(',')
|
||||||
end
|
end
|
||||||
|
|
||||||
def finalize_objects(_signature_field, signature)
|
def finalize_objects(_signature_field, signature)
|
||||||
@@ -37,7 +37,16 @@ module Submissions
|
|||||||
response = conn.post(uri.path, build_payload(digest.digest),
|
response = conn.post(uri.path, build_payload(digest.digest),
|
||||||
'content-type' => 'application/timestamp-query')
|
'content-type' => 'application/timestamp-query')
|
||||||
|
|
||||||
raise TimestampError if response.status != 200 || response.body.blank?
|
if response.status != 200 || response.body.blank?
|
||||||
|
raise TimestampError if tsa_fallback_url.blank?
|
||||||
|
|
||||||
|
Rollbar.error('TimestampError: use fallback URL') if defined?(Rollbar)
|
||||||
|
|
||||||
|
response = Faraday.post(tsa_fallback_url, build_payload(digest.digest),
|
||||||
|
'content-type' => 'application/timestamp-query')
|
||||||
|
|
||||||
|
raise TimestampError if response.status != 200 || response.body.blank?
|
||||||
|
end
|
||||||
|
|
||||||
OpenSSL::Timestamp::Response.new(response.body).token.to_der
|
OpenSSL::Timestamp::Response.new(response.body).token.to_der
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -14,14 +14,17 @@ module Submitters
|
|||||||
unless submitter.submission_events.exists?(event_type: 'start_form')
|
unless submitter.submission_events.exists?(event_type: 'start_form')
|
||||||
SubmissionEvents.create_with_tracking_data(submitter, 'start_form', request)
|
SubmissionEvents.create_with_tracking_data(submitter, 'start_form', request)
|
||||||
|
|
||||||
SendFormStartedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id })
|
WebhookUrls.for_account_id(submitter.account_id, 'form.started').each do |webhook_url|
|
||||||
|
SendFormStartedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
|
||||||
|
'webhook_url_id' => webhook_url.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
update_submitter!(submitter, params, request)
|
update_submitter!(submitter, params, request)
|
||||||
|
|
||||||
submitter.submission.save!
|
submitter.submission.save!
|
||||||
|
|
||||||
ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => submitter.id }) if submitter.completed_at?
|
ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id) if submitter.completed_at?
|
||||||
|
|
||||||
submitter
|
submitter
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -25,9 +25,15 @@ module Templates
|
|||||||
name: :preview_images)
|
name: :preview_images)
|
||||||
.preload(:blob)
|
.preload(:blob)
|
||||||
|
|
||||||
json[:documents] = template.schema.map do |item|
|
json[:documents] = template.schema.filter_map do |item|
|
||||||
attachment = schema_documents.find { |e| e.uuid == item['attachment_uuid'] }
|
attachment = schema_documents.find { |e| e.uuid == item['attachment_uuid'] }
|
||||||
|
|
||||||
|
unless attachment
|
||||||
|
Rollbar.error("Documents missing: #{template.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
first_page_blob = preview_image_attachments.find { |e| e.record_id == attachment.id }&.blob
|
first_page_blob = preview_image_attachments.find { |e| e.record_id == attachment.id }&.blob
|
||||||
first_page_blob ||= attachment.preview_images.joins(:blob).find_by(blob: { filename: ['0.jpg', '0.png'] })&.blob
|
first_page_blob ||= attachment.preview_images.joins(:blob).find_by(blob: { filename: ['0.jpg', '0.png'] })&.blob
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module WebhookUrls
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def for_account_id(account_id, events)
|
||||||
|
events = Array.wrap(events)
|
||||||
|
|
||||||
|
rel = WebhookUrl.where(account_id:)
|
||||||
|
|
||||||
|
event_arel = events.map { |event| Arel::Table.new(:webhook_urls)[:events].matches("%\"#{event}\"%") }.reduce(:or)
|
||||||
|
|
||||||
|
if Docuseal.multitenant?
|
||||||
|
rel.where(event_arel)
|
||||||
|
else
|
||||||
|
linked_account_rel =
|
||||||
|
AccountLinkedAccount.where(linked_account_id: account_id).where.not(account_type: :testing).select(:account_id)
|
||||||
|
|
||||||
|
webhook_urls = rel.or(WebhookUrl.where(account_id: linked_account_rel).where(event_arel))
|
||||||
|
|
||||||
|
account_urls, linked_urls = webhook_urls.partition { |w| w.account_id == account_id }
|
||||||
|
|
||||||
|
account_urls.select { |w| w.events.intersect?(events) }.presence ||
|
||||||
|
(account_urls.present? ? WebhookUrl.none : linked_urls)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
FactoryBot.define do
|
||||||
|
factory :webhook_url do
|
||||||
|
account
|
||||||
|
url { Faker::Internet.url }
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendFormCompletedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:submission) { create(:submission, template:, created_by_user: user) }
|
||||||
|
let(:submitter) do
|
||||||
|
create(:submitter, submission:, uuid: template.submitters.first['uuid'], completed_at: Time.current)
|
||||||
|
end
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['form.completed']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'form.completed',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submitters::SerializeForWebhook.call(submitter.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'form.completed',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submitters::SerializeForWebhook.call(submitter.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['form.declined'])
|
||||||
|
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['submitter_id']).to eq(submitter.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendFormDeclinedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:submission) { create(:submission, template:, created_by_user: user) }
|
||||||
|
let(:submitter) do
|
||||||
|
create(:submitter, submission:, uuid: template.submitters.first['uuid'], completed_at: Time.current)
|
||||||
|
end
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['form.declined']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'form.declined',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submitters::SerializeForWebhook.call(submitter.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'form.declined',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submitters::SerializeForWebhook.call(submitter.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['form.completed'])
|
||||||
|
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['submitter_id']).to eq(submitter.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendFormStartedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:submission) { create(:submission, template:, created_by_user: user) }
|
||||||
|
let(:submitter) do
|
||||||
|
create(:submitter, submission:, uuid: template.submitters.first['uuid'], completed_at: Time.current)
|
||||||
|
end
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['form.started']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'form.started',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submitters::SerializeForWebhook.call(submitter.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'form.started',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submitters::SerializeForWebhook.call(submitter.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['form.declined'])
|
||||||
|
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['submitter_id']).to eq(submitter.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendFormViewedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:submission) { create(:submission, template:, created_by_user: user) }
|
||||||
|
let(:submitter) do
|
||||||
|
create(:submitter, submission:, uuid: template.submitters.first['uuid'], completed_at: Time.current)
|
||||||
|
end
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['form.viewed']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'form.viewed',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submitters::SerializeForWebhook.call(submitter.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'form.viewed',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submitters::SerializeForWebhook.call(submitter.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['form.started'])
|
||||||
|
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['submitter_id']).to eq(submitter.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submitter_id' => submitter.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendSubmissionArchivedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:submission) { create(:submission, template:, created_by_user: user) }
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['submission.archived']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'submission.archived',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => submission.reload.as_json(only: %i[id archived_at])
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'submission.archived',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => submission.reload.as_json(only: %i[id archived_at])
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['submission.created'])
|
||||||
|
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['submission_id']).to eq(submission.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id,
|
||||||
|
'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendSubmissionCompletedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:submission) { create(:submission, :with_submitters, template:, created_by_user: user) }
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['submission.completed']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'submission.completed',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submissions::SerializeForApi.call(submission.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'submission.completed',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submissions::SerializeForApi.call(submission.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['submission.archived'])
|
||||||
|
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['submission_id']).to eq(submission.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id,
|
||||||
|
'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendSubmissionCreatedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:submission) { create(:submission, :with_submitters, template:, created_by_user: user) }
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['submission.created']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'submission.created',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submissions::SerializeForApi.call(submission.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'submission.created',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Submissions::SerializeForApi.call(submission.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['submission.completed'])
|
||||||
|
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['submission_id']).to eq(submission.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('submission_id' => submission.id, 'webhook_url_id' => webhook_url.id,
|
||||||
|
'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendTemplateCreatedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['template.created']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'template.created',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Templates::SerializeForApi.call(template.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'template.created',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Templates::SerializeForApi.call(template.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['template.updated'])
|
||||||
|
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['template_id']).to eq(template.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe SendTemplateUpdatedWebhookRequestJob do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:user) { create(:user, account:) }
|
||||||
|
let(:template) { create(:template, account:, author: user) }
|
||||||
|
let(:webhook_url) { create(:webhook_url, account:, events: ['template.updated']) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||||
|
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#perform' do
|
||||||
|
before do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request' do
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'template.updated',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Templates::SerializeForApi.call(template.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a webhook request with the secret' do
|
||||||
|
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||||
|
body: replace_timestamps({
|
||||||
|
'event_type' => 'template.updated',
|
||||||
|
'timestamp' => Time.current,
|
||||||
|
'data' => Templates::SerializeForApi.call(template.reload)
|
||||||
|
}.deep_stringify_keys),
|
||||||
|
headers: {
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'User-Agent' => 'DocuSeal.com Webhook',
|
||||||
|
'X-Secret-Header' => 'secret_value'
|
||||||
|
}
|
||||||
|
).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||||
|
webhook_url.update!(events: ['template.created'])
|
||||||
|
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends again if the response status is 400 or higher' do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id)
|
||||||
|
end.to change(described_class.jobs, :size).by(1)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
|
||||||
|
args = described_class.jobs.last['args'].first
|
||||||
|
|
||||||
|
expect(args['attempt']).to eq(1)
|
||||||
|
expect(args['last_status']).to eq(401)
|
||||||
|
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||||
|
expect(args['template_id']).to eq(template.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't send again if the max attempts is reached" do
|
||||||
|
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||||
|
|
||||||
|
expect do
|
||||||
|
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id, 'attempt' => 11)
|
||||||
|
end.not_to change(described_class.jobs, :size)
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -67,3 +67,19 @@ RSpec.configure do |config|
|
|||||||
Sidekiq::Testing.inline! if example.metadata[:sidekiq] == :inline
|
Sidekiq::Testing.inline! if example.metadata[:sidekiq] == :inline
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def replace_timestamps(data, replace = /.*/)
|
||||||
|
timestamp_fields = %w[created_at updated_at completed_at sent_at opened_at timestamp]
|
||||||
|
|
||||||
|
data.each do |key, value|
|
||||||
|
if timestamp_fields.include?(key) && (value.is_a?(String) || value.is_a?(Time))
|
||||||
|
data[key] = replace
|
||||||
|
elsif value.is_a?(Hash)
|
||||||
|
replace_timestamps(value)
|
||||||
|
elsif value.is_a?(Array)
|
||||||
|
value.each { |item| replace_timestamps(item) if item.is_a?(Hash) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
data
|
||||||
|
end
|
||||||
@@ -8,24 +8,110 @@ RSpec.describe 'Webhook Settings' do
|
|||||||
|
|
||||||
before do
|
before do
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
visit settings_webhooks_path
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'shows webhook settings page' do
|
it 'shows webhook settings page' do
|
||||||
|
visit settings_webhooks_path
|
||||||
|
|
||||||
expect(page).to have_content('Webhooks')
|
expect(page).to have_content('Webhooks')
|
||||||
expect(page).to have_field('Webhook URL')
|
expect(page).to have_field('Webhook URL')
|
||||||
expect(page).to have_button('Save')
|
expect(page).to have_button('Save')
|
||||||
|
|
||||||
|
WebhookUrl::EVENTS.each do |event|
|
||||||
|
expect(page).to have_field(event, type: 'checkbox', disabled: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'updates the webhook URL' do
|
it 'creates the webhook' do
|
||||||
fill_in 'Webhook URL', with: 'https://example.com'
|
visit settings_webhooks_path
|
||||||
|
|
||||||
|
fill_in 'Webhook URL', with: 'https://example.com/webhook'
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
click_button 'Save'
|
click_button 'Save'
|
||||||
end.to change(EncryptedConfig, :count).by(1)
|
end.to change(WebhookUrl, :count).by(1)
|
||||||
|
|
||||||
encrypted_config = EncryptedConfig.find_by(account:, key: EncryptedConfig::WEBHOOK_URL_KEY)
|
webhook_url = account.webhook_urls.first
|
||||||
|
|
||||||
expect(encrypted_config.value).to eq('https://example.com')
|
expect(webhook_url.url).to eq('https://example.com/webhook')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates the webhook' do
|
||||||
|
webhook_url = create(:webhook_url, account:, url: 'https://example.com/webhook')
|
||||||
|
|
||||||
|
visit settings_webhooks_path
|
||||||
|
|
||||||
|
fill_in 'Webhook URL', with: 'https://example.org/webhook'
|
||||||
|
click_button 'Save'
|
||||||
|
|
||||||
|
webhook_url.reload
|
||||||
|
|
||||||
|
expect(webhook_url.url).to eq('https://example.org/webhook')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'deletes the webhook' do
|
||||||
|
create(:webhook_url, account:)
|
||||||
|
|
||||||
|
visit settings_webhooks_path
|
||||||
|
|
||||||
|
fill_in 'Webhook URL', with: ''
|
||||||
|
|
||||||
|
expect do
|
||||||
|
click_button 'Save'
|
||||||
|
end.to change(WebhookUrl, :count).by(-1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates the webhook events' do
|
||||||
|
webhook_url = create(:webhook_url, account:)
|
||||||
|
|
||||||
|
visit settings_webhooks_path
|
||||||
|
|
||||||
|
expect(webhook_url.events).not_to include('submission.created')
|
||||||
|
|
||||||
|
check('submission.created')
|
||||||
|
|
||||||
|
webhook_url.reload
|
||||||
|
|
||||||
|
expect(webhook_url.events).to include('submission.created')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds a secret to the webhook' do
|
||||||
|
webhook_url = create(:webhook_url, account:)
|
||||||
|
|
||||||
|
visit settings_webhooks_path
|
||||||
|
|
||||||
|
expect(webhook_url.secret).to eq({})
|
||||||
|
|
||||||
|
click_link 'Add Secret'
|
||||||
|
|
||||||
|
within '#modal' do
|
||||||
|
fill_in 'Key', with: 'X-Signature'
|
||||||
|
fill_in 'Value', with: 'secret-value'
|
||||||
|
|
||||||
|
click_button 'Submit'
|
||||||
|
|
||||||
|
webhook_url.reload
|
||||||
|
|
||||||
|
expect(webhook_url.secret).to eq({ 'X-Signature' => 'secret-value' })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes a secret from the webhook' do
|
||||||
|
webhook_url = create(:webhook_url, account:, secret: { 'X-Signature' => 'secret-value' })
|
||||||
|
|
||||||
|
visit settings_webhooks_path
|
||||||
|
|
||||||
|
click_link 'Edit Secret'
|
||||||
|
|
||||||
|
within '#modal' do
|
||||||
|
fill_in 'Key', with: ''
|
||||||
|
fill_in 'Value', with: ''
|
||||||
|
|
||||||
|
click_button 'Submit'
|
||||||
|
|
||||||
|
webhook_url.reload
|
||||||
|
|
||||||
|
expect(webhook_url.secret).to eq({})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
Reference in new issue
Block a user