mirror of
https://github.com/docusealco/docuseal.git
synced 2026-08-07 07:14:43 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 22d4c1bd10 | |||
| e3dfb0ae83 | |||
| 8d7c3f0e26 | |||
| b11775ebda | |||
| 608191183a | |||
| 2ba903e170 | |||
| 342b64d502 | |||
| 1a8f048aaa | |||
| 7309581b75 | |||
| 36d59c40a7 | |||
| ed3c7c324b |
@@ -4,6 +4,7 @@ source 'https://rubygems.org'
|
||||
|
||||
ruby '4.0.1'
|
||||
|
||||
gem 'addressable'
|
||||
gem 'arabic-letter-connector', require: false
|
||||
gem 'aws-sdk-s3', require: false
|
||||
gem 'aws-sdk-secretsmanager', require: false
|
||||
@@ -28,7 +29,6 @@ gem 'oj'
|
||||
gem 'onnxruntime', require: false
|
||||
gem 'pagy'
|
||||
gem 'pg', require: false
|
||||
gem 'premailer-rails'
|
||||
gem 'pretender'
|
||||
gem 'puma', require: false
|
||||
gem 'rack'
|
||||
@@ -43,7 +43,7 @@ gem 'shakapacker'
|
||||
gem 'sidekiq'
|
||||
gem 'sqlite3', require: false
|
||||
gem 'strip_attributes'
|
||||
gem 'trilogy', github: 'trilogy-libraries/trilogy', glob: 'contrib/ruby/*.gemspec', require: false
|
||||
gem 'trilogy', require: false
|
||||
gem 'turbo-rails'
|
||||
gem 'twitter_cldr', require: false
|
||||
gem 'tzinfo-data'
|
||||
|
||||
+29
-44
@@ -1,11 +1,3 @@
|
||||
GIT
|
||||
remote: https://github.com/trilogy-libraries/trilogy.git
|
||||
revision: 3963d490459df7a2b5bedb42424c3285f25eab22
|
||||
glob: contrib/ruby/*.gemspec
|
||||
specs:
|
||||
trilogy (2.10.0)
|
||||
bigdecimal
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
@@ -116,7 +108,7 @@ GEM
|
||||
cgi
|
||||
rexml
|
||||
base64 (0.3.0)
|
||||
bcrypt (3.1.21)
|
||||
bcrypt (3.1.22)
|
||||
better_html (2.2.0)
|
||||
actionview (>= 7.0)
|
||||
activesupport (>= 7.0)
|
||||
@@ -158,8 +150,6 @@ GEM
|
||||
bigdecimal
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
css_parser (1.21.1)
|
||||
addressable
|
||||
csv (3.3.5)
|
||||
csv-safe (3.3.1)
|
||||
csv (~> 3.0)
|
||||
@@ -171,16 +161,16 @@ GEM
|
||||
irb (~> 1.10)
|
||||
reline (>= 0.3.8)
|
||||
declarative (0.0.20)
|
||||
devise (4.9.4)
|
||||
devise (5.0.3)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0)
|
||||
railties (>= 7.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise-two-factor (6.3.1)
|
||||
activesupport (>= 7.0, < 8.2)
|
||||
devise (>= 4.0, < 5.0)
|
||||
railties (>= 7.0, < 8.2)
|
||||
devise-two-factor (6.4.0)
|
||||
activesupport (>= 7.2, < 8.2)
|
||||
devise (>= 4.0, < 6.0)
|
||||
railties (>= 7.2, < 8.2)
|
||||
rotp (~> 6.0)
|
||||
diff-lcs (1.6.2)
|
||||
digest-crc (0.7.0)
|
||||
@@ -189,7 +179,7 @@ GEM
|
||||
dotenv (3.2.0)
|
||||
drb (2.2.3)
|
||||
email_typo (0.2.3)
|
||||
erb (6.0.1)
|
||||
erb (6.0.2)
|
||||
erb_lint (0.9.0)
|
||||
activesupport
|
||||
better_html (>= 2.0.1)
|
||||
@@ -272,19 +262,19 @@ GEM
|
||||
geom2d (~> 0.4, >= 0.4.1)
|
||||
openssl (>= 2.2.1)
|
||||
strscan (>= 3.1.2)
|
||||
htmlentities (4.4.2)
|
||||
i18n (1.14.8)
|
||||
concurrent-ruby (~> 1.0)
|
||||
image_processing (1.14.0)
|
||||
mini_magick (>= 4.9.5, < 6)
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
io-console (0.8.2)
|
||||
irb (1.16.0)
|
||||
irb (1.17.0)
|
||||
pp (>= 0.6.0)
|
||||
prism (>= 1.3.0)
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
jmespath (1.6.2)
|
||||
json (2.18.1)
|
||||
json (2.19.2)
|
||||
jwt (3.1.2)
|
||||
base64
|
||||
language_server-protocol (3.17.0.5)
|
||||
@@ -306,7 +296,7 @@ GEM
|
||||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
request_store (~> 1.0)
|
||||
loofah (2.25.0)
|
||||
loofah (2.25.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.9.0)
|
||||
@@ -321,7 +311,8 @@ GEM
|
||||
mini_magick (5.3.1)
|
||||
logger
|
||||
mini_mime (1.1.5)
|
||||
minitest (6.0.1)
|
||||
minitest (6.0.2)
|
||||
drb (~> 2.0)
|
||||
prism (~> 1.5)
|
||||
msgpack (1.8.0)
|
||||
multi_json (1.19.1)
|
||||
@@ -337,17 +328,17 @@ GEM
|
||||
net-smtp (0.5.1)
|
||||
net-protocol
|
||||
nio4r (2.7.5)
|
||||
nokogiri (1.19.1-aarch64-linux-gnu)
|
||||
nokogiri (1.19.2-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.19.1-aarch64-linux-musl)
|
||||
nokogiri (1.19.2-aarch64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.19.1-arm64-darwin)
|
||||
nokogiri (1.19.2-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.19.1-x86_64-linux-gnu)
|
||||
nokogiri (1.19.2-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.19.1-x86_64-linux-musl)
|
||||
nokogiri (1.19.2-x86_64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
numo-narray-alt (0.9.13)
|
||||
numo-narray-alt (0.10.3)
|
||||
oj (3.16.13)
|
||||
bigdecimal (>= 3.0)
|
||||
ostruct (>= 0.2)
|
||||
@@ -376,18 +367,10 @@ GEM
|
||||
pg (1.6.3-x86_64-linux-musl)
|
||||
pp (0.6.3)
|
||||
prettyprint
|
||||
premailer (1.27.0)
|
||||
addressable
|
||||
css_parser (>= 1.19.0)
|
||||
htmlentities (>= 4.0.0)
|
||||
premailer-rails (1.12.0)
|
||||
actionmailer (>= 3)
|
||||
net-smtp
|
||||
premailer (~> 1.7, >= 1.7.9)
|
||||
pretender (0.6.0)
|
||||
actionpack (>= 7.1)
|
||||
prettyprint (0.2.0)
|
||||
prism (1.8.0)
|
||||
prism (1.9.0)
|
||||
pry (0.16.0)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
@@ -429,8 +412,8 @@ GEM
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.6.2)
|
||||
loofah (~> 2.21)
|
||||
rails-html-sanitizer (1.7.0)
|
||||
loofah (~> 2.25)
|
||||
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
||||
rails-i18n (8.1.0)
|
||||
i18n (>= 0.7, < 2)
|
||||
@@ -446,7 +429,7 @@ GEM
|
||||
zeitwerk (~> 2.6)
|
||||
rainbow (3.1.1)
|
||||
rake (13.3.1)
|
||||
rdoc (7.1.0)
|
||||
rdoc (7.2.0)
|
||||
erb
|
||||
psych (>= 4.0.0)
|
||||
tsort
|
||||
@@ -562,6 +545,8 @@ GEM
|
||||
thor (1.5.0)
|
||||
timeout (0.6.0)
|
||||
trailblazer-option (0.1.2)
|
||||
trilogy (2.10.0)
|
||||
bigdecimal
|
||||
tsort (0.2.0)
|
||||
turbo-rails (2.0.21)
|
||||
actionpack (>= 7.1.0)
|
||||
@@ -601,7 +586,7 @@ GEM
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
yaml (0.4.0)
|
||||
zeitwerk (2.7.4)
|
||||
zeitwerk (2.7.5)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux
|
||||
@@ -611,6 +596,7 @@ PLATFORMS
|
||||
x86_64-linux-musl
|
||||
|
||||
DEPENDENCIES
|
||||
addressable
|
||||
annotaterb
|
||||
arabic-letter-connector
|
||||
aws-sdk-s3
|
||||
@@ -647,7 +633,6 @@ DEPENDENCIES
|
||||
onnxruntime
|
||||
pagy
|
||||
pg
|
||||
premailer-rails
|
||||
pretender
|
||||
pry-rails
|
||||
puma
|
||||
@@ -669,7 +654,7 @@ DEPENDENCIES
|
||||
simplecov
|
||||
sqlite3
|
||||
strip_attributes
|
||||
trilogy!
|
||||
trilogy
|
||||
turbo-rails
|
||||
twitter_cldr
|
||||
tzinfo-data
|
||||
|
||||
@@ -69,6 +69,7 @@ module Api
|
||||
SearchEntries.enqueue_reindex(@template)
|
||||
|
||||
WebhookUrls.enqueue_events(@template, 'template.updated')
|
||||
WebhookUrls.enqueue_events(@template, 'template.archived') if archived == true
|
||||
|
||||
render json: @template.as_json(only: %i[id updated_at])
|
||||
end
|
||||
@@ -78,6 +79,8 @@ module Api
|
||||
@template.destroy!
|
||||
else
|
||||
@template.update!(archived_at: Time.current)
|
||||
|
||||
WebhookUrls.enqueue_events(@template, 'template.archived')
|
||||
end
|
||||
|
||||
render json: @template.as_json(only: %i[id archived_at])
|
||||
|
||||
@@ -84,6 +84,8 @@ class TemplatesController < ApplicationController
|
||||
else
|
||||
@template.update!(archived_at: Time.current)
|
||||
|
||||
WebhookUrls.enqueue_events(@template, 'template.archived')
|
||||
|
||||
I18n.t('template_has_been_archived')
|
||||
end
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ class TemplatesRestoreController < ApplicationController
|
||||
def create
|
||||
@template.update!(archived_at: nil)
|
||||
|
||||
WebhookUrls.enqueue_events(@template, 'template.updated')
|
||||
|
||||
redirect_to template_path(@template), notice: I18n.t('template_has_been_unarchived')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,7 +15,7 @@ class WebhookEventsController < ApplicationController
|
||||
Submissions::SerializeForApi.call(@webhook_event.record)
|
||||
when 'template.created', 'template.updated'
|
||||
Templates::SerializeForApi.call(@webhook_event.record)
|
||||
when 'submission.archived'
|
||||
when 'submission.archived', 'template.archived'
|
||||
@webhook_event.record.as_json(only: %i[id archived_at])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2735,10 +2735,13 @@ export default {
|
||||
} else {
|
||||
this.isSaving = true
|
||||
|
||||
this.documentRefs.filter((ref) => ref.update).map((ref) => ref.update())
|
||||
const dynamicDocumentRefs = this.documentRefs.filter((ref) => ref.isDynamic)
|
||||
|
||||
dynamicDocumentRefs.map((ref) => ref.update())
|
||||
|
||||
this.rebuildVariablesSchema({ disable: false })
|
||||
|
||||
const dynamicDocumentSaves = this.documentRefs.filter((ref) => ref.saveBody).map((ref) => ref.saveBody())
|
||||
const dynamicDocumentSaves = dynamicDocumentRefs.map((ref) => ref.saveBody())
|
||||
|
||||
Promise.all([this.save(), ...dynamicDocumentSaves]).then(() => {
|
||||
window.Turbo.visit(`/templates/${this.template.id}`)
|
||||
@@ -3028,6 +3031,8 @@ export default {
|
||||
} else {
|
||||
dynamicDocumentRef.syncVariablesSchema(this.template.variables_schema, parsed, { disable })
|
||||
}
|
||||
} else {
|
||||
this.template.variables_schema = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SendTemplateArchivedWebhookRequestJob
|
||||
include Sidekiq::Job
|
||||
|
||||
sidekiq_options queue: :webhooks
|
||||
|
||||
MAX_ATTEMPTS = 10
|
||||
|
||||
def perform(params = {})
|
||||
template = Template.find_by(id: params['template_id'])
|
||||
|
||||
return unless template
|
||||
|
||||
webhook_url = WebhookUrl.find_by(id: params['webhook_url_id'])
|
||||
|
||||
return unless webhook_url
|
||||
|
||||
attempt = params['attempt'].to_i
|
||||
|
||||
return if webhook_url.url.blank? || webhook_url.events.exclude?('template.archived')
|
||||
|
||||
resp = SendWebhookRequest.call(webhook_url, event_type: 'template.archived',
|
||||
event_uuid: params['event_uuid'],
|
||||
record: template,
|
||||
attempt:,
|
||||
data: template.as_json(only: %i[id archived_at]))
|
||||
|
||||
if (resp.nil? || resp.status.to_i >= 400) && attempt <= MAX_ATTEMPTS &&
|
||||
(!Docuseal.multitenant? || template.account.account_configs.exists?(key: :plan))
|
||||
SendTemplateArchivedWebhookRequestJob.perform_in((2**attempt).minutes, {
|
||||
**params,
|
||||
'attempt' => attempt + 1,
|
||||
'last_status' => resp&.status.to_i
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,6 +5,8 @@ class ApplicationMailer < ActionMailer::Base
|
||||
layout 'mailer'
|
||||
|
||||
register_interceptor ActionMailerConfigsInterceptor
|
||||
register_interceptor HtmlToPlainTextInterceptor
|
||||
register_preview_interceptor HtmlToPlainTextInterceptor
|
||||
|
||||
register_observer ActionMailerEventsObserver
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ class WebhookUrl < ApplicationRecord
|
||||
submission.archived
|
||||
template.created
|
||||
template.updated
|
||||
template.archived
|
||||
].freeze
|
||||
|
||||
belongs_to :account
|
||||
|
||||
@@ -88,6 +88,8 @@ Rails.application.configure do
|
||||
openssl_verify_mode: ENV['SMTP_SSL_VERIFY'] == 'false' ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER,
|
||||
authentication: ENV.fetch('SMTP_PASSWORD', nil).present? ? ENV.fetch('SMTP_AUTHENTICATION', 'plain') : nil,
|
||||
enable_starttls: ENV['SMTP_ENABLE_STARTTLS'] != 'false',
|
||||
ssl: ENV['SMTP_ENABLE_SSL'] == 'true',
|
||||
tls: ENV['SMTP_ENABLE_TLS'] == 'true',
|
||||
open_timeout: ENV.fetch('SMTP_OPEN_TIMEOUT', '15').to_i,
|
||||
read_timeout: ENV.fetch('SMTP_READ_TIMEOUT', '25').to_i
|
||||
}.compact
|
||||
|
||||
@@ -914,6 +914,9 @@ en: &en
|
||||
connect_to_docuseal_mcp: Connect to DocuSeal MCP
|
||||
add_the_following_to_your_mcp_client_configuration: Add the following to your MCP client configuration
|
||||
works_with_claude_desktop_cursor_windsurf_vs_code_and_any_mcp_compatible_client: Works with Claude Desktop, Cursor, Windsurf, VS Code, and any MCP-compatible client.
|
||||
your_email_address_has_been_changed: Your email address has been changed
|
||||
the_email_address_for_your_account_has_been_changed_to_new_email: The email address for your account has been changed to %{new_email}.
|
||||
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: If you did not make this change, please contact us by replying to this email.
|
||||
devise:
|
||||
confirmations:
|
||||
confirmed: Your email address has been successfully confirmed.
|
||||
@@ -1938,6 +1941,9 @@ es: &es
|
||||
connect_to_docuseal_mcp: Conectar a DocuSeal MCP
|
||||
add_the_following_to_your_mcp_client_configuration: Agregue lo siguiente a la configuración de su cliente MCP
|
||||
works_with_claude_desktop_cursor_windsurf_vs_code_and_any_mcp_compatible_client: Funciona con Claude Desktop, Cursor, Windsurf, VS Code y cualquier cliente compatible con MCP.
|
||||
your_email_address_has_been_changed: Tu dirección de correo electrónico ha sido cambiada
|
||||
the_email_address_for_your_account_has_been_changed_to_new_email: La dirección de correo electrónico de tu cuenta ha sido cambiada a %{new_email}.
|
||||
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Si no realizaste este cambio, contáctanos respondiendo a este correo electrónico.
|
||||
devise:
|
||||
confirmations:
|
||||
confirmed: Tu dirección de correo electrónico ha sido confirmada correctamente.
|
||||
@@ -2963,6 +2969,9 @@ it: &it
|
||||
connect_to_docuseal_mcp: Connetti a DocuSeal MCP
|
||||
add_the_following_to_your_mcp_client_configuration: Aggiungi quanto segue alla configurazione del tuo client MCP
|
||||
works_with_claude_desktop_cursor_windsurf_vs_code_and_any_mcp_compatible_client: Funziona con Claude Desktop, Cursor, Windsurf, VS Code e qualsiasi client compatibile con MCP.
|
||||
your_email_address_has_been_changed: Il tuo indirizzo email è stato modificato
|
||||
the_email_address_for_your_account_has_been_changed_to_new_email: L'indirizzo email del tuo account è stato modificato in %{new_email}.
|
||||
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Se non hai effettuato questa modifica, contattaci rispondendo a questa email.
|
||||
devise:
|
||||
confirmations:
|
||||
confirmed: Il tuo indirizzo email è stato confermato con successo.
|
||||
@@ -3984,6 +3993,9 @@ fr: &fr
|
||||
connect_to_docuseal_mcp: Se connecter à DocuSeal MCP
|
||||
add_the_following_to_your_mcp_client_configuration: Ajoutez ce qui suit à la configuration de votre client MCP
|
||||
works_with_claude_desktop_cursor_windsurf_vs_code_and_any_mcp_compatible_client: Fonctionne avec Claude Desktop, Cursor, Windsurf, VS Code et tout client compatible MCP.
|
||||
your_email_address_has_been_changed: Votre adresse e-mail a été modifiée
|
||||
the_email_address_for_your_account_has_been_changed_to_new_email: "L'adresse e-mail de votre compte a été modifiée en %{new_email}."
|
||||
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Si vous n'avez pas effectué ce changement, veuillez nous contacter en répondant à cet e-mail.
|
||||
devise:
|
||||
confirmations:
|
||||
confirmed: Votre adresse e-mail a été confirmée avec succès.
|
||||
@@ -5008,6 +5020,9 @@ pt: &pt
|
||||
connect_to_docuseal_mcp: Conectar ao DocuSeal MCP
|
||||
add_the_following_to_your_mcp_client_configuration: Adicione o seguinte à configuração do seu cliente MCP
|
||||
works_with_claude_desktop_cursor_windsurf_vs_code_and_any_mcp_compatible_client: Funciona com Claude Desktop, Cursor, Windsurf, VS Code e qualquer cliente compatível com MCP.
|
||||
your_email_address_has_been_changed: Seu endereço de e-mail foi alterado
|
||||
the_email_address_for_your_account_has_been_changed_to_new_email: O endereço de e-mail da sua conta foi alterado para %{new_email}.
|
||||
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Se você não fez essa alteração, entre em contato conosco respondendo a este e-mail.
|
||||
devise:
|
||||
confirmations:
|
||||
confirmed: Seu endereço de e-mail foi confirmado com sucesso.
|
||||
@@ -6032,6 +6047,9 @@ de: &de
|
||||
connect_to_docuseal_mcp: Mit DocuSeal MCP verbinden
|
||||
add_the_following_to_your_mcp_client_configuration: Fügen Sie Folgendes zu Ihrer MCP-Client-Konfiguration hinzu
|
||||
works_with_claude_desktop_cursor_windsurf_vs_code_and_any_mcp_compatible_client: Funktioniert mit Claude Desktop, Cursor, Windsurf, VS Code und jedem MCP-kompatiblen Client.
|
||||
your_email_address_has_been_changed: Ihre E-Mail-Adresse wurde geändert
|
||||
the_email_address_for_your_account_has_been_changed_to_new_email: Die E-Mail-Adresse Ihres Kontos wurde in %{new_email} geändert.
|
||||
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Wenn Sie diese Änderung nicht vorgenommen haben, kontaktieren Sie uns bitte, indem Sie auf diese E-Mail antworten.
|
||||
devise:
|
||||
confirmations:
|
||||
confirmed: Ihre E-Mail-Adresse wurde erfolgreich bestätigt.
|
||||
@@ -7441,6 +7459,9 @@ nl: &nl
|
||||
connect_to_docuseal_mcp: Verbinden met DocuSeal MCP
|
||||
add_the_following_to_your_mcp_client_configuration: Voeg het volgende toe aan uw MCP-clientconfiguratie
|
||||
works_with_claude_desktop_cursor_windsurf_vs_code_and_any_mcp_compatible_client: Werkt met Claude Desktop, Cursor, Windsurf, VS Code en elke MCP-compatibele client.
|
||||
your_email_address_has_been_changed: Je e-mailadres is gewijzigd
|
||||
the_email_address_for_your_account_has_been_changed_to_new_email: Het e-mailadres van je account is gewijzigd naar %{new_email}.
|
||||
if_you_did_not_make_this_change_please_contact_us_by_replying_to_this_email: Als je deze wijziging niet hebt aangebracht, neem dan contact met ons op door op deze e-mail te antwoorden.
|
||||
devise:
|
||||
confirmations:
|
||||
confirmed: Je e-mailadres is succesvol bevestigd.
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module HtmlToPlainText
|
||||
module_function
|
||||
|
||||
def call(html, line_length = 65)
|
||||
return '' if html.nil? || html.strip.empty?
|
||||
|
||||
cleaned = html.gsub(%r{<!-- start text/html -->.*?<!-- end text/html -->}m, '')
|
||||
|
||||
doc = Nokogiri::HTML.fragment(cleaned)
|
||||
|
||||
doc.css('script').each(&:remove)
|
||||
|
||||
result = process_nodes(doc, line_length)
|
||||
|
||||
result.gsub!(/\r\n?/, "\n")
|
||||
result.gsub!(/[ \t]*\u00A0+[ \t]*/, ' ')
|
||||
result.gsub!(/\n[ \t]+/, "\n")
|
||||
result.gsub!(/[ \t]+\n/, "\n")
|
||||
result.gsub!(/\n{3,}/, "\n\n")
|
||||
|
||||
result = word_wrap(result, line_length)
|
||||
|
||||
result.gsub!(/\(([ \n])(http[^)]+)([\n ])\)/) do
|
||||
"#{"\n" if ::Regexp.last_match(1) == "\n"}( #{::Regexp.last_match(2)} )#{"\n" if ::Regexp.last_match(3) == "\n"}"
|
||||
end
|
||||
|
||||
result.strip
|
||||
end
|
||||
|
||||
def process_nodes(node, line_length)
|
||||
result = +''
|
||||
|
||||
node.children.each do |child|
|
||||
case child
|
||||
when Nokogiri::XML::Text
|
||||
result << child.text
|
||||
when Nokogiri::XML::Comment
|
||||
next
|
||||
when Nokogiri::XML::Element
|
||||
result << process_element(child, line_length)
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def process_element(node, line_length)
|
||||
case node.name
|
||||
when 'br'
|
||||
"\n"
|
||||
when 'p', 'div'
|
||||
inner = process_nodes(node, line_length)
|
||||
inner.strip.empty? ? '' : "#{inner}\n\n"
|
||||
when 'img'
|
||||
node['alt'] || ''
|
||||
when 'a'
|
||||
process_link(node, line_length)
|
||||
when /\Ah([1-6])\z/
|
||||
process_heading(node, ::Regexp.last_match(1).to_i, line_length)
|
||||
when 'li'
|
||||
inner = process_nodes(node, line_length)
|
||||
"* #{inner.strip}\n"
|
||||
else
|
||||
process_nodes(node, line_length)
|
||||
end
|
||||
end
|
||||
|
||||
def process_link(node, line_length)
|
||||
text = process_nodes(node, line_length).strip
|
||||
return '' if text.empty?
|
||||
|
||||
href = node['href']
|
||||
href = href.sub(/\Amailto:/i, '') if href
|
||||
|
||||
if href.nil? || text.casecmp(href.strip) == 0
|
||||
text
|
||||
else
|
||||
"#{text} ( #{href.strip} )"
|
||||
end
|
||||
end
|
||||
|
||||
def process_heading(node, level, line_length)
|
||||
text = +''
|
||||
node.children.each do |child|
|
||||
text << if child.name == 'br'
|
||||
"\n"
|
||||
else
|
||||
child.text
|
||||
end
|
||||
end
|
||||
text.strip!
|
||||
|
||||
hlength = text.each_line.map { |l| l.strip.length }.max || 0
|
||||
hlength = line_length if hlength > line_length
|
||||
|
||||
decorated = case level
|
||||
when 1
|
||||
"#{'*' * hlength}\n#{text}\n#{'*' * hlength}"
|
||||
when 2
|
||||
"#{'-' * hlength}\n#{text}\n#{'-' * hlength}"
|
||||
else
|
||||
"#{text}\n#{'-' * hlength}"
|
||||
end
|
||||
|
||||
"\n\n#{decorated}\n\n"
|
||||
end
|
||||
|
||||
def word_wrap(txt, line_length)
|
||||
txt.split("\n").map do |line|
|
||||
if line.length > line_length
|
||||
line.gsub(/(.{1,#{line_length}})(\s+|$)/, "\\1\n").strip
|
||||
else
|
||||
line
|
||||
end
|
||||
end.join("\n")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,65 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module HtmlToPlainTextInterceptor
|
||||
module_function
|
||||
|
||||
def delivering_email(message)
|
||||
process(message)
|
||||
end
|
||||
|
||||
def previewing_email(message)
|
||||
process(message)
|
||||
end
|
||||
|
||||
def process(message)
|
||||
return message unless html_part(message)
|
||||
return message if message.text_part
|
||||
|
||||
add_text_part(message)
|
||||
|
||||
message
|
||||
end
|
||||
|
||||
def add_text_part(message)
|
||||
html = html_part(message).decoded
|
||||
text = HtmlToPlainText.call(html)
|
||||
|
||||
text_part = Mail::Part.new do
|
||||
content_type 'text/plain; charset=UTF-8'
|
||||
body text
|
||||
end
|
||||
|
||||
if pure_html_message?(message)
|
||||
message.body = nil
|
||||
message.content_type = 'multipart/alternative'
|
||||
message.add_part(text_part)
|
||||
message.add_part(Mail::Part.new do
|
||||
content_type 'text/html; charset=UTF-8'
|
||||
body html
|
||||
end)
|
||||
else
|
||||
alternative = Mail::Part.new(content_type: 'multipart/alternative')
|
||||
alternative.add_part(text_part)
|
||||
alternative.add_part(message.html_part)
|
||||
replace_part(message.parts, message.html_part, alternative)
|
||||
end
|
||||
end
|
||||
|
||||
def pure_html_message?(message)
|
||||
message.content_type.to_s.include?('text/html')
|
||||
end
|
||||
|
||||
def html_part(message)
|
||||
pure_html_message?(message) ? message : message.html_part
|
||||
end
|
||||
|
||||
def replace_part(parts, old_part, new_part)
|
||||
if (index = parts.index(old_part))
|
||||
parts[index] = new_part
|
||||
else
|
||||
parts.each do |part|
|
||||
replace_part(part.parts, old_part, new_part) if part.respond_to?(:parts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -236,9 +236,10 @@ module Submissions
|
||||
|
||||
page[:Annots] ||= []
|
||||
page[:Annots] = page[:Annots].try(:reject) do |e|
|
||||
next if e.is_a?(Integer) || e.is_a?(Symbol)
|
||||
next if e.is_a?(Integer) || e.is_a?(Symbol) || e.is_a?(HexaPDF::PDFArray)
|
||||
|
||||
e.present? && e[:A] && e[:A][:URI].to_s.starts_with?('file:///docuseal_field')
|
||||
e.present? && e[:A] && !e[:A].is_a?(HexaPDF::PDFArray) &&
|
||||
e[:A][:URI].to_s.starts_with?('file:///docuseal_field')
|
||||
end || page[:Annots]
|
||||
|
||||
width = page.box.width
|
||||
|
||||
@@ -10,7 +10,7 @@ module Templates
|
||||
pdf.pages.flat_map.with_index do |page, index|
|
||||
(page[:Annots] || []).filter_map do |annot|
|
||||
next if annot.blank?
|
||||
next if annot.is_a?(Integer) || annot.is_a?(Symbol)
|
||||
next if annot.is_a?(Integer) || annot.is_a?(Symbol) || annot.is_a?(HexaPDF::PDFArray)
|
||||
next if annot[:A].blank? || annot[:A][:URI].blank?
|
||||
next unless annot[:Subtype] == :Link
|
||||
next if !annot[:A][:URI].starts_with?('https://') && !annot[:A][:URI].starts_with?('http://')
|
||||
|
||||
+2
-1
@@ -11,7 +11,8 @@ module WebhookUrls
|
||||
'submission.expired' => SendSubmissionExpiredWebhookRequestJob,
|
||||
'submission.archived' => SendSubmissionArchivedWebhookRequestJob,
|
||||
'template.created' => SendTemplateCreatedWebhookRequestJob,
|
||||
'template.updated' => SendTemplateUpdatedWebhookRequestJob
|
||||
'template.updated' => SendTemplateUpdatedWebhookRequestJob,
|
||||
'template.archived' => SendTemplateArchivedWebhookRequestJob
|
||||
}.freeze
|
||||
|
||||
EVENT_TYPE_ID_KEYS = {
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe SendTemplateArchivedWebhookRequestJob do
|
||||
let(:account) { create(:account) }
|
||||
let(:user) { create(:user, account:) }
|
||||
let(:template) { create(:template, account:, author: user) }
|
||||
let(:webhook_url) { create(:webhook_url, account:, events: ['template.archived']) }
|
||||
|
||||
before do
|
||||
create(:encrypted_config, key: EncryptedConfig::ESIGN_CERTS_KEY,
|
||||
value: GenerateCertificate.call.transform_values(&:to_pem))
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
around do |example|
|
||||
freeze_time { example.run }
|
||||
end
|
||||
|
||||
before do
|
||||
stub_request(:post, webhook_url.url).to_return(status: 200)
|
||||
end
|
||||
|
||||
it 'sends a webhook request' do
|
||||
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id,
|
||||
'event_uuid' => SecureRandom.uuid)
|
||||
|
||||
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||
body: {
|
||||
'event_type' => 'template.archived',
|
||||
'timestamp' => /.*/,
|
||||
'data' => template.reload.as_json(only: %i[id archived_at])
|
||||
},
|
||||
headers: {
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => 'DocuSeal.com Webhook'
|
||||
}
|
||||
).once
|
||||
end
|
||||
|
||||
it 'sends a webhook request with the secret' do
|
||||
webhook_url.update(secret: { 'X-Secret-Header' => 'secret_value' })
|
||||
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id,
|
||||
'event_uuid' => SecureRandom.uuid)
|
||||
|
||||
expect(WebMock).to have_requested(:post, webhook_url.url).with(
|
||||
body: {
|
||||
'event_type' => 'template.archived',
|
||||
'timestamp' => /.*/,
|
||||
'data' => template.reload.as_json(only: %i[id archived_at])
|
||||
},
|
||||
headers: {
|
||||
'Content-Type' => 'application/json',
|
||||
'User-Agent' => 'DocuSeal.com Webhook',
|
||||
'X-Secret-Header' => 'secret_value'
|
||||
}
|
||||
).once
|
||||
end
|
||||
|
||||
it "doesn't send a webhook request if the event is not in the webhook's events" do
|
||||
webhook_url.update!(events: ['template.updated'])
|
||||
|
||||
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id,
|
||||
'event_uuid' => SecureRandom.uuid)
|
||||
|
||||
expect(WebMock).not_to have_requested(:post, webhook_url.url)
|
||||
end
|
||||
|
||||
it 'sends again if the response status is 400 or higher' do
|
||||
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||
|
||||
event_uuid = SecureRandom.uuid
|
||||
|
||||
expect do
|
||||
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id,
|
||||
'event_uuid' => event_uuid)
|
||||
end.to change(described_class.jobs, :size).by(1)
|
||||
|
||||
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||
|
||||
args = described_class.jobs.last['args'].first
|
||||
|
||||
expect(args['attempt']).to eq(1)
|
||||
expect(args['last_status']).to eq(401)
|
||||
expect(args['event_uuid']).to eq(event_uuid)
|
||||
expect(args['webhook_url_id']).to eq(webhook_url.id)
|
||||
expect(args['template_id']).to eq(template.id)
|
||||
end
|
||||
|
||||
it "doesn't send again if the max attempts is reached" do
|
||||
stub_request(:post, webhook_url.url).to_return(status: 401)
|
||||
|
||||
expect do
|
||||
described_class.new.perform('template_id' => template.id, 'webhook_url_id' => webhook_url.id,
|
||||
'event_uuid' => SecureRandom.uuid, 'attempt' => 11)
|
||||
end.not_to change(described_class.jobs, :size)
|
||||
|
||||
expect(WebMock).to have_requested(:post, webhook_url.url).once
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -23,7 +23,7 @@ RSpec.describe 'Sign In' do
|
||||
fill_in 'Password', with: 'wrong_password'
|
||||
click_button 'Sign In'
|
||||
|
||||
expect(page).to have_content('Invalid Email or password')
|
||||
expect(page).to have_content('Invalid email or password')
|
||||
expect(page).not_to have_content('Document Templates')
|
||||
end
|
||||
end
|
||||
@@ -51,7 +51,7 @@ RSpec.describe 'Sign In' do
|
||||
fill_in 'Two-Factor Code from Authenticator App', with: '123456'
|
||||
click_button 'Sign In'
|
||||
|
||||
expect(page).to have_content('Invalid Email or password')
|
||||
expect(page).to have_content('Invalid email or password')
|
||||
expect(page).not_to have_content('Document Templates')
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user