From 3ee41975fdefac63e8806c9790b66679024c112a Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 15 Oct 2024 19:16:09 +0300 Subject: [PATCH 01/15] add documents preview endpoint --- .../api/submission_documents_controller.rb | 45 +++++++++ app/models/submission.rb | 2 + config/routes.rb | 1 + .../generate_preview_attachments.rb | 96 +++++++++++++++++++ .../generate_result_attachments.rb | 44 +++++---- 5 files changed, 167 insertions(+), 21 deletions(-) create mode 100644 app/controllers/api/submission_documents_controller.rb create mode 100644 lib/submissions/generate_preview_attachments.rb diff --git a/app/controllers/api/submission_documents_controller.rb b/app/controllers/api/submission_documents_controller.rb new file mode 100644 index 000000000..aa3021c5d --- /dev/null +++ b/app/controllers/api/submission_documents_controller.rb @@ -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 diff --git a/app/models/submission.rb b/app/models/submission.rb index ff6efa8f5..3972b6e5b 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -55,6 +55,8 @@ class Submission < ApplicationRecord has_one_attached :audit_trail has_one_attached :combined_document + has_many_attached :preview_documents + has_many :template_schema_documents, ->(e) { where(uuid: (e.template_schema.presence || e.template.schema).pluck('attachment_uuid')) }, through: :template, source: :documents_attachments diff --git a/config/routes.rb b/config/routes.rb index a9e383cc9..3d10d04db 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -34,6 +34,7 @@ resources :submitter_form_views, only: %i[create] resources :submitters, only: %i[index show update] resources :submissions, only: %i[index show create destroy] do + resources :documents, only: %i[index], controller: 'submission_documents' collection do resources :init, only: %i[create], controller: 'submissions' resources :emails, only: %i[create], controller: 'submissions', as: :submissions_emails diff --git a/lib/submissions/generate_preview_attachments.rb b/lib/submissions/generate_preview_attachments.rb new file mode 100644 index 000000000..b2c9ea27d --- /dev/null +++ b/lib/submissions/generate_preview_attachments.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +module Submissions + module GeneratePreviewAttachments + module_function + + # rubocop:disable Metrics + def call(submission, values_hash: nil) + values_hash ||= build_values_hash(submission) + + with_signature_id = submission.account.account_configs + .exists?(key: AccountConfig::WITH_SIGNATURE_ID, value: true) + + is_flatten = + submission.account.account_configs + .find_or_initialize_by(key: AccountConfig::FLATTEN_RESULT_PDF_KEY).value != false + + pdfs_index = GenerateResultAttachments.build_pdfs_index(submission, flatten: is_flatten) + + submission.submitters.where(completed_at: nil).preload(attachments_attachments: :blob).each do |submitter| + GenerateResultAttachments.fill_submitter_fields(submitter, submission.account, pdfs_index, + with_signature_id:, is_flatten:) + end + + template = submission.template + + image_pdfs = [] + original_documents = template.documents.preload(:blob) + + result_attachments = + submission.template_schema.map do |item| + pdf = pdfs_index[item['attachment_uuid']] + + if original_documents.find { |a| a.uuid == item['attachment_uuid'] }.image? + pdf = GenerateResultAttachments.normalize_image_pdf(pdf) + + image_pdfs << pdf + end + + build_pdf_attachment(pdf:, submission:, + uuid: item['attachment_uuid'], + values_hash:, + name: item['name']) + end + + return ApplicationRecord.no_touching { result_attachments.map { |e| e.tap(&:save!) } } if image_pdfs.size < 2 + + images_pdf = + image_pdfs.each_with_object(HexaPDF::Document.new) do |pdf, doc| + pdf.pages.each { |page| doc.pages << doc.import(page) } + end + + images_pdf = GenerateResultAttachments.normalize_image_pdf(images_pdf) + + images_pdf_attachment = + build_pdf_attachment( + pdf: images_pdf, + submission:, + uuid: GenerateResultAttachments.images_pdf_uuid(original_documents.select(&:image?)), + values_hash:, + name: template.name + ) + + ApplicationRecord.no_touching do + (result_attachments + [images_pdf_attachment]).map { |e| e.tap(&:save!) } + end + end + + def build_values_hash(submission) + submission.submitters.reduce({}) { |acc, s| acc.merge(s.values) }.hash + end + + def build_pdf_attachment(pdf:, submission:, uuid:, name:, values_hash:) + io = StringIO.new + + begin + pdf.write(io, incremental: true, validate: false) + rescue HexaPDF::MalformedPDFError => e + Rollbar.error(e) if defined?(Rollbar) + + pdf.write(io, incremental: false, validate: false) + end + + ActiveStorage::Attachment.new( + blob: ActiveStorage::Blob.create_and_upload!(io: io.tap(&:rewind), filename: "#{name}.pdf"), + metadata: { original_uuid: uuid, + values_hash:, + analyzed: true, + sha256: Base64.urlsafe_encode64(Digest::SHA256.digest(io.string)) }, + name: 'preview_documents', + record: submission + ) + end + # rubocop:enable Metrics + end +end diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index c396ba31b..1bcd7a537 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -92,19 +92,13 @@ def call(submitter) end def generate_pdfs(submitter) - cell_layouter = HexaPDF::Layout::TextLayouter.new(text_valign: :center, text_align: :center) - - with_signature_id = submitter.account.account_configs - .exists?(key: AccountConfig::WITH_SIGNATURE_ID, value: true) + configs = submitter.account.account_configs.where(key: [AccountConfig::FLATTEN_RESULT_PDF_KEY, + AccountConfig::WITH_SIGNATURE_ID]) - is_flatten = - submitter.account.account_configs - .find_or_initialize_by(key: AccountConfig::FLATTEN_RESULT_PDF_KEY).value != false - - account = submitter.account - attachments_data_cache = {} + with_signature_id = configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID }&.value == true + is_flatten = configs.find { |c| c.key == AccountConfig::FLATTEN_RESULT_PDF_KEY }&.value != false - pdfs_index = build_pdfs_index(submitter, flatten: is_flatten) + pdfs_index = build_pdfs_index(submitter.submission, submitter:, flatten: is_flatten) if with_signature_id || submitter.account.testing? pdfs_index.each_value do |pdf| @@ -145,6 +139,16 @@ def generate_pdfs(submitter) end end + fill_submitter_fields(submitter, submitter.account, pdfs_index, with_signature_id:, is_flatten:) + + pdfs_index + end + + def fill_submitter_fields(submitter, account, pdfs_index, with_signature_id:, is_flatten:) + cell_layouter = HexaPDF::Layout::TextLayouter.new(text_valign: :center, text_align: :center) + + attachments_data_cache = {} + submitter.submission.template_fields.each do |field| next if field['submitter_uuid'] != submitter.uuid @@ -442,8 +446,6 @@ def generate_pdfs(submitter) end end end - - pdfs_index end def build_pdf_attachment(pdf:, submitter:, pkcs:, tsa_url:, uuid:, name:) @@ -514,13 +516,13 @@ def images_pdf_uuid(attachments) Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, attachments.map(&:uuid).sort.join(':')) end - def build_pdfs_index(submitter, flatten: true) - latest_submitter = find_last_submitter(submitter) + def build_pdfs_index(submission, submitter: nil, flatten: true) + latest_submitter = find_last_submitter(submission, submitter:) Submissions::EnsureResultGenerated.call(latest_submitter) if latest_submitter documents = latest_submitter&.documents&.preload(:blob).to_a.presence - documents ||= submitter.submission.template_schema_documents.preload(:blob) + documents ||= submission.template_schema_documents.preload(:blob) documents.to_h do |attachment| pdf = @@ -582,11 +584,11 @@ def on_missing_glyph(character, font_wrapper) font_wrapper.custom_glyph(replace_with, character) end - def find_last_submitter(submitter) - submitter.submission.submitters - .select(&:completed_at?) - .select { |e| e.id != submitter.id && e.completed_at <= submitter.completed_at } - .max_by(&:completed_at) + def find_last_submitter(submission, submitter: nil) + submission.submitters + .select(&:completed_at?) + .select { |e| submitter.nil? ? true : e.id != submitter.id && e.completed_at <= submitter.completed_at } + .max_by(&:completed_at) end def build_pdf_from_image(attachment) From 386f130d7aea6311015b92ed0502c5ca9d3fce66 Mon Sep 17 00:00:00 2001 From: Oleksandr Turchyn Date: Tue, 15 Oct 2024 19:24:30 +0300 Subject: [PATCH 02/15] allow to paste date in date field --- app/javascript/submission_form/date_step.vue | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/javascript/submission_form/date_step.vue b/app/javascript/submission_form/date_step.vue index 7215bbaee..fcd9cc0c8 100644 --- a/app/javascript/submission_form/date_step.vue +++ b/app/javascript/submission_form/date_step.vue @@ -45,6 +45,7 @@ :name="`values[${field.uuid}]`" @keydown.enter="onEnter" @focus="$emit('focus')" + @paste="onPaste" > @@ -98,6 +99,25 @@ export default { 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 () { const inputEl = this.$refs.input From 6568f81ee93d07370705f6702d9484dfaf9640d3 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 15 Oct 2024 20:49:19 +0300 Subject: [PATCH 03/15] adjust pdf generation --- lib/submissions/generate_preview_attachments.rb | 2 +- lib/submissions/generate_result_attachments.rb | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/submissions/generate_preview_attachments.rb b/lib/submissions/generate_preview_attachments.rb index b2c9ea27d..94c836c3f 100644 --- a/lib/submissions/generate_preview_attachments.rb +++ b/lib/submissions/generate_preview_attachments.rb @@ -28,7 +28,7 @@ def call(submission, values_hash: nil) original_documents = template.documents.preload(:blob) result_attachments = - submission.template_schema.map do |item| + (submission.template_schema || template.schema).map do |item| pdf = pdfs_index[item['attachment_uuid']] if original_documents.find { |a| a.uuid == item['attachment_uuid'] }.image? diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index 1bcd7a537..f1ef7fb75 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -140,8 +140,6 @@ def generate_pdfs(submitter) end fill_submitter_fields(submitter, submitter.account, pdfs_index, with_signature_id:, is_flatten:) - - pdfs_index end def fill_submitter_fields(submitter, account, pdfs_index, with_signature_id:, is_flatten:) @@ -149,6 +147,8 @@ def fill_submitter_fields(submitter, account, pdfs_index, with_signature_id:, is attachments_data_cache = {} + return pdfs_index if submitter.submission.template_fields.blank? + submitter.submission.template_fields.each do |field| next if field['submitter_uuid'] != submitter.uuid @@ -446,6 +446,8 @@ def fill_submitter_fields(submitter, account, pdfs_index, with_signature_id:, is end end end + + pdfs_index end def build_pdf_attachment(pdf:, submitter:, pkcs:, tsa_url:, uuid:, name:) From e0e79e343d0fbdc9dfd84684a2f8fcaa3a3426bd Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 16 Oct 2024 12:16:20 +0300 Subject: [PATCH 04/15] update email --- SECURITY.md | 2 +- lib/accounts.rb | 2 +- lib/docuseal.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 0eef4d027..b1692488c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,6 +1,6 @@ # 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.** diff --git a/lib/accounts.rb b/lib/accounts.rb index d6088bab3..644032b1e 100644 --- a/lib/accounts.rb +++ b/lib/accounts.rb @@ -11,7 +11,7 @@ def create_duplicate(account) new_user.uuid = SecureRandom.uuid new_user.account = new_account 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| new_template = template.dup diff --git a/lib/docuseal.rb b/lib/docuseal.rb index 2b3e283df..9ea6f35ec 100644 --- a/lib/docuseal.rb +++ b/lib/docuseal.rb @@ -12,7 +12,7 @@ module Docuseal TWITTER_URL = 'https://twitter.com/docusealco' TWITTER_HANDLE = '@docusealco' CHATGPT_URL = 'https://chatgpt.com/g/g-9hg8AAw0r-docuseal' - SUPPORT_EMAIL = 'support@docuseal.co' + SUPPORT_EMAIL = 'support@docuseal.com' HOST = ENV.fetch('HOST', 'localhost') AATL_CERT_NAME = 'docuseal_aatl' CONSOLE_URL = if Rails.env.development? From ce70016603acd3ef328b6ba5df5dc19f6f61d420 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 16 Oct 2024 23:58:37 +0300 Subject: [PATCH 05/15] update rails --- Gemfile.lock | 112 +++++++++++++++++++++++++-------------------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index c4f222dfb..44d78ef98 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,29 +1,29 @@ GEM remote: https://rubygems.org/ specs: - actioncable (7.2.1) - actionpack (= 7.2.1) - activesupport (= 7.2.1) + actioncable (7.2.1.1) + actionpack (= 7.2.1.1) + activesupport (= 7.2.1.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.2.1) - actionpack (= 7.2.1) - activejob (= 7.2.1) - activerecord (= 7.2.1) - activestorage (= 7.2.1) - activesupport (= 7.2.1) + actionmailbox (7.2.1.1) + actionpack (= 7.2.1.1) + activejob (= 7.2.1.1) + activerecord (= 7.2.1.1) + activestorage (= 7.2.1.1) + activesupport (= 7.2.1.1) mail (>= 2.8.0) - actionmailer (7.2.1) - actionpack (= 7.2.1) - actionview (= 7.2.1) - activejob (= 7.2.1) - activesupport (= 7.2.1) + actionmailer (7.2.1.1) + actionpack (= 7.2.1.1) + actionview (= 7.2.1.1) + activejob (= 7.2.1.1) + activesupport (= 7.2.1.1) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (7.2.1) - actionview (= 7.2.1) - activesupport (= 7.2.1) + actionpack (7.2.1.1) + actionview (= 7.2.1.1) + activesupport (= 7.2.1.1) nokogiri (>= 1.8.5) racc rack (>= 2.2.4, < 3.2) @@ -32,35 +32,35 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (7.2.1) - actionpack (= 7.2.1) - activerecord (= 7.2.1) - activestorage (= 7.2.1) - activesupport (= 7.2.1) + actiontext (7.2.1.1) + actionpack (= 7.2.1.1) + activerecord (= 7.2.1.1) + activestorage (= 7.2.1.1) + activesupport (= 7.2.1.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.2.1) - activesupport (= 7.2.1) + actionview (7.2.1.1) + activesupport (= 7.2.1.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (7.2.1) - activesupport (= 7.2.1) + activejob (7.2.1.1) + activesupport (= 7.2.1.1) globalid (>= 0.3.6) - activemodel (7.2.1) - activesupport (= 7.2.1) - activerecord (7.2.1) - activemodel (= 7.2.1) - activesupport (= 7.2.1) + activemodel (7.2.1.1) + activesupport (= 7.2.1.1) + activerecord (7.2.1.1) + activemodel (= 7.2.1.1) + activesupport (= 7.2.1.1) timeout (>= 0.4.0) - activestorage (7.2.1) - actionpack (= 7.2.1) - activejob (= 7.2.1) - activerecord (= 7.2.1) - activesupport (= 7.2.1) + activestorage (7.2.1.1) + actionpack (= 7.2.1.1) + activejob (= 7.2.1.1) + activerecord (= 7.2.1.1) + activesupport (= 7.2.1.1) marcel (~> 1.0) - activesupport (7.2.1) + activesupport (7.2.1.1) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) @@ -320,7 +320,7 @@ GEM mysql2 (0.5.6) net-http-persistent (4.0.4) connection_pool (~> 2.2) - net-imap (0.4.16) + net-imap (0.4.17) date net-protocol net-pop (0.1.2) @@ -372,7 +372,7 @@ GEM puma (6.4.3) nio4r (~> 2.0) racc (1.8.1) - rack (3.1.7) + rack (3.1.8) rack-proxy (0.7.7) rack rack-session (2.0.0) @@ -382,20 +382,20 @@ GEM rackup (2.1.0) rack (>= 3) webrick (~> 1.8) - rails (7.2.1) - actioncable (= 7.2.1) - actionmailbox (= 7.2.1) - actionmailer (= 7.2.1) - actionpack (= 7.2.1) - actiontext (= 7.2.1) - actionview (= 7.2.1) - activejob (= 7.2.1) - activemodel (= 7.2.1) - activerecord (= 7.2.1) - activestorage (= 7.2.1) - activesupport (= 7.2.1) + rails (7.2.1.1) + actioncable (= 7.2.1.1) + actionmailbox (= 7.2.1.1) + actionmailer (= 7.2.1.1) + actionpack (= 7.2.1.1) + actiontext (= 7.2.1.1) + actionview (= 7.2.1.1) + activejob (= 7.2.1.1) + activemodel (= 7.2.1.1) + activerecord (= 7.2.1.1) + activestorage (= 7.2.1.1) + activesupport (= 7.2.1.1) bundler (>= 1.15.0) - railties (= 7.2.1) + railties (= 7.2.1.1) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -410,9 +410,9 @@ GEM actionview (> 3.1) activesupport (> 3.1) railties (> 3.1) - railties (7.2.1) - actionpack (= 7.2.1) - activesupport (= 7.2.1) + railties (7.2.1.1) + actionpack (= 7.2.1.1) + activesupport (= 7.2.1.1) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -559,7 +559,7 @@ GEM websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.18) + zeitwerk (2.7.0) PLATFORMS aarch64-linux From 1fc1e9af16da70e992a1742ba34f8bfca12e1fe0 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 18 Oct 2024 16:38:51 +0300 Subject: [PATCH 06/15] fix font size --- app/javascript/submission_form/area.vue | 3 ++- app/views/submissions/_value.html.erb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/javascript/submission_form/area.vue b/app/javascript/submission_form/area.vue index fb8de7c5d..c77222e6a 100644 --- a/app/javascript/submission_form/area.vue +++ b/app/javascript/submission_form/area.vue @@ -405,7 +405,8 @@ export default { } 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 diff --git a/app/views/submissions/_value.html.erb b/app/views/submissions/_value.html.erb index 9ecdbfef1..9d0f09543 100644 --- a/app/views/submissions/_value.html.erb +++ b/app/views/submissions/_value.html.erb @@ -1,5 +1,5 @@ <% align = field.dig('preferences', 'align') %> -"> +"> <% if field['type'] == 'signature' %>
From 48f3b13f3ec52723ece707cb18b93f64881e4882 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 18 Oct 2024 22:58:21 +0300 Subject: [PATCH 07/15] adjust smtp from --- lib/action_mailer_configs_interceptor.rb | 6 +++++- lib/action_mailer_events_observer.rb | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/action_mailer_configs_interceptor.rb b/lib/action_mailer_configs_interceptor.rb index b868fc277..f4af2002b 100644 --- a/lib/action_mailer_configs_interceptor.rb +++ b/lib/action_mailer_configs_interceptor.rb @@ -16,7 +16,11 @@ def delivering_email(message) end if Rails.env.production? && Rails.application.config.action_mailer.delivery_method - message.from = ENV.fetch('SMTP_FROM') + message.from = ENV.fetch('SMTP_FROM').to_s.split(',').sample + + if message.from == 'DocuSeal ' + message.body.raw_source.gsub!('https://docuseal.co/', 'https://docuseal.com/') + end return message end diff --git a/lib/action_mailer_events_observer.rb b/lib/action_mailer_events_observer.rb index c7786bda2..273564ac2 100644 --- a/lib/action_mailer_events_observer.rb +++ b/lib/action_mailer_events_observer.rb @@ -22,7 +22,7 @@ def delivered_email(mail) emailable_type:, event_type: :send, email:, - data: { method: mail.delivery_method.class.name.underscore }, + data: { from: mail.from, method: mail.delivery_method.class.name.underscore }, event_datetime: Time.current ) end From 1d20c79cf4041cb5848615c39718d5946373c0a8 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 18 Oct 2024 23:15:55 +0300 Subject: [PATCH 08/15] com host --- app/controllers/application_controller.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0e052a87c..da40c11b6 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -38,6 +38,10 @@ class ApplicationController < ActionController::Base end 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 end From 2948910c252caaeb9fb8664d84cb82c2d58e4bc7 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 19 Oct 2024 09:41:13 +0300 Subject: [PATCH 09/15] fix com selector --- app/views/devise/shared/_select_server.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/devise/shared/_select_server.html.erb b/app/views/devise/shared/_select_server.html.erb index cea25f452..60f5e56ae 100644 --- a/app/views/devise/shared/_select_server.html.erb +++ b/app/views/devise/shared/_select_server.html.erb @@ -1,6 +1,6 @@
- + <%= svg_icon 'world', class: 'w-5 h-5' %> Global From 64b39b53a23d073916d76dac0240142b70cad2bb Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 19 Oct 2024 09:49:54 +0300 Subject: [PATCH 10/15] fix com from --- lib/action_mailer_configs_interceptor.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/action_mailer_configs_interceptor.rb b/lib/action_mailer_configs_interceptor.rb index f4af2002b..0eeddfa7c 100644 --- a/lib/action_mailer_configs_interceptor.rb +++ b/lib/action_mailer_configs_interceptor.rb @@ -16,10 +16,14 @@ def delivering_email(message) end if Rails.env.production? && Rails.application.config.action_mailer.delivery_method - message.from = ENV.fetch('SMTP_FROM').to_s.split(',').sample + from = ENV.fetch('SMTP_FROM').to_s.split(',').sample - if message.from == 'DocuSeal ' - message.body.raw_source.gsub!('https://docuseal.co/', 'https://docuseal.com/') + message.from = from + + if from == 'DocuSeal ' + message.body.instance_variable_set( + :@raw_source, message.body.raw_source.gsub('https://docuseal.co/', 'https://docuseal.com/') + ) end return message From 3ebaacbb7ae16f6f6a5cb50352f046ca7e97d7a5 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 20 Oct 2024 09:48:39 +0300 Subject: [PATCH 11/15] locale date format --- app/javascript/template_builder/builder.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index 284c9ff51..ac671e9fa 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -693,6 +693,7 @@ export default { 'de-DE': 'DD.MM.YYYY', 'fr-FR': 'DD/MM/YYYY', 'it-IT': 'DD/MM/YYYY', + 'en-GB': 'DD/MM/YYYY', 'es-ES': 'DD/MM/YYYY' } }, From 6f90147a11edb5d677f8fd883adb77f2371c69a0 Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Tue, 15 Oct 2024 20:10:47 +0300 Subject: [PATCH 12/15] validate the uniqueness of submitter roles --- lib/params/base_validator.rb | 9 ++++++++ lib/params/submission_create_validator.rb | 1 + spec/requests/submissions_spec.rb | 27 +++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/lib/params/base_validator.rb b/lib/params/base_validator.rb index cc0373008..100b0e436 100644 --- a/lib/params/base_validator.rb +++ b/lib/params/base_validator.rb @@ -65,6 +65,15 @@ def format(params, key, regexp, message: nil) raise_error(message || "#{key} must follow the #{regexp.source} format") end + def unique(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).uniq.size == params.pluck(key).size + + raise_error(message || "#{key} must be unique") + end + def in_path(params, path = []) old_path = @current_path diff --git a/lib/params/submission_create_validator.rb b/lib/params/submission_create_validator.rb index 7ea764019..1d830d135 100644 --- a/lib/params/submission_create_validator.rb +++ b/lib/params/submission_create_validator.rb @@ -60,6 +60,7 @@ def validate_creation_from_submitters(params) if params[:submitters].present? in_path(params, :submitters) do |submitters_params| type(submitters_params, 0, Hash) + unique(submitters_params, :role) end end diff --git a/spec/requests/submissions_spec.rb b/spec/requests/submissions_spec.rb index 81cdb6c28..23544cc06 100644 --- a/spec/requests/submissions_spec.rb +++ b/spec/requests/submissions_spec.rb @@ -56,6 +56,33 @@ expect(response.parsed_body).to eq(JSON.parse(create_submission_body(submission).to_json)) end + + it 'returns an error if the template fields are missing' do + templates[0].update(fields: []) + + post '/api/submissions', headers: { 'x-auth-token': author.access_token.token }, params: { + template_id: templates[0].id, + send_email: true, + submitters: [{ role: 'First Role', email: 'john.doe@example.com' }] + }.to_json + + expect(response).to have_http_status(:unprocessable_entity) + expect(response.parsed_body).to eq({ 'error' => 'Template does not contain fields' }) + end + + it 'returns an error if submitter roles are not unique' do + post '/api/submissions', headers: { 'x-auth-token': author.access_token.token }, params: { + template_id: templates[0].id, + send_email: true, + submitters: [ + { role: 'First Role', email: 'john.doe@example.com' }, + { role: 'First Role', email: 'jane.doe@example.com' } + ] + }.to_json + + expect(response).to have_http_status(:unprocessable_entity) + expect(response.parsed_body).to eq({ 'error' => 'role must be unique in `submitters`.' }) + end end describe 'POST /api/submissions/emails' do From 178ee470933f6b4945538a10722d36f569df25e3 Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Tue, 15 Oct 2024 21:44:15 +0300 Subject: [PATCH 13/15] add more specs --- lib/params/base_validator.rb | 4 +- lib/params/submission_create_validator.rb | 2 +- spec/factories/templates.rb | 75 +++++++++++++---------- spec/requests/submissions_spec.rb | 28 ++++++++- spec/requests/templates_spec.rb | 8 +-- 5 files changed, 77 insertions(+), 40 deletions(-) diff --git a/lib/params/base_validator.rb b/lib/params/base_validator.rb index 100b0e436..964ad553c 100644 --- a/lib/params/base_validator.rb +++ b/lib/params/base_validator.rb @@ -65,11 +65,11 @@ def format(params, key, regexp, message: nil) raise_error(message || "#{key} must follow the #{regexp.source} format") end - def unique(params, key, message: nil) + 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).uniq.size == params.pluck(key).size + return if params.pluck(key).compact_blank.uniq.size == params.pluck(key).compact_blank.size raise_error(message || "#{key} must be unique") end diff --git a/lib/params/submission_create_validator.rb b/lib/params/submission_create_validator.rb index 1d830d135..0658aac24 100644 --- a/lib/params/submission_create_validator.rb +++ b/lib/params/submission_create_validator.rb @@ -60,7 +60,7 @@ def validate_creation_from_submitters(params) if params[:submitters].present? in_path(params, :submitters) do |submitters_params| type(submitters_params, 0, Hash) - unique(submitters_params, :role) + unique_value(submitters_params, :role) end end diff --git a/spec/factories/templates.rb b/spec/factories/templates.rb index d1c433d2e..f4f2ff624 100644 --- a/spec/factories/templates.rb +++ b/spec/factories/templates.rb @@ -7,7 +7,11 @@ author factory: %i[user] name { Faker::Book.title } - after(:create) do |template| + transient do + submitter_count { 1 } + end + + after(:create) do |template, evaluator| blob = ActiveStorage::Blob.create_and_upload!( io: Rails.root.join('spec/fixtures/sample-document.pdf').open, filename: 'sample-document.pdf', @@ -22,39 +26,46 @@ Templates::ProcessDocument.call(attachment, attachment.download) template.schema = [{ attachment_uuid: attachment.uuid, name: 'sample-document' }] - template.submitters = [ - { - 'name' => 'First Party', - 'uuid' => '513848eb-1096-4abc-a743-68596b5aaa4c' - } - ] - template.fields = [ - { - 'uuid' => '21637fc9-0655-45df-8952-04ec64949e85', - 'submitter_uuid' => '513848eb-1096-4abc-a743-68596b5aaa4c', - 'name' => 'First Name', - 'type' => 'text', - 'required' => true, - 'areas' => [ - { - 'x' => 0.09027777777777778, - 'y' => 0.1197252208047105, - 'w' => 0.3069444444444444, - 'h' => 0.03336604514229637, - 'attachment_uuid' => attachment.uuid, - 'page' => 0 - } - ] - }, + number_words = %w[first second third fourth fifth sixth seventh eighth ninth tenth] + + template.submitters = Array.new(evaluator.submitter_count) do |i| { - 'uuid' => '1f97f8e3-dc82-4586-aeea-6ebed6204e46', - 'submitter_uuid' => '513848eb-1096-4abc-a743-68596b5aaa4c', - 'name' => '', - 'type' => 'signature', - 'required' => true, - 'areas' => [] + 'name' => "#{number_words[i]&.capitalize} Party", + 'uuid' => SecureRandom.uuid } - ] + end + + template.fields = template.submitters.reduce([]) do |fields, submitter| + fields += [ + { + 'uuid' => SecureRandom.uuid, + 'submitter_uuid' => submitter['uuid'], + 'name' => 'First Name', + 'type' => 'text', + 'required' => true, + 'areas' => [ + { + 'x' => 0.09027777777777778, + 'y' => 0.1197252208047105, + 'w' => 0.3069444444444444, + 'h' => 0.03336604514229637, + 'attachment_uuid' => attachment.uuid, + 'page' => 0 + } + ] + }, + { + 'uuid' => SecureRandom.uuid, + 'submitter_uuid' => submitter['uuid'], + 'name' => '', + 'type' => 'signature', + 'required' => true, + 'areas' => [] + } + ] + + fields + end template.save! end diff --git a/spec/requests/submissions_spec.rb b/spec/requests/submissions_spec.rb index 23544cc06..5062662c7 100644 --- a/spec/requests/submissions_spec.rb +++ b/spec/requests/submissions_spec.rb @@ -7,6 +7,7 @@ let!(:author) { create(:user, account:) } let!(:folder) { create(:template_folder, account:) } let!(:templates) { create_list(:template, 2, account:, author:, folder:) } + let!(:multiple_submitters_template) { create(:template, submitter_count: 3, account:, author:, folder:) } describe 'GET /api/submissions' do it 'returns a list of submissions' do @@ -57,6 +58,31 @@ expect(response.parsed_body).to eq(JSON.parse(create_submission_body(submission).to_json)) end + it 'creates a submission when some submitter roles are not provided' do + post '/api/submissions', headers: { 'x-auth-token': author.access_token.token }, params: { + template_id: multiple_submitters_template.id, + send_email: true, + submitters: [ + { role: 'First Role', email: 'john.doe@example.com' }, + { email: 'jane.doe@example.com' }, + { email: 'mike.doe@example.com' } + ] + }.to_json + + expect(response).to have_http_status(:ok) + + submission = Submission.last + + expect(response.parsed_body).to eq(JSON.parse(create_submission_body(submission).to_json)) + expect(response.parsed_body).to eq(JSON.parse(create_submission_body(submission).to_json)) + expect(response.parsed_body[0]['role']).to eq('First Party') + expect(response.parsed_body[0]['email']).to eq('john.doe@example.com') + expect(response.parsed_body[1]['role']).to eq('Second Party') + expect(response.parsed_body[1]['email']).to eq('jane.doe@example.com') + expect(response.parsed_body[2]['role']).to eq('Third Party') + expect(response.parsed_body[2]['email']).to eq('mike.doe@example.com') + end + it 'returns an error if the template fields are missing' do templates[0].update(fields: []) @@ -72,7 +98,7 @@ it 'returns an error if submitter roles are not unique' do post '/api/submissions', headers: { 'x-auth-token': author.access_token.token }, params: { - template_id: templates[0].id, + template_id: multiple_submitters_template.id, send_email: true, submitters: [ { role: 'First Role', email: 'john.doe@example.com' }, diff --git a/spec/requests/templates_spec.rb b/spec/requests/templates_spec.rb index aa4951bfc..67430ed80 100644 --- a/spec/requests/templates_spec.rb +++ b/spec/requests/templates_spec.rb @@ -144,8 +144,8 @@ def template_body(template) name: template.name, fields: [ { - 'uuid' => '21637fc9-0655-45df-8952-04ec64949e85', - 'submitter_uuid' => '513848eb-1096-4abc-a743-68596b5aaa4c', + 'uuid' => template.fields[0]['uuid'], + 'submitter_uuid' => template.submitters[0]['uuid'], 'name' => 'First Name', 'type' => 'text', 'required' => true, @@ -161,8 +161,8 @@ def template_body(template) ] }, { - 'uuid' => '1f97f8e3-dc82-4586-aeea-6ebed6204e46', - 'submitter_uuid' => '513848eb-1096-4abc-a743-68596b5aaa4c', + 'uuid' => template.fields[1]['uuid'], + 'submitter_uuid' => template.submitters[0]['uuid'], 'name' => '', 'type' => 'signature', 'required' => true, From ab810be25023809334f69849bd96ee838a71152b Mon Sep 17 00:00:00 2001 From: Alex Turchyn Date: Wed, 16 Oct 2024 22:44:40 +0300 Subject: [PATCH 14/15] validate email format --- lib/params/base_validator.rb | 10 ++ lib/params/submission_create_validator.rb | 11 +- spec/lib/params/base_validator_spec.rb | 139 ++++++++++++++++++++++ spec/requests/submissions_spec.rb | 34 +++++- 4 files changed, 185 insertions(+), 9 deletions(-) create mode 100644 spec/lib/params/base_validator_spec.rb diff --git a/lib/params/base_validator.rb b/lib/params/base_validator.rb index 964ad553c..c394519aa 100644 --- a/lib/params/base_validator.rb +++ b/lib/params/base_validator.rb @@ -2,6 +2,8 @@ module Params class BaseValidator + EMAIL_REGEXP = /\A[a-z0-9][\.']?(?:(?:[a-z0-9_-]+[\.\+'])*[a-z0-9_-]+)*@(?:[a-z0-9]+[\.-])*[a-z0-9]+\.[a-z]{2,}\z/i + InvalidParameterError = Class.new(StandardError) def self.call(...) @@ -65,6 +67,14 @@ def format(params, key, regexp, message: nil) raise_error(message || "#{key} must follow the #{regexp.source} format") end + def email_format(params, key, message: nil) + return if params.blank? + return if params[key].blank? + return if params[key].to_s.strip.split(/\s*[;,]\s*/).all? { |email| email.match?(EMAIL_REGEXP) } + + raise_error(message || "#{key} must follow the email format") + end + def unique_value(params, key, message: nil) return unless params.is_a?(Array) return if params.none? diff --git a/lib/params/submission_create_validator.rb b/lib/params/submission_create_validator.rb index 0658aac24..eef14391e 100644 --- a/lib/params/submission_create_validator.rb +++ b/lib/params/submission_create_validator.rb @@ -22,6 +22,7 @@ def validate_creation_from_emails(params) required(params, %i[emails email]) type(params, :emails, String) + email_format(params, :emails, message: 'emails are invalid') boolean(params, :send_email) type(params, :message, Hash) @@ -43,8 +44,8 @@ def validate_creation_from_submitters(params) type(params, :completed_redirect_url, String) type(params, :bcc_completed, String) type(params, :reply_to, String) - format(params, :bcc_completed, /@/, message: 'bcc_completed email is invalid') - format(params, :reply_to, /@/, message: 'reply_to email is invalid') + email_format(params, :bcc_completed, message: 'bcc_completed email is invalid') + email_format(params, :reply_to, message: 'reply_to email is invalid') type(params, :message, Hash) type(params, :submitters, Array) @@ -75,8 +76,8 @@ def validate_submitter(submitter_params) type(submitter_params, :name, String) type(submitter_params, :reply_to, String) type(submitter_params, :email, String) - format(submitter_params, :email, /@/, message: 'email is invalid') - format(submitter_params, :reply_to, /@/, message: 'reply_to email is invalid') + email_format(submitter_params, :email, message: 'email is invalid') + email_format(submitter_params, :reply_to, message: 'reply_to email is invalid') type(submitter_params, :phone, String) format(submitter_params, :phone, /\A\+\d+\z/, message: 'phone should start with + and contain only digits') @@ -106,7 +107,7 @@ def validate_creation_from_submission(params) type(params, :order, String) type(params, :completed_redirect_url, 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) in_path(params, :message) do |message_params| diff --git a/spec/lib/params/base_validator_spec.rb b/spec/lib/params/base_validator_spec.rb new file mode 100644 index 000000000..193b9b33f --- /dev/null +++ b/spec/lib/params/base_validator_spec.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Params::BaseValidator do + let(:validator) { described_class.new({}) } + + describe '#email_format' do + it 'when email is valid' do + emails = [ + ' john.doe@example.com ', + 'john.doe@example.com', + 'jane+newsletter@domain.org', + 'mike_smith@company.net', + 'lisa-wong@sub.example.co.uk', + 'peter@webmail.com', + 'anna.jones123@my-domain.com', + 'contact@company.email', + 'info@my-company123.org', + 'hello.world@business.info', + 'feedback@new-domain.com', + 'alerts+user@localdomain.net', + 'webmaster@industry.biz', + 'services@agency.example', + 'george123@consultant.pro', + 'sales-team@company.io' + ] + + emails.each do |email| + expect { validator.email_format({ email: }, :email) }.not_to raise_error + end + end + + it 'when signle email is invalid' do + emails = [ + 'jone.doe@', + 'mike.smith@', + 'jane.doe@@example.com', + '@example.com', + 'lisa.wong@example', + 'peter.parker..@example.com', + 'anna.jones@.com', + 'jack.brown@com', + 'john doe@example.com', + 'laura.martin@ example.com', + 'dave.clark@example .com', + 'susan.green@example,com', + 'chris.lee@example;com', + 'jenny.king@.example.com', + '.henry.ford@example.com', + 'amy.baker@sub_domain.com', + 'george.morris@-example.com', + 'nancy.davis@example..com', + 'kevin.white@.', + 'diana.robinson@.example..com', + 'oliver.scott@example.c', + 'email1@g.comemail@g.com', + 'user.name@subdomain.example@example.com', + 'double@at@sign.com', + 'user@@example.com', + 'email@123.123.123.123', + 'this...is@strange.but.valid.com', + 'mix-and.match@strangely-formed-email_address.com', + 'email@domain..com', + 'user@-weird-domain-.com', + 'user.name@[IPv6:2001:db8::1]', + 'tricky.email@sub.example-.com', + 'user@domain.c0m' + ] + + emails.each do |email| + expect do + validator.email_format({ email: }, :email) + end.to raise_error(described_class::InvalidParameterError, 'email must follow the email format') + end + end + + it 'when multiple emails are valid' do + emails = [ + + 'john.doe@example.com, jane.doe+newsletter@domain.org', + 'joshua@automobile.car ; chloe+fashion@food.delivery', + 'mike-smith@company.net;lisa.wong-sales@sub.example.co.uk', + 'peter.parker+info@webmail.com,laura.martin-office@company.co', + 'anna.jones123@my-domain.com, jack.brown+work@college.edu', + 'susan.green@business-info.org; dave.clark+personal@nonprofit.org', + 'chris.lee+team@new-domain.com;jenny.king.marketing@localdomain.net', + 'george.morris@consultant.pro; nancy.davis-office@company.io', + 'joshua-jones@automobile.car; chloe.taylor+fashion@food.delivery', + 'ryan.moore+alerts@music-band.com,isabella.walker.design@fashion.design', + 'support-team@company.com, contact.us@domain.org', + 'admin.office@industry.biz, hr.department@service.pro', + 'feedback@agency-example.org; hello.world@creative-studio.net', + 'sales-team@e-commerce.shop, support.department@technology.co', + 'media.contact@financial.servicesl; events-coordinator@food.delivery', + 'order@music-band.com; info.support@creative.example', + 'design.team@webmail.com , admin-office@company.co', + 'contact.sales@sub-example.co.uk, support+info@legal.gov', + 'support@media.group;subscribe-updates@concert.events' + ] + + emails.each do |email| + expect { validator.email_format({ email: }, :email) }.not_to raise_error + end + end + + it 'when multiple emails are invalid' do + emails = [ + 'jone@gmail.com, ,mike@gmail.com', + 'john.doe@example.com dave@nonprofit.org', + '; oliver.scott@example.com', + 'amy.baker@ example.com, george.morris@ example.com', + 'jenny.king@example.com . diana.robinson@example.com', + 'nancy.davis@.com, henry.ford@.com', + 'jack.brown@example.com, laura.martin@example .com', + 'anna.jones@example,com lisa.wong@example.com', + 'dave.clark@example.com kevin.white@example;com', + 'susan.green@ example.com; john.doe@example.com', + 'amy.baker@sub_domain.com george.morris@-example.com', + 'nancy.davis@example..com john.doe@example.c', + 'peter.parker@example.com, .henry.ford@example.com', + 'diana.robinson@.example..com, mike.smith@.', + 'oliver.scott@example.com; laura.martin@ example.com, jane.doe@@example.com' + ] + + emails.each do |email| + expect do + validator.email_format({ email: }, :email) + end.to raise_error(described_class::InvalidParameterError, 'email must follow the email format') + end + end + + it 'when email is invalid with custom message' do + expect do + validator.email_format({ email: 'jone.doe@' }, :email, message: 'email is invalid') + end.to raise_error(described_class::InvalidParameterError, 'email is invalid') + end + end +end diff --git a/spec/requests/submissions_spec.rb b/spec/requests/submissions_spec.rb index 5062662c7..3a9590d8f 100644 --- a/spec/requests/submissions_spec.rb +++ b/spec/requests/submissions_spec.rb @@ -83,6 +83,20 @@ expect(response.parsed_body[2]['email']).to eq('mike.doe@example.com') end + it 'returns an error if the submitter email is invalid' do + post '/api/submissions', headers: { 'x-auth-token': author.access_token.token }, params: { + template_id: templates[0].id, + send_email: true, + submitters: [ + { role: 'First Role', email: 'john@example' } + ] + }.to_json + + expect(response).to have_http_status(:unprocessable_entity) + + expect(response.parsed_body).to eq({ 'error' => 'email is invalid in `submitters[0]`.' }) + end + it 'returns an error if the template fields are missing' do templates[0].update(fields: []) @@ -113,16 +127,28 @@ describe 'POST /api/submissions/emails' do it 'creates a submission using email' do - post '/api/submissions', headers: { 'x-auth-token': author.access_token.token }, params: { + post '/api/submissions/emails', headers: { 'x-auth-token': author.access_token.token }, params: { template_id: templates[0].id, - emails: 'john.doe@example.com' + emails: 'john.doe@example.com,jane.doe@example.com' }.to_json expect(response).to have_http_status(:ok) - submission = Submission.last + submissions = Submission.last(2) + submissions_body = submissions.reduce([]) { |acc, submission| acc + create_submission_body(submission) } - expect(response.parsed_body).to eq(JSON.parse(create_submission_body(submission).to_json)) + expect(response.parsed_body).to eq(JSON.parse(submissions_body.to_json)) + end + + it 'returns an error if emails are invalid' do + post '/api/submissions/emails', headers: { 'x-auth-token': author.access_token.token }, params: { + template_id: templates[0].id, + emails: 'amy.baker@example.com, george.morris@example.com@gmail.com' + }.to_json + + expect(response).to have_http_status(:unprocessable_entity) + + expect(response.parsed_body).to eq({ 'error' => 'emails are invalid' }) end end From 910490528deeeab1c95c4c6c7753be41d3590356 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 20 Oct 2024 15:44:24 +0300 Subject: [PATCH 15/15] dry run email validation --- lib/params/base_validator.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/params/base_validator.rb b/lib/params/base_validator.rb index c394519aa..4c2571e84 100644 --- a/lib/params/base_validator.rb +++ b/lib/params/base_validator.rb @@ -72,7 +72,11 @@ def email_format(params, key, message: nil) return if params[key].blank? return if params[key].to_s.strip.split(/\s*[;,]\s*/).all? { |email| email.match?(EMAIL_REGEXP) } - raise_error(message || "#{key} must follow the email format") + if Rails.env.production? + Rollbar.error(message || "#{key} must follow the email format") if defined?(Rollbar) + else + raise_error(message || "#{key} must follow the email format") + end end def unique_value(params, key, message: nil)