mirror of
https://github.com/docusealco/docuseal
synced 2026-11-05 14:42:33 -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 | ||
|
|
05d578d88f | ||
|
|
8773b30b10 | ||
|
|
1b636c5878 | ||
|
|
46e948ab99 | ||
|
|
889103aa5e | ||
|
|
558a14a5f2 | ||
|
|
72bbdb38bf | ||
|
|
3975a03cb4 | ||
|
|
e398327fb7 | ||
|
|
9266694d65 | ||
|
|
350c4b1c2a | ||
|
|
c3a933d87d | ||
|
|
970e53a5a3 | ||
|
|
520bac2ec7 | ||
|
|
e1eddd31c4 | ||
|
|
3c616a824c | ||
|
|
90d86b58c2 | ||
|
|
4332b4ebb8 | ||
|
|
e2f930f6f7 | ||
|
|
c303159c63 | ||
|
|
c1ce2bec00 | ||
|
|
3d2046f227 | ||
|
|
babaf1aa48 | ||
|
|
3f03d343ca | ||
|
|
110cdb5123 | ||
|
|
440d1114be | ||
|
|
9032517d4e | ||
|
|
7c71fefc47 | ||
|
|
e8bd8ff76f | ||
|
|
fed420cae1 | ||
|
|
910490528d | ||
|
|
ab810be250 | ||
|
|
178ee47093 | ||
|
|
6f90147a11 | ||
|
|
3ebaacbb7a | ||
|
|
64b39b53a2 | ||
|
|
2948910c25 | ||
|
|
1d20c79cf4 | ||
|
|
48f3b13f3e | ||
|
|
1fc1e9af16 | ||
|
|
ce70016603 | ||
|
|
e0e79e343d | ||
|
|
6568f81ee9 | ||
|
|
386f130d7a | ||
|
|
3ee41975fd |
No files matched your search
+1
-1
@@ -66,7 +66,7 @@ RSpec/ExampleLength:
|
|||||||
Max: 40
|
Max: 40
|
||||||
|
|
||||||
RSpec/MultipleMemoizedHelpers:
|
RSpec/MultipleMemoizedHelpers:
|
||||||
Max: 6
|
Max: 9
|
||||||
|
|
||||||
Metrics/BlockNesting:
|
Metrics/BlockNesting:
|
||||||
Max: 4
|
Max: 4
|
||||||
|
|||||||
+57
-57
@@ -1,29 +1,29 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (7.2.1)
|
actioncable (7.2.1.1)
|
||||||
actionpack (= 7.2.1)
|
actionpack (= 7.2.1.1)
|
||||||
activesupport (= 7.2.1)
|
activesupport (= 7.2.1.1)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
actionmailbox (7.2.1)
|
actionmailbox (7.2.1.1)
|
||||||
actionpack (= 7.2.1)
|
actionpack (= 7.2.1.1)
|
||||||
activejob (= 7.2.1)
|
activejob (= 7.2.1.1)
|
||||||
activerecord (= 7.2.1)
|
activerecord (= 7.2.1.1)
|
||||||
activestorage (= 7.2.1)
|
activestorage (= 7.2.1.1)
|
||||||
activesupport (= 7.2.1)
|
activesupport (= 7.2.1.1)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
actionmailer (7.2.1)
|
actionmailer (7.2.1.1)
|
||||||
actionpack (= 7.2.1)
|
actionpack (= 7.2.1.1)
|
||||||
actionview (= 7.2.1)
|
actionview (= 7.2.1.1)
|
||||||
activejob (= 7.2.1)
|
activejob (= 7.2.1.1)
|
||||||
activesupport (= 7.2.1)
|
activesupport (= 7.2.1.1)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
actionpack (7.2.1)
|
actionpack (7.2.1.1)
|
||||||
actionview (= 7.2.1)
|
actionview (= 7.2.1.1)
|
||||||
activesupport (= 7.2.1)
|
activesupport (= 7.2.1.1)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
racc
|
racc
|
||||||
rack (>= 2.2.4, < 3.2)
|
rack (>= 2.2.4, < 3.2)
|
||||||
@@ -32,35 +32,35 @@ GEM
|
|||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
useragent (~> 0.16)
|
useragent (~> 0.16)
|
||||||
actiontext (7.2.1)
|
actiontext (7.2.1.1)
|
||||||
actionpack (= 7.2.1)
|
actionpack (= 7.2.1.1)
|
||||||
activerecord (= 7.2.1)
|
activerecord (= 7.2.1.1)
|
||||||
activestorage (= 7.2.1)
|
activestorage (= 7.2.1.1)
|
||||||
activesupport (= 7.2.1)
|
activesupport (= 7.2.1.1)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (7.2.1)
|
actionview (7.2.1.1)
|
||||||
activesupport (= 7.2.1)
|
activesupport (= 7.2.1.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.11)
|
erubi (~> 1.11)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
activejob (7.2.1)
|
activejob (7.2.1.1)
|
||||||
activesupport (= 7.2.1)
|
activesupport (= 7.2.1.1)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (7.2.1)
|
activemodel (7.2.1.1)
|
||||||
activesupport (= 7.2.1)
|
activesupport (= 7.2.1.1)
|
||||||
activerecord (7.2.1)
|
activerecord (7.2.1.1)
|
||||||
activemodel (= 7.2.1)
|
activemodel (= 7.2.1.1)
|
||||||
activesupport (= 7.2.1)
|
activesupport (= 7.2.1.1)
|
||||||
timeout (>= 0.4.0)
|
timeout (>= 0.4.0)
|
||||||
activestorage (7.2.1)
|
activestorage (7.2.1.1)
|
||||||
actionpack (= 7.2.1)
|
actionpack (= 7.2.1.1)
|
||||||
activejob (= 7.2.1)
|
activejob (= 7.2.1.1)
|
||||||
activerecord (= 7.2.1)
|
activerecord (= 7.2.1.1)
|
||||||
activesupport (= 7.2.1)
|
activesupport (= 7.2.1.1)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
activesupport (7.2.1)
|
activesupport (7.2.1.1)
|
||||||
base64
|
base64
|
||||||
bigdecimal
|
bigdecimal
|
||||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||||
@@ -320,7 +320,7 @@ GEM
|
|||||||
mysql2 (0.5.6)
|
mysql2 (0.5.6)
|
||||||
net-http-persistent (4.0.4)
|
net-http-persistent (4.0.4)
|
||||||
connection_pool (~> 2.2)
|
connection_pool (~> 2.2)
|
||||||
net-imap (0.4.16)
|
net-imap (0.4.17)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
@@ -372,7 +372,7 @@ GEM
|
|||||||
puma (6.4.3)
|
puma (6.4.3)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (3.1.7)
|
rack (3.1.8)
|
||||||
rack-proxy (0.7.7)
|
rack-proxy (0.7.7)
|
||||||
rack
|
rack
|
||||||
rack-session (2.0.0)
|
rack-session (2.0.0)
|
||||||
@@ -382,20 +382,20 @@ GEM
|
|||||||
rackup (2.1.0)
|
rackup (2.1.0)
|
||||||
rack (>= 3)
|
rack (>= 3)
|
||||||
webrick (~> 1.8)
|
webrick (~> 1.8)
|
||||||
rails (7.2.1)
|
rails (7.2.1.1)
|
||||||
actioncable (= 7.2.1)
|
actioncable (= 7.2.1.1)
|
||||||
actionmailbox (= 7.2.1)
|
actionmailbox (= 7.2.1.1)
|
||||||
actionmailer (= 7.2.1)
|
actionmailer (= 7.2.1.1)
|
||||||
actionpack (= 7.2.1)
|
actionpack (= 7.2.1.1)
|
||||||
actiontext (= 7.2.1)
|
actiontext (= 7.2.1.1)
|
||||||
actionview (= 7.2.1)
|
actionview (= 7.2.1.1)
|
||||||
activejob (= 7.2.1)
|
activejob (= 7.2.1.1)
|
||||||
activemodel (= 7.2.1)
|
activemodel (= 7.2.1.1)
|
||||||
activerecord (= 7.2.1)
|
activerecord (= 7.2.1.1)
|
||||||
activestorage (= 7.2.1)
|
activestorage (= 7.2.1.1)
|
||||||
activesupport (= 7.2.1)
|
activesupport (= 7.2.1.1)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 7.2.1)
|
railties (= 7.2.1.1)
|
||||||
rails-dom-testing (2.2.0)
|
rails-dom-testing (2.2.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
@@ -410,9 +410,9 @@ GEM
|
|||||||
actionview (> 3.1)
|
actionview (> 3.1)
|
||||||
activesupport (> 3.1)
|
activesupport (> 3.1)
|
||||||
railties (> 3.1)
|
railties (> 3.1)
|
||||||
railties (7.2.1)
|
railties (7.2.1.1)
|
||||||
actionpack (= 7.2.1)
|
actionpack (= 7.2.1.1)
|
||||||
activesupport (= 7.2.1)
|
activesupport (= 7.2.1.1)
|
||||||
irb (~> 1.13)
|
irb (~> 1.13)
|
||||||
rackup (>= 1.0.0)
|
rackup (>= 1.0.0)
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
@@ -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)
|
||||||
@@ -559,7 +559,7 @@ GEM
|
|||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
zeitwerk (2.6.18)
|
zeitwerk (2.7.0)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
aarch64-linux
|
aarch64-linux
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -25,12 +25,12 @@
|
|||||||
DocuSeal is an open source platform that provides secure and efficient digital document signing and processing. Create PDF forms to have them filled and signed online on any device with an easy-to-use, mobile-optimized web tool.
|
DocuSeal is an open source platform that provides secure and efficient digital document signing and processing. Create PDF forms to have them filled and signed online on any device with an easy-to-use, mobile-optimized web tool.
|
||||||
</p>
|
</p>
|
||||||
<h2 align="center">
|
<h2 align="center">
|
||||||
<a href="https://demo.docuseal.co">✨ Live Demo</a>
|
<a href="https://demo.docuseal.tech">✨ Live Demo</a>
|
||||||
<span>|</span>
|
<span>|</span>
|
||||||
<a href="https://docuseal.co/sign_up">☁️ Try in Cloud</a>
|
<a href="https://docuseal.com/sign_up">☁️ Try in Cloud</a>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
[](https://demo.docuseal.co)
|
[](https://demo.docuseal.tech)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- PDF form fields builder (WYSIWYG)
|
- PDF form fields builder (WYSIWYG)
|
||||||
@@ -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)
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# Reporting a Vulnerability
|
# Reporting a Vulnerability
|
||||||
|
|
||||||
If you come across any security concern or vulnarability, please report the information via email to security@docuseal.co instead of opening a GitHub issue. We will promptly respond and will collaborate with you to validate the issue, and resolve it ASAP.
|
If you come across any security concern or vulnarability, please report the information via email to security@docuseal.com instead of opening a GitHub issue. We will promptly respond and will collaborate with you to validate the issue, and resolve it ASAP.
|
||||||
|
|
||||||
**We have a bug bounty program to reward security researchers.**
|
**We have a bug bounty program to reward security researchers.**
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class AccountConfigsController < ApplicationController
|
|||||||
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
||||||
AccountConfig::FORCE_MFA,
|
AccountConfig::FORCE_MFA,
|
||||||
AccountConfig::ALLOW_TO_RESUBMIT,
|
AccountConfig::ALLOW_TO_RESUBMIT,
|
||||||
|
AccountConfig::ALLOW_TO_DECLINE_KEY,
|
||||||
AccountConfig::FORM_PREFILL_SIGNATURE_KEY,
|
AccountConfig::FORM_PREFILL_SIGNATURE_KEY,
|
||||||
AccountConfig::ESIGNING_PREFERENCE_KEY,
|
AccountConfig::ESIGNING_PREFERENCE_KEY,
|
||||||
AccountConfig::FORM_WITH_CONFETTI_KEY,
|
AccountConfig::FORM_WITH_CONFETTI_KEY,
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ module Api
|
|||||||
render json: { error: 'Too many requests' }, status: :too_many_requests
|
render json: { error: 'Too many requests' }, status: :too_many_requests
|
||||||
end
|
end
|
||||||
|
|
||||||
if Rails.env.production?
|
unless Rails.env.development?
|
||||||
rescue_from CanCan::AccessDenied do |e|
|
rescue_from CanCan::AccessDenied do |e|
|
||||||
render json: { error: e.message }, status: :forbidden
|
render json: { error: access_denied_error_message(e) }, status: :forbidden
|
||||||
end
|
end
|
||||||
|
|
||||||
rescue_from JSON::ParserError do |e|
|
rescue_from JSON::ParserError do |e|
|
||||||
@@ -39,6 +39,33 @@ module Api
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def access_denied_error_message(error)
|
||||||
|
return 'Not authorized' if request.headers['X-Auth-Token'].blank?
|
||||||
|
return 'Not authorized' unless error.subject.is_a?(ActiveRecord::Base)
|
||||||
|
return 'Not authorized' unless error.subject.respond_to?(:account_id)
|
||||||
|
|
||||||
|
linked_account_record_exists =
|
||||||
|
if current_user.account.testing?
|
||||||
|
current_user.account.linked_account_accounts.where(account_type: 'testing')
|
||||||
|
.exists?(account_id: error.subject.account_id)
|
||||||
|
else
|
||||||
|
current_user.account.testing_accounts.exists?(id: error.subject.account_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
return 'Not authorized' unless linked_account_record_exists
|
||||||
|
|
||||||
|
object_name = error.subject.model_name.human
|
||||||
|
id = error.subject.id
|
||||||
|
|
||||||
|
if current_user.account.testing?
|
||||||
|
"#{object_name} #{id} not found using testing API key; Use production API key to " \
|
||||||
|
"access production #{object_name.downcase.pluralize}."
|
||||||
|
else
|
||||||
|
"#{object_name} #{id} not found using production API key; Use testing API key to " \
|
||||||
|
"access testing #{object_name.downcase.pluralize}."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def paginate(relation, field: :id)
|
def paginate(relation, field: :id)
|
||||||
result = relation.order(field => :desc)
|
result = relation.order(field => :desc)
|
||||||
.limit([params.fetch(:limit, DEFAULT_LIMIT).to_i, MAX_LIMIT].min)
|
.limit([params.fetch(:limit, DEFAULT_LIMIT).to_i, MAX_LIMIT].min)
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Api
|
||||||
|
class SubmissionDocumentsController < ApiBaseController
|
||||||
|
load_and_authorize_resource :submission
|
||||||
|
|
||||||
|
def index
|
||||||
|
documents =
|
||||||
|
if @submission.submitters.all?(&:completed_at?)
|
||||||
|
last_submitter = @submission.submitters.max_by(&:completed_at)
|
||||||
|
|
||||||
|
if last_submitter.documents_attachments.blank?
|
||||||
|
last_submitter.documents_attachments = Submissions::EnsureResultGenerated.call(submitter)
|
||||||
|
end
|
||||||
|
|
||||||
|
last_submitter.documents_attachments
|
||||||
|
else
|
||||||
|
values_hash = Submissions::GeneratePreviewAttachments.build_values_hash(@submission)
|
||||||
|
|
||||||
|
if @submission.preview_documents.present? &&
|
||||||
|
@submission.preview_documents.all? { |s| s.metadata['values_hash'] == values_hash }
|
||||||
|
@submission.preview_documents
|
||||||
|
else
|
||||||
|
ApplicationRecord.no_touching do
|
||||||
|
@submission.preview_documents.each(&:destroy)
|
||||||
|
end
|
||||||
|
|
||||||
|
Submissions::GeneratePreviewAttachments.call(@submission, values_hash:)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
ActiveRecord::Associations::Preloader.new(
|
||||||
|
records: documents,
|
||||||
|
associations: [:blob]
|
||||||
|
).call
|
||||||
|
|
||||||
|
render json: {
|
||||||
|
id: @submission.id,
|
||||||
|
documents: documents.map do |attachment|
|
||||||
|
{ name: attachment.filename.base, url: ActiveStorage::Blob.proxy_url(attachment.blob) }
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -67,20 +67,24 @@ 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|
|
||||||
if submission.submitters.all?(&:completed_at?) && submission.submitters.last
|
submission.submitters.each do |submitter|
|
||||||
ProcessSubmitterCompletionJob.perform_async({ 'submitter_id' => submission.submitters.last.id })
|
ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id) if submitter.completed_at?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: build_create_json(submissions)
|
render json: build_create_json(submissions)
|
||||||
rescue Submitters::NormalizeValues::BaseError, DownloadUtils::UnableToDownload => e
|
rescue Submitters::NormalizeValues::BaseError, Submissions::CreateFromSubmitters::BaseError,
|
||||||
|
DownloadUtils::UnableToDownload => e
|
||||||
Rollbar.warning(e) if defined?(Rollbar)
|
Rollbar.warning(e) if defined?(Rollbar)
|
||||||
|
|
||||||
render json: { error: e.message }, status: :unprocessable_entity
|
render json: { error: e.message }, status: :unprocessable_entity
|
||||||
@@ -92,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
|
||||||
@@ -120,6 +120,7 @@ module Api
|
|||||||
end&.dig('uuid')
|
end&.dig('uuid')
|
||||||
|
|
||||||
submitter.email = Submissions.normalize_email(attrs[:email]) if attrs.key?(:email)
|
submitter.email = Submissions.normalize_email(attrs[:email]) if attrs.key?(:email)
|
||||||
|
submitter.name = attrs[:name] if attrs.key?(:name)
|
||||||
|
|
||||||
if attrs.key?(:phone)
|
if attrs.key?(:phone)
|
||||||
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '')
|
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '')
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -20,10 +20,7 @@ module Api
|
|||||||
pdf = HexaPDF::Document.new(io: StringIO.new(file))
|
pdf = HexaPDF::Document.new(io: StringIO.new(file))
|
||||||
|
|
||||||
trusted_certs = Accounts.load_trusted_certs(current_account)
|
trusted_certs = Accounts.load_trusted_certs(current_account)
|
||||||
|
is_checksum_found = CompletedDocument.exists?(sha256: Base64.urlsafe_encode64(Digest::SHA256.digest(file)))
|
||||||
is_checksum_found = ActiveStorage::Attachment.joins(:blob)
|
|
||||||
.where(name: 'documents', record_type: 'Submitter')
|
|
||||||
.exists?(blob: { checksum: Digest::MD5.base64digest(file) })
|
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
checksum_status: is_checksum_found ? 'verified' : 'not_found',
|
checksum_status: is_checksum_found ? 'verified' : 'not_found',
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ class ApplicationController < ActionController::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def default_url_options
|
def default_url_options
|
||||||
|
if request.domain == 'docuseal.com'
|
||||||
|
return { host: 'docuseal.com', protocol: ENV['FORCE_SSL'].present? ? 'https' : 'http' }
|
||||||
|
end
|
||||||
|
|
||||||
Docuseal.default_url_options
|
Docuseal.default_url_options
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -100,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
|
||||||
@@ -9,7 +9,7 @@ class EmailSmtpSettingsController < ApplicationController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
if @encrypted_config.update(email_configs)
|
if @encrypted_config.update(email_configs)
|
||||||
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email']).deliver_now!
|
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email'] || current_user.email).deliver_now!
|
||||||
|
|
||||||
redirect_to settings_email_index_path, notice: I18n.t('changes_have_been_saved')
|
redirect_to settings_email_index_path, notice: I18n.t('changes_have_been_saved')
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -9,9 +9,11 @@ class UsersController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
@users =
|
@users =
|
||||||
if params[:status] == 'archived'
|
if params[:status] == 'archived'
|
||||||
@users.archived
|
@users.archived.where.not(role: 'integration')
|
||||||
|
elsif params[:status] == 'integration'
|
||||||
|
@users.active.where(role: 'integration')
|
||||||
else
|
else
|
||||||
@users.active
|
@users.active.where.not(role: 'integration')
|
||||||
end
|
end
|
||||||
|
|
||||||
@pagy, @users = pagy(@users.where(account: current_account).order(id: :desc))
|
@pagy, @users = pagy(@users.where(account: current_account).order(id: :desc))
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -29,6 +29,7 @@ import SearchInput from './elements/search_input'
|
|||||||
import ToggleAttribute from './elements/toggle_attribute'
|
import ToggleAttribute from './elements/toggle_attribute'
|
||||||
import LinkedInput from './elements/linked_input'
|
import LinkedInput from './elements/linked_input'
|
||||||
import CheckboxGroup from './elements/checkbox_group'
|
import CheckboxGroup from './elements/checkbox_group'
|
||||||
|
import MaskedInput from './elements/masked_input'
|
||||||
|
|
||||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||||
|
|
||||||
@@ -95,6 +96,7 @@ safeRegisterElement('search-input', SearchInput)
|
|||||||
safeRegisterElement('toggle-attribute', ToggleAttribute)
|
safeRegisterElement('toggle-attribute', ToggleAttribute)
|
||||||
safeRegisterElement('linked-input', LinkedInput)
|
safeRegisterElement('linked-input', LinkedInput)
|
||||||
safeRegisterElement('checkbox-group', CheckboxGroup)
|
safeRegisterElement('checkbox-group', CheckboxGroup)
|
||||||
|
safeRegisterElement('masked-input', MaskedInput)
|
||||||
|
|
||||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
const maskedToken = this.input.value
|
||||||
|
|
||||||
|
this.input.addEventListener('focus', () => {
|
||||||
|
this.input.value = this.dataset.token
|
||||||
|
this.input.select()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.input.addEventListener('focusout', () => {
|
||||||
|
this.input.value = maskedToken
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get input () {
|
||||||
|
return this.querySelector('input')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -405,7 +405,8 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.field.preferences?.font_size) {
|
if (this.field.preferences?.font_size) {
|
||||||
style.fontSize = this.field.preferences.font_size + 'pt'
|
style.fontSize = `clamp(4pt, 1.6vw, ${this.field.preferences.font_size}pt)`
|
||||||
|
style.lineHeight = `clamp(6pt, 2.0vw, ${parseInt(this.field.preferences.font_size) + 3}pt)`
|
||||||
}
|
}
|
||||||
|
|
||||||
return style
|
return style
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
v-if="isDemo"
|
v-if="isDemo"
|
||||||
href="https://docuseal.co/sign_up"
|
href="https://docuseal.com/sign_up"
|
||||||
class="white-button flex items-center space-x-1 w-full"
|
class="white-button flex items-center space-x-1 w-full"
|
||||||
>
|
>
|
||||||
<IconLogin />
|
<IconLogin />
|
||||||
@@ -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') }}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@
|
|||||||
:name="`values[${field.uuid}]`"
|
:name="`values[${field.uuid}]`"
|
||||||
@keydown.enter="onEnter"
|
@keydown.enter="onEnter"
|
||||||
@focus="$emit('focus')"
|
@focus="$emit('focus')"
|
||||||
|
@paste="onPaste"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,6 +99,25 @@ export default {
|
|||||||
this.$emit('submit')
|
this.$emit('submit')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onPaste (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
|
let pasteData = e.clipboardData.getData('text').trim()
|
||||||
|
|
||||||
|
if (pasteData.match(/^\d{2}\.\d{2}\.\d{4}$/)) {
|
||||||
|
pasteData = pasteData.split('.').reverse().join('-')
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedDate = new Date(pasteData)
|
||||||
|
|
||||||
|
if (!isNaN(parsedDate)) {
|
||||||
|
const inputEl = this.$refs.input
|
||||||
|
|
||||||
|
inputEl.valueAsDate = new Date(parsedDate.getTime() - parsedDate.getTimezoneOffset() * 60000)
|
||||||
|
|
||||||
|
inputEl.dispatchEvent(new Event('input', { bubbles: true }))
|
||||||
|
}
|
||||||
|
},
|
||||||
setCurrentDate () {
|
setCurrentDate () {
|
||||||
const inputEl = this.$refs.input
|
const inputEl = this.$refs.input
|
||||||
|
|
||||||
|
|||||||
@@ -1107,6 +1107,10 @@ export default {
|
|||||||
const currentFieldUuids = this.currentStepFields.map((f) => f.uuid)
|
const currentFieldUuids = this.currentStepFields.map((f) => f.uuid)
|
||||||
const currentFieldType = this.currentField.type
|
const currentFieldType = this.currentField.type
|
||||||
|
|
||||||
|
if (!formData && !this.$refs.form.checkValidity()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (this.dryRun) {
|
if (this.dryRun) {
|
||||||
currentFieldUuids.forEach((fieldUuid) => {
|
currentFieldUuids.forEach((fieldUuid) => {
|
||||||
this.submittedValues[fieldUuid] = this.values[fieldUuid]
|
this.submittedValues[fieldUuid] = this.values[fieldUuid]
|
||||||
|
|||||||
@@ -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 () {
|
||||||
@@ -693,6 +700,7 @@ export default {
|
|||||||
'de-DE': 'DD.MM.YYYY',
|
'de-DE': 'DD.MM.YYYY',
|
||||||
'fr-FR': 'DD/MM/YYYY',
|
'fr-FR': 'DD/MM/YYYY',
|
||||||
'it-IT': 'DD/MM/YYYY',
|
'it-IT': 'DD/MM/YYYY',
|
||||||
|
'en-GB': 'DD/MM/YYYY',
|
||||||
'es-ES': 'DD/MM/YYYY'
|
'es-ES': 'DD/MM/YYYY'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -748,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>
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<select
|
<select
|
||||||
v-if="conditionField(condition)?.options?.length"
|
v-if="['radio', 'select', 'multiple'].includes(conditionField(condition)?.type) && conditionField(condition)?.options"
|
||||||
class="select select-bordered select-sm w-full bg-white h-11 pl-4 text-base font-normal"
|
class="select select-bordered select-sm w-full bg-white h-11 pl-4 text-base font-normal"
|
||||||
:class="{ 'text-gray-300': !condition.value }"
|
:class="{ 'text-gray-300': !condition.value }"
|
||||||
required
|
required
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ class ProcessSubmitterCompletionJob
|
|||||||
def perform(params = {})
|
def perform(params = {})
|
||||||
submitter = Submitter.find(params['submitter_id'])
|
submitter = Submitter.find(params['submitter_id'])
|
||||||
|
|
||||||
|
create_completed_submitter!(submitter)
|
||||||
|
|
||||||
is_all_completed = !submitter.submission.submitters.exists?(completed_at: nil)
|
is_all_completed = !submitter.submission.submitters.exists?(completed_at: nil)
|
||||||
|
|
||||||
if !is_all_completed && submitter.submission.submitters_order_preserved?
|
if !is_all_completed && submitter.submission.submitters_order_preserved?
|
||||||
@@ -24,36 +26,52 @@ class ProcessSubmitterCompletionJob
|
|||||||
enqueue_completed_emails(submitter)
|
enqueue_completed_emails(submitter)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_completed_documents!(submitter)
|
||||||
|
|
||||||
enqueue_completed_webhooks(submitter, is_all_completed:)
|
enqueue_completed_webhooks(submitter, is_all_completed:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def enqueue_completed_webhooks(submitter, is_all_completed: false)
|
def create_completed_submitter!(submitter)
|
||||||
webhook_config = Accounts.load_webhook_config(submitter.account)
|
completed_submitter = CompletedSubmitter.find_or_initialize_by(submitter_id: submitter.id)
|
||||||
|
|
||||||
if webhook_config
|
return completed_submitter if completed_submitter.persisted?
|
||||||
SendFormCompletedWebhookRequestJob.perform_async({ 'submitter_id' => submitter.id,
|
|
||||||
'encrypted_config_id' => webhook_config.id })
|
|
||||||
end
|
|
||||||
|
|
||||||
webhook_urls = submitter.account.webhook_urls
|
submission = submitter.submission
|
||||||
|
|
||||||
webhook_urls = webhook_urls.where(
|
completed_submitter.assign_attributes(
|
||||||
Arel::Table.new(:webhook_urls)[:events].matches('%"form.completed"%')
|
submission_id: submitter.submission_id,
|
||||||
).or(
|
account_id: submission.account_id,
|
||||||
webhook_urls.where(
|
template_id: submission.template_id,
|
||||||
Arel::Table.new(:webhook_urls)[:events].matches('%"submission.completed"%')
|
source: submission.source,
|
||||||
)
|
sms_count: submitter.submission_events.where(event_type: %w[send_sms send_2fa_sms]).count,
|
||||||
|
completed_at: submitter.completed_at
|
||||||
)
|
)
|
||||||
|
|
||||||
webhook_urls.each do |webhook|
|
completed_submitter.save!
|
||||||
|
|
||||||
|
completed_submitter
|
||||||
|
rescue ActiveRecord::RecordNotUnique
|
||||||
|
retry
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_completed_documents!(submitter)
|
||||||
|
submitter.documents.filter_map do |attachment|
|
||||||
|
next if attachment.metadata['sha256'].blank?
|
||||||
|
|
||||||
|
CompletedDocument.find_or_create_by!(sha256: attachment.metadata['sha256'], submitter_id: submitter.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def enqueue_completed_webhooks(submitter, is_all_completed: false)
|
||||||
|
WebhookUrls.for_account_id(submitter.account_id, %w[form.completed submission.completed]).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
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ApplicationMailer < ActionMailer::Base
|
class ApplicationMailer < ActionMailer::Base
|
||||||
default from: 'DocuSeal <info@docuseal.co>'
|
default from: 'DocuSeal <info@docuseal.com>'
|
||||||
layout 'mailer'
|
layout 'mailer'
|
||||||
|
|
||||||
register_interceptor ActionMailerConfigsInterceptor
|
register_interceptor ActionMailerConfigsInterceptor
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
class SettingsMailer < ApplicationMailer
|
class SettingsMailer < ApplicationMailer
|
||||||
def smtp_successful_setup(email)
|
def smtp_successful_setup(email)
|
||||||
mail(to: email, subject: 'SMTP has been configured')
|
mail(to: email, from: email, subject: 'SMTP has been configured')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -28,13 +28,13 @@ class AccountConfig < ApplicationRecord
|
|||||||
FORCE_MFA = 'force_mfa'
|
FORCE_MFA = 'force_mfa'
|
||||||
ALLOW_TYPED_SIGNATURE = 'allow_typed_signature'
|
ALLOW_TYPED_SIGNATURE = 'allow_typed_signature'
|
||||||
ALLOW_TO_RESUBMIT = 'allow_to_resubmit'
|
ALLOW_TO_RESUBMIT = 'allow_to_resubmit'
|
||||||
|
ALLOW_TO_DECLINE_KEY = 'allow_to_decline'
|
||||||
SUBMITTER_REMINDERS = 'submitter_reminders'
|
SUBMITTER_REMINDERS = 'submitter_reminders'
|
||||||
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
||||||
FORM_COMPLETED_MESSAGE_KEY = 'form_completed_message'
|
FORM_COMPLETED_MESSAGE_KEY = 'form_completed_message'
|
||||||
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'
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: completed_documents
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# sha256 :string not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# submitter_id :bigint not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_completed_documents_on_sha256 (sha256)
|
||||||
|
# index_completed_documents_on_submitter_id (submitter_id)
|
||||||
|
#
|
||||||
|
class CompletedDocument < ApplicationRecord
|
||||||
|
belongs_to :submitter, optional: true
|
||||||
|
|
||||||
|
has_one :completed_submitter, primary_key: :submitter_id, inverse_of: :completed_documents, dependent: :destroy
|
||||||
|
end
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: completed_submitters
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# completed_at :datetime not null
|
||||||
|
# sms_count :integer not null
|
||||||
|
# source :string not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# account_id :bigint not null
|
||||||
|
# submission_id :bigint not null
|
||||||
|
# submitter_id :bigint not null
|
||||||
|
# template_id :bigint not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_completed_submitters_on_account_id (account_id)
|
||||||
|
# index_completed_submitters_on_submitter_id (submitter_id) UNIQUE
|
||||||
|
#
|
||||||
|
class CompletedSubmitter < ApplicationRecord
|
||||||
|
belongs_to :submitter
|
||||||
|
belongs_to :submission
|
||||||
|
belongs_to :account
|
||||||
|
belongs_to :template
|
||||||
|
|
||||||
|
has_many :completed_documents, dependent: :destroy,
|
||||||
|
primary_key: :submitter_id,
|
||||||
|
foreign_key: :submitter_id,
|
||||||
|
inverse_of: :submitter
|
||||||
|
end
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ class Submission < ApplicationRecord
|
|||||||
has_one_attached :audit_trail
|
has_one_attached :audit_trail
|
||||||
has_one_attached :combined_document
|
has_one_attached :combined_document
|
||||||
|
|
||||||
|
has_many_attached :preview_documents
|
||||||
|
|
||||||
has_many :template_schema_documents,
|
has_many :template_schema_documents,
|
||||||
->(e) { where(uuid: (e.template_schema.presence || e.template.schema).pluck('attachment_uuid')) },
|
->(e) { where(uuid: (e.template_schema.presence || e.template.schema).pluck('attachment_uuid')) },
|
||||||
through: :template, source: :documents_attachments
|
through: :template, source: :documents_attachments
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ class User < ApplicationRecord
|
|||||||
|
|
||||||
EMAIL_REGEXP = /[^@;,<>\s]+@[^@;,<>\s]+/
|
EMAIL_REGEXP = /[^@;,<>\s]+@[^@;,<>\s]+/
|
||||||
|
|
||||||
|
FULL_EMAIL_REGEXP =
|
||||||
|
/\A[a-z0-9][\.']?(?:(?:[a-z0-9_-]+[\.\+'])*[a-z0-9_-]+)*@(?:[a-z0-9]+[\.-])*[a-z0-9]+\.[a-z]{2,}\z/i
|
||||||
|
|
||||||
has_one_attached :signature
|
has_one_attached :signature
|
||||||
has_one_attached :initials
|
has_one_attached :initials
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -103,6 +103,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TO_DECLINE_KEY) %>
|
||||||
|
<% if can?(:manage, account_config) %>
|
||||||
|
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||||
|
<%= f.hidden_field :key %>
|
||||||
|
<div class="flex items-center justify-between py-2.5">
|
||||||
|
<span>
|
||||||
|
<%= t('allow_to_decline_documents') %>
|
||||||
|
</span>
|
||||||
|
<%= f.check_box :value, class: 'toggle', checked: account_config.value != false, onchange: 'this.form.requestSubmit()' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::FORM_PREFILL_SIGNATURE_KEY) %>
|
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::FORM_PREFILL_SIGNATURE_KEY) %>
|
||||||
<% if can?(:manage, account_config) %>
|
<% if can?(:manage, account_config) %>
|
||||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||||
|
|||||||
@@ -10,8 +10,11 @@
|
|||||||
<label for="api_key" class="text-sm font-semibold">X-Auth-Token</label>
|
<label for="api_key" class="text-sm font-semibold">X-Auth-Token</label>
|
||||||
<div class="flex flex-col md:flex-row gap-4">
|
<div class="flex flex-col md:flex-row gap-4">
|
||||||
<div class="flex w-full space-x-4">
|
<div class="flex w-full space-x-4">
|
||||||
<input id="api_key" type="text" value="<%= current_user.access_token.token %>" class="input font-mono input-bordered w-full" autocomplete="off" readonly>
|
<% token = current_user.access_token.token %>
|
||||||
<%= 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') %>
|
<masked-input class="block w-full" data-token="<%= token %>">
|
||||||
|
<input id="api_key" type="text" value="<%= token.sub(token[5..], '*' * token[5..].size) %>" class="input font-mono input-bordered w-full" autocomplete="off" readonly>
|
||||||
|
</masked-input>
|
||||||
|
<%= render 'shared/clipboard_copy', icon: 'copy', text: token, class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: t('copy'), copied_title: t('copied') %>
|
||||||
</div>
|
</div>
|
||||||
<%= button_to button_title(title: t('rotate'), disabled_with: t('rotate'), icon: svg_icon('reload', class: 'w-6 h-6')), settings_api_index_path, class: 'white-button w-full', data: { turbo_confirm: t('remove_existing_api_token_and_generated_a_new_one_are_you_sure_') } %>
|
<%= button_to button_title(title: t('rotate'), disabled_with: t('rotate'), icon: svg_icon('reload', class: 'w-6 h-6')), settings_api_index_path, class: 'white-button w-full', data: { turbo_confirm: t('remove_existing_api_token_and_generated_a_new_one_are_you_sure_') } %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="join">
|
<div class="join">
|
||||||
<a href="https://docuseal.co<%= request.fullpath.gsub('docuseal.eu', 'docuseal.co') %>" class="btn bg-base-200 join-item w-32 <%= 'bg-base-300' if request.host == 'docuseal.co' %>">
|
<a href="https://docuseal.com<%= request.fullpath.gsub('docuseal.eu', 'docuseal.com') %>" class="btn bg-base-200 join-item w-32 <%= 'bg-base-300' if request.host == 'docuseal.com' %>">
|
||||||
<%= svg_icon 'world', class: 'w-5 h-5' %>
|
<%= svg_icon 'world', class: 'w-5 h-5' %>
|
||||||
Global
|
Global
|
||||||
</a>
|
</a>
|
||||||
<a href="https://docuseal.eu<%= request.fullpath.gsub('docuseal.co', 'docuseal.eu') %>" class="btn bg-base-200 join-item w-32 <%= 'bg-base-300' if request.host == 'docuseal.eu' %>">
|
<a href="https://docuseal.eu<%= request.fullpath.gsub('docuseal.com', 'docuseal.eu') %>" class="btn bg-base-200 join-item w-32 <%= 'bg-base-300' if request.host == 'docuseal.eu' %>">
|
||||||
<%= svg_icon 'eu_flag', class: 'w-5 h-5' %>
|
<%= svg_icon 'eu_flag', class: 'w-5 h-5' %>
|
||||||
Europe
|
Europe
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :from_email, t('send_from_email'), class: 'label' %>
|
<%= ff.label :from_email, t('send_from_email'), class: 'label' %>
|
||||||
<%= ff.email_field :from_email, value: value['from_email'], required: true, class: 'base-input' %>
|
<%= ff.email_field :from_email, value: value['from_email'], required: !Docuseal.multitenant?, class: 'base-input' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="form-control pt-2">
|
<div class="form-control pt-2">
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<% if signed_in? %>
|
<% if signed_in? %>
|
||||||
<div class="space-x-4 flex items-center">
|
<div class="space-x-4 flex items-center">
|
||||||
<% if Docuseal.demo? %>
|
<% if Docuseal.demo? %>
|
||||||
<a href="https://docuseal.co/sign_up" class="btn btn-neutral btn-sm btn-outline inline-flex items-center justify-center" style="height: 37px">
|
<a href="https://docuseal.com/sign_up" class="btn btn-neutral btn-sm btn-outline inline-flex items-center justify-center" style="height: 37px">
|
||||||
<%= t('sign_up') %>
|
<%= t('sign_up') %>
|
||||||
</a>
|
</a>
|
||||||
<span class="hidden sm:inline">
|
<span class="hidden sm:inline">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="text-center px-2">
|
<div class="text-center px-2">
|
||||||
<% if local_assigns[:with_counter] %>
|
<% if local_assigns[:with_counter] %>
|
||||||
<% count = Submitter.where.not(completed_at: nil).distinct.count(:submission_id) %>
|
<% count = CompletedSubmitter.distinct.count(:submission_id) %>
|
||||||
<% if count > 1 %>
|
<% if count > 1 %>
|
||||||
<%= t('count_documents_signed_with_html', count:) %>
|
<%= t('count_documents_signed_with_html', count:) %>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<% align = field.dig('preferences', 'align') %>
|
<% align = field.dig('preferences', 'align') %>
|
||||||
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-serif' if field.dig('preferences', 'font') == 'Times' %> <%= align == 'right' ? 'justify-end' : (align == 'center' ? 'justify-center' : '') %>" style="width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: #{field['preferences']['font_size']}pt" if field.dig('preferences', 'font_size') %>">
|
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-serif' if field.dig('preferences', 'font') == 'Times' %> <%= align == 'right' ? 'justify-end' : (align == 'center' ? 'justify-center' : '') %>" style="width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size']}pt); line-height: `clamp(6pt, 2.0vw, #{field['preferences']['font_size'].to_i + 3}pt)`" if field.dig('preferences', 'font_size') %>">
|
||||||
<% if field['type'] == 'signature' %>
|
<% if field['type'] == 'signature' %>
|
||||||
<div class="flex flex-col justify-between h-full overflow-hidden">
|
<div class="flex flex-col justify-between h-full overflow-hidden">
|
||||||
<div class="flex-grow flex overflow-hidden" style="min-height: 50%">
|
<div class="flex-grow flex overflow-hidden" style="min-height: 50%">
|
||||||
|
|||||||
@@ -9,50 +9,56 @@
|
|||||||
<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 %>
|
||||||
<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 %>">
|
||||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||||
<%= svg_icon('download', class: 'w-6 h-6') %>
|
<%= svg_icon('download', class: 'w-6 h-6') %>
|
||||||
<span class="hidden md:inline"><%= t('download') %></span>
|
<span class="hidden md:inline"><%= t('download') %></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
||||||
<%= svg_icon('loader', class: 'w-6 h-6 animate-spin') %>
|
<%= svg_icon('loader', class: 'w-6 h-6 animate-spin') %>
|
||||||
<span class="hidden md:inline"><%= t('downloading') %></span>
|
<span class="hidden md:inline"><%= t('downloading') %></span>
|
||||||
</span>
|
</span>
|
||||||
</download-button>
|
</download-button>
|
||||||
<% if is_all_completed && !is_combined_enabled %>
|
<% if is_all_completed && !is_combined_enabled %>
|
||||||
<div class="dropdown dropdown-end">
|
<div class="dropdown dropdown-end">
|
||||||
<label tabindex="0" class="base-button !rounded-l-none !pl-1 !pr-2 !border-l-neutral-500">
|
<label tabindex="0" class="base-button !rounded-l-none !pl-1 !pr-2 !border-l-neutral-500">
|
||||||
<span class="text-sm align-text-top">
|
<span class="text-sm align-text-top">
|
||||||
<%= svg_icon('chevron_down', class: 'w-6 h-6 flex-shrink-0 stroke-2') %>
|
<%= svg_icon('chevron_down', class: 'w-6 h-6 flex-shrink-0 stroke-2') %>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<ul tabindex="0" class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box text-right">
|
<ul tabindex="0" class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box text-right">
|
||||||
<li>
|
<li>
|
||||||
<download-button data-src="<%= submitter_download_index_path(last_submitter.slug, { sig: params[:sig], combined: true }.compact) %>" class="flex items-center">
|
<download-button data-src="<%= submitter_download_index_path(last_submitter.slug, { sig: params[:sig], combined: true }.compact) %>" class="flex items-center">
|
||||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||||
<%= svg_icon('download', class: 'w-6 h-6 flex-shrink-0') %>
|
<%= svg_icon('download', class: 'w-6 h-6 flex-shrink-0') %>
|
||||||
<span class="whitespace-nowrap"><%= t('download_combined_pdf') %></span>
|
<span class="whitespace-nowrap"><%= t('download_combined_pdf') %></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
||||||
<%= svg_icon('loader', class: 'w-6 h-6 animate-spin') %>
|
<%= svg_icon('loader', class: 'w-6 h-6 animate-spin') %>
|
||||||
<span><%= t('downloading') %></span>
|
<span><%= t('downloading') %></span>
|
||||||
</span>
|
</span>
|
||||||
</download-button>
|
</download-button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% elsif @submission.submitters.to_a.size == 1 && !@submission.expired? && !@submission.submitters.to_a.first.declined_at? %>
|
<% end %>
|
||||||
|
<% 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>
|
||||||
|
|||||||
@@ -15,11 +15,13 @@
|
|||||||
<%= @submitter.submission.template.name %>
|
<%= @submitter.submission.template.name %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-2" style="margin-left: 20px; flex-shrink: 0">
|
<div class="flex items-center space-x-2" style="margin-left: 20px; flex-shrink: 0">
|
||||||
<div>
|
<% if @form_configs[:with_decline] %>
|
||||||
<%= render 'shared/html_modal', title: t(:decline), btn_text: t(:decline), btn_class: 'btn btn-sm !px-5', button_id: 'decline_button' do %>
|
<div>
|
||||||
<%= render 'submit_form/decline_form', submitter: @submitter %>
|
<%= render 'shared/html_modal', title: t(:decline), btn_text: t(:decline), btn_class: 'btn btn-sm !px-5', button_id: 'decline_button' do %>
|
||||||
<% end %>
|
<%= render 'submit_form/decline_form', submitter: @submitter %>
|
||||||
</div>
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
<download-button data-src="<%= submit_form_download_index_path(@submitter.slug) %>" class="btn btn-neutral text-white btn-sm !px-4">
|
<download-button data-src="<%= submit_form_download_index_path(@submitter.slug) %>" class="btn btn-neutral text-white btn-sm !px-4">
|
||||||
<span class="flex items-center justify-center" data-target="download-button.defaultButton">
|
<span class="flex items-center justify-center" data-target="download-button.defaultButton">
|
||||||
<%= svg_icon('download', class: 'w-6 h-6 inline md:hidden') %>
|
<%= svg_icon('download', class: 'w-6 h-6 inline md:hidden') %>
|
||||||
|
|||||||
@@ -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 %>
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<div class="flex items-center space-x-1">
|
||||||
|
<% if %w[archived integration].include?(params[:status]) %>
|
||||||
|
<%= link_to t('view_active'), settings_users_path, class: 'link text-sm' %>
|
||||||
|
<% else %>
|
||||||
|
<% archived_exists = current_account.users.archived.where.not(role: 'integration').exists? %>
|
||||||
|
<% if current_account.users.active.exists?(role: 'integration') %>
|
||||||
|
<%= link_to t('view_embedding_users'), settings_integration_users_path, class: 'link text-sm' %>
|
||||||
|
<% if archived_exists %>
|
||||||
|
<span class="text-neutral-700">|</span>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% if archived_exists %>
|
||||||
|
<%= link_to t('view_archived'), settings_archived_users_path, class: 'link text-sm' %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
@@ -1,12 +1,18 @@
|
|||||||
<div class="flex-wrap space-y-4 md:flex md:flex-nowrap md:space-y-0 md:space-x-10">
|
<div class="flex-wrap space-y-4 md:flex md:flex-nowrap md:space-y-0 md:space-x-10">
|
||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="md:flex-grow">
|
<div class="md:flex-grow">
|
||||||
<div class="flex flex-col md:flex-row gap-2 md:justify-between md:items-end mb-4">
|
<div class="flex flex-col md:flex-row gap-2 md:justify-between md:items-end mb-4 min-h-12">
|
||||||
<h1 class="text-4xl font-bold">
|
<h1 class="text-4xl font-bold">
|
||||||
<%= t('status_users', status: params[:status].to_s.titleize) %>
|
<% if params[:status] == 'archived' %>
|
||||||
|
<%= t('archived_users') %>
|
||||||
|
<% elsif params[:status] == 'integration' %>
|
||||||
|
<%= t('embedding_users') %>
|
||||||
|
<% else %>
|
||||||
|
<%= t('users') %>
|
||||||
|
<% end %>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="flex flex-col md:flex-row gap-y-2 gap-x-4 md:items-center">
|
<div class="flex flex-col md:flex-row gap-y-2 gap-x-4 md:items-center">
|
||||||
<% if can?(:create, User.new(account: current_account)) %>
|
<% if params[:status].blank? && can?(:create, User.new(account: current_account)) %>
|
||||||
<%= render 'users/extra_buttons' %>
|
<%= render 'users/extra_buttons' %>
|
||||||
<% if content_for(:add_user_button) %>
|
<% if content_for(:add_user_button) %>
|
||||||
<%= content_for(:add_user_button) %>
|
<%= content_for(:add_user_button) %>
|
||||||
@@ -57,17 +63,17 @@
|
|||||||
<%= user.last_sign_in_at ? l(user.last_sign_in_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) : '-' %>
|
<%= user.last_sign_in_at ? l(user.last_sign_in_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) : '-' %>
|
||||||
</td>
|
</td>
|
||||||
<td class="flex items-center space-x-2 justify-end">
|
<td class="flex items-center space-x-2 justify-end">
|
||||||
<% if can?(:update, user) && user.archived_at.blank? %>
|
<% if params[:status].blank? && can?(:update, user) && user.archived_at.blank? %>
|
||||||
<%= link_to edit_user_path(user), class: 'btn btn-outline btn-xs', title: t('edit'), data: { turbo_frame: 'modal' } do %>
|
<%= link_to edit_user_path(user), class: 'btn btn-outline btn-xs', title: t('edit'), data: { turbo_frame: 'modal' } do %>
|
||||||
<%= t('edit') %>
|
<%= t('edit') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if params[:status].blank? && can?(:destroy, user) && user != current_user %>
|
<% if params[:status] != 'archived' && can?(:destroy, user) && user != current_user %>
|
||||||
<%= button_to user_path(user), method: :delete, class: 'btn btn-outline btn-error btn-xs', title: t('remove'), data: { turbo_confirm: t('are_you_sure_') } do %>
|
<%= button_to user_path(user), method: :delete, class: 'btn btn-outline btn-error btn-xs', title: t('remove'), data: { turbo_confirm: t('are_you_sure_') } do %>
|
||||||
<%= t('remove') %>
|
<%= t('remove') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if params[:status].present? && can?(:manage, user) && user != current_user %>
|
<% if params[:status] == 'archived' && can?(:manage, user) && user != current_user && user.archived_at? %>
|
||||||
<%= button_to user_path(user), method: :put, params: { user: { archived_at: nil } }, class: 'btn btn-outline btn-xs', title: t('unarchive'), data: { turbo_confirm: t('are_you_sure_') } do %>
|
<%= button_to user_path(user), method: :put, params: { user: { archived_at: nil } }, class: 'btn btn-outline btn-xs', title: t('unarchive'), data: { turbo_confirm: t('are_you_sure_') } do %>
|
||||||
<%= t('unarchive') %>
|
<%= t('unarchive') %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -78,22 +84,11 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<% view_archived_html = capture do %>
|
|
||||||
<% if current_account.users.archived.exists? %>
|
|
||||||
<div>
|
|
||||||
<% if params[:status] == 'archived' %>
|
|
||||||
<a href="<%= settings_users_path %>" class="link text-sm"><%= t('view_active') %></a>
|
|
||||||
<% else %>
|
|
||||||
<a href="<%= settings_archived_users_path %>" class="link text-sm"><%= t('view_archived') %></a>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
<% if @pagy.pages > 1 %>
|
<% if @pagy.pages > 1 %>
|
||||||
<%= render 'shared/pagination', pagy: @pagy, items_name: 'users', left_additional_html: view_archived_html %>
|
<%= render 'shared/pagination', pagy: @pagy, items_name: 'users', left_additional_html: render('bottom_links') %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<%= view_archived_html %>
|
<%= render 'bottom_links' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
Rails.configuration.to_prepare do
|
Rails.configuration.to_prepare do
|
||||||
ActiveRecord::Tasks::DatabaseTasks.migrate if ENV['RAILS_ENV'] == 'production'
|
ActiveRecord::Tasks::DatabaseTasks.migrate if ENV['RAILS_ENV'] == 'production' && ENV['RUN_MIGRATIONS'] != 'false'
|
||||||
end
|
end
|
||||||
+73
-19
@@ -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
|
||||||
@@ -134,6 +135,7 @@ en: &en
|
|||||||
require_signing_reason: Require signing reason
|
require_signing_reason: Require signing reason
|
||||||
allow_typed_text_signatures: Allow typed text signatures
|
allow_typed_text_signatures: Allow typed text signatures
|
||||||
allow_to_resubmit_completed_forms: Allow to resubmit completed forms
|
allow_to_resubmit_completed_forms: Allow to resubmit completed forms
|
||||||
|
allow_to_decline_documents: Allow to decline documents
|
||||||
remember_and_pre_fill_signatures: Remember and pre-fill signatures
|
remember_and_pre_fill_signatures: Remember and pre-fill signatures
|
||||||
require_authentication_for_file_download_links: Require authentication for file download links
|
require_authentication_for_file_download_links: Require authentication for file download links
|
||||||
combine_completed_documents_and_audit_log: Combine completed documents and Audit Log
|
combine_completed_documents_and_audit_log: Combine completed documents and Audit Log
|
||||||
@@ -472,8 +474,8 @@ 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
|
||||||
status_users: '%{status} Users'
|
|
||||||
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
|
||||||
signed_with_external_certificate: Signed with external certificate
|
signed_with_external_certificate: Signed with external 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.
|
||||||
@@ -618,6 +621,12 @@ en: &en
|
|||||||
use_international_format_1xxx_: 'Use internatioanl format: +1xxx...'
|
use_international_format_1xxx_: 'Use internatioanl format: +1xxx...'
|
||||||
submitter_cannot_be_updated: Submitter cannot be updated.
|
submitter_cannot_be_updated: Submitter cannot be updated.
|
||||||
at_least_one_field_must_be_filled: At least one field must be filled.
|
at_least_one_field_must_be_filled: At least one field must be filled.
|
||||||
|
archived_users: Archived Users
|
||||||
|
embedding_users: Embedding Users
|
||||||
|
view_embedding_users: View Embedding 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}'
|
||||||
@@ -654,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'
|
||||||
@@ -755,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
|
||||||
@@ -770,6 +780,7 @@ es: &es
|
|||||||
require_signing_reason: Requerir motivo de firma
|
require_signing_reason: Requerir motivo de firma
|
||||||
allow_typed_text_signatures: Permitir firmas de texto mecanografiadas
|
allow_typed_text_signatures: Permitir firmas de texto mecanografiadas
|
||||||
allow_to_resubmit_completed_forms: Permitir reenviar formularios completados
|
allow_to_resubmit_completed_forms: Permitir reenviar formularios completados
|
||||||
|
allow_to_decline_documents: Permitir rechazar documentos
|
||||||
remember_and_pre_fill_signatures: Recordar y prellenar firmas
|
remember_and_pre_fill_signatures: Recordar y prellenar firmas
|
||||||
require_authentication_for_file_download_links: Requerir autenticación para enlaces de descarga de archivos
|
require_authentication_for_file_download_links: Requerir autenticación para enlaces de descarga de archivos
|
||||||
combine_completed_documents_and_audit_log: Combinar documentos completados y Registro de Auditoría
|
combine_completed_documents_and_audit_log: Combinar documentos completados y Registro de Auditoría
|
||||||
@@ -1108,8 +1119,8 @@ 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
|
||||||
status_users: '%{status} Usuarios'
|
|
||||||
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
|
||||||
signed_with_external_certificate: Firmado con certificado externo
|
signed_with_external_certificate: Firmado con certificado externo
|
||||||
@@ -1134,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.
|
||||||
@@ -1254,6 +1266,12 @@ es: &es
|
|||||||
use_international_format_1xxx_: 'Usa el formato internacional: +1xxx...'
|
use_international_format_1xxx_: 'Usa el formato internacional: +1xxx...'
|
||||||
submitter_cannot_be_updated: El remitente no puede ser actualizado.
|
submitter_cannot_be_updated: El remitente no puede ser actualizado.
|
||||||
at_least_one_field_must_be_filled: Al menos un campo debe estar completo.
|
at_least_one_field_must_be_filled: Al menos un campo debe estar completo.
|
||||||
|
archived_users: Usuarios Archivados
|
||||||
|
embedding_users: Usuarios Integrados
|
||||||
|
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}'
|
||||||
@@ -1290,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'
|
||||||
@@ -1391,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
|
||||||
@@ -1406,6 +1425,7 @@ it: &it
|
|||||||
require_signing_reason: Richiedere il motivo della firma
|
require_signing_reason: Richiedere il motivo della firma
|
||||||
allow_typed_text_signatures: Permettere firme con testo digitato
|
allow_typed_text_signatures: Permettere firme con testo digitato
|
||||||
allow_to_resubmit_completed_forms: Permettere di reinviare i moduli completati
|
allow_to_resubmit_completed_forms: Permettere di reinviare i moduli completati
|
||||||
|
allow_to_decline_documents: Permettere di rifiutare i documenti
|
||||||
remember_and_pre_fill_signatures: Ricordare e precompilare le firme
|
remember_and_pre_fill_signatures: Ricordare e precompilare le firme
|
||||||
require_authentication_for_file_download_links: "Richiedere l'autenticazione per i link di download dei file"
|
require_authentication_for_file_download_links: "Richiedere l'autenticazione per i link di download dei file"
|
||||||
combine_completed_documents_and_audit_log: Combinare i documenti completati e il Registro di Audit
|
combine_completed_documents_and_audit_log: Combinare i documenti completati e il Registro di Audit
|
||||||
@@ -1744,8 +1764,8 @@ 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
|
||||||
status_users: '%{status} Utenti'
|
|
||||||
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
|
||||||
signed_with_external_certificate: Firmato con certificato esterno
|
signed_with_external_certificate: Firmato con certificato esterno
|
||||||
@@ -1770,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.
|
||||||
@@ -1890,6 +1911,12 @@ it: &it
|
|||||||
use_international_format_1xxx_: 'Utilizza il formato internazionale: +1xxx...'
|
use_international_format_1xxx_: 'Utilizza il formato internazionale: +1xxx...'
|
||||||
submitter_cannot_be_updated: Il mittente non può essere aggiornato.
|
submitter_cannot_be_updated: Il mittente non può essere aggiornato.
|
||||||
at_least_one_field_must_be_filled: Almeno un campo deve essere compilato.
|
at_least_one_field_must_be_filled: Almeno un campo deve essere compilato.
|
||||||
|
archived_users: Utenti Archiviati
|
||||||
|
embedding_users: 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}'
|
||||||
@@ -1926,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'
|
||||||
@@ -2028,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
|
||||||
@@ -2043,6 +2071,7 @@ fr: &fr
|
|||||||
require_signing_reason: Demander une raison pour la signature
|
require_signing_reason: Demander une raison pour la signature
|
||||||
allow_typed_text_signatures: Autoriser les signatures en texte tapé
|
allow_typed_text_signatures: Autoriser les signatures en texte tapé
|
||||||
allow_to_resubmit_completed_forms: Autoriser la resoumission des formulaires complétés
|
allow_to_resubmit_completed_forms: Autoriser la resoumission des formulaires complétés
|
||||||
|
allow_to_decline_documents: Autoriser le refus des documents
|
||||||
remember_and_pre_fill_signatures: Se souvenir et préremplir les signatures
|
remember_and_pre_fill_signatures: Se souvenir et préremplir les signatures
|
||||||
require_authentication_for_file_download_links: Exiger une authentification pour les liens de téléchargement des fichiers
|
require_authentication_for_file_download_links: Exiger une authentification pour les liens de téléchargement des fichiers
|
||||||
combine_completed_documents_and_audit_log: "Combiner les documents complétés et le journal d'audit"
|
combine_completed_documents_and_audit_log: "Combiner les documents complétés et le journal d'audit"
|
||||||
@@ -2381,8 +2410,8 @@ 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
|
||||||
status_users: '%{status} Utilisateurs'
|
|
||||||
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
|
||||||
signed_with_external_certificate: Signé avec un certificat externe
|
signed_with_external_certificate: Signé avec un certificat externe
|
||||||
@@ -2407,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.
|
||||||
@@ -2527,6 +2557,12 @@ fr: &fr
|
|||||||
use_international_format_1xxx_: 'Utilizza il formato internazionale: +1xxx...'
|
use_international_format_1xxx_: 'Utilizza il formato internazionale: +1xxx...'
|
||||||
submitter_cannot_be_updated: Il mittente non può essere aggiornato.
|
submitter_cannot_be_updated: Il mittente non può essere aggiornato.
|
||||||
at_least_one_field_must_be_filled: Almeno un campo deve essere compilato.
|
at_least_one_field_must_be_filled: Almeno un campo deve essere compilato.
|
||||||
|
archived_users: Utilisateurs Archivés
|
||||||
|
embedding_users: 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}'
|
||||||
@@ -2563,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'
|
||||||
@@ -2664,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
|
||||||
@@ -2679,6 +2716,7 @@ pt: &pt
|
|||||||
require_signing_reason: Requerer motivo para assinatura
|
require_signing_reason: Requerer motivo para assinatura
|
||||||
allow_typed_text_signatures: Permitir assinaturas digitadas
|
allow_typed_text_signatures: Permitir assinaturas digitadas
|
||||||
allow_to_resubmit_completed_forms: Permitir reenviar formulários concluídos
|
allow_to_resubmit_completed_forms: Permitir reenviar formulários concluídos
|
||||||
|
allow_to_decline_documents: Permitir recusar documentos
|
||||||
remember_and_pre_fill_signatures: Lembrar e preencher assinaturas automaticamente
|
remember_and_pre_fill_signatures: Lembrar e preencher assinaturas automaticamente
|
||||||
require_authentication_for_file_download_links: Requerer autenticação para links de download de arquivos
|
require_authentication_for_file_download_links: Requerer autenticação para links de download de arquivos
|
||||||
combine_completed_documents_and_audit_log: Combinar documentos concluídos e log de auditoria
|
combine_completed_documents_and_audit_log: Combinar documentos concluídos e log de auditoria
|
||||||
@@ -3017,8 +3055,8 @@ 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
|
||||||
status_users: '%{status} Usuários'
|
|
||||||
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
|
||||||
signed_with_external_certificate: Assinado com certificado externo
|
signed_with_external_certificate: Assinado com certificado externo
|
||||||
@@ -3043,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.
|
||||||
@@ -3163,6 +3202,12 @@ pt: &pt
|
|||||||
use_international_format_1xxx_: 'Use o formato internacional: +1xxx...'
|
use_international_format_1xxx_: 'Use o formato internacional: +1xxx...'
|
||||||
submitter_cannot_be_updated: O remetente não pode ser atualizado.
|
submitter_cannot_be_updated: O remetente não pode ser atualizado.
|
||||||
at_least_one_field_must_be_filled: Pelo menos um campo deve ser preenchido.
|
at_least_one_field_must_be_filled: Pelo menos um campo deve ser preenchido.
|
||||||
|
archived_users: Usuários Arquivados
|
||||||
|
embedding_users: 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}'
|
||||||
@@ -3199,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'
|
||||||
@@ -3300,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
|
||||||
@@ -3315,6 +3361,7 @@ de: &de
|
|||||||
require_signing_reason: Grund für die Signatur erforderlich
|
require_signing_reason: Grund für die Signatur erforderlich
|
||||||
allow_typed_text_signatures: Unterschriften mit getipptem Text zulassen
|
allow_typed_text_signatures: Unterschriften mit getipptem Text zulassen
|
||||||
allow_to_resubmit_completed_forms: Erneutes Einreichen abgeschlossener Formulare zulassen
|
allow_to_resubmit_completed_forms: Erneutes Einreichen abgeschlossener Formulare zulassen
|
||||||
|
allow_to_decline_documents: Erlauben, Dokumente abzulehnen
|
||||||
remember_and_pre_fill_signatures: Signaturen merken und vorab ausfüllen
|
remember_and_pre_fill_signatures: Signaturen merken und vorab ausfüllen
|
||||||
require_authentication_for_file_download_links: Authentifizierung für Dateidownload-Links erforderlich
|
require_authentication_for_file_download_links: Authentifizierung für Dateidownload-Links erforderlich
|
||||||
combine_completed_documents_and_audit_log: Abgeschlossene Dokumente und Prüfprotokoll kombinieren
|
combine_completed_documents_and_audit_log: Abgeschlossene Dokumente und Prüfprotokoll kombinieren
|
||||||
@@ -3653,8 +3700,8 @@ 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
|
||||||
status_users: '%{status} Benutzer'
|
|
||||||
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
|
||||||
signed_with_external_certificate: Signiert mit externem Zertifikat
|
signed_with_external_certificate: Signiert mit externem Zertifikat
|
||||||
@@ -3679,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.
|
||||||
@@ -3799,6 +3847,12 @@ de: &de
|
|||||||
use_international_format_1xxx_: 'Verwenden Sie das internationale Format: +1xxx...'
|
use_international_format_1xxx_: 'Verwenden Sie das internationale Format: +1xxx...'
|
||||||
submitter_cannot_be_updated: Der Absender kann nicht aktualisiert werden.
|
submitter_cannot_be_updated: Der Absender kann nicht aktualisiert werden.
|
||||||
at_least_one_field_must_be_filled: Mindestens ein Feld muss ausgefüllt werden.
|
at_least_one_field_must_be_filled: Mindestens ein Feld muss ausgefüllt werden.
|
||||||
|
archived_users: Archivierte Benutzer
|
||||||
|
embedding_users: Einbettende Benutzer
|
||||||
|
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}'
|
||||||
@@ -3885,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:
|
||||||
@@ -3939,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:
|
||||||
@@ -3993,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:
|
||||||
@@ -4047,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:
|
||||||
@@ -4101,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:
|
||||||
@@ -4155,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:
|
||||||
@@ -4209,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:
|
||||||
|
|||||||
+9
-4
@@ -34,6 +34,7 @@ Rails.application.routes.draw do
|
|||||||
resources :submitter_form_views, only: %i[create]
|
resources :submitter_form_views, only: %i[create]
|
||||||
resources :submitters, only: %i[index show update]
|
resources :submitters, only: %i[index show update]
|
||||||
resources :submissions, only: %i[index show create destroy] do
|
resources :submissions, only: %i[index show create destroy] do
|
||||||
|
resources :documents, only: %i[index], controller: 'submission_documents'
|
||||||
collection do
|
collection do
|
||||||
resources :init, only: %i[create], controller: 'submissions'
|
resources :init, only: %i[create], controller: 'submissions'
|
||||||
resources :emails, only: %i[create], controller: 'submissions', as: :submissions_emails
|
resources :emails, only: %i[create], controller: 'submissions', as: :submissions_emails
|
||||||
@@ -68,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'
|
||||||
@@ -77,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'
|
||||||
@@ -152,15 +155,17 @@ Rails.application.routes.draw do
|
|||||||
scope '/settings', as: :settings do
|
scope '/settings', as: :settings do
|
||||||
unless Docuseal.multitenant?
|
unless Docuseal.multitenant?
|
||||||
resources :storage, only: %i[index create], controller: 'storage_settings'
|
resources :storage, only: %i[index create], controller: 'storage_settings'
|
||||||
resources :email, only: %i[index create], controller: 'email_smtp_settings'
|
|
||||||
resources :sms, only: %i[index], controller: 'sms_settings'
|
resources :sms, only: %i[index], controller: 'sms_settings'
|
||||||
end
|
end
|
||||||
|
resources :email, only: %i[index create], controller: 'email_smtp_settings'
|
||||||
resources :sso, only: %i[index], controller: 'sso_settings'
|
resources :sso, only: %i[index], controller: 'sso_settings'
|
||||||
resources :notifications, only: %i[index create], controller: 'notifications_settings'
|
resources :notifications, only: %i[index create], controller: 'notifications_settings'
|
||||||
resource :esign, only: %i[show create new update destroy], controller: 'esign_settings'
|
resource :esign, only: %i[show create new update destroy], controller: 'esign_settings'
|
||||||
resources :users, only: %i[index]
|
resources :users, only: %i[index]
|
||||||
resources :archived_users, only: %i[index], path: 'users/:status', controller: 'users',
|
resources :archived_users, only: %i[index], path: 'users/:status', controller: 'users',
|
||||||
defaults: { status: :archived }
|
defaults: { status: :archived }
|
||||||
|
resources :integration_users, only: %i[index], path: 'users/:status', controller: 'users',
|
||||||
|
defaults: { status: :integration }
|
||||||
resource :personalization, only: %i[show create], controller: 'personalization_settings'
|
resource :personalization, only: %i[show create], controller: 'personalization_settings'
|
||||||
resources :api, only: %i[index create], controller: 'api_settings'
|
resources :api, only: %i[index create], controller: 'api_settings'
|
||||||
resource :webhooks, only: %i[show create update], controller: 'webhook_settings'
|
resource :webhooks, only: %i[show create update], controller: 'webhook_settings'
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreateCompletedSubmittersAndDocuments < ActiveRecord::Migration[7.2]
|
||||||
|
def change
|
||||||
|
create_table :completed_submitters do |t|
|
||||||
|
t.bigint :submitter_id, null: false, index: true
|
||||||
|
t.bigint :submission_id, null: false
|
||||||
|
t.bigint :account_id, null: false, index: true
|
||||||
|
t.bigint :template_id, null: false
|
||||||
|
t.string :source, null: false
|
||||||
|
t.integer :sms_count, null: false
|
||||||
|
t.datetime :completed_at, null: false
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :completed_documents do |t|
|
||||||
|
t.bigint :submitter_id, null: false, index: true
|
||||||
|
t.string :sha256, null: false, index: true
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class PopulateCompletedSubmittersAndDocuments < ActiveRecord::Migration[7.2]
|
||||||
|
disable_ddl_transaction
|
||||||
|
|
||||||
|
class MigrationSubmitter < ApplicationRecord
|
||||||
|
self.table_name = 'submitters'
|
||||||
|
|
||||||
|
belongs_to :submission, class_name: 'MigrationSubmission'
|
||||||
|
has_many :submission_sms_events, -> { where(event_type: %w[send_sms send_2fa_sms]) },
|
||||||
|
class_name: 'MigrationSubmissionEvent', foreign_key: :submitter_id
|
||||||
|
end
|
||||||
|
|
||||||
|
class MigrationSubmission < ApplicationRecord
|
||||||
|
self.table_name = 'submissions'
|
||||||
|
end
|
||||||
|
|
||||||
|
class MigrationSubmissionEvent < ApplicationRecord
|
||||||
|
self.table_name = 'submission_events'
|
||||||
|
end
|
||||||
|
|
||||||
|
class MigrationCompletedSubmitter < ApplicationRecord
|
||||||
|
self.table_name = 'completed_submitters'
|
||||||
|
end
|
||||||
|
|
||||||
|
class MigrationCompletedDocument < ApplicationRecord
|
||||||
|
self.table_name = 'completed_documents'
|
||||||
|
end
|
||||||
|
|
||||||
|
def up
|
||||||
|
submitters = MigrationSubmitter.where.not(completed_at: nil)
|
||||||
|
.preload(:submission, :submission_sms_events)
|
||||||
|
|
||||||
|
count = submitters.count
|
||||||
|
|
||||||
|
puts "Updating the database - it might take ~#{(count / 1000 * 3) + 1} seconds" if count > 2000
|
||||||
|
|
||||||
|
submitters.find_each do |submitter|
|
||||||
|
completed_submitter = MigrationCompletedSubmitter.find_or_initialize_by(submitter_id: submitter.id)
|
||||||
|
|
||||||
|
next if completed_submitter.persisted?
|
||||||
|
|
||||||
|
submission = submitter.submission
|
||||||
|
|
||||||
|
completed_submitter.assign_attributes(
|
||||||
|
submission_id: submitter.submission_id,
|
||||||
|
account_id: submission.account_id,
|
||||||
|
template_id: submission.template_id,
|
||||||
|
source: submission.source,
|
||||||
|
sms_count: submitter.submission_sms_events.size,
|
||||||
|
completed_at: submitter.completed_at,
|
||||||
|
created_at: submitter.completed_at,
|
||||||
|
updated_at: submitter.completed_at
|
||||||
|
)
|
||||||
|
|
||||||
|
completed_submitter.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
attachments = ActiveStorage::Attachment.where(record_type: 'Submitter', name: 'documents').preload(:blob)
|
||||||
|
|
||||||
|
attachments.find_each do |attachment|
|
||||||
|
sha256 = attachment.metadata['sha256']
|
||||||
|
|
||||||
|
next if sha256.blank?
|
||||||
|
|
||||||
|
completed_document = MigrationCompletedDocument.find_or_initialize_by(submitter_id: attachment.record_id, sha256:)
|
||||||
|
|
||||||
|
next if completed_document.persisted?
|
||||||
|
|
||||||
|
completed_document.assign_attributes(created_at: attachment.created_at, updated_at: attachment.created_at)
|
||||||
|
|
||||||
|
completed_document.save!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddUniqueIndexOnCompletedSubmitters < ActiveRecord::Migration[7.2]
|
||||||
|
def change
|
||||||
|
remove_index :completed_submitters, :submitter_id
|
||||||
|
add_index :completed_submitters, :submitter_id, unique: true
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -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
|
||||||
+25
-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.1].define(version: 2024_08_20_180922) 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"
|
||||||
|
|
||||||
@@ -89,6 +89,29 @@ ActiveRecord::Schema[7.1].define(version: 2024_08_20_180922) do
|
|||||||
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
|
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "completed_documents", force: :cascade do |t|
|
||||||
|
t.bigint "submitter_id", null: false
|
||||||
|
t.string "sha256", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["sha256"], name: "index_completed_documents_on_sha256"
|
||||||
|
t.index ["submitter_id"], name: "index_completed_documents_on_submitter_id"
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table "completed_submitters", force: :cascade do |t|
|
||||||
|
t.bigint "submitter_id", null: false
|
||||||
|
t.bigint "submission_id", null: false
|
||||||
|
t.bigint "account_id", null: false
|
||||||
|
t.bigint "template_id", null: false
|
||||||
|
t.string "source", null: false
|
||||||
|
t.integer "sms_count", null: false
|
||||||
|
t.datetime "completed_at", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["account_id"], name: "index_completed_submitters_on_account_id"
|
||||||
|
t.index ["submitter_id"], name: "index_completed_submitters_on_submitter_id", unique: true
|
||||||
|
end
|
||||||
|
|
||||||
create_table "document_generation_events", force: :cascade do |t|
|
create_table "document_generation_events", force: :cascade do |t|
|
||||||
t.bigint "submitter_id", null: false
|
t.bigint "submitter_id", null: false
|
||||||
t.string "event_name", null: false
|
t.string "event_name", null: false
|
||||||
@@ -344,6 +367,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_08_20_180922) 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
|
||||||
+1
-25
@@ -11,7 +11,7 @@ module Accounts
|
|||||||
new_user.uuid = SecureRandom.uuid
|
new_user.uuid = SecureRandom.uuid
|
||||||
new_user.account = new_account
|
new_user.account = new_account
|
||||||
new_user.encrypted_password = SecureRandom.hex
|
new_user.encrypted_password = SecureRandom.hex
|
||||||
new_user.email = "#{SecureRandom.hex}@docuseal.co"
|
new_user.email = "#{SecureRandom.hex}@docuseal.com"
|
||||||
|
|
||||||
account.templates.each do |template|
|
account.templates.each do |template|
|
||||||
new_template = template.dup
|
new_template = template.dup
|
||||||
@@ -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?
|
||||||
|
|||||||
@@ -16,19 +16,27 @@ module ActionMailerConfigsInterceptor
|
|||||||
end
|
end
|
||||||
|
|
||||||
if Rails.env.production? && Rails.application.config.action_mailer.delivery_method
|
if Rails.env.production? && Rails.application.config.action_mailer.delivery_method
|
||||||
message.from = ENV.fetch('SMTP_FROM')
|
from = ENV.fetch('SMTP_FROM').to_s.split(',').sample
|
||||||
|
|
||||||
|
if from.match?(User::FULL_EMAIL_REGEXP)
|
||||||
|
message[:from] = message[:from].to_s.sub(User::EMAIL_REGEXP, from)
|
||||||
|
else
|
||||||
|
message.from = from
|
||||||
|
end
|
||||||
|
|
||||||
return message
|
return message
|
||||||
end
|
end
|
||||||
|
|
||||||
email_configs = EncryptedConfig.find_by(key: EncryptedConfig::EMAIL_SMTP_KEY)
|
unless Docuseal.multitenant?
|
||||||
|
email_configs = EncryptedConfig.find_by(key: EncryptedConfig::EMAIL_SMTP_KEY)
|
||||||
|
|
||||||
if email_configs
|
if email_configs
|
||||||
message.delivery_method(:smtp, build_smtp_configs_hash(email_configs))
|
message.delivery_method(:smtp, build_smtp_configs_hash(email_configs))
|
||||||
|
|
||||||
message.from = %("#{email_configs.account.name.to_s.delete('"')}" <#{email_configs.value['from_email']}>)
|
message.from = %("#{email_configs.account.name.to_s.delete('"')}" <#{email_configs.value['from_email']}>)
|
||||||
else
|
else
|
||||||
message.delivery_method(:test)
|
message.delivery_method(:test)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
message
|
message
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ module ActionMailerEventsObserver
|
|||||||
emailable_type:,
|
emailable_type:,
|
||||||
event_type: :send,
|
event_type: :send,
|
||||||
email:,
|
email:,
|
||||||
data: { method: mail.delivery_method.class.name.underscore },
|
data: { from: mail.from, method: mail.delivery_method.class.name.underscore },
|
||||||
event_datetime: Time.current
|
event_datetime: Time.current
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
+5
-5
@@ -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'
|
||||||
@@ -12,7 +12,7 @@ module Docuseal
|
|||||||
TWITTER_URL = 'https://twitter.com/docusealco'
|
TWITTER_URL = 'https://twitter.com/docusealco'
|
||||||
TWITTER_HANDLE = '@docusealco'
|
TWITTER_HANDLE = '@docusealco'
|
||||||
CHATGPT_URL = 'https://chatgpt.com/g/g-9hg8AAw0r-docuseal'
|
CHATGPT_URL = 'https://chatgpt.com/g/g-9hg8AAw0r-docuseal'
|
||||||
SUPPORT_EMAIL = 'support@docuseal.co'
|
SUPPORT_EMAIL = 'support@docuseal.com'
|
||||||
HOST = ENV.fetch('HOST', 'localhost')
|
HOST = ENV.fetch('HOST', 'localhost')
|
||||||
AATL_CERT_NAME = 'docuseal_aatl'
|
AATL_CERT_NAME = 'docuseal_aatl'
|
||||||
CONSOLE_URL = if Rails.env.development?
|
CONSOLE_URL = if Rails.env.development?
|
||||||
@@ -20,19 +20,19 @@ module Docuseal
|
|||||||
elsif ENV['MULTITENANT'] == 'true'
|
elsif ENV['MULTITENANT'] == 'true'
|
||||||
"https://console.#{HOST}"
|
"https://console.#{HOST}"
|
||||||
else
|
else
|
||||||
'https://console.docuseal.co'
|
'https://console.docuseal.com'
|
||||||
end
|
end
|
||||||
CLOUD_URL = if Rails.env.development?
|
CLOUD_URL = if Rails.env.development?
|
||||||
'http://localhost:3000'
|
'http://localhost:3000'
|
||||||
else
|
else
|
||||||
'https://docuseal.co'
|
'https://docuseal.com'
|
||||||
end
|
end
|
||||||
CDN_URL = if Rails.env.development?
|
CDN_URL = if Rails.env.development?
|
||||||
'http://localhost:3000'
|
'http://localhost:3000'
|
||||||
elsif ENV['MULTITENANT'] == 'true'
|
elsif ENV['MULTITENANT'] == 'true'
|
||||||
"https://cdn.#{HOST}"
|
"https://cdn.#{HOST}"
|
||||||
else
|
else
|
||||||
'https://cdn.docuseal.co'
|
'https://cdn.docuseal.com'
|
||||||
end
|
end
|
||||||
|
|
||||||
CERTS = JSON.parse(ENV.fetch('CERTS', '{}'))
|
CERTS = JSON.parse(ENV.fetch('CERTS', '{}'))
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
module Params
|
module Params
|
||||||
class BaseValidator
|
class BaseValidator
|
||||||
|
EMAIL_REGEXP = User::FULL_EMAIL_REGEXP
|
||||||
|
|
||||||
InvalidParameterError = Class.new(StandardError)
|
InvalidParameterError = Class.new(StandardError)
|
||||||
|
|
||||||
def self.call(...)
|
def self.call(...)
|
||||||
@@ -65,6 +67,28 @@ module Params
|
|||||||
raise_error(message || "#{key} must follow the #{regexp.source} format")
|
raise_error(message || "#{key} must follow the #{regexp.source} format")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def email_format(params, key, message: nil)
|
||||||
|
return if params.blank?
|
||||||
|
return if params[key].blank?
|
||||||
|
return if params[key].to_s.include?('<')
|
||||||
|
|
||||||
|
if params[key].to_s.strip.split(/\s*[;,]\s*/).compact_blank
|
||||||
|
.all? { |email| EmailTypo::DotCom.call(email).match?(EMAIL_REGEXP) }
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
raise_error(message || "#{key} must follow the email format: '#{params[key]}'")
|
||||||
|
end
|
||||||
|
|
||||||
|
def unique_value(params, key, message: nil)
|
||||||
|
return unless params.is_a?(Array)
|
||||||
|
return if params.none?
|
||||||
|
return if params.all? { |p| p[key].blank? }
|
||||||
|
return if params.pluck(key).compact_blank.uniq.size == params.pluck(key).compact_blank.size
|
||||||
|
|
||||||
|
raise_error(message || "#{key} must be unique")
|
||||||
|
end
|
||||||
|
|
||||||
def in_path(params, path = [])
|
def in_path(params, path = [])
|
||||||
old_path = @current_path
|
old_path = @current_path
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ module Params
|
|||||||
required(params, %i[emails email])
|
required(params, %i[emails email])
|
||||||
|
|
||||||
type(params, :emails, String)
|
type(params, :emails, String)
|
||||||
|
email_format(params, :emails, message: 'emails are invalid')
|
||||||
boolean(params, :send_email)
|
boolean(params, :send_email)
|
||||||
type(params, :message, Hash)
|
type(params, :message, Hash)
|
||||||
|
|
||||||
@@ -43,8 +44,8 @@ module Params
|
|||||||
type(params, :completed_redirect_url, String)
|
type(params, :completed_redirect_url, String)
|
||||||
type(params, :bcc_completed, String)
|
type(params, :bcc_completed, String)
|
||||||
type(params, :reply_to, String)
|
type(params, :reply_to, String)
|
||||||
format(params, :bcc_completed, /@/, message: 'bcc_completed email is invalid')
|
email_format(params, :bcc_completed, message: 'bcc_completed email is invalid')
|
||||||
format(params, :reply_to, /@/, message: 'reply_to email is invalid')
|
email_format(params, :reply_to, message: 'reply_to email is invalid')
|
||||||
type(params, :message, Hash)
|
type(params, :message, Hash)
|
||||||
type(params, :submitters, Array)
|
type(params, :submitters, Array)
|
||||||
|
|
||||||
@@ -60,6 +61,7 @@ module Params
|
|||||||
if params[:submitters].present?
|
if params[:submitters].present?
|
||||||
in_path(params, :submitters) do |submitters_params|
|
in_path(params, :submitters) do |submitters_params|
|
||||||
type(submitters_params, 0, Hash)
|
type(submitters_params, 0, Hash)
|
||||||
|
unique_value(submitters_params, :role)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -74,8 +76,8 @@ module Params
|
|||||||
type(submitter_params, :name, String)
|
type(submitter_params, :name, String)
|
||||||
type(submitter_params, :reply_to, String)
|
type(submitter_params, :reply_to, String)
|
||||||
type(submitter_params, :email, String)
|
type(submitter_params, :email, String)
|
||||||
format(submitter_params, :email, /@/, message: 'email is invalid')
|
email_format(submitter_params, :email, message: 'email is invalid')
|
||||||
format(submitter_params, :reply_to, /@/, message: 'reply_to email is invalid')
|
email_format(submitter_params, :reply_to, message: 'reply_to email is invalid')
|
||||||
type(submitter_params, :phone, String)
|
type(submitter_params, :phone, String)
|
||||||
format(submitter_params, :phone, /\A\+\d+\z/,
|
format(submitter_params, :phone, /\A\+\d+\z/,
|
||||||
message: 'phone should start with +<country code> and contain only digits')
|
message: 'phone should start with +<country code> and contain only digits')
|
||||||
@@ -105,7 +107,7 @@ module Params
|
|||||||
type(params, :order, String)
|
type(params, :order, String)
|
||||||
type(params, :completed_redirect_url, String)
|
type(params, :completed_redirect_url, String)
|
||||||
type(params, :bcc_completed, String)
|
type(params, :bcc_completed, String)
|
||||||
format(params, :bcc_completed, /@/, message: 'bcc_completed email is invalid')
|
email_format(params, :bcc_completed, message: 'bcc_completed email is invalid')
|
||||||
type(params, :message, Hash)
|
type(params, :message, Hash)
|
||||||
|
|
||||||
in_path(params, :message) do |message_params|
|
in_path(params, :message) do |message_params|
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
|
|
||||||
module Submissions
|
module Submissions
|
||||||
module CreateFromSubmitters
|
module CreateFromSubmitters
|
||||||
|
BaseError = Class.new(StandardError)
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
|
# rubocop:disable Metrics/BlockLength
|
||||||
def call(template:, user:, submissions_attrs:, source:, submitters_order:, params: {})
|
def call(template:, user:, submissions_attrs:, source:, submitters_order:, params: {})
|
||||||
preferences = Submitters.normalize_preferences(user.account, user, params)
|
preferences = Submitters.normalize_preferences(user.account, user, params)
|
||||||
|
|
||||||
@@ -37,6 +40,10 @@ module Submissions
|
|||||||
preferences: preferences.merge(submission_preferences))
|
preferences: preferences.merge(submission_preferences))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if submission.submitters.size > template.submitters.size
|
||||||
|
raise BaseError, 'Defined more signing parties than in template'
|
||||||
|
end
|
||||||
|
|
||||||
next if submission.submitters.blank?
|
next if submission.submitters.blank?
|
||||||
|
|
||||||
maybe_add_invite_submitters(submission, template)
|
maybe_add_invite_submitters(submission, template)
|
||||||
@@ -44,6 +51,7 @@ module Submissions
|
|||||||
submission.tap(&:save!)
|
submission.tap(&:save!)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
# rubocop:enable Metrics/BlockLength
|
||||||
|
|
||||||
def maybe_add_invite_submitters(submission, template)
|
def maybe_add_invite_submitters(submission, template)
|
||||||
template.submitters.each do |item|
|
template.submitters.each do |item|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -393,7 +402,7 @@ module Submissions
|
|||||||
end
|
end
|
||||||
|
|
||||||
def sign_reason
|
def sign_reason
|
||||||
'Signed with DocuSeal.co'
|
'Signed with DocuSeal.com'
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_add_background(_canvas, _submission, _page_size); end
|
def maybe_add_background(_canvas, _submission, _page_size); end
|
||||||
|
|||||||
Loaded 100 of 133 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user