Merge pull request #39 from docusealco/wip
This commit is contained in:
+56
-56
@@ -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)
|
||||||
@@ -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
-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.**
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -693,6 +693,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'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<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.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' || request.host == 'docuseal.com' %>">
|
||||||
<%= svg_icon 'world', class: 'w-5 h-5' %>
|
<%= svg_icon 'world', class: 'w-5 h-5' %>
|
||||||
Global
|
Global
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -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%">
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+1
-1
@@ -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
|
||||||
|
|||||||
@@ -16,7 +16,15 @@ 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
|
||||||
|
|
||||||
|
message.from = from
|
||||||
|
|
||||||
|
if from == 'DocuSeal <info@docuseal.com>'
|
||||||
|
message.body.instance_variable_set(
|
||||||
|
:@raw_source, message.body.raw_source.gsub('https://docuseal.co/', 'https://docuseal.com/')
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
return message
|
return message
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+1
-1
@@ -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?
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
module Params
|
module Params
|
||||||
class BaseValidator
|
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)
|
InvalidParameterError = Class.new(StandardError)
|
||||||
|
|
||||||
def self.call(...)
|
def self.call(...)
|
||||||
@@ -65,6 +67,27 @@ 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.strip.split(/\s*[;,]\s*/).all? { |email| email.match?(EMAIL_REGEXP) }
|
||||||
|
|
||||||
|
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)
|
||||||
|
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|
|
||||||
|
|||||||
@@ -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 || 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
|
||||||
@@ -92,19 +92,13 @@ module Submissions
|
|||||||
end
|
end
|
||||||
|
|
||||||
def generate_pdfs(submitter)
|
def generate_pdfs(submitter)
|
||||||
cell_layouter = HexaPDF::Layout::TextLayouter.new(text_valign: :center, text_align: :center)
|
configs = submitter.account.account_configs.where(key: [AccountConfig::FLATTEN_RESULT_PDF_KEY,
|
||||||
|
AccountConfig::WITH_SIGNATURE_ID])
|
||||||
|
|
||||||
with_signature_id = submitter.account.account_configs
|
with_signature_id = configs.find { |c| c.key == AccountConfig::WITH_SIGNATURE_ID }&.value == true
|
||||||
.exists?(key: AccountConfig::WITH_SIGNATURE_ID, value: true)
|
is_flatten = configs.find { |c| c.key == AccountConfig::FLATTEN_RESULT_PDF_KEY }&.value != false
|
||||||
|
|
||||||
is_flatten =
|
pdfs_index = build_pdfs_index(submitter.submission, submitter:, flatten: is_flatten)
|
||||||
submitter.account.account_configs
|
|
||||||
.find_or_initialize_by(key: AccountConfig::FLATTEN_RESULT_PDF_KEY).value != false
|
|
||||||
|
|
||||||
account = submitter.account
|
|
||||||
attachments_data_cache = {}
|
|
||||||
|
|
||||||
pdfs_index = build_pdfs_index(submitter, flatten: is_flatten)
|
|
||||||
|
|
||||||
if with_signature_id || submitter.account.testing?
|
if with_signature_id || submitter.account.testing?
|
||||||
pdfs_index.each_value do |pdf|
|
pdfs_index.each_value do |pdf|
|
||||||
@@ -145,6 +139,16 @@ module Submissions
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
fill_submitter_fields(submitter, submitter.account, pdfs_index, with_signature_id:, is_flatten:)
|
||||||
|
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 = {}
|
||||||
|
|
||||||
|
return pdfs_index if submitter.submission.template_fields.blank?
|
||||||
|
|
||||||
submitter.submission.template_fields.each do |field|
|
submitter.submission.template_fields.each do |field|
|
||||||
next if field['submitter_uuid'] != submitter.uuid
|
next if field['submitter_uuid'] != submitter.uuid
|
||||||
|
|
||||||
@@ -514,13 +518,13 @@ module Submissions
|
|||||||
Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, attachments.map(&:uuid).sort.join(':'))
|
Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, attachments.map(&:uuid).sort.join(':'))
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_pdfs_index(submitter, flatten: true)
|
def build_pdfs_index(submission, submitter: nil, flatten: true)
|
||||||
latest_submitter = find_last_submitter(submitter)
|
latest_submitter = find_last_submitter(submission, submitter:)
|
||||||
|
|
||||||
Submissions::EnsureResultGenerated.call(latest_submitter) if latest_submitter
|
Submissions::EnsureResultGenerated.call(latest_submitter) if latest_submitter
|
||||||
|
|
||||||
documents = latest_submitter&.documents&.preload(:blob).to_a.presence
|
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|
|
documents.to_h do |attachment|
|
||||||
pdf =
|
pdf =
|
||||||
@@ -582,11 +586,11 @@ module Submissions
|
|||||||
font_wrapper.custom_glyph(replace_with, character)
|
font_wrapper.custom_glyph(replace_with, character)
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_last_submitter(submitter)
|
def find_last_submitter(submission, submitter: nil)
|
||||||
submitter.submission.submitters
|
submission.submitters
|
||||||
.select(&:completed_at?)
|
.select(&:completed_at?)
|
||||||
.select { |e| e.id != submitter.id && e.completed_at <= submitter.completed_at }
|
.select { |e| submitter.nil? ? true : e.id != submitter.id && e.completed_at <= submitter.completed_at }
|
||||||
.max_by(&:completed_at)
|
.max_by(&:completed_at)
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_pdf_from_image(attachment)
|
def build_pdf_from_image(attachment)
|
||||||
|
|||||||
+43
-32
@@ -7,7 +7,11 @@ FactoryBot.define do
|
|||||||
author factory: %i[user]
|
author factory: %i[user]
|
||||||
name { Faker::Book.title }
|
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!(
|
blob = ActiveStorage::Blob.create_and_upload!(
|
||||||
io: Rails.root.join('spec/fixtures/sample-document.pdf').open,
|
io: Rails.root.join('spec/fixtures/sample-document.pdf').open,
|
||||||
filename: 'sample-document.pdf',
|
filename: 'sample-document.pdf',
|
||||||
@@ -22,39 +26,46 @@ FactoryBot.define do
|
|||||||
Templates::ProcessDocument.call(attachment, attachment.download)
|
Templates::ProcessDocument.call(attachment, attachment.download)
|
||||||
|
|
||||||
template.schema = [{ attachment_uuid: attachment.uuid, name: 'sample-document' }]
|
template.schema = [{ attachment_uuid: attachment.uuid, name: 'sample-document' }]
|
||||||
template.submitters = [
|
number_words = %w[first second third fourth fifth sixth seventh eighth ninth tenth]
|
||||||
|
|
||||||
|
template.submitters = Array.new(evaluator.submitter_count) do |i|
|
||||||
{
|
{
|
||||||
'name' => 'First Party',
|
'name' => "#{number_words[i]&.capitalize} Party",
|
||||||
'uuid' => '513848eb-1096-4abc-a743-68596b5aaa4c'
|
'uuid' => SecureRandom.uuid
|
||||||
}
|
}
|
||||||
]
|
end
|
||||||
template.fields = [
|
|
||||||
{
|
template.fields = template.submitters.reduce([]) do |fields, submitter|
|
||||||
'uuid' => '21637fc9-0655-45df-8952-04ec64949e85',
|
fields += [
|
||||||
'submitter_uuid' => '513848eb-1096-4abc-a743-68596b5aaa4c',
|
{
|
||||||
'name' => 'First Name',
|
'uuid' => SecureRandom.uuid,
|
||||||
'type' => 'text',
|
'submitter_uuid' => submitter['uuid'],
|
||||||
'required' => true,
|
'name' => 'First Name',
|
||||||
'areas' => [
|
'type' => 'text',
|
||||||
{
|
'required' => true,
|
||||||
'x' => 0.09027777777777778,
|
'areas' => [
|
||||||
'y' => 0.1197252208047105,
|
{
|
||||||
'w' => 0.3069444444444444,
|
'x' => 0.09027777777777778,
|
||||||
'h' => 0.03336604514229637,
|
'y' => 0.1197252208047105,
|
||||||
'attachment_uuid' => attachment.uuid,
|
'w' => 0.3069444444444444,
|
||||||
'page' => 0
|
'h' => 0.03336604514229637,
|
||||||
}
|
'attachment_uuid' => attachment.uuid,
|
||||||
]
|
'page' => 0
|
||||||
},
|
}
|
||||||
{
|
]
|
||||||
'uuid' => '1f97f8e3-dc82-4586-aeea-6ebed6204e46',
|
},
|
||||||
'submitter_uuid' => '513848eb-1096-4abc-a743-68596b5aaa4c',
|
{
|
||||||
'name' => '',
|
'uuid' => SecureRandom.uuid,
|
||||||
'type' => 'signature',
|
'submitter_uuid' => submitter['uuid'],
|
||||||
'required' => true,
|
'name' => '',
|
||||||
'areas' => []
|
'type' => 'signature',
|
||||||
}
|
'required' => true,
|
||||||
]
|
'areas' => []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
fields
|
||||||
|
end
|
||||||
|
|
||||||
template.save!
|
template.save!
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -7,6 +7,7 @@ describe 'Submission API', type: :request do
|
|||||||
let!(:author) { create(:user, account:) }
|
let!(:author) { create(:user, account:) }
|
||||||
let!(:folder) { create(:template_folder, account:) }
|
let!(:folder) { create(:template_folder, account:) }
|
||||||
let!(:templates) { create_list(:template, 2, account:, author:, folder:) }
|
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
|
describe 'GET /api/submissions' do
|
||||||
it 'returns a list of submissions' do
|
it 'returns a list of submissions' do
|
||||||
@@ -56,13 +57,16 @@ describe 'Submission API', type: :request do
|
|||||||
|
|
||||||
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))
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
describe 'POST /api/submissions/emails' do
|
it 'creates a submission when some submitter roles are not provided' do
|
||||||
it 'creates a submission using email' do
|
|
||||||
post '/api/submissions', headers: { 'x-auth-token': author.access_token.token }, params: {
|
post '/api/submissions', headers: { 'x-auth-token': author.access_token.token }, params: {
|
||||||
template_id: templates[0].id,
|
template_id: multiple_submitters_template.id,
|
||||||
emails: 'john.doe@example.com'
|
send_email: true,
|
||||||
|
submitters: [
|
||||||
|
{ role: 'First Role', email: 'john.doe@example.com' },
|
||||||
|
{ email: 'jane.doe@example.com' },
|
||||||
|
{ email: 'mike.doe@example.com' }
|
||||||
|
]
|
||||||
}.to_json
|
}.to_json
|
||||||
|
|
||||||
expect(response).to have_http_status(:ok)
|
expect(response).to have_http_status(:ok)
|
||||||
@@ -70,6 +74,81 @@ describe 'Submission API', type: :request do
|
|||||||
submission = Submission.last
|
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).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 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: [])
|
||||||
|
|
||||||
|
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: multiple_submitters_template.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
|
||||||
|
it 'creates a submission using email' do
|
||||||
|
post '/api/submissions/emails', headers: { 'x-auth-token': author.access_token.token }, params: {
|
||||||
|
template_id: templates[0].id,
|
||||||
|
emails: 'john.doe@example.com,jane.doe@example.com'
|
||||||
|
}.to_json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
submissions = Submission.last(2)
|
||||||
|
submissions_body = submissions.reduce([]) { |acc, submission| acc + create_submission_body(submission) }
|
||||||
|
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -144,8 +144,8 @@ describe 'Templates API', type: :request do
|
|||||||
name: template.name,
|
name: template.name,
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
'uuid' => '21637fc9-0655-45df-8952-04ec64949e85',
|
'uuid' => template.fields[0]['uuid'],
|
||||||
'submitter_uuid' => '513848eb-1096-4abc-a743-68596b5aaa4c',
|
'submitter_uuid' => template.submitters[0]['uuid'],
|
||||||
'name' => 'First Name',
|
'name' => 'First Name',
|
||||||
'type' => 'text',
|
'type' => 'text',
|
||||||
'required' => true,
|
'required' => true,
|
||||||
@@ -161,8 +161,8 @@ describe 'Templates API', type: :request do
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'uuid' => '1f97f8e3-dc82-4586-aeea-6ebed6204e46',
|
'uuid' => template.fields[1]['uuid'],
|
||||||
'submitter_uuid' => '513848eb-1096-4abc-a743-68596b5aaa4c',
|
'submitter_uuid' => template.submitters[0]['uuid'],
|
||||||
'name' => '',
|
'name' => '',
|
||||||
'type' => 'signature',
|
'type' => 'signature',
|
||||||
'required' => true,
|
'required' => true,
|
||||||
|
|||||||
Reference in New Issue
Block a user