Merge from docusealco/wip

pull/381/merge 2.1.1
Alex Turchyn 2 months ago committed by GitHub
commit 993698ec01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -50,7 +50,7 @@ ENV OPENSSL_CONF=/app/openssl_legacy.cnf
WORKDIR /app WORKDIR /app
RUN echo '@edge https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev@edge redis libheif@edge vips-heif@edge gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf RUN echo '@edge https://dl-cdn.alpinelinux.org/alpine/edge/community' >> /etc/apk/repositories && apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev@edge yaml-dev redis libheif@edge vips-heif@edge gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
RUN echo $'.include = /etc/ssl/openssl.cnf\n\ RUN echo $'.include = /etc/ssl/openssl.cnf\n\
\n\ \n\

@ -1,29 +1,29 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (8.0.1) actioncable (8.0.2.1)
actionpack (= 8.0.1) actionpack (= 8.0.2.1)
activesupport (= 8.0.1) activesupport (= 8.0.2.1)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (8.0.1) actionmailbox (8.0.2.1)
actionpack (= 8.0.1) actionpack (= 8.0.2.1)
activejob (= 8.0.1) activejob (= 8.0.2.1)
activerecord (= 8.0.1) activerecord (= 8.0.2.1)
activestorage (= 8.0.1) activestorage (= 8.0.2.1)
activesupport (= 8.0.1) activesupport (= 8.0.2.1)
mail (>= 2.8.0) mail (>= 2.8.0)
actionmailer (8.0.1) actionmailer (8.0.2.1)
actionpack (= 8.0.1) actionpack (= 8.0.2.1)
actionview (= 8.0.1) actionview (= 8.0.2.1)
activejob (= 8.0.1) activejob (= 8.0.2.1)
activesupport (= 8.0.1) activesupport (= 8.0.2.1)
mail (>= 2.8.0) mail (>= 2.8.0)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (8.0.1) actionpack (8.0.2.1)
actionview (= 8.0.1) actionview (= 8.0.2.1)
activesupport (= 8.0.1) activesupport (= 8.0.2.1)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
rack (>= 2.2.4) rack (>= 2.2.4)
rack-session (>= 1.0.1) rack-session (>= 1.0.1)
@ -31,35 +31,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 (8.0.1) actiontext (8.0.2.1)
actionpack (= 8.0.1) actionpack (= 8.0.2.1)
activerecord (= 8.0.1) activerecord (= 8.0.2.1)
activestorage (= 8.0.1) activestorage (= 8.0.2.1)
activesupport (= 8.0.1) activesupport (= 8.0.2.1)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (8.0.1) actionview (8.0.2.1)
activesupport (= 8.0.1) activesupport (= 8.0.2.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 (8.0.1) activejob (8.0.2.1)
activesupport (= 8.0.1) activesupport (= 8.0.2.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (8.0.1) activemodel (8.0.2.1)
activesupport (= 8.0.1) activesupport (= 8.0.2.1)
activerecord (8.0.1) activerecord (8.0.2.1)
activemodel (= 8.0.1) activemodel (= 8.0.2.1)
activesupport (= 8.0.1) activesupport (= 8.0.2.1)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (8.0.1) activestorage (8.0.2.1)
actionpack (= 8.0.1) actionpack (= 8.0.2.1)
activejob (= 8.0.1) activejob (= 8.0.2.1)
activerecord (= 8.0.1) activerecord (= 8.0.2.1)
activesupport (= 8.0.1) activesupport (= 8.0.2.1)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (8.0.1) activesupport (8.0.2.1)
base64 base64
benchmark (>= 0.3) benchmark (>= 0.3)
bigdecimal bigdecimal
@ -104,9 +104,9 @@ GEM
faraday_middleware (~> 1.0, >= 1.0.0.rc1) faraday_middleware (~> 1.0, >= 1.0.0.rc1)
net-http-persistent (~> 4.0) net-http-persistent (~> 4.0)
nokogiri (~> 1, >= 1.10.8) nokogiri (~> 1, >= 1.10.8)
base64 (0.2.0) base64 (0.3.0)
bcrypt (3.1.20) bcrypt (3.1.20)
benchmark (0.4.0) benchmark (0.4.1)
better_html (2.1.1) better_html (2.1.1)
actionview (>= 6.0) actionview (>= 6.0)
activesupport (>= 6.0) activesupport (>= 6.0)
@ -114,7 +114,7 @@ GEM
erubi (~> 1.4) erubi (~> 1.4)
parser (>= 2.4) parser (>= 2.4)
smart_properties smart_properties
bigdecimal (3.1.8) bigdecimal (3.2.2)
bindex (0.8.1) bindex (0.8.1)
bootsnap (1.18.4) bootsnap (1.18.4)
msgpack (~> 1.2) msgpack (~> 1.2)
@ -141,8 +141,8 @@ GEM
cldr-plurals-runtime-rb (1.1.0) cldr-plurals-runtime-rb (1.1.0)
cmdparse (3.0.7) cmdparse (3.0.7)
coderay (1.1.3) coderay (1.1.3)
concurrent-ruby (1.3.4) concurrent-ruby (1.3.5)
connection_pool (2.4.1) connection_pool (2.5.3)
crack (1.0.0) crack (1.0.0)
bigdecimal bigdecimal
rexml rexml
@ -174,8 +174,9 @@ GEM
rake (>= 12.0.0, < 14.0.0) rake (>= 12.0.0, < 14.0.0)
docile (1.4.1) docile (1.4.1)
dotenv (3.1.7) dotenv (3.1.7)
drb (2.2.1) drb (2.2.3)
email_typo (0.2.3) email_typo (0.2.3)
erb (5.0.2)
erb_lint (0.7.0) erb_lint (0.7.0)
activesupport activesupport
better_html (>= 2.0.1) better_html (>= 2.0.1)
@ -276,13 +277,14 @@ GEM
openssl (>= 2.2.1) openssl (>= 2.2.1)
htmlentities (4.3.4) htmlentities (4.3.4)
httpclient (2.8.3) httpclient (2.8.3)
i18n (1.14.6) i18n (1.14.7)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
image_processing (1.13.0) image_processing (1.13.0)
mini_magick (>= 4.9.5, < 5) mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3) ruby-vips (>= 2.0.17, < 3)
io-console (0.8.0) io-console (0.8.1)
irb (1.14.3) irb (1.15.2)
pp (>= 0.6.0)
rdoc (>= 4.0.0) rdoc (>= 4.0.0)
reline (>= 0.4.2) reline (>= 0.4.2)
jmespath (1.6.2) jmespath (1.6.2)
@ -300,13 +302,13 @@ GEM
letter_opener (~> 1.9) letter_opener (~> 1.9)
railties (>= 6.1) railties (>= 6.1)
rexml rexml
logger (1.6.4) logger (1.7.0)
lograge (0.14.0) lograge (0.14.0)
actionpack (>= 4) actionpack (>= 4)
activesupport (>= 4) activesupport (>= 4)
railties (>= 4) railties (>= 4)
request_store (~> 1.0) request_store (~> 1.0)
loofah (2.23.1) loofah (2.24.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
mail (2.8.1) mail (2.8.1)
@ -320,7 +322,7 @@ GEM
mini_magick (4.13.2) mini_magick (4.13.2)
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.9) mini_portile2 (2.8.9)
minitest (5.25.4) minitest (5.25.5)
msgpack (1.7.5) msgpack (1.7.5)
multi_json (1.15.0) multi_json (1.15.0)
multipart-post (2.4.1) multipart-post (2.4.1)
@ -328,14 +330,14 @@ GEM
mysql2 (0.5.6) mysql2 (0.5.6)
net-http-persistent (4.0.5) net-http-persistent (4.0.5)
connection_pool (~> 2.2) connection_pool (~> 2.2)
net-imap (0.5.6) net-imap (0.5.9)
date date
net-protocol net-protocol
net-pop (0.1.2) net-pop (0.1.2)
net-protocol net-protocol
net-protocol (0.2.2) net-protocol (0.2.2)
timeout timeout
net-smtp (0.5.0) net-smtp (0.5.1)
net-protocol net-protocol
nio4r (2.7.4) nio4r (2.7.4)
nokogiri (1.18.9) nokogiri (1.18.9)
@ -365,6 +367,8 @@ GEM
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
pg (1.5.9) pg (1.5.9)
pp (0.6.2)
prettyprint
premailer (1.27.0) premailer (1.27.0)
addressable addressable
css_parser (>= 1.19.0) css_parser (>= 1.19.0)
@ -375,42 +379,44 @@ GEM
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
pretender (0.5.0) pretender (0.5.0)
actionpack (>= 6.1) actionpack (>= 6.1)
prettyprint (0.2.0)
pry (0.15.0) pry (0.15.0)
coderay (~> 1.1) coderay (~> 1.1)
method_source (~> 1.0) method_source (~> 1.0)
pry-rails (0.3.11) pry-rails (0.3.11)
pry (>= 0.13.0) pry (>= 0.13.0)
psych (5.2.2) psych (5.2.6)
date date
stringio stringio
public_suffix (6.0.1) public_suffix (6.0.1)
puma (6.5.0) puma (6.5.0)
nio4r (~> 2.0) nio4r (~> 2.0)
racc (1.8.1) racc (1.8.1)
rack (3.1.16) rack (3.2.0)
rack-proxy (0.7.7) rack-proxy (0.7.7)
rack rack
rack-session (2.0.0) rack-session (2.1.1)
base64 (>= 0.1.0)
rack (>= 3.0.0) rack (>= 3.0.0)
rack-test (2.1.0) rack-test (2.2.0)
rack (>= 1.3) rack (>= 1.3)
rackup (2.2.1) rackup (2.2.1)
rack (>= 3) rack (>= 3)
rails (8.0.1) rails (8.0.2.1)
actioncable (= 8.0.1) actioncable (= 8.0.2.1)
actionmailbox (= 8.0.1) actionmailbox (= 8.0.2.1)
actionmailer (= 8.0.1) actionmailer (= 8.0.2.1)
actionpack (= 8.0.1) actionpack (= 8.0.2.1)
actiontext (= 8.0.1) actiontext (= 8.0.2.1)
actionview (= 8.0.1) actionview (= 8.0.2.1)
activejob (= 8.0.1) activejob (= 8.0.2.1)
activemodel (= 8.0.1) activemodel (= 8.0.2.1)
activerecord (= 8.0.1) activerecord (= 8.0.2.1)
activestorage (= 8.0.1) activestorage (= 8.0.2.1)
activesupport (= 8.0.1) activesupport (= 8.0.2.1)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 8.0.1) railties (= 8.0.2.1)
rails-dom-testing (2.2.0) rails-dom-testing (2.3.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
minitest minitest
nokogiri (>= 1.6) nokogiri (>= 1.6)
@ -424,22 +430,23 @@ GEM
actionview (> 3.1) actionview (> 3.1)
activesupport (> 3.1) activesupport (> 3.1)
railties (> 3.1) railties (> 3.1)
railties (8.0.1) railties (8.0.2.1)
actionpack (= 8.0.1) actionpack (= 8.0.2.1)
activesupport (= 8.0.1) activesupport (= 8.0.2.1)
irb (~> 1.13) irb (~> 1.13)
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0, >= 1.2.2) thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.2.1) rake (13.3.0)
rdoc (6.10.0) rdoc (6.14.2)
erb
psych (>= 4.0.0) psych (>= 4.0.0)
redis-client (0.23.0) redis-client (0.23.0)
connection_pool connection_pool
regexp_parser (2.9.3) regexp_parser (2.9.3)
reline (0.6.0) reline (0.6.2)
io-console (~> 0.5) io-console (~> 0.5)
representable (3.2.0) representable (3.2.0)
declarative (< 0.1.0) declarative (< 0.1.0)
@ -538,7 +545,7 @@ GEM
sqlite3 (2.5.0-arm64-darwin) sqlite3 (2.5.0-arm64-darwin)
sqlite3 (2.5.0-x86_64-linux-gnu) sqlite3 (2.5.0-x86_64-linux-gnu)
sqlite3 (2.5.0-x86_64-linux-musl) sqlite3 (2.5.0-x86_64-linux-musl)
stringio (3.1.2) stringio (3.1.7)
strip_attributes (1.14.1) strip_attributes (1.14.1)
activemodel (>= 3.0, < 9.0) activemodel (>= 3.0, < 9.0)
thor (1.4.0) thor (1.4.0)
@ -574,12 +581,13 @@ GEM
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0) hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.9.1) webrick (1.9.1)
websocket-driver (0.7.6) websocket-driver (0.8.0)
base64
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
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.7.1) zeitwerk (2.7.3)
PLATFORMS PLATFORMS
aarch64-linux aarch64-linux

@ -27,7 +27,7 @@ class AccountsController < ApplicationController
unless URI.parse(@encrypted_config.value.to_s).class.in?([URI::HTTP, URI::HTTPS]) unless URI.parse(@encrypted_config.value.to_s).class.in?([URI::HTTP, URI::HTTPS])
@encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url')) @encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url'))
return render :show, status: :unprocessable_entity return render :show, status: :unprocessable_content
end end
@encrypted_config.save! @encrypted_config.save!
@ -39,7 +39,7 @@ class AccountsController < ApplicationController
redirect_to settings_account_path, notice: I18n.t('account_information_has_been_updated') redirect_to settings_account_path, notice: I18n.t('account_information_has_been_updated')
end end
rescue ActiveRecord::RecordInvalid rescue ActiveRecord::RecordInvalid
render :show, status: :unprocessable_entity render :show, status: :unprocessable_content
end end
def destroy def destroy

@ -16,7 +16,7 @@ module Api
check_authorization check_authorization
rescue_from Params::BaseValidator::InvalidParameterError do |e| rescue_from Params::BaseValidator::InvalidParameterError do |e|
render json: { error: e.message }, status: :unprocessable_entity render json: { error: e.message }, status: :unprocessable_content
end end
rescue_from RateLimit::LimitApproached do |e| rescue_from RateLimit::LimitApproached do |e|
@ -33,7 +33,7 @@ module Api
rescue_from JSON::ParserError do |e| rescue_from JSON::ParserError do |e|
Rollbar.warning(e) if defined?(Rollbar) Rollbar.warning(e) if defined?(Rollbar)
render json: { error: "JSON parse error: #{e.message}" }, status: :unprocessable_entity render json: { error: "JSON parse error: #{e.message}" }, status: :unprocessable_content
end end
end end

@ -16,14 +16,14 @@ module Api
if ImageUtils.blank?(image) if ImageUtils.blank?(image)
Rollbar.error("Empty signature: #{submitter.id}") if defined?(Rollbar) Rollbar.error("Empty signature: #{submitter.id}") if defined?(Rollbar)
return render json: { error: "#{params[:type]} is empty" }, status: :unprocessable_entity return render json: { error: "#{params[:type]} is empty" }, status: :unprocessable_content
end end
if ImageUtils.error?(image) if ImageUtils.error?(image)
Rollbar.error("Error signature: #{submitter.id}") if defined?(Rollbar) Rollbar.error("Error signature: #{submitter.id}") if defined?(Rollbar)
return render json: { error: "#{params[:type]} error, try to sign on another device" }, return render json: { error: "#{params[:type]} error, try to sign on another device" },
status: :unprocessable_entity status: :unprocessable_content
end end
end end

@ -50,12 +50,12 @@ module Api
def create def create
Params::SubmissionCreateValidator.call(params) Params::SubmissionCreateValidator.call(params)
return render json: { error: 'Template not found' }, status: :unprocessable_entity if @template.nil? return render json: { error: 'Template not found' }, status: :unprocessable_content if @template.nil?
if @template.fields.blank? if @template.fields.blank?
Rollbar.warning("Template does not contain fields: #{@template.id}") if defined?(Rollbar) Rollbar.warning("Template does not contain fields: #{@template.id}") if defined?(Rollbar)
return render json: { error: 'Template does not contain fields' }, status: :unprocessable_entity return render json: { error: 'Template does not contain fields' }, status: :unprocessable_content
end end
params[:send_email] = true unless params.key?(:send_email) params[:send_email] = true unless params.key?(:send_email)
@ -82,7 +82,7 @@ module Api
DownloadUtils::UnableToDownload => e DownloadUtils::UnableToDownload => e
Rollbar.warning(e) if defined?(Rollbar) Rollbar.warning(e) if defined?(Rollbar)
render json: { error: e.message }, status: :unprocessable_entity render json: { error: e.message }, status: :unprocessable_content
end end
def destroy def destroy

@ -34,7 +34,7 @@ module Api
def update def update
if @submitter.completed_at? if @submitter.completed_at?
return render json: { error: 'Submitter has already completed the submission.' }, status: :unprocessable_entity return render json: { error: 'Submitter has already completed the submission.' }, status: :unprocessable_content
end end
submission = @submitter.submission submission = @submitter.submission
@ -74,7 +74,7 @@ module Api
rescue Submitters::NormalizeValues::BaseError, DownloadUtils::UnableToDownload => e rescue Submitters::NormalizeValues::BaseError, DownloadUtils::UnableToDownload => e
Rollbar.warning(e) if defined?(Rollbar) Rollbar.warning(e) if defined?(Rollbar)
render json: { error: e.message }, status: :unprocessable_entity render json: { error: e.message }, status: :unprocessable_content
end end
def submitter_params def submitter_params

@ -7,8 +7,8 @@ module Api
def merge def merge
files = params[:files] || [] files = params[:files] || []
return render json: { error: 'Files are required' }, status: :unprocessable_entity if files.blank? return render json: { error: 'Files are required' }, status: :unprocessable_content if files.blank?
return render json: { error: 'At least 2 files are required' }, status: :unprocessable_entity if files.size < 2 return render json: { error: 'At least 2 files are required' }, status: :unprocessable_content if files.size < 2
render json: { render json: {
data: Base64.encode64(PdfUtils.merge(files.map { |base64| StringIO.new(Base64.decode64(base64)) }).string) data: Base64.encode64(PdfUtils.merge(files.map { |base64| StringIO.new(Base64.decode64(base64)) }).string)
@ -35,7 +35,7 @@ module Api
end end
} }
rescue HexaPDF::MalformedPDFError rescue HexaPDF::MalformedPDFError
render json: { error: 'Malformed PDF' }, status: :unprocessable_entity render json: { error: 'Malformed PDF' }, status: :unprocessable_content
end end
end end
end end

@ -15,12 +15,12 @@ class EmailSmtpSettingsController < ApplicationController
redirect_to settings_email_index_path, notice: I18n.t('changes_have_been_saved') redirect_to settings_email_index_path, notice: I18n.t('changes_have_been_saved')
else else
render :index, status: :unprocessable_entity render :index, status: :unprocessable_content
end end
rescue StandardError => e rescue StandardError => e
flash[:alert] = e.message flash[:alert] = e.message
render :index, status: :unprocessable_entity render :index, status: :unprocessable_content
end end
private private

@ -53,7 +53,7 @@ class EsignSettingsController < ApplicationController
@cert_record.errors.add(:name, I18n.t('already_exists')) @cert_record.errors.add(:name, I18n.t('already_exists'))
return render turbo_stream: turbo_stream.replace(:modal, template: 'esign_settings/new'), return render turbo_stream: turbo_stream.replace(:modal, template: 'esign_settings/new'),
status: :unprocessable_entity status: :unprocessable_content
end end
save_new_cert!(@encrypted_config, @cert_record) save_new_cert!(@encrypted_config, @cert_record)
@ -64,7 +64,7 @@ class EsignSettingsController < ApplicationController
@cert_record.errors.add(:password, e.message) @cert_record.errors.add(:password, e.message)
render turbo_stream: turbo_stream.replace(:modal, template: 'esign_settings/new'), status: :unprocessable_entity render turbo_stream: turbo_stream.replace(:modal, template: 'esign_settings/new'), status: :unprocessable_content
end end
def update def update

@ -24,7 +24,7 @@ class MfaSetupController < ApplicationController
@error_message = I18n.t('code_is_invalid') @error_message = I18n.t('code_is_invalid')
render turbo_stream: turbo_stream.replace(:mfa_form, partial: 'mfa_setup/form'), status: :unprocessable_entity render turbo_stream: turbo_stream.replace(:mfa_form, partial: 'mfa_setup/form'), status: :unprocessable_content
end end
end end
@ -36,7 +36,7 @@ class MfaSetupController < ApplicationController
else else
@error_message = I18n.t('code_is_invalid') @error_message = I18n.t('code_is_invalid')
render turbo_stream: turbo_stream.replace(:modal, template: 'mfa_setup/edit'), status: :unprocessable_entity render turbo_stream: turbo_stream.replace(:modal, template: 'mfa_setup/edit'), status: :unprocessable_content
end end
end end

@ -11,7 +11,7 @@ class ProfileController < ApplicationController
if current_user.update(contact_params) if current_user.update(contact_params)
redirect_to settings_profile_index_path, notice: I18n.t('contact_information_has_been_update') redirect_to settings_profile_index_path, notice: I18n.t('contact_information_has_been_update')
else else
render :index, status: :unprocessable_entity render :index, status: :unprocessable_content
end end
end end
@ -20,7 +20,7 @@ class ProfileController < ApplicationController
bypass_sign_in(current_user) bypass_sign_in(current_user)
redirect_to settings_profile_index_path, notice: I18n.t('password_has_been_changed') redirect_to settings_profile_index_path, notice: I18n.t('password_has_been_changed')
else else
render :index, status: :unprocessable_entity render :index, status: :unprocessable_content
end end
end end

@ -16,7 +16,7 @@ class SessionsController < Devise::SessionsController
end end
if User.exists?(email:, otp_required_for_login: true) && sign_in_params[:otp_attempt].blank? if User.exists?(email:, otp_required_for_login: true) && sign_in_params[:otp_attempt].blank?
return render :otp, locals: { resource: User.new(sign_in_params) }, status: :unprocessable_entity return render :otp, locals: { resource: User.new(sign_in_params) }, status: :unprocessable_content
end end
super super

@ -23,10 +23,10 @@ class SetupController < ApplicationController
unless URI.parse(encrypted_config_params[:value].to_s).class.in?([URI::HTTP, URI::HTTPS]) unless URI.parse(encrypted_config_params[:value].to_s).class.in?([URI::HTTP, URI::HTTPS])
@encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url')) @encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url'))
return render :index, status: :unprocessable_entity return render :index, status: :unprocessable_content
end end
return render :index, status: :unprocessable_entity unless @account.valid? return render :index, status: :unprocessable_content unless @account.valid?
if @user.save if @user.save
encrypted_configs = [ encrypted_configs = [
@ -42,7 +42,7 @@ class SetupController < ApplicationController
redirect_to newsletter_path redirect_to newsletter_path
else else
render :index, status: :unprocessable_entity render :index, status: :unprocessable_content
end end
end end

@ -42,7 +42,7 @@ class StartFormController < ApplicationController
if filter_undefined_submitters(@template).size > 1 && @submitter.new_record? if filter_undefined_submitters(@template).size > 1 && @submitter.new_record?
@error_message = multiple_submitters_error_message @error_message = multiple_submitters_error_message
return render :show, status: :unprocessable_entity return render :show, status: :unprocessable_content
end end
if (is_new_record = @submitter.new_record?) if (is_new_record = @submitter.new_record?)
@ -60,7 +60,7 @@ class StartFormController < ApplicationController
redirect_to submit_form_path(@submitter.slug) redirect_to submit_form_path(@submitter.slug)
else else
render :show, status: :unprocessable_entity render :show, status: :unprocessable_content
end end
end end
end end
@ -206,7 +206,7 @@ class StartFormController < ApplicationController
end end
def handle_require_2fa(submitter, is_new_record:) def handle_require_2fa(submitter, is_new_record:)
return render :show, status: :unprocessable_entity if submitter.errors.present? return render :show, status: :unprocessable_content if submitter.errors.present?
is_otp_verified = Submitters.verify_link_otp!(params[:one_time_code], submitter) is_otp_verified = Submitters.verify_link_otp!(params[:one_time_code], submitter)
@ -223,7 +223,7 @@ class StartFormController < ApplicationController
redirect_to submit_form_path(submitter.slug) redirect_to submit_form_path(submitter.slug)
else else
render :show, status: :unprocessable_entity render :show, status: :unprocessable_content
end end
else else
Submitters.send_shared_link_email_verification_code(submitter, request:) Submitters.send_shared_link_email_verification_code(submitter, request:)

@ -13,7 +13,7 @@ class StorageSettingsController < ApplicationController
redirect_to settings_storage_index_path, notice: I18n.t('changes_have_been_saved') redirect_to settings_storage_index_path, notice: I18n.t('changes_have_been_saved')
else else
render :index, status: :unprocessable_entity render :index, status: :unprocessable_content
end end
end end

@ -66,7 +66,7 @@ class SubmissionsController < ApplicationController
rescue Submissions::CreateFromSubmitters::BaseError => e rescue Submissions::CreateFromSubmitters::BaseError => e
render turbo_stream: turbo_stream.replace(:submitters_error, partial: 'submissions/error', render turbo_stream: turbo_stream.replace(:submitters_error, partial: 'submissions/error',
locals: { error: e.message }), locals: { error: e.message }),
status: :unprocessable_entity status: :unprocessable_content
end end
def destroy def destroy

@ -48,20 +48,20 @@ class SubmitFormController < ApplicationController
def update def update
if @submitter.completed_at? if @submitter.completed_at?
return render json: { error: I18n.t('form_has_been_completed_already') }, status: :unprocessable_entity return render json: { error: I18n.t('form_has_been_completed_already') }, status: :unprocessable_content
end end
if @submitter.submission.template&.archived_at? || @submitter.submission.archived_at? if @submitter.submission.template&.archived_at? || @submitter.submission.archived_at?
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_entity return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_content
end end
if @submitter.submission.expired? if @submitter.submission.expired?
return render json: { error: I18n.t('form_has_been_expired') }, status: :unprocessable_entity return render json: { error: I18n.t('form_has_been_expired') }, status: :unprocessable_content
end end
if @submitter.declined_at? if @submitter.declined_at?
return render json: { error: I18n.t('form_has_been_declined') }, return render json: { error: I18n.t('form_has_been_declined') },
status: :unprocessable_entity status: :unprocessable_content
end end
Submitters::SubmitValues.call(@submitter, params, request) Submitters::SubmitValues.call(@submitter, params, request)
@ -70,9 +70,9 @@ class SubmitFormController < ApplicationController
rescue Submitters::SubmitValues::RequiredFieldError => e rescue Submitters::SubmitValues::RequiredFieldError => e
Rollbar.warning("Required field #{@submitter.id}: #{e.message}") if defined?(Rollbar) Rollbar.warning("Required field #{@submitter.id}: #{e.message}") if defined?(Rollbar)
render json: { field_uuid: e.message }, status: :unprocessable_entity render json: { field_uuid: e.message }, status: :unprocessable_content
rescue Submitters::SubmitValues::ValidationError => e rescue Submitters::SubmitValues::ValidationError => e
render json: { error: e.message }, status: :unprocessable_entity render json: { error: e.message }, status: :unprocessable_content
end end
def completed; end def completed; end

@ -11,13 +11,13 @@ class SubmitFormDownloadController < ApplicationController
return redirect_to submitter_download_index_path(@submitter.slug) if @submitter.completed_at? return redirect_to submitter_download_index_path(@submitter.slug) if @submitter.completed_at?
return head :unprocessable_entity if @submitter.declined_at? || return head :unprocessable_content if @submitter.declined_at? ||
@submitter.submission.archived_at? || @submitter.submission.archived_at? ||
@submitter.submission.expired? || @submitter.submission.expired? ||
@submitter.submission.template&.archived_at? || @submitter.submission.template&.archived_at? ||
AccountConfig.exists?(account_id: @submitter.account_id, AccountConfig.exists?(account_id: @submitter.account_id,
key: AccountConfig::ALLOW_TO_PARTIAL_DOWNLOAD_KEY, key: AccountConfig::ALLOW_TO_PARTIAL_DOWNLOAD_KEY,
value: false) value: false)
last_completed_submitter = @submitter.submission.submitters last_completed_submitter = @submitter.submission.submitters
.where.not(id: @submitter.id) .where.not(id: @submitter.id)

@ -7,7 +7,7 @@ class SubmitFormInviteController < ApplicationController
def create def create
submitter = Submitter.find_by!(slug: params[:submit_form_slug]) submitter = Submitter.find_by!(slug: params[:submit_form_slug])
return head :unprocessable_entity unless can_invite?(submitter) return head :unprocessable_content unless can_invite?(submitter)
invite_submitters = filter_invite_submitters(submitter, 'invite_by_uuid') invite_submitters = filter_invite_submitters(submitter, 'invite_by_uuid')
optional_invite_submitters = filter_invite_submitters(submitter, 'optional_invite_by_uuid') optional_invite_submitters = filter_invite_submitters(submitter, 'optional_invite_by_uuid')
@ -34,7 +34,7 @@ class SubmitFormInviteController < ApplicationController
head :ok head :ok
else else
head :unprocessable_entity head :unprocessable_content
end end
end end

@ -5,7 +5,7 @@ class TemplateDocumentsController < ApplicationController
def create def create
if params[:blobs].blank? && params[:files].blank? if params[:blobs].blank? && params[:files].blank?
return render json: { error: I18n.t('file_is_missing') }, status: :unprocessable_entity return render json: { error: I18n.t('file_is_missing') }, status: :unprocessable_content
end end
old_fields_hash = @template.fields.hash old_fields_hash = @template.fields.hash
@ -28,6 +28,6 @@ class TemplateDocumentsController < ApplicationController
) )
} }
rescue Templates::CreateAttachments::PdfEncrypted rescue Templates::CreateAttachments::PdfEncrypted
render json: { error: 'PDF encrypted', status: 'pdf_encrypted' }, status: :unprocessable_entity render json: { error: 'PDF encrypted', status: 'pdf_encrypted' }, status: :unprocessable_content
end end
end end

@ -4,7 +4,7 @@ class TemplatesCloneAndReplaceController < ApplicationController
load_and_authorize_resource :template load_and_authorize_resource :template
def create def create
return head :unprocessable_entity if params[:files].blank? return head :unprocessable_content if params[:files].blank?
ActiveRecord::Associations::Preloader.new( ActiveRecord::Associations::Preloader.new(
records: [@template], records: [@template],
@ -31,7 +31,7 @@ class TemplatesCloneAndReplaceController < ApplicationController
rescue Templates::CreateAttachments::PdfEncrypted rescue Templates::CreateAttachments::PdfEncrypted
respond_to do |f| respond_to do |f|
f.html { render turbo_stream: turbo_stream.append(params[:form_id], html: helpers.tag.prompt_password) } f.html { render turbo_stream: turbo_stream.append(params[:form_id], html: helpers.tag.prompt_password) }
f.json { render json: { error: 'PDF encrypted', status: 'pdf_encrypted' }, status: :unprocessable_entity } f.json { render json: { error: 'PDF encrypted', status: 'pdf_encrypted' }, status: :unprocessable_content }
end end
end end
end end

@ -78,7 +78,7 @@ class TemplatesController < ApplicationController
maybe_redirect_to_template(@template) maybe_redirect_to_template(@template)
else else
render turbo_stream: turbo_stream.replace(:modal, template: 'templates/new'), status: :unprocessable_entity render turbo_stream: turbo_stream.replace(:modal, template: 'templates/new'), status: :unprocessable_content
end end
end end

@ -3,6 +3,8 @@
class TemplatesDebugController < ApplicationController class TemplatesDebugController < ApplicationController
load_and_authorize_resource :template load_and_authorize_resource :template
DEBUG_FILE = ''
def show def show
attachment = @template.documents.first attachment = @template.documents.first
@ -16,6 +18,8 @@ class TemplatesDebugController < ApplicationController
@template.update!(fields: Templates::ProcessDocument.normalize_attachment_fields(@template, [attachment])) @template.update!(fields: Templates::ProcessDocument.normalize_attachment_fields(@template, [attachment]))
debug_file if DEBUG_FILE.present?
ActiveRecord::Associations::Preloader.new( ActiveRecord::Associations::Preloader.new(
records: [@template], records: [@template],
associations: [schema_documents: { preview_images_attachments: :blob }] associations: [schema_documents: { preview_images_attachments: :blob }]
@ -31,4 +35,27 @@ class TemplatesDebugController < ApplicationController
render 'templates/edit', layout: 'plain' render 'templates/edit', layout: 'plain'
end end
def debug_file
tempfile = Tempfile.new
tempfile.binmode
tempfile.write(File.read(DEBUG_FILE))
tempfile.rewind
filename = File.basename(DEBUG_FILE)
file = ActionDispatch::Http::UploadedFile.new(
tempfile:,
filename:,
type: Marcel::MimeType.for(tempfile)
)
params = { files: [file] }
documents = Templates::CreateAttachments.call(@template, params)
schema = documents.map { |doc| { attachment_uuid: doc.uuid, name: doc.filename.base } }
@template.update!(schema:)
end
end end

@ -27,7 +27,7 @@ class UsersController < ApplicationController
if User.accessible_by(current_ability).exists?(email: @user.email) if User.accessible_by(current_ability).exists?(email: @user.email)
@user.errors.add(:email, I18n.t('already_exists')) @user.errors.add(:email, I18n.t('already_exists'))
return render turbo_stream: turbo_stream.replace(:modal, template: 'users/new'), status: :unprocessable_entity return render turbo_stream: turbo_stream.replace(:modal, template: 'users/new'), status: :unprocessable_content
end end
@user.role = User::ADMIN_ROLE unless role_valid?(@user.role) @user.role = User::ADMIN_ROLE unless role_valid?(@user.role)
@ -37,7 +37,7 @@ class UsersController < ApplicationController
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_invited') redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_invited')
else else
render turbo_stream: turbo_stream.replace(:modal, template: 'users/new'), status: :unprocessable_entity render turbo_stream: turbo_stream.replace(:modal, template: 'users/new'), status: :unprocessable_content
end end
end end
@ -57,7 +57,7 @@ class UsersController < ApplicationController
if @user.update(attrs.except(current_user == @user ? :role : nil)) if @user.update(attrs.except(current_user == @user ? :role : nil))
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_updated') redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_updated')
else else
render turbo_stream: turbo_stream.replace(:modal, template: 'users/edit'), status: :unprocessable_entity render turbo_stream: turbo_stream.replace(:modal, template: 'users/edit'), status: :unprocessable_content
end end
end end

@ -660,7 +660,7 @@ export default {
acceptFileTypes: { acceptFileTypes: {
type: String, type: String,
required: false, required: false,
default: 'image/*, application/pdf' default: 'image/*, application/pdf, application/zip'
}, },
baseUrl: { baseUrl: {
type: String, type: String,

@ -66,7 +66,7 @@ export default {
acceptFileTypes: { acceptFileTypes: {
type: String, type: String,
required: false, required: false,
default: 'image/*, application/pdf' default: 'image/*, application/pdf, application/zip'
}, },
withReplaceButton: { withReplaceButton: {
type: Boolean, type: Boolean,

@ -107,7 +107,7 @@ export default {
acceptFileTypes: { acceptFileTypes: {
type: String, type: String,
required: false, required: false,
default: 'image/*, application/pdf' default: 'image/*, application/pdf, application/zip'
} }
}, },
emits: ['success', 'error', 'loading'], emits: ['success', 'error', 'loading'],
@ -131,7 +131,7 @@ export default {
message () { message () {
if (this.isLoading) { if (this.isLoading) {
return this.t('uploading') return this.t('uploading')
} else if (this.acceptFileTypes === 'image/*, application/pdf') { } else if (this.acceptFileTypes === 'image/*, application/pdf, application/zip') {
return this.title || this.t('add_pdf_documents_or_images') return this.title || this.t('add_pdf_documents_or_images')
} else { } else {
return this.title || this.t('add_documents_or_images') return this.title || this.t('add_documents_or_images')
@ -146,7 +146,7 @@ export default {
methods: { methods: {
upload: Upload.methods.upload, upload: Upload.methods.upload,
onDropFiles (e) { onDropFiles (e) {
if (this.acceptFileTypes !== 'image/*, application/pdf' || [...e.dataTransfer.files].every((f) => f.type.match(/(?:image\/)|(?:application\/pdf)/))) { if (this.acceptFileTypes !== 'image/*, application/pdf, application/zip' || [...e.dataTransfer.files].every((f) => f.type.match(/(?:image\/)|(?:application\/pdf)|(?:application\/zip)/))) {
this.$refs.input.files = e.dataTransfer.files this.$refs.input.files = e.dataTransfer.files
this.upload() this.upload()

@ -78,7 +78,7 @@ export default {
acceptFileTypes: { acceptFileTypes: {
type: String, type: String,
required: false, required: false,
default: 'image/*, application/pdf' default: 'image/*, application/pdf, application/zip'
} }
}, },
emits: ['add', 'replace', 'replace-and-clone', 'error'], emits: ['add', 'replace', 'replace-and-clone', 'error'],

@ -157,7 +157,7 @@ export default {
acceptFileTypes: { acceptFileTypes: {
type: String, type: String,
required: false, required: false,
default: 'image/*, application/pdf' default: 'image/*, application/pdf, application/zip'
}, },
withReplaceButton: { withReplaceButton: {
type: Boolean, type: Boolean,

@ -35,7 +35,7 @@ export default {
acceptFileTypes: { acceptFileTypes: {
type: String, type: String,
required: false, required: false,
default: 'image/*, application/pdf' default: 'image/*, application/pdf, application/zip'
} }
}, },
emits: ['success'], emits: ['success'],

@ -57,7 +57,7 @@ export default {
acceptFileTypes: { acceptFileTypes: {
type: String, type: String,
required: false, required: false,
default: 'image/*, application/pdf' default: 'image/*, application/pdf, application/zip'
} }
}, },
emits: ['success', 'error'], emits: ['success', 'error'],

@ -146,7 +146,8 @@ class ProcessSubmitterCompletionJob
current_group_index = submitter_groups.find { |_, group| group.any? { |s| s['uuid'] == submitter.uuid } }&.first current_group_index = submitter_groups.find { |_, group| group.any? { |s| s['uuid'] == submitter.uuid } }&.first
if submitter_groups[current_group_index + 1] && if submitter_groups[current_group_index + 1] &&
submitters_index.values_at(*submitter_groups[current_group_index].pluck('uuid')).all?(&:completed_at?) submitters_index.values_at(*submitter_groups[current_group_index].pluck('uuid'))
.compact.all?(&:completed_at?)
submitter_groups[current_group_index + 1] submitter_groups[current_group_index + 1]
end end
else else
@ -159,7 +160,7 @@ class ProcessSubmitterCompletionJob
end end
end end
next_submitters = submitters_index.values_at(*Array.wrap(next_submitter_items).pluck('uuid')) next_submitters = submitters_index.values_at(*Array.wrap(next_submitter_items).pluck('uuid')).compact
Submitters.send_signature_requests(next_submitters) Submitters.send_signature_requests(next_submitters)
end end

@ -109,7 +109,7 @@
<%= render 'submissions/annotation', annot: %> <%= render 'submissions/annotation', annot: %>
<% end %> <% end %>
<% fields_index.dig(document.uuid, index)&.each do |(area, field)| %> <% fields_index.dig(document.uuid, index)&.each do |(area, field)| %>
<% value = values[field['uuid']] %> <% value = values[field['uuid']].presence || (field['default_value'] != '{{date}}' && field['readonly'] == true && field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
<% value ||= field['default_value'] if field['type'] == 'heading' %> <% value ||= field['default_value'] if field['type'] == 'heading' %>
<% next if value.blank? %> <% next if value.blank? %>
<% submitter = submitters_index[field['submitter_uuid']] %> <% submitter = submitters_index[field['submitter_uuid']] %>
@ -238,7 +238,7 @@
<div class="px-1.5 mb-4"> <div class="px-1.5 mb-4">
<% submitter_fields_index[item['uuid']].to_a.each_with_index do |field, index| %> <% submitter_fields_index[item['uuid']].to_a.each_with_index do |field, index| %>
<% submitter_field_counters[field['type']] += 1 %> <% submitter_field_counters[field['type']] += 1 %>
<% value = values[field['uuid']] %> <% value = values[field['uuid']].presence || (field['default_value'] != '{{date}}' && field['readonly'] == true && field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
<% next if value.blank? %> <% next if value.blank? %>
<% next if field['type'] == 'heading' %> <% next if field['type'] == 'heading' %>
<div class="pt-2.5 border-b border-base-300"> <div class="pt-2.5 border-b border-base-300">

@ -77,7 +77,7 @@
<%= render 'submit_form/annotations', annots: %> <%= render 'submit_form/annotations', annots: %>
<% end %> <% end %>
<% fields_index.dig(document.uuid, index)&.each do |(area, field)| %> <% fields_index.dig(document.uuid, index)&.each do |(area, field)| %>
<% value = values[field['uuid']].presence || (field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submitter.submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %> <% value = values[field['uuid']].presence || (field['readonly'] == true && field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submitter.submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
<% next if value.blank? %> <% next if value.blank? %>
<% next if !field['readonly'] && field['submitter_uuid'] == @submitter.uuid %> <% next if !field['readonly'] && field['submitter_uuid'] == @submitter.uuid %>
<% next if value == '{{date}}' && field['submitter_uuid'] != @submitter.uuid %> <% next if value == '{{date}}' && field['submitter_uuid'] != @submitter.uuid %>

@ -23,7 +23,7 @@
</div> </div>
</span> </span>
</div> </div>
<input id="file_dropzone_input" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="image/*, application/pdf<%= ', .docx, .doc, .xlsx, .xls, .odt, .rtf' if Docuseal.multitenant? %>" multiple> <input id="file_dropzone_input" name="files[]" class="hidden" data-action="change:file-dropzone#onSelectFiles" data-target="file-dropzone.input" type="file" accept="image/*, application/pdf, application/zip<%= ", #{Templates::CreateAttachments::DOCUMENT_EXTENSIONS.join(', ')}" if Docuseal.advanced_formats? %>" multiple>
</div> </div>
</label> </label>
</file-dropzone> </file-dropzone>

@ -14,6 +14,6 @@
</span> </span>
</button> </button>
<input type="hidden" name="form_id" value="<%= form_id %>"> <input type="hidden" name="form_id" value="<%= form_id %>">
<input id="upload_template" name="files[]" class="hidden" onchange="this.form.requestSubmit()" type="file" accept="image/*, application/pdf<%= ', .docx, .doc, .xlsx, .xls, .odt, .rtf' if Docuseal.advanced_formats? %>" multiple> <input id="upload_template" name="files[]" class="hidden" onchange="this.form.requestSubmit()" type="file" accept="image/*, application/pdf, application/zip<%= ", #{Templates::CreateAttachments::DOCUMENT_EXTENSIONS.join(', ')}" if Docuseal.advanced_formats? %>" multiple>
<input hidden name="folder_name" value="<%= local_assigns[:folder_name] %>"> <input hidden name="folder_name" value="<%= local_assigns[:folder_name] %>">
<% end %> <% end %>

@ -321,7 +321,7 @@ Devise.setup do |config|
# apps is `200 OK` and `302 Found respectively`, but new apps are generated with # apps is `200 OK` and `302 Found respectively`, but new apps are generated with
# these new defaults that match Hotwire/Turbo behavior. # these new defaults that match Hotwire/Turbo behavior.
# Note: These might become the new default in future versions of Devise. # Note: These might become the new default in future versions of Devise.
config.responder.error_status = :unprocessable_entity config.responder.error_status = :unprocessable_content
config.responder.redirect_status = :see_other config.responder.redirect_status = :see_other
# ==> Configuration for :registerable # ==> Configuration for :registerable

@ -24,6 +24,7 @@ en: &en
thanks: Thanks thanks: Thanks
private: Private private: Private
select: Select select: Select
party: Party
edit_order: Edit Order edit_order: Edit Order
invite_form_fields: Invite form fields invite_form_fields: Invite form fields
default_parties: Default parties default_parties: Default parties
@ -899,6 +900,7 @@ en: &en
range_without_total: "%{from}-%{to} events" range_without_total: "%{from}-%{to} events"
es: &es es: &es
party: Parte
edit_order: Edita Pedido edit_order: Edita Pedido
select: Seleccionar select: Seleccionar
invite_form_fields: Invitar campos del formulario invite_form_fields: Invitar campos del formulario
@ -1778,6 +1780,7 @@ es: &es
range_without_total: "%{from}-%{to} eventos" range_without_total: "%{from}-%{to} eventos"
it: &it it: &it
party: Parte
edit_order: Modifica Ordine edit_order: Modifica Ordine
select: Seleziona select: Seleziona
invite_form_fields: Invita campi modulo invite_form_fields: Invita campi modulo
@ -2657,6 +2660,7 @@ it: &it
range_without_total: "%{from}-%{to} eventi" range_without_total: "%{from}-%{to} eventi"
fr: &fr fr: &fr
party: Partie
edit_order: Modifier la commande edit_order: Modifier la commande
select: Sélectionner select: Sélectionner
invite_form_fields: Inviter des champs de formulaire invite_form_fields: Inviter des champs de formulaire
@ -3539,6 +3543,7 @@ fr: &fr
range_without_total: "%{from} à %{to} événements" range_without_total: "%{from} à %{to} événements"
pt: &pt pt: &pt
party: Parte
edit_order: Edita Pedido edit_order: Edita Pedido
select: Selecionar select: Selecionar
invite_form_fields: Convidar campos do formulário invite_form_fields: Convidar campos do formulário
@ -4419,6 +4424,7 @@ pt: &pt
range_without_total: "%{from}-%{to} eventos" range_without_total: "%{from}-%{to} eventos"
de: &de de: &de
party: Partei
edit_order: Bestellung bearbeiten edit_order: Bestellung bearbeiten
select: Auswählen select: Auswählen
invite_form_fields: Formularfelder einladen invite_form_fields: Formularfelder einladen

@ -53,8 +53,11 @@ module Accounts
ApplicationRecord.transaction do ApplicationRecord.transaction do
account.testing_accounts << testing_account account.testing_accounts << testing_account
original_email = account.users.order(:id).first.email
test_email = generate_unique_test_email(original_email)
testing_account.users.create!( testing_account.users.create!(
email: account.users.order(:id).first.email.sub('@', '+test@'), email: test_email,
first_name: 'Testing', first_name: 'Testing',
last_name: 'Environment', last_name: 'Environment',
password: SecureRandom.hex, password: SecureRandom.hex,
@ -63,6 +66,22 @@ module Accounts
end end
end end
def generate_unique_test_email(original_email)
base_email = original_email.sub('@', '+test@')
return base_email unless User.exists?(email: base_email)
(1..3).each do |i|
test_email = original_email.sub('@', "+test#{i}@")
return test_email unless User.exists?(email: test_email)
end
timestamp = Time.current.to_i
original_email.sub('@', "+test#{timestamp}@")
end
def create_default_template(account) def create_default_template(account)
template = Template.find(1) template = Template.find(1)

@ -177,7 +177,11 @@ module Submitters
submitter_items.first(submitter_items.find_index { |e| e['uuid'] == submitter.uuid }) submitter_items.first(submitter_items.find_index { |e| e['uuid'] == submitter.uuid })
end end
before_items.all? { |item| submission.submitters.find { |e| e.uuid == item['uuid'] }&.completed_at? } before_items.all? do |item|
submitter = submission.submitters.find { |e| e.uuid == item['uuid'] }
submitter.nil? || submitter.completed_at?
end
end end
def build_document_filename(submitter, blob, filename_format) def build_document_filename(submitter, blob, filename_format)

@ -8,6 +8,14 @@ module Submitters
VARIABLE_REGEXP = /\{\{?(\w+)\}\}?/ VARIABLE_REGEXP = /\{\{?(\w+)\}\}?/
NONEDITABLE_FIELD_TYPES = %w[stamp heading].freeze NONEDITABLE_FIELD_TYPES = %w[stamp heading].freeze
STRFTIME_MAP = {
'hour' => '%-k',
'minute' => '%M',
'day' => '%-d',
'month' => '%-m',
'year' => '%Y'
}.freeze
module_function module_function
def call(submitter, params, request, validate_required: true) def call(submitter, params, request, validate_required: true)
@ -327,12 +335,10 @@ module Submitters
else else
e e
end end
when 'hour', 'minute', 'day', 'month', 'year'
with_time ? Time.current.in_time_zone(submission.account.timezone).strftime(STRFTIME_MAP[key]) : e
when 'date' when 'date'
if with_time with_time ? Time.current.in_time_zone(submission.account.timezone).to_date.to_s : e
Time.current.in_time_zone(submission.account.timezone).to_date.to_s
else
e
end
when 'role', 'email', 'phone', 'name' when 'role', 'email', 'phone', 'name'
attrs[key] || e attrs[key] || e
else else

@ -3,6 +3,20 @@
module Templates module Templates
module CreateAttachments module CreateAttachments
PDF_CONTENT_TYPE = 'application/pdf' PDF_CONTENT_TYPE = 'application/pdf'
ZIP_CONTENT_TYPE = 'application/zip'
X_ZIP_CONTENT_TYPE = 'application/x-zip-compressed'
JSON_CONTENT_TYPE = 'application/json'
DOCUMENT_EXTENSIONS = %w[.docx .doc .xlsx .xls .odt .rtf].freeze
DOCUMENT_CONTENT_TYPES = %w[
application/vnd.openxmlformats-officedocument.wordprocessingml.document
application/msword
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
application/vnd.ms-excel
application/vnd.oasis.opendocument.text
application/rtf
].freeze
ANNOTATIONS_SIZE_LIMIT = 6.megabytes ANNOTATIONS_SIZE_LIMIT = 6.megabytes
InvalidFileType = Class.new(StandardError) InvalidFileType = Class.new(StandardError)
PdfEncrypted = Class.new(StandardError) PdfEncrypted = Class.new(StandardError)
@ -10,7 +24,7 @@ module Templates
module_function module_function
def call(template, params, extract_fields: false) def call(template, params, extract_fields: false)
Array.wrap(params[:files].presence || params[:file]).map do |file| extract_zip_files(params[:files].presence || params[:file]).flat_map do |file|
handle_file_types(template, file, params, extract_fields:) handle_file_types(template, file, params, extract_fields:)
end end
end end
@ -53,6 +67,40 @@ module Templates
raise PdfEncrypted raise PdfEncrypted
end end
def extract_zip_files(files)
extracted_files = []
Array.wrap(files).each do |file|
if file.content_type == ZIP_CONTENT_TYPE || file.content_type == X_ZIP_CONTENT_TYPE
Zip::File.open(file.tempfile).each do |entry|
next if entry.directory?
tempfile = Tempfile.new(entry.name)
tempfile.binmode
entry.get_input_stream { |in_stream| IO.copy_stream(in_stream, tempfile) }
tempfile.rewind
type = Marcel::MimeType.for(tempfile, name: entry.name)
next if type.exclude?('image') &&
type != PDF_CONTENT_TYPE &&
type != JSON_CONTENT_TYPE &&
DOCUMENT_CONTENT_TYPES.exclude?(type)
extracted_files << ActionDispatch::Http::UploadedFile.new(
filename: File.basename(entry.name),
type:,
tempfile:
)
end
else
extracted_files << file
end
end
extracted_files
end
def handle_file_types(template, file, params, extract_fields:) def handle_file_types(template, file, params, extract_fields:)
if file.content_type.include?('image') || file.content_type == PDF_CONTENT_TYPE if file.content_type.include?('image') || file.content_type == PDF_CONTENT_TYPE
return handle_pdf_or_image(template, file, file.read, params, extract_fields:) return handle_pdf_or_image(template, file, file.read, params, extract_fields:)

@ -151,7 +151,7 @@ describe 'Submission API' do
] ]
}.to_json }.to_json
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
expect(response.parsed_body).to eq({ 'error' => 'email is invalid in `submitters[0]`.' }) expect(response.parsed_body).to eq({ 'error' => 'email is invalid in `submitters[0]`.' })
end end
@ -165,7 +165,7 @@ describe 'Submission API' do
submitters: [{ role: 'First Party', email: 'john.doe@example.com' }] submitters: [{ role: 'First Party', email: 'john.doe@example.com' }]
}.to_json }.to_json
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
expect(response.parsed_body).to eq({ 'error' => 'Template does not contain fields' }) expect(response.parsed_body).to eq({ 'error' => 'Template does not contain fields' })
end end
@ -179,7 +179,7 @@ describe 'Submission API' do
] ]
}.to_json }.to_json
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
expect(response.parsed_body).to eq({ 'error' => 'role must be unique in `submitters`.' }) expect(response.parsed_body).to eq({ 'error' => 'role must be unique in `submitters`.' })
end end
@ -193,7 +193,7 @@ describe 'Submission API' do
] ]
}.to_json }.to_json
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
expect(response.parsed_body).to eq({ 'error' => 'Defined more signing parties than in template' }) expect(response.parsed_body).to eq({ 'error' => 'Defined more signing parties than in template' })
end end
@ -209,7 +209,7 @@ describe 'Submission API' do
} }
}.to_json }.to_json
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
expect(response.parsed_body).to eq({ 'error' => 'body is required in `message`.' }) expect(response.parsed_body).to eq({ 'error' => 'body is required in `message`.' })
end end
end end
@ -235,7 +235,7 @@ describe 'Submission API' do
emails: 'amy.baker@example.com, george.morris@example.com@gmail.com' emails: 'amy.baker@example.com, george.morris@example.com@gmail.com'
}.to_json }.to_json
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_content)
expect(response.parsed_body).to eq({ 'error' => 'emails are invalid' }) expect(response.parsed_body).to eq({ 'error' => 'emails are invalid' })
end end

Loading…
Cancel
Save