mirror of
https://github.com/docusealco/docuseal
synced 2026-11-06 15:02:33 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
993698ec01 | ||
|
|
fcdc44ddbe | ||
|
|
0cb6b0ae12 | ||
|
|
0388927c5b | ||
|
|
2c8f5d2129 | ||
|
|
2bffbf83c6 | ||
|
|
ec01529014 | ||
|
|
ea1abf5662 | ||
|
|
fcd5263af1 | ||
|
|
1fa8616dbc | ||
|
|
f627277b45 | ||
|
|
809c3ea270 | ||
|
|
9caa9d79d4 | ||
|
|
a8f1c1c323 | ||
|
|
59fbea5c5f | ||
|
|
d3273d879a | ||
|
|
ea227e82c9 | ||
|
|
676b330aa3 | ||
|
|
8d3295e125 | ||
|
|
8cb74e0bcc |
No files matched your search
@@ -12,10 +12,15 @@ on:
|
|||||||
type: string
|
type: string
|
||||||
required: false
|
required: false
|
||||||
default: tonistiigi/binfmt:latest
|
default: tonistiigi/binfmt:latest
|
||||||
|
os:
|
||||||
|
description: OS
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
default: ubuntu-24.04-arm
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-24.04-arm
|
runs-on: ${{ inputs.os }}
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -29,7 +34,7 @@ jobs:
|
|||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
images: docuseal/docuseal
|
images: docuseal/docuseal
|
||||||
tags: type=semver,pattern={{version}}
|
tags: latest,${{ inputs.version }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|||||||
+1
-1
@@ -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\
|
||||||
|
|||||||
+86
-78
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,21 @@ module Api
|
|||||||
def create
|
def create
|
||||||
submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
submitter = Submitter.find_by!(slug: params[:submitter_slug])
|
||||||
|
|
||||||
if params[:type].in?(%w[initials signature]) && ImageUtils.blank?(Vips::Image.new_from_file(params[:file].path))
|
if params[:type].in?(%w[initials signature])
|
||||||
Rollbar.error("Empty signature: #{submitter.id}") if defined?(Rollbar)
|
image = Vips::Image.new_from_file(params[:file].path)
|
||||||
|
|
||||||
return render json: { error: "#{params[:type]} is empty" }, status: :unprocessable_entity
|
if ImageUtils.blank?(image)
|
||||||
|
Rollbar.error("Empty signature: #{submitter.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
|
return render json: { error: "#{params[:type]} is empty" }, status: :unprocessable_content
|
||||||
|
end
|
||||||
|
|
||||||
|
if ImageUtils.error?(image)
|
||||||
|
Rollbar.error("Error signature: #{submitter.id}") if defined?(Rollbar)
|
||||||
|
|
||||||
|
return render json: { error: "#{params[:type]} error, try to sign on another device" },
|
||||||
|
status: :unprocessable_content
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attachment = Submitters.create_attachment!(submitter, params)
|
attachment = Submitters.create_attachment!(submitter, params)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -185,7 +185,7 @@ module Api
|
|||||||
message: %i[subject body],
|
message: %i[subject body],
|
||||||
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
|
||||||
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
|
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
|
||||||
:require_phone_2fa,
|
:require_phone_2fa, :order,
|
||||||
{ metadata: {}, values: {}, roles: [], readonly_fields: [], message: %i[subject body],
|
{ metadata: {}, values: {}, roles: [], readonly_fields: [], message: %i[subject body],
|
||||||
fields: [:name, :uuid, :default_value, :value, :title, :description,
|
fields: [:name, :uuid, :default_value, :value, :title, :description,
|
||||||
:readonly, :required, :validation_pattern, :invalid_message,
|
:readonly, :required, :validation_pattern, :invalid_message,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ module Api
|
|||||||
:external_id,
|
:external_id,
|
||||||
:shared_link,
|
:shared_link,
|
||||||
{
|
{
|
||||||
submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email]],
|
submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email order]],
|
||||||
fields: [[:uuid, :submitter_uuid, :name, :type,
|
fields: [[:uuid, :submitter_uuid, :name, :type,
|
||||||
:required, :readonly, :default_value,
|
:required, :readonly, :default_value,
|
||||||
:title, :description, :prefillable,
|
:title, :description, :prefillable,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ class TemplatesController < ApplicationController
|
|||||||
params.require(:template).permit(
|
params.require(:template).permit(
|
||||||
:name,
|
:name,
|
||||||
{ schema: [[:attachment_uuid, :name, { conditions: [%i[field_uuid value action operation]] }]],
|
{ schema: [[:attachment_uuid, :name, { conditions: [%i[field_uuid value action operation]] }]],
|
||||||
submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid optional_invite_by_uuid email]],
|
submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid optional_invite_by_uuid email order]],
|
||||||
fields: [[:uuid, :submitter_uuid, :name, :type,
|
fields: [[:uuid, :submitter_uuid, :name, :type,
|
||||||
:required, :readonly, :default_value,
|
:required, :readonly, :default_value,
|
||||||
:title, :description, :prefillable,
|
:title, :description, :prefillable,
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -9,6 +9,10 @@ class TemplatesRecipientsController < ApplicationController
|
|||||||
@template.submitters =
|
@template.submitters =
|
||||||
submitters_params.map { |s| s.reject { |_, v| v.is_a?(String) && v.blank? } }
|
submitters_params.map { |s| s.reject { |_, v| v.is_a?(String) && v.blank? } }
|
||||||
|
|
||||||
|
if @template.submitters.each_with_index.all? { |s, index| s['order'] == index }
|
||||||
|
@template.submitters.each { |s| s.delete('order') }
|
||||||
|
end
|
||||||
|
|
||||||
@template.save!
|
@template.save!
|
||||||
|
|
||||||
render json: { submitters: @template.submitters }
|
render json: { submitters: @template.submitters }
|
||||||
@@ -18,7 +22,7 @@ class TemplatesRecipientsController < ApplicationController
|
|||||||
|
|
||||||
def submitters_params
|
def submitters_params
|
||||||
permit_params = { submitters: [%i[name uuid is_requester optional_invite_by_uuid
|
permit_params = { submitters: [%i[name uuid is_requester optional_invite_by_uuid
|
||||||
invite_by_uuid linked_to_uuid email option]] }
|
invite_by_uuid linked_to_uuid email option order]] }
|
||||||
|
|
||||||
params.require(:template).permit(permit_params).fetch(:submitters, {}).values.filter_map do |s|
|
params.require(:template).permit(permit_params).fetch(:submitters, {}).values.filter_map do |s|
|
||||||
next if s[:uuid].blank?
|
next if s[:uuid].blank?
|
||||||
@@ -29,6 +33,7 @@ class TemplatesRecipientsController < ApplicationController
|
|||||||
s.delete(:is_requester)
|
s.delete(:is_requester)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
s[:order] = s[:order].to_i if s[:order].present?
|
||||||
s.delete(:invite_by_uuid) if s[:invite_by_uuid].blank?
|
s.delete(:invite_by_uuid) if s[:invite_by_uuid].blank?
|
||||||
s.delete(:optional_invite_by_uuid) if s[:optional_invite_by_uuid].blank?
|
s.delete(:optional_invite_by_uuid) if s[:optional_invite_by_uuid].blank?
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import DashboardDropzone from './elements/dashboard_dropzone'
|
|||||||
import RequiredCheckboxGroup from './elements/required_checkbox_group'
|
import RequiredCheckboxGroup from './elements/required_checkbox_group'
|
||||||
import PageContainer from './elements/page_container'
|
import PageContainer from './elements/page_container'
|
||||||
import EmailEditor from './elements/email_editor'
|
import EmailEditor from './elements/email_editor'
|
||||||
|
import MountOnClick from './elements/mount_on_click'
|
||||||
|
|
||||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||||
|
|
||||||
@@ -111,6 +112,7 @@ safeRegisterElement('check-on-click', CheckOnClick)
|
|||||||
safeRegisterElement('required-checkbox-group', RequiredCheckboxGroup)
|
safeRegisterElement('required-checkbox-group', RequiredCheckboxGroup)
|
||||||
safeRegisterElement('page-container', PageContainer)
|
safeRegisterElement('page-container', PageContainer)
|
||||||
safeRegisterElement('email-editor', EmailEditor)
|
safeRegisterElement('email-editor', EmailEditor)
|
||||||
|
safeRegisterElement('mount-on-click', MountOnClick)
|
||||||
|
|
||||||
safeRegisterElement('template-builder', class extends HTMLElement {
|
safeRegisterElement('template-builder', class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export default class extends HTMLElement {
|
||||||
|
connectedCallback () {
|
||||||
|
this.addEventListener('click', () => {
|
||||||
|
document.body.append(this.template.content)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get template () {
|
||||||
|
return document.getElementById(this.dataset.templateId)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<div class="space-x-2 flex flex-none">
|
<div class="space-x-2 flex flex-none">
|
||||||
<span
|
<span
|
||||||
v-if="isTextSignature && format !== 'typed' && format !== 'upload'"
|
v-if="isTextSignature && format !== 'typed_or_upload' && format !== 'typed' && format !== 'upload'"
|
||||||
class="tooltip"
|
class="tooltip"
|
||||||
:data-tip="t('draw_signature')"
|
:data-tip="t('draw_signature')"
|
||||||
>
|
>
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="withTypedSignature && format !== 'typed' && format !== 'drawn' && format !== 'upload'"
|
v-else-if="withTypedSignature && format !== 'drawn_or_upload' && format !== 'typed_or_upload' && format !== 'typed' && format !== 'drawn' && format !== 'upload'"
|
||||||
class="tooltip ml-2"
|
class="tooltip ml-2"
|
||||||
:class="{ 'hidden sm:inline': modelValue || computedPreviousValue }"
|
:class="{ 'hidden sm:inline': modelValue || computedPreviousValue }"
|
||||||
:data-tip="t('type_text')"
|
:data-tip="t('type_text')"
|
||||||
@@ -85,8 +85,8 @@
|
|||||||
{{ t(format === 'upload' ? 'reupload' : 'redraw') }}
|
{{ t(format === 'upload' ? 'reupload' : 'redraw') }}
|
||||||
</a>
|
</a>
|
||||||
<span
|
<span
|
||||||
v-if="withQrButton && !modelValue && !computedPreviousValue && format !== 'typed' && format !== 'upload'"
|
v-if="withQrButton && !modelValue && !computedPreviousValue && format !== 'typed_or_upload' && format !== 'typed' && format !== 'upload'"
|
||||||
class=" tooltip"
|
class="tooltip before:translate-x-[-90%]"
|
||||||
:data-tip="t('drawn_signature_on_a_touchscreen_device')"
|
:data-tip="t('drawn_signature_on_a_touchscreen_device')"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
@@ -395,7 +395,7 @@ export default {
|
|||||||
isShowQr: false,
|
isShowQr: false,
|
||||||
isOtherReason: false,
|
isOtherReason: false,
|
||||||
isUsePreviousValue: true,
|
isUsePreviousValue: true,
|
||||||
isTextSignature: this.field.preferences?.format === 'typed',
|
isTextSignature: this.field.preferences?.format === 'typed' || this.field.preferences?.format === 'typed_or_upload',
|
||||||
uploadImageInputKey: Math.random().toString()
|
uploadImageInputKey: Math.random().toString()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -304,7 +304,7 @@
|
|||||||
{{ t('any') }}
|
{{ t('any') }}
|
||||||
</option>
|
</option>
|
||||||
<option
|
<option
|
||||||
v-for="type in ['drawn', 'typed', 'drawn_or_typed', 'upload']"
|
v-for="type in ['drawn', 'typed', 'drawn_or_typed', 'drawn_or_upload', 'upload']"
|
||||||
:key="type"
|
:key="type"
|
||||||
:value="type"
|
:value="type"
|
||||||
:selected="field.preferences?.format === type"
|
:selected="field.preferences?.format === type"
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ const en = {
|
|||||||
any: 'Any',
|
any: 'Any',
|
||||||
drawn: 'Drawn',
|
drawn: 'Drawn',
|
||||||
drawn_or_typed: 'Drawn or Typed',
|
drawn_or_typed: 'Drawn or Typed',
|
||||||
|
drawn_or_upload: 'Drawn or Upload',
|
||||||
upload: 'Upload',
|
upload: 'Upload',
|
||||||
formula: 'Formula',
|
formula: 'Formula',
|
||||||
typed: 'Typed',
|
typed: 'Typed',
|
||||||
@@ -322,6 +323,7 @@ const es = {
|
|||||||
any: 'Cualquier',
|
any: 'Cualquier',
|
||||||
drawn: 'Dibujado',
|
drawn: 'Dibujado',
|
||||||
drawn_or_typed: 'Dibujado o Escrito',
|
drawn_or_typed: 'Dibujado o Escrito',
|
||||||
|
drawn_or_upload: 'Dibujado o Subido',
|
||||||
upload: 'Subir',
|
upload: 'Subir',
|
||||||
typed: 'Escrito',
|
typed: 'Escrito',
|
||||||
none: 'Ninguno',
|
none: 'Ninguno',
|
||||||
@@ -410,6 +412,7 @@ const it = {
|
|||||||
any: 'Qualsiasi',
|
any: 'Qualsiasi',
|
||||||
drawn: 'Disegnato',
|
drawn: 'Disegnato',
|
||||||
drawn_or_typed: 'Disegnato o Digitato',
|
drawn_or_typed: 'Disegnato o Digitato',
|
||||||
|
drawn_or_upload: 'Disegnato o Caricato',
|
||||||
upload: 'Caricare',
|
upload: 'Caricare',
|
||||||
formula: 'Formula',
|
formula: 'Formula',
|
||||||
typed: 'Digitato',
|
typed: 'Digitato',
|
||||||
@@ -672,6 +675,7 @@ const pt = {
|
|||||||
any: 'Qualquer',
|
any: 'Qualquer',
|
||||||
drawn: 'Desenhado',
|
drawn: 'Desenhado',
|
||||||
drawn_or_typed: 'Desenhado ou Digitado',
|
drawn_or_typed: 'Desenhado ou Digitado',
|
||||||
|
drawn_or_upload: 'Desenhado ou Enviado',
|
||||||
upload: 'Carregar',
|
upload: 'Carregar',
|
||||||
typed: 'Digitado',
|
typed: 'Digitado',
|
||||||
none: 'Nenhum',
|
none: 'Nenhum',
|
||||||
@@ -847,6 +851,7 @@ const fr = {
|
|||||||
any: 'Tout',
|
any: 'Tout',
|
||||||
drawn: 'Dessiné',
|
drawn: 'Dessiné',
|
||||||
drawn_or_typed: 'Dessiné ou Tapé',
|
drawn_or_typed: 'Dessiné ou Tapé',
|
||||||
|
drawn_or_upload: 'Dessiné ou Téléchargé',
|
||||||
upload: 'Téléverser',
|
upload: 'Téléverser',
|
||||||
typed: 'Tapé',
|
typed: 'Tapé',
|
||||||
none: 'Aucun',
|
none: 'Aucun',
|
||||||
@@ -1022,6 +1027,7 @@ const de = {
|
|||||||
any: 'Jede',
|
any: 'Jede',
|
||||||
drawn: 'Gezeichnet',
|
drawn: 'Gezeichnet',
|
||||||
drawn_or_typed: 'Gezeichnet oder getippt',
|
drawn_or_typed: 'Gezeichnet oder getippt',
|
||||||
|
drawn_or_upload: 'Gezeichnet oder hochgeladen',
|
||||||
upload: 'Upload',
|
upload: 'Upload',
|
||||||
typed: 'Getippt',
|
typed: 'Getippt',
|
||||||
none: 'Keine',
|
none: 'Keine',
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
|||||||
@@ -135,19 +135,33 @@ class ProcessSubmitterCompletionJob
|
|||||||
end
|
end
|
||||||
|
|
||||||
def enqueue_next_submitter_request_notification(submitter)
|
def enqueue_next_submitter_request_notification(submitter)
|
||||||
next_submitter_item =
|
submission = submitter.submission
|
||||||
submitter.submission.template_submitters.find do |e|
|
submitters_index = submission.submitters.index_by(&:uuid)
|
||||||
sub = submitter.submission.submitters.find { |s| s.uuid == e['uuid'] }
|
|
||||||
|
|
||||||
next unless sub
|
next_submitter_items =
|
||||||
|
if submission.template_submitters.any? { |s| s['order'] }
|
||||||
|
submitter_groups =
|
||||||
|
submission.template_submitters.group_by.with_index { |s, index| s['order'] || index }
|
||||||
|
|
||||||
sub.completed_at.blank? && sub.sent_at.blank?
|
current_group_index = submitter_groups.find { |_, group| group.any? { |s| s['uuid'] == submitter.uuid } }&.first
|
||||||
|
|
||||||
|
if submitter_groups[current_group_index + 1] &&
|
||||||
|
submitters_index.values_at(*submitter_groups[current_group_index].pluck('uuid'))
|
||||||
|
.compact.all?(&:completed_at?)
|
||||||
|
submitter_groups[current_group_index + 1]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
submission.template_submitters.find do |e|
|
||||||
|
sub = submitters_index[e['uuid']]
|
||||||
|
|
||||||
|
next unless sub
|
||||||
|
|
||||||
|
sub.completed_at.blank? && sub.sent_at.blank?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return unless next_submitter_item
|
next_submitters = submitters_index.values_at(*Array.wrap(next_submitter_items).pluck('uuid')).compact
|
||||||
|
|
||||||
next_submitter = submitter.submission.submitters.find { |s| s.uuid == next_submitter_item['uuid'] }
|
Submitters.send_signature_requests(next_submitters)
|
||||||
|
|
||||||
Submitters.send_signature_requests([next_submitter])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
class TemplateMailer < ApplicationMailer
|
class TemplateMailer < ApplicationMailer
|
||||||
def otp_verification_email(template, email:)
|
def otp_verification_email(template, email:)
|
||||||
|
@current_account = template.account
|
||||||
@template = template
|
@template = template
|
||||||
|
|
||||||
@otp_code = EmailVerificationCodes.generate([email.downcase.strip, template.slug].join(':'))
|
@otp_code = EmailVerificationCodes.generate([email.downcase.strip, template.slug].join(':'))
|
||||||
|
|||||||
@@ -35,17 +35,19 @@
|
|||||||
<%= ff.select :authentication, options_for_select([%w[Plain plain], %w[Login login], %w[CRAM-MD5 cram_md5]], value.fetch('authentication', 'plain')), { prompt: true }, required: true, class: 'base-select' %>
|
<%= ff.select :authentication, options_for_select([%w[Plain plain], %w[Login login], %w[CRAM-MD5 cram_md5]], value.fetch('authentication', 'plain')), { prompt: true }, required: true, class: 'base-select' %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<% if !Docuseal.multitenant? || can?(:manage, :personalization_advanced) %>
|
||||||
<%= ff.label :security_label, 'SMTP Security', class: 'label' %>
|
<div class="form-control">
|
||||||
<div class="flex items-center space-x-6">
|
<%= ff.label :security_label, 'SMTP Security', class: 'label' %>
|
||||||
<% [%w[Auto none], %w[SSL ssl], %w[TLS tls], %w[Noverify noverify]].each do |(label, val)| %>
|
<div class="flex items-center space-x-6">
|
||||||
<%= ff.label :security, value: val, for: "#{val}_radio", class: 'label' do %>
|
<% [%w[Auto none], %w[SSL ssl], %w[TLS tls], %w[Noverify noverify]].each do |(label, val)| %>
|
||||||
<%= ff.radio_button :security, val, checked: (value['security'].blank? && val == 'none') || value['security'] == val, id: "#{val}_radio", class: 'base-radio mr-2' %>
|
<%= ff.label :security, value: val, for: "#{val}_radio", class: 'label' do %>
|
||||||
<%= label %>
|
<%= ff.radio_button :security, val, checked: (value['security'].blank? && val == 'none') || value['security'] == val, id: "#{val}_radio", class: 'base-radio mr-2' %>
|
||||||
|
<%= label %>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<% end %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :from_email, t('send_from_email'), class: 'label' %>
|
<%= ff.label :from_email, t('send_from_email'), class: 'label' %>
|
||||||
<%= ff.email_field :from_email, value: value['from_email'], required: !Docuseal.multitenant?, class: 'base-input' %>
|
<%= ff.email_field :from_email, value: value['from_email'], required: !Docuseal.multitenant?, class: 'base-input' %>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<% if template.preferences['submitters_order'] == 'preserved' %>
|
<% if template.preferences['submitters_order'] == 'preserved' || template.submitters.any? { |s| s['order'] } %>
|
||||||
<%= f.hidden_field :preserve_order, value: '1' %>
|
<%= f.hidden_field :preserve_order, value: '1' %>
|
||||||
<% elsif template.submitters.size > 1 %>
|
<% elsif template.submitters.size > 1 %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
|
|||||||
@@ -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 %>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<% close_on_submit = local_assigns.fetch(:close_on_submit, true) %>
|
<% close_on_submit = local_assigns.fetch(:close_on_submit, true) %>
|
||||||
|
<% is_order_set = template.submitters.any? { |s| s['order'] } %>
|
||||||
<%= form_for template, url: template_recipients_path(template), method: :post, html: { autocomplete: 'off', class: 'mt-1', id: :submitters_form }, data: { close_on_submit: } do |f| %>
|
<%= form_for template, url: template_recipients_path(template), method: :post, html: { autocomplete: 'off', class: 'mt-1', id: :submitters_form }, data: { close_on_submit: } do |f| %>
|
||||||
<% unless close_on_submit %>
|
<% unless close_on_submit %>
|
||||||
<toggle-on-submit data-element-id="form_saved_alert"></toggle-on-submit>
|
<toggle-on-submit data-element-id="form_saved_alert"></toggle-on-submit>
|
||||||
@@ -6,11 +7,24 @@
|
|||||||
<div class="space-y-3 divide-y">
|
<div class="space-y-3 divide-y">
|
||||||
<% template.submitters.each_with_index do |submitter, index| %>
|
<% template.submitters.each_with_index do |submitter, index| %>
|
||||||
<div class="<%= 'pt-3' if index.positive? %>">
|
<div class="<%= 'pt-3' if index.positive? %>">
|
||||||
<%= f.fields_for :submitters, item = Struct.new(:name, :uuid, :is_requester, :email, :invite_by_uuid, :optional_invite_by_uuid, :linked_to_uuid, :option).new(*submitter.values_at('name', 'uuid', 'is_requester', 'email', 'invite_by_uuid', 'optional_invite_by_uuid', 'linked_to_uuid')), index: do |ff| %>
|
<%= f.fields_for :submitters, item = Struct.new(:name, :uuid, :is_requester, :email, :invite_by_uuid, :optional_invite_by_uuid, :linked_to_uuid, :order, :option).new(*submitter.values_at('name', 'uuid', 'is_requester', 'email', 'invite_by_uuid', 'optional_invite_by_uuid', 'linked_to_uuid', 'order')), index: do |ff| %>
|
||||||
<% item.option = item.is_requester.present? ? 'is_requester' : (item.email.present? ? 'email' : (item.linked_to_uuid.present? ? "linked_to_#{item.linked_to_uuid}" : (item.invite_by_uuid.present? ? "invite_by_#{item.invite_by_uuid}" : (item.optional_invite_by_uuid.present? ? "optional_invite_by_#{item.optional_invite_by_uuid}" : '')))) %>
|
<% item.option = item.is_requester.present? ? 'is_requester' : (item.email.present? ? 'email' : (item.linked_to_uuid.present? ? "linked_to_#{item.linked_to_uuid}" : (item.invite_by_uuid.present? ? "invite_by_#{item.invite_by_uuid}" : (item.optional_invite_by_uuid.present? ? "optional_invite_by_#{item.optional_invite_by_uuid}" : '')))) %>
|
||||||
<%= ff.hidden_field :uuid %>
|
<%= ff.hidden_field :uuid %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.text_field :name, class: 'w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer mb-2', autocomplete: 'off', placeholder: "#{index + 1}#{(index + 1).ordinal} Party", required: true %>
|
<div class="flex justify-between">
|
||||||
|
<%= ff.text_field :name, class: 'w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer mb-2', autocomplete: 'off', placeholder: "#{index + 1}#{(index + 1).ordinal} Party", required: true %>
|
||||||
|
<% if template.submitters.size > 2 %>
|
||||||
|
<div id="order_<%= submitter['uuid'] %>" class="mr-0.5">
|
||||||
|
<% if is_order_set %>
|
||||||
|
<%= ff.select :order, options_for_select(template.submitters.map.with_index { |_, i| [(i + 1).ordinalize, i] }, submitter['order'].presence || index), {}, class: 'select select-xs text-sm input-bordered bg-white pl-3.5' %>
|
||||||
|
<% elsif index == 0 %>
|
||||||
|
<mount-on-click data-template-id="order_fields" class="link whitespace-nowrap text-sm mt-1 mr-1 block">
|
||||||
|
<%= t('edit_order') %>
|
||||||
|
</mount-on-click>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
<% if template.submitters.size == 2 %>
|
<% if template.submitters.size == 2 %>
|
||||||
<%= tag.input name: ff.field_name(:email), value: ff.object.email, type: :email, class: 'base-input', multiple: true, autocomplete: 'off', placeholder: t('default_email'), disabled: ff.object.is_requester || ff.object.invite_by_uuid.present? || ff.object.optional_invite_by_uuid.present?, id: field_uuid = SecureRandom.uuid %>
|
<%= tag.input name: ff.field_name(:email), value: ff.object.email, type: :email, class: 'base-input', multiple: true, autocomplete: 'off', placeholder: t('default_email'), disabled: ff.object.is_requester || ff.object.invite_by_uuid.present? || ff.object.optional_invite_by_uuid.present?, id: field_uuid = SecureRandom.uuid %>
|
||||||
<% else %>
|
<% else %>
|
||||||
@@ -85,3 +99,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
<% if template.submitters.size > 2 && !is_order_set %>
|
||||||
|
<template id="order_fields">
|
||||||
|
<% template.submitters.each_with_index do |submitter, index| %>
|
||||||
|
<turbo-stream action="replace" target="order_<%= submitter['uuid'] %>">
|
||||||
|
<template>
|
||||||
|
<div id="order_<%= submitter['uuid'] %>">
|
||||||
|
<%= select_tag "template[submitters][#{index}][order]", options_for_select(template.submitters.map.with_index { |_, i| [(i + 1).ordinalize, i] }, submitter['order'].presence || index), class: 'select select-xs text-sm input-bordered bg-white pl-3.5' %>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</turbo-stream>
|
||||||
|
<% end %>
|
||||||
|
</template>
|
||||||
|
<% 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,8 @@ en: &en
|
|||||||
thanks: Thanks
|
thanks: Thanks
|
||||||
private: Private
|
private: Private
|
||||||
select: Select
|
select: Select
|
||||||
|
party: Party
|
||||||
|
edit_order: Edit Order
|
||||||
invite_form_fields: Invite form fields
|
invite_form_fields: Invite form fields
|
||||||
default_parties: Default parties
|
default_parties: Default parties
|
||||||
authenticate_embedded_form_preview_with_token: Authenticate embedded form preview with token
|
authenticate_embedded_form_preview_with_token: Authenticate embedded form preview with token
|
||||||
@@ -898,6 +900,8 @@ en: &en
|
|||||||
range_without_total: "%{from}-%{to} events"
|
range_without_total: "%{from}-%{to} events"
|
||||||
|
|
||||||
es: &es
|
es: &es
|
||||||
|
party: Parte
|
||||||
|
edit_order: Edita Pedido
|
||||||
select: Seleccionar
|
select: Seleccionar
|
||||||
invite_form_fields: Invitar campos del formulario
|
invite_form_fields: Invitar campos del formulario
|
||||||
pro: Pro
|
pro: Pro
|
||||||
@@ -1776,6 +1780,8 @@ es: &es
|
|||||||
range_without_total: "%{from}-%{to} eventos"
|
range_without_total: "%{from}-%{to} eventos"
|
||||||
|
|
||||||
it: &it
|
it: &it
|
||||||
|
party: Parte
|
||||||
|
edit_order: Modifica Ordine
|
||||||
select: Seleziona
|
select: Seleziona
|
||||||
invite_form_fields: Invita campi modulo
|
invite_form_fields: Invita campi modulo
|
||||||
pro: Pro
|
pro: Pro
|
||||||
@@ -2654,6 +2660,8 @@ 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
|
||||||
select: Sélectionner
|
select: Sélectionner
|
||||||
invite_form_fields: Inviter des champs de formulaire
|
invite_form_fields: Inviter des champs de formulaire
|
||||||
pro: Pro
|
pro: Pro
|
||||||
@@ -3535,6 +3543,8 @@ 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
|
||||||
select: Selecionar
|
select: Selecionar
|
||||||
invite_form_fields: Convidar campos do formulário
|
invite_form_fields: Convidar campos do formulário
|
||||||
pro: Pro
|
pro: Pro
|
||||||
@@ -4414,6 +4424,8 @@ pt: &pt
|
|||||||
range_without_total: "%{from}-%{to} eventos"
|
range_without_total: "%{from}-%{to} eventos"
|
||||||
|
|
||||||
de: &de
|
de: &de
|
||||||
|
party: Partei
|
||||||
|
edit_order: Bestellung bearbeiten
|
||||||
select: Auswählen
|
select: Auswählen
|
||||||
invite_form_fields: Formularfelder einladen
|
invite_form_fields: Formularfelder einladen
|
||||||
pro: Pro
|
pro: Pro
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class PopulateSubmitterAccountId < ActiveRecord::Migration[7.1]
|
class PopulateSubmitterAccountId < ActiveRecord::Migration[7.1]
|
||||||
disable_ddl_transaction
|
disable_ddl_transaction!
|
||||||
|
|
||||||
class MigrationSubmitter < ApplicationRecord
|
class MigrationSubmitter < ApplicationRecord
|
||||||
self.table_name = 'submitters'
|
self.table_name = 'submitters'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class PopulateCompletedSubmittersAndDocuments < ActiveRecord::Migration[7.2]
|
class PopulateCompletedSubmittersAndDocuments < ActiveRecord::Migration[7.2]
|
||||||
disable_ddl_transaction
|
disable_ddl_transaction!
|
||||||
|
|
||||||
class MigrationSubmitter < ApplicationRecord
|
class MigrationSubmitter < ApplicationRecord
|
||||||
self.table_name = 'submitters'
|
self.table_name = 'submitters'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class PopulateWebhookUrls < ActiveRecord::Migration[7.2]
|
class PopulateWebhookUrls < ActiveRecord::Migration[7.2]
|
||||||
disable_ddl_transaction
|
disable_ddl_transaction!
|
||||||
|
|
||||||
class MigrationWebhookUrl < ActiveRecord::Base
|
class MigrationWebhookUrl < ActiveRecord::Base
|
||||||
self.table_name = 'webhook_urls'
|
self.table_name = 'webhook_urls'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AddSharedLinkToTemplates < ActiveRecord::Migration[8.0]
|
class AddSharedLinkToTemplates < ActiveRecord::Migration[8.0]
|
||||||
disable_ddl_transaction
|
disable_ddl_transaction!
|
||||||
|
|
||||||
class MigrationTemplate < ActiveRecord::Base
|
class MigrationTemplate < ActiveRecord::Base
|
||||||
self.table_name = 'templates'
|
self.table_name = 'templates'
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class CreateSearchEnties < ActiveRecord::Migration[8.0]
|
class CreateSearchEnties < ActiveRecord::Migration[8.0]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
def up
|
def up
|
||||||
return unless adapter_name == 'PostgreSQL'
|
return unless adapter_name == 'PostgreSQL'
|
||||||
|
|
||||||
enable_extension 'btree_gin'
|
|
||||||
|
|
||||||
create_table :search_entries do |t|
|
create_table :search_entries do |t|
|
||||||
t.references :record, null: false, polymorphic: true, index: false
|
t.references :record, null: false, polymorphic: true, index: false
|
||||||
t.bigint :account_id, null: false
|
t.bigint :account_id, null: false
|
||||||
@@ -14,12 +14,24 @@ class CreateSearchEnties < ActiveRecord::Migration[8.0]
|
|||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index :search_entries, %i[account_id tsvector], using: :gin, where: "record_type = 'Submitter'",
|
begin
|
||||||
name: 'index_search_entries_on_account_id_tsvector_submitter'
|
enable_extension 'btree_gin'
|
||||||
add_index :search_entries, %i[account_id tsvector], using: :gin, where: "record_type = 'Submission'",
|
rescue StandardError
|
||||||
name: 'index_search_entries_on_account_id_tsvector_submission'
|
nil
|
||||||
add_index :search_entries, %i[account_id tsvector], using: :gin, where: "record_type = 'Template'",
|
end
|
||||||
name: 'index_search_entries_on_account_id_tsvector_template'
|
|
||||||
|
btree_gin_enabled = extension_enabled?('btree_gin')
|
||||||
|
|
||||||
|
add_index :search_entries, btree_gin_enabled ? %i[account_id tsvector] : :tsvector,
|
||||||
|
using: :gin, where: "record_type = 'Submitter'",
|
||||||
|
name: 'index_search_entries_on_account_id_tsvector_submitter'
|
||||||
|
add_index :search_entries, btree_gin_enabled ? %i[account_id tsvector] : :tsvector,
|
||||||
|
using: :gin, where: "record_type = 'Submission'",
|
||||||
|
name: 'index_search_entries_on_account_id_tsvector_submission'
|
||||||
|
add_index :search_entries, btree_gin_enabled ? %i[account_id tsvector] : :tsvector,
|
||||||
|
using: :gin, where: "record_type = 'Template'",
|
||||||
|
name: 'index_search_entries_on_account_id_tsvector_template'
|
||||||
|
|
||||||
add_index :search_entries, %i[record_id record_type], unique: true
|
add_index :search_entries, %i[record_id record_type], unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -28,6 +40,10 @@ class CreateSearchEnties < ActiveRecord::Migration[8.0]
|
|||||||
|
|
||||||
drop_table :search_entries
|
drop_table :search_entries
|
||||||
|
|
||||||
disable_extension 'btree_gin'
|
begin
|
||||||
|
disable_extension 'btree_gin'
|
||||||
|
rescue StandardError
|
||||||
|
nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -4,13 +4,18 @@ class AddNgramToSearchIndex < ActiveRecord::Migration[8.0]
|
|||||||
def change
|
def change
|
||||||
return unless adapter_name == 'PostgreSQL'
|
return unless adapter_name == 'PostgreSQL'
|
||||||
|
|
||||||
|
btree_gin_enabled = extension_enabled?('btree_gin')
|
||||||
|
|
||||||
add_column :search_entries, :ngram, :tsvector
|
add_column :search_entries, :ngram, :tsvector
|
||||||
|
|
||||||
add_index :search_entries, %i[account_id ngram], using: :gin, where: "record_type = 'Submitter'",
|
add_index :search_entries, btree_gin_enabled ? %i[account_id tsvector] : :tsvector,
|
||||||
name: 'index_search_entries_on_account_id_ngram_submitter'
|
using: :gin, where: "record_type = 'Submitter'",
|
||||||
add_index :search_entries, %i[account_id ngram], using: :gin, where: "record_type = 'Submission'",
|
name: 'index_search_entries_on_account_id_ngram_submitter'
|
||||||
name: 'index_search_entries_on_account_id_ngram_submission'
|
add_index :search_entries, btree_gin_enabled ? %i[account_id tsvector] : :tsvector,
|
||||||
add_index :search_entries, %i[account_id ngram], using: :gin, where: "record_type = 'Template'",
|
using: :gin, where: "record_type = 'Submission'",
|
||||||
name: 'index_search_entries_on_account_id_ngram_template'
|
name: 'index_search_entries_on_account_id_ngram_submission'
|
||||||
|
add_index :search_entries, btree_gin_enabled ? %i[account_id tsvector] : :tsvector,
|
||||||
|
using: :gin, where: "record_type = 'Template'",
|
||||||
|
name: 'index_search_entries_on_account_id_ngram_template'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
+20
-1
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -14,4 +14,12 @@ module ImageUtils
|
|||||||
|
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def error?(image)
|
||||||
|
image = image.crop(0, 0, image.width / 4, 2)
|
||||||
|
|
||||||
|
row1, row2 = image.to_a
|
||||||
|
|
||||||
|
row1[3..] == row2[..-4] && row1.each_cons(2).none? { |a, b| a == b }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
@@ -76,6 +76,7 @@ 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)
|
||||||
|
type(submitter_params, :order, Integer)
|
||||||
email_format(submitter_params, :email, message: 'email is invalid')
|
email_format(submitter_params, :email, message: 'email is invalid')
|
||||||
email_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)
|
||||||
|
|||||||
+13
-5
@@ -142,15 +142,23 @@ module Submissions
|
|||||||
submissions.each_with_index do |submission, index|
|
submissions.each_with_index do |submission, index|
|
||||||
delay_seconds = (delay + index).seconds if delay
|
delay_seconds = (delay + index).seconds if delay
|
||||||
|
|
||||||
submitters = submission.submitters.reject(&:completed_at?)
|
template_submitters = submission.template_submitters
|
||||||
|
submitters_index = submission.submitters.reject(&:completed_at?).index_by(&:uuid)
|
||||||
|
|
||||||
if submission.submitters_order_preserved?
|
if template_submitters.any? { |s| s['order'] }
|
||||||
first_submitter =
|
min_order = template_submitters.map.with_index { |s, i| s['order'] || i }.min
|
||||||
submission.template_submitters.filter_map { |s| submitters.find { |e| e.uuid == s['uuid'] } }.first
|
|
||||||
|
first_submitters = template_submitters.filter_map do |s|
|
||||||
|
submitters_index[s['uuid']] if s['order'] == min_order
|
||||||
|
end
|
||||||
|
|
||||||
|
Submitters.send_signature_requests(first_submitters, delay_seconds:)
|
||||||
|
elsif submission.submitters_order_preserved?
|
||||||
|
first_submitter = template_submitters.filter_map { |s| submitters_index[s['uuid']] }.first
|
||||||
|
|
||||||
Submitters.send_signature_requests([first_submitter], delay_seconds:) if first_submitter
|
Submitters.send_signature_requests([first_submitter], delay_seconds:) if first_submitter
|
||||||
else
|
else
|
||||||
Submitters.send_signature_requests(submitters, delay_seconds:)
|
Submitters.send_signature_requests(submitters_index.values, delay_seconds:)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -52,9 +52,12 @@ module Submissions
|
|||||||
template_submitter = template_submitters.find { |e| e['uuid'] == uuid }
|
template_submitter = template_submitters.find { |e| e['uuid'] == uuid }
|
||||||
end
|
end
|
||||||
|
|
||||||
submission.template_submitters << template_submitter.except('optional_invite_by_uuid', 'invite_by_uuid')
|
template_submitter = template_submitter.except('optional_invite_by_uuid', 'invite_by_uuid')
|
||||||
|
template_submitter['order'] = submitter_attrs['order'] if submitter_attrs['order'].present?
|
||||||
|
|
||||||
is_order_sent = submitters_order == 'random' || index.zero?
|
submission.template_submitters << template_submitter
|
||||||
|
|
||||||
|
is_order_sent = submitters_order == 'random' || (template_submitter['order'] || index).zero?
|
||||||
|
|
||||||
build_submitter(submission:, attrs: submitter_attrs,
|
build_submitter(submission:, attrs: submitter_attrs,
|
||||||
uuid:, is_order_sent:, user:, params:,
|
uuid:, is_order_sent:, user:, params:,
|
||||||
|
|||||||
+17
-4
@@ -162,12 +162,25 @@ module Submitters
|
|||||||
end
|
end
|
||||||
|
|
||||||
def current_submitter_order?(submitter)
|
def current_submitter_order?(submitter)
|
||||||
submitter_items = submitter.submission.template_submitters || submitter.submission.template.submitters
|
submission = submitter.submission
|
||||||
|
|
||||||
before_items = submitter_items[0...(submitter_items.find_index { |e| e['uuid'] == submitter.uuid })]
|
submitter_items = submission.template_submitters || submission.template.submitters
|
||||||
|
|
||||||
before_items.reduce(true) do |acc, item|
|
before_items =
|
||||||
acc && submitter.submission.submitters.find { |e| e.uuid == item['uuid'] }&.completed_at?
|
if submitter_items.any? { |s| s['order'] }
|
||||||
|
submitter_groups = submitter_items.group_by.with_index { |s, index| s['order'] || index }.sort_by(&:first)
|
||||||
|
|
||||||
|
current_group_index = submitter_groups.find_index { |_, group| group.any? { |s| s['uuid'] == submitter.uuid } }
|
||||||
|
|
||||||
|
submitter_groups.first(current_group_index).flat_map(&:last)
|
||||||
|
else
|
||||||
|
submitter_items.first(submitter_items.find_index { |e| e['uuid'] == submitter.uuid })
|
||||||
|
end
|
||||||
|
|
||||||
|
before_items.all? do |item|
|
||||||
|
submitter = submission.submitters.find { |e| e.uuid == item['uuid'] }
|
||||||
|
|
||||||
|
submitter.nil? || submitter.completed_at?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in new issue
Block a user