Compare commits

..

691 Commits

Author SHA1 Message Date
Alex Turchyn dd18c51384 Merge from docusealco/wip 2024-12-09 17:25:23 +02:00
Alex Turchyn c2014a1549 fix field drawings on tablets 2024-12-09 14:43:44 +02:00
Pete Matsyburka 46edc7dc37 adjust title 2024-12-09 12:55:02 +02:00
Pete Matsyburka d62d83ef8e fix duplciate email 2024-12-09 01:06:57 +02:00
Pete Matsyburka 79350c8fba fix submission page 2024-12-08 17:50:23 +02:00
Pete Matsyburka 6702dd52b6 update dep 2024-12-08 15:19:39 +02:00
Pete Matsyburka c8b74a2c43 adjust role 2024-12-08 15:12:02 +02:00
Pete Matsyburka 1746920eb5 add template access 2024-12-08 14:52:22 +02:00
Pete Matsyburka bacbc4309c skip heading 2024-12-07 12:59:28 +02:00
Pete Matsyburka 4429d742b3 document conditions 2024-12-07 02:04:50 +02:00
Pete Matsyburka 38b5eccb65 adjust readonly fields 2024-12-06 00:47:40 +02:00
Pete Matsyburka d8126c4a47 uniq submission.submitters 2024-12-05 19:39:10 +02:00
Pete Matsyburka b1c31672ee use tz country code 2024-12-04 17:40:46 +02:00
Pete Matsyburka 975f3a95fe add db search path 2024-12-04 14:00:13 +02:00
Alex Turchyn e3bed5ae67 add iso codes to phone data 2024-12-04 12:16:55 +02:00
Pete Matsyburka 4737b103c0 fix i18n 2024-12-02 20:17:39 +02:00
Alex Turchyn c5178b96d3 Merge from docusealco/wip 2024-12-02 14:30:33 +02:00
Pete Matsyburka 39a2159196 fix values hash 2024-12-01 11:41:19 +02:00
Pete Matsyburka 5724159b7b verification 2024-12-01 11:23:51 +02:00
Pete Matsyburka 05b7cc9fa6 fix link 2024-11-29 22:03:09 +02:00
Pete Matsyburka 18b13b5b1e adjust policy links 2024-11-29 19:49:44 +02:00
Alex Turchyn d25555b9b0 add policy links to personalization settings 2024-11-29 13:52:47 +02:00
Pete Matsyburka eec266ee83 fix archived false api 2024-11-29 13:30:33 +02:00
Alex Turchyn 75683631f0 add logical 'or' to field conditions 2024-11-29 13:28:11 +02:00
Pete Matsyburka 36c64d1de4 fix counters 2024-11-28 14:57:20 +02:00
Pete Matsyburka 2552b24f21 add documents copy attachment toggle 2024-11-27 21:59:45 +02:00
Alex Turchyn e6ca6dfd85 fix missing file format 2024-11-25 19:40:21 +02:00
Alex Turchyn fa2d86b20a Merge from docusealco/wip 2024-11-25 12:49:04 +02:00
Pete Matsyburka 8e212e4586 adjust title size 2024-11-24 13:18:37 +02:00
Pete Matsyburka a59c18f04b adjust filters ui 2024-11-24 13:03:58 +02:00
Pete Matsyburka 1ab0f1c90c fix form error message 2024-11-23 22:21:25 +02:00
Pete Matsyburka 9beaaa0851 adjust filters 2024-11-23 17:38:11 +02:00
Alex Turchyn b86aac86d1 add template submissions filters 2024-11-22 22:23:20 +02:00
Pete Matsyburka c2eb94b571 update url 2024-11-22 12:10:29 +02:00
Pete Matsyburka a377e45a63 use product email url 2024-11-22 10:30:42 +02:00
Pete Matsyburka 58574abc6b fix filename 2024-11-21 23:18:27 +02:00
Pete Matsyburka cbae888971 add file host param 2024-11-21 23:03:30 +02:00
Pete Matsyburka 944e1f31db adjust condition 2024-11-21 18:23:34 +02:00
Pete Matsyburka 711b3b3190 use filename format in email attachments 2024-11-20 21:49:37 +02:00
Pete Matsyburka e7abadcefb add filename format option 2024-11-20 01:12:26 +02:00
Alex Turchyn 18d60f5522 add signing form specs 2024-11-19 21:28:58 +02:00
Pete Matsyburka 6d5c6e086d use email host 2024-11-19 00:05:41 +02:00
Pete Matsyburka 5a48ab0360 configure form link 2024-11-18 21:08:32 +02:00
Pete Matsyburka 599785b6d1 fix number cast empty string 2024-11-17 21:29:20 +02:00
Pete Matsyburka f2fd5fb82b fix default number 2024-11-17 21:15:10 +02:00
Pete Matsyburka f2782bd84e fix form show 2024-11-16 23:06:00 +02:00
Pete Matsyburka e52a721fc9 fix typo 2024-11-16 18:19:25 +02:00
Pete Matsyburka b035123ff9 disable email var 2024-11-16 16:13:30 +02:00
Pete Matsyburka ca8de23082 use account archived at 2024-11-15 22:17:55 +02:00
Pete Matsyburka f514654c40 rails spec hook 2024-11-15 00:30:33 +02:00
Pete Matsyburka 99bc528a6c fix spec 2024-11-14 23:36:03 +02:00
Pete Matsyburka f3c77d0177 fix bulk order 2024-11-14 22:19:22 +02:00
Pete Matsyburka db2b5f35bd fix cells length 2024-11-14 21:15:40 +02:00
Pete Matsyburka 5728381725 fix role 2024-11-14 20:09:29 +02:00
Pete Matsyburka a7affb1a49 add link to export 2024-11-14 20:04:51 +02:00
Pete Matsyburka 21bf5ed9f7 fix fields message 2024-11-14 14:38:28 +02:00
Pete Matsyburka 8ddd4ee916 adjust duration 2024-11-14 09:35:20 +02:00
Pete Matsyburka 0e2fd8ee4f use email host 2024-11-13 17:53:55 +02:00
Pete Matsyburka 27901063ed fix preview 2024-11-12 17:11:09 +02:00
Pete Matsyburka ce31ebde1e add delay 2024-11-11 19:32:27 +02:00
Alex Turchyn 0c2abf211e Merge from docusealco/wip 2024-11-11 17:44:28 +02:00
Pete Matsyburka 8ecaa4fd7a adjust custom font size 2024-11-11 14:25:07 +02:00
Pete Matsyburka 6759c26d86 fix rubocop 2024-11-10 13:11:25 +02:00
Pete Matsyburka 76f1f140ac add field color 2024-11-10 12:48:53 +02:00
Pete Matsyburka ca7ab5aab7 adjust webhooks controller 2024-11-09 11:15:19 +02:00
Alex Turchyn fd3530ac62 update Webhooks UI 2024-11-09 00:41:46 +02:00
Pete Matsyburka 4ea863592b fix form 2024-11-08 22:12:07 +02:00
Pete Matsyburka 3f87d0336d do not use same reply to as to 2024-11-08 13:02:32 +02:00
Pete Matsyburka e251482481 unique party names 2024-11-08 12:15:13 +02:00
Pete Matsyburka c79fca75f3 add complete button 2024-11-07 23:41:49 +02:00
Alex Turchyn 0b0f0b497e fix acroform select field 2024-11-06 20:30:01 +02:00
Pete Matsyburka e9034d8da7 webhook secret priority 2024-11-06 14:06:33 +02:00
Pete Matsyburka 8fbb8b8f6e do not touch updated_at 2024-11-05 22:55:03 +02:00
Alex Turchyn 4635fdd32b remove replace_timestamps helper 2024-11-05 22:34:14 +02:00
Pete Matsyburka 6dd4382446 optimize webhooks query 2024-11-04 16:19:43 +02:00
Alex Turchyn 50acff2e81 Merge from docusealco/wip 2024-11-04 16:13:29 +02:00
Pete Matsyburka cb6d24dfca add canonical urls 2024-11-04 11:37:58 +02:00
Pete Matsyburka ab4a22d8ba fix test links 2024-11-03 13:43:28 +02:00
Pete Matsyburka bc40a15de1 use com 2024-11-03 10:19:59 +02:00
Pete Matsyburka 2542f26d96 adjust webhooks 2024-11-03 09:49:30 +02:00
Alex Turchyn 84edb6dbda use webhook urls instead of encrypted configs 2024-11-02 22:12:41 +02:00
Pete Matsyburka 88776e7316 Merge from docusealco/wip 2024-11-02 14:48:21 +02:00
Pete Matsyburka f669045362 redirect com 2024-11-02 09:48:47 +02:00
Pete Matsyburka 68d3efbdba use com 2024-11-02 09:02:50 +02:00
Pete Matsyburka 22d4833496 allow to unarchive submissions 2024-11-01 21:37:02 +02:00
Pete Matsyburka ff14caa9f7 fix image 2024-11-01 19:01:51 +02:00
Pete Matsyburka 05647135bc add status to export 2024-10-31 17:22:14 +02:00
Pete Matsyburka ae76c2a4a8 disable draw when type 2024-10-31 16:43:43 +02:00
Pete Matsyburka fb2b3d44fe fix bulk 2024-10-31 00:51:21 +02:00
Pete Matsyburka 3868557efd add define submitters 2024-10-30 23:07:10 +02:00
dependabot[bot] 9bef28c8ba Bump rexml from 3.3.8 to 3.3.9 in the bundler group across 1 directory (#50) 2024-10-30 12:12:36 +02:00
Pete Matsyburka f25fd3b03d do not edit if declined 2024-10-29 17:11:44 +02:00
Pete Matsyburka d1d7571ee4 disable decline email 2024-10-29 16:44:07 +02:00
Pete Matsyburka 6a298a068c fix updated templates 2024-10-29 12:11:21 +02:00
Pete Matsyburka d4aef79b68 use tsa fallback url 2024-10-29 08:29:26 +02:00
Alex Turchyn 05d578d88f Merge from docusealco/wip 2024-10-28 14:44:22 +02:00
Pete Matsyburka 8773b30b10 adjust auto migration 2024-10-27 14:26:52 +02:00
Pete Matsyburka 1b636c5878 fix completed submitters 2024-10-26 19:46:22 +03:00
Pete Matsyburka 46e948ab99 download combined completed 2024-10-26 19:30:23 +03:00
Pete Matsyburka 889103aa5e use com 2024-10-26 09:54:41 +03:00
Pete Matsyburka 558a14a5f2 fix email validate 2024-10-25 22:17:23 +03:00
Pete Matsyburka 72bbdb38bf mask key 2024-10-25 16:17:57 +03:00
Pete Matsyburka 3975a03cb4 adjust from email 2024-10-24 23:32:36 +03:00
Pete Matsyburka e398327fb7 refactor 2024-10-24 20:27:29 +03:00
Oleksandr Turchyn 9266694d65 add integration users page 2024-10-24 20:09:57 +03:00
Pete Matsyburka 350c4b1c2a do not submit invalid field value 2024-10-24 18:45:08 +03:00
Pete Matsyburka c3a933d87d fix fields detection 2024-10-24 17:56:02 +03:00
Pete Matsyburka 970e53a5a3 remove log 2024-10-24 12:30:14 +03:00
Pete Matsyburka 520bac2ec7 add decline account preferences toggle 2024-10-24 12:15:32 +03:00
Pete Matsyburka e1eddd31c4 fix field condition 2024-10-24 12:08:10 +03:00
Oleksandr Turchyn 3c616a824c show error for cross-environment API key usage 2024-10-24 12:07:58 +03:00
Pete Matsyburka 90d86b58c2 adjust migration 2024-10-23 22:57:33 +03:00
Pete Matsyburka 4332b4ebb8 fix specs 2024-10-23 12:01:55 +03:00
Oleksandr Turchyn e2f930f6f7 add completed submitters and documents 2024-10-23 12:01:43 +03:00
Pete Matsyburka c303159c63 adjust email interceptor 2024-10-23 10:46:53 +03:00
Pete Matsyburka c1ce2bec00 fix update name 2024-10-23 10:45:30 +03:00
Oleksandr Turchyn 3d2046f227 add validation for submitters count 2024-10-22 22:27:11 +03:00
Pete Matsyburka babaf1aa48 adjust error message 2024-10-22 18:18:14 +03:00
Pete Matsyburka 3f03d343ca Revert "dry run email validation"
This reverts commit 910490528d.
2024-10-22 17:41:25 +03:00
Pete Matsyburka 110cdb5123 fix specs 2024-10-22 15:56:59 +03:00
Pete Matsyburka 440d1114be fix email validation 2024-10-22 09:58:07 +03:00
Pete Matsyburka 9032517d4e smtp settings 2024-10-21 23:40:56 +03:00
Pete Matsyburka 7c71fefc47 optimize query 2024-10-21 14:34:11 +03:00
Pete Matsyburka e8bd8ff76f fix cdn url 2024-10-21 12:33:36 +03:00
Pete Matsyburka fed420cae1 Merge pull request #39 from docusealco/wip 2024-10-21 12:15:46 +03:00
Pete Matsyburka 910490528d dry run email validation 2024-10-20 15:44:24 +03:00
Alex Turchyn ab810be250 validate email format 2024-10-20 15:42:20 +03:00
Alex Turchyn 178ee47093 add more specs 2024-10-20 15:26:14 +03:00
Alex Turchyn 6f90147a11 validate the uniqueness of submitter roles 2024-10-20 15:26:04 +03:00
Pete Matsyburka 3ebaacbb7a locale date format 2024-10-20 09:48:39 +03:00
Pete Matsyburka 64b39b53a2 fix com from 2024-10-19 10:09:53 +03:00
Pete Matsyburka 2948910c25 fix com selector 2024-10-19 09:41:13 +03:00
Pete Matsyburka 1d20c79cf4 com host 2024-10-18 23:15:55 +03:00
Pete Matsyburka 48f3b13f3e adjust smtp from 2024-10-18 23:07:40 +03:00
Pete Matsyburka 1fc1e9af16 fix font size 2024-10-18 17:48:31 +03:00
Pete Matsyburka ce70016603 update rails 2024-10-16 23:58:37 +03:00
Pete Matsyburka e0e79e343d update email 2024-10-16 12:16:20 +03:00
Pete Matsyburka 6568f81ee9 adjust pdf generation 2024-10-15 20:58:08 +03:00
Oleksandr Turchyn 386f130d7a allow to paste date in date field 2024-10-15 19:31:25 +03:00
Pete Matsyburka 3ee41975fd add documents preview endpoint 2024-10-15 19:31:24 +03:00
Pete Matsyburka d9f314898b with style false 2024-10-14 12:28:26 +03:00
Pete Matsyburka 4fc7412cac fix ios 18 heif images 2024-10-14 12:28:26 +03:00
Pete Matsyburka 9adbfc52c2 fix signing date format 2024-10-14 12:28:26 +03:00
Pete Matsyburka 1ad8d9196a optimize phone step 2024-10-14 12:28:26 +03:00
Oleksandr Turchyn e9172392b5 add the phone code selector 2024-10-14 12:28:26 +03:00
Pete Matsyburka d6d94de328 fix submit form step 2024-10-14 12:28:26 +03:00
Alex Turchyn 77261f457c fix selector colors 2024-10-14 12:28:26 +03:00
Pete Matsyburka c258915031 fix z index 2024-10-14 12:28:26 +03:00
Pete Matsyburka c1db8bdd3b rename job 2024-10-14 12:28:26 +03:00
Pete Matsyburka c700484ce8 add reuse signature config 2024-10-14 12:28:26 +03:00
Pete Matsyburka b62bd200ed fix layout 2024-10-14 12:28:26 +03:00
Pete Matsyburka a164fe8dd8 fix sign 2024-10-14 12:28:26 +03:00
Pete Matsyburka bb40e872b3 fix custom message 2024-10-14 12:28:26 +03:00
Pete Matsyburka bab80e9488 fix image preview format 2024-10-14 12:28:26 +03:00
Pete Matsyburka d090d79ebf process images async 2024-10-14 12:28:26 +03:00
Pete Matsyburka 2299ba4e29 fix clear button 2024-10-14 12:28:26 +03:00
Pete Matsyburka d6474f0e6e adjust submitter edit 2024-10-14 12:28:26 +03:00
Oleksandr Turchyn e0a297cf81 add ability to edit submitter details 2024-10-14 12:28:26 +03:00
Pete Matsyburka 40f5fa4217 add recurrent payment 2024-10-14 12:28:26 +03:00
Oleksandr Turchyn 94e552d170 improve translations 2024-10-07 15:30:39 +03:00
Pete Matsyburka cf1b0a4c16 add noverify smtp option 2024-10-07 15:30:39 +03:00
Pete Matsyburka 9aa0a8ecbf skip autocorrect tld 2024-10-07 15:30:39 +03:00
Pete Matsyburka fd4bef1706 fix i18n 2024-10-07 15:30:39 +03:00
Pete Matsyburka d32b0e6d20 add completed id 2024-10-07 15:30:39 +03:00
Pete Matsyburka 4a846be214 fix js 2024-10-07 15:30:39 +03:00
Pete Matsyburka 3dd3877ea9 update deps 2024-10-07 15:30:39 +03:00
Pete Matsyburka b4dba0d809 fix smtp without password 2024-10-07 15:30:39 +03:00
Pete Matsyburka dea80a82d8 fix pagination 2024-10-07 15:30:39 +03:00
Pete Matsyburka eefa994724 rails 7.2 2024-10-07 15:30:39 +03:00
Pete Matsyburka 35cce24539 adjust audit values 2024-10-07 15:30:39 +03:00
Pete Matsyburka e6c8203959 fix variables 2024-10-07 15:30:39 +03:00
Oleksandr Turchyn 9479771fc8 fix i18n 2024-10-07 15:30:39 +03:00
Pete Matsyburka 711427521c fix file url performance 2024-10-07 15:30:39 +03:00
Alex Turchyn 5dae742361 add I18n invitation email 2024-10-07 15:30:39 +03:00
Pete Matsyburka d8ccd4beee i18n signature 2024-10-07 15:30:39 +03:00
Pete Matsyburka c532a0115d remove audit log file links 2024-10-07 15:30:39 +03:00
Alex Turchyn c9c3364764 add matcher 2024-10-07 15:30:39 +03:00
Alex Turchyn 1ff23d7c40 add I18n Audit log 2024-10-07 15:30:39 +03:00
Pete Matsyburka 95e9b34033 fix sign field name 2024-10-07 15:30:39 +03:00
Pete Matsyburka 6b42dcf91b adjust combined pdf sign reason 2024-10-07 15:30:39 +03:00
Pete Matsyburka 28962c949b configure audit log values 2024-10-07 15:30:39 +03:00
Pete Matsyburka 4ecbbf8410 update rubocop 2024-09-30 12:13:07 +03:00
Pete Matsyburka 990b35be18 i18n emails 2024-09-30 12:13:07 +03:00
Pete Matsyburka 8002764855 update readme 2024-09-30 12:13:07 +03:00
Pete Matsyburka b80982658c fix i18n 2024-09-30 12:13:07 +03:00
Pete Matsyburka 443ac23c29 fix account form 2024-09-30 12:13:07 +03:00
Pete Matsyburka 37bb987e3e fix i18n 2024-09-30 12:13:07 +03:00
Alex Turchyn c7b08e8d30 add languages 2024-09-30 12:13:07 +03:00
Pete Matsyburka dd502e8cef remove unused code 2024-09-30 12:13:07 +03:00
Pete Matsyburka a554182e57 fix html modal 2024-09-30 12:13:07 +03:00
Pete Matsyburka 0c039ee64f fix tempalte clone roles 2024-09-30 12:13:07 +03:00
Pete Matsyburka c6b004f107 fix submitter name 2024-09-30 12:13:07 +03:00
Pete Matsyburka 2fcb08d27d await for completion when order enforced 2024-09-30 12:13:07 +03:00
Pete Matsyburka 75fe5ce381 fix custom certs 2024-09-30 12:13:07 +03:00
Pete Matsyburka fd7d0eaf53 enforce order toggle 2024-09-30 12:13:07 +03:00
Pete Matsyburka 7e537e4232 adjust signing certs 2024-09-30 12:13:07 +03:00
Pete Matsyburka a892975010 adjust download documents 2024-09-30 12:13:07 +03:00
Alex Turchyn 05ec92186b fix i18n 2024-09-30 12:13:07 +03:00
Oleksandr Turchyn 8b035f80b3 fix i18n typos 2024-09-23 11:45:07 +03:00
Pete Matsyburka c717293306 fix form preview 2024-09-23 11:45:07 +03:00
Pete Matsyburka b3f3cf2aa6 fix label 2024-09-23 11:45:07 +03:00
Pete Matsyburka 7406334f77 adjust i18n 2024-09-23 11:45:07 +03:00
Pete Matsyburka 3e1350a221 upgrade puma 2024-09-23 11:45:07 +03:00
Pete Matsyburka 961e0a1c89 fix i18n 2024-09-23 11:45:07 +03:00
Pete Matsyburka 2ce9bbecb9 fix cells field 2024-09-23 11:45:07 +03:00
Pete Matsyburka 704da3a2ec adjust email edit button 2024-09-23 11:45:07 +03:00
Pete Matsyburka 2f530ccfca i18n emails 2024-09-23 11:45:07 +03:00
Pete Matsyburka e6c20f5ff6 adjust submit button 2024-09-23 11:45:07 +03:00
Pete Matsyburka d267978437 set emails copy duration 2024-09-23 11:45:07 +03:00
Oleksandr Turchyn e9d728fa4a add i18n 2024-09-23 11:45:07 +03:00
Pete Matsyburka 3c60fb4ee6 adjust show continue 2024-09-23 11:45:07 +03:00
Oleksandr Turchyn f1e494b669 adjust wording 2024-09-23 11:45:07 +03:00
Pete Matsyburka f7a8e4102b fix scroll 2024-09-23 11:45:07 +03:00
dependabot[bot] 311c2b0e34 Bump devise-two-factor in the bundler group across 1 directory
Bumps the bundler group with 1 update in the / directory: [devise-two-factor](https://github.com/tinfoil/devise-two-factor).


Updates `devise-two-factor` from 5.0.0 to 6.0.0
- [Changelog](https://github.com/devise-two-factor/devise-two-factor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tinfoil/devise-two-factor/compare/v5.0.0...v6.0.0)

---
updated-dependencies:
- dependency-name: devise-two-factor
  dependency-type: direct:production
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 11:45:07 +03:00
Pete Matsyburka 5dd59f4f7c optimize image 2024-09-23 11:45:07 +03:00
Pete Matsyburka 571f5d8221 set font style 2024-09-23 11:45:07 +03:00
Pete Matsyburka 136b0eb5ed fix typo 2024-09-23 11:45:07 +03:00
Pete Matsyburka 2d813e184b fix padding 2024-09-16 12:11:20 +03:00
Pete Matsyburka fd23674e87 add decline reason in webhook 2024-09-16 12:11:20 +03:00
Pete Matsyburka 3b9d35acf1 fix tld autocorrection 2024-09-16 12:11:20 +03:00
Pete Matsyburka 17fc6e2f1c reduce textarea size 2024-09-16 12:11:20 +03:00
Oleksandr Turchyn 2e132d8b5a update submission modal 2024-09-16 12:11:20 +03:00
Pete Matsyburka 53bef85a63 adjust cert vars 2024-09-16 12:11:20 +03:00
Pete Matsyburka 99ad6e36ac fix preview preload 2024-09-16 12:11:20 +03:00
Pete Matsyburka bfa41ac35f concurrent preview processing 2024-09-16 12:11:20 +03:00
Pete Matsyburka bc4414a368 use png for previews 2024-09-16 12:11:20 +03:00
Pete Matsyburka 26ae5f1d80 update textarea size 2024-09-16 12:11:20 +03:00
Pete Matsyburka be4962a02a adjust form completion 2024-09-16 12:11:20 +03:00
Pete Matsyburka e72090c2e0 add template form preferences 2024-09-16 12:11:20 +03:00
Pete Matsyburka ad8bca8bf4 fix spec 2024-09-16 12:11:20 +03:00
Pete Matsyburka 22be3b37cf fix complete page 2024-09-16 12:11:20 +03:00
Pete Matsyburka cfc32ee522 set textarea max height 2024-09-16 12:11:20 +03:00
Pete Matsyburka 10fbdf6612 allow to invite party 2024-09-16 12:11:20 +03:00
Pete Matsyburka 3604fb6461 add readonly field attr 2024-09-16 12:11:20 +03:00
Pete Matsyburka 24392d0258 remove redacted 2024-09-16 12:11:20 +03:00
Pete Matsyburka 4ab1065773 adjust default recipients 2024-09-16 12:11:20 +03:00
Pete Matsyburka 50b0f7e5ed adjust default recipients 2024-09-16 12:11:20 +03:00
Pete Matsyburka be4a0505bc assign default submitters 2024-09-09 13:57:46 +03:00
Pete Matsyburka bd853c6265 html escape simple format 2024-09-09 13:57:46 +03:00
Pete Matsyburka 5a1efd0e27 add download utils 2024-09-09 13:57:46 +03:00
Pete Matsyburka 71b8cc7172 add recipient preferences 2024-09-09 13:57:46 +03:00
Pete Matsyburka 126a36dbf3 clear log 2024-09-09 13:57:46 +03:00
Pete Matsyburka 90db64cc03 fix file prefill 2024-09-09 13:57:46 +03:00
Pete Matsyburka 227a067c77 fix readonly condition 2024-09-09 13:57:46 +03:00
Pete Matsyburka dc24df0d4c remove log 2024-09-09 13:57:46 +03:00
Pete Matsyburka 2598962c5b configure image quality 2024-09-09 13:57:46 +03:00
Pete Matsyburka b4884e94f6 add preferences 2024-09-09 13:57:46 +03:00
Pete Matsyburka 4741e64178 fix specs 2024-09-02 20:33:28 +03:00
Pete Matsyburka 25fe35e212 ruby 3.3.4 2024-09-02 20:29:47 +03:00
Pete Matsyburka 92909cea09 clear base64 prefix 2024-09-02 20:11:13 +03:00
Pete Matsyburka b16322dd5b add first name var 2024-09-02 20:11:13 +03:00
dependabot[bot] a04c04141c Bump webpack in the npm_and_yarn group across 1 directory
Bumps the npm_and_yarn group with 1 update in the / directory: [webpack](https://github.com/webpack/webpack).


Updates `webpack` from 5.82.1 to 5.94.0
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.82.1...v5.94.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-02 20:11:13 +03:00
Pete Matsyburka 90f21d3ed9 add expired condition 2024-09-02 20:11:13 +03:00
Pete Matsyburka f31225e8c2 fix spec 2024-09-02 20:11:13 +03:00
Oleksandr Turchyn eead1b2bc4 add basic API specs 2024-09-02 20:11:13 +03:00
Pete Matsyburka f90a4e5576 adjust document id pdf 2024-09-02 20:11:13 +03:00
Pete Matsyburka 50987ce38b hide app url input 2024-09-02 20:11:13 +03:00
Pete Matsyburka 26113ac3a6 allow signing link with multiple signers 2024-09-02 20:11:13 +03:00
Pete Matsyburka 0b7b72ce16 clear log 2024-09-02 20:11:13 +03:00
Pete Matsyburka 95d48b2808 adjust submissions response 2024-09-02 20:11:13 +03:00
Pete Matsyburka 8fecf94a28 fix typo 2024-09-02 20:11:13 +03:00
Pete Matsyburka 66dd87b290 adjust padding 2024-09-02 20:11:13 +03:00
Pete Matsyburka d20e8cc37d remove checkboxes label 2024-09-02 20:11:13 +03:00
Pete Matsyburka c189e2ed20 clear log 2024-09-02 20:11:13 +03:00
Pete Matsyburka 57f8f45320 archived template preview 2024-09-02 20:11:13 +03:00
Pete Matsyburka 5684baf1e0 add app url var 2024-08-25 16:03:23 +03:00
Pete Matsyburka b6f307690d add combine pdf toggle 2024-08-25 16:03:23 +03:00
Pete Matsyburka 66c44f8ff5 adjust error page 2024-08-25 16:03:23 +03:00
dependabot[bot] 8f3592c481 Bump rexml from 3.3.3 to 3.3.6 in the bundler group across 1 directory
Bumps the bundler group with 1 update in the / directory: [rexml](https://github.com/ruby/rexml).


Updates `rexml` from 3.3.3 to 3.3.6
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.3.3...v3.3.6)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
  dependency-group: bundler
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-25 16:03:23 +03:00
Pete Matsyburka dc8b1e0741 rescue error 2024-08-25 16:03:23 +03:00
Pete Matsyburka b806a6afc5 disable decline button 2024-08-25 16:03:23 +03:00
Pete Matsyburka d43f8f4dd8 add event data 2024-08-25 16:03:23 +03:00
Pete Matsyburka 37c9bf5741 add reply to 2024-08-25 16:03:23 +03:00
Pete Matsyburka a0d37f516f add audit document id 2024-08-25 16:03:23 +03:00
Pete Matsyburka 2764a18252 decline submission 2024-08-25 16:03:23 +03:00
Pete Matsyburka 96cf228e74 fix upload signature 2024-08-25 16:03:23 +03:00
Pete Matsyburka ff6ff3a51e adjust template resp 2024-08-25 16:03:23 +03:00
Pete Matsyburka 9472a6b5b0 allow to upload initial 2024-08-25 16:03:23 +03:00
Pete Matsyburka 5f3a8bf391 fix condition 2024-08-19 19:51:24 +03:00
Pete Matsyburka a0a0fdd1b5 clean log 2024-08-19 19:51:24 +03:00
Pete Matsyburka 7a6f901b09 add log 2024-08-19 19:51:24 +03:00
Pete Matsyburka c4f05fbd9e remove unique index 2024-08-19 19:51:24 +03:00
Pete Matsyburka 50c342ec5c fix spec 2024-08-19 19:51:24 +03:00
Pete Matsyburka a9cfd2bcfc update submission index 2024-08-19 19:51:24 +03:00
Pete Matsyburka e2bdb2acee use account_id 2024-08-19 19:51:24 +03:00
Pete Matsyburka b8828b503c populate account_id 2024-08-19 19:51:24 +03:00
Pete Matsyburka d63f62109b add submitter account_id 2024-08-19 19:51:24 +03:00
Pete Matsyburka c04483f0ea allow to update external_id 2024-08-19 19:51:24 +03:00
Pete Matsyburka 1efaf30fb7 fix net smtp without creds 2024-08-19 19:51:24 +03:00
Pete Matsyburka 2e462e9633 fix erblint 2024-08-19 19:51:24 +03:00
Pete Matsyburka 838df5f41d fix image documents 2024-08-19 19:51:24 +03:00
Pete Matsyburka 32445f0e19 adjust mobile ui 2024-08-19 19:51:24 +03:00
Pete Matsyburka 5f9067baee fix mobile field types 2024-08-19 19:51:24 +03:00
Pete Matsyburka ad56d5ee14 adjust mobile ui 2024-08-19 19:51:24 +03:00
Pete Matsyburka a091d121d9 confgurable SMTP timeout 2024-08-19 19:51:24 +03:00
Pete Matsyburka cd9c3ee3c1 fix ci 2024-08-19 19:51:24 +03:00
Pete Matsyburka 534c3a2e10 add submission.completed 2024-08-19 19:51:24 +03:00
Pete Matsyburka 7bc8e194df fix font url 2024-08-19 19:51:24 +03:00
Pete Matsyburka de5c4d5bcd adjust retries 2024-08-19 19:51:24 +03:00
dependabot[bot] 1e6b5c684f bump deps 2024-08-10 15:34:13 +03:00
Pete Matsyburka 4651f6b652 simplify variables 2024-08-10 15:34:13 +03:00
Pete Matsyburka e5b0a2355f use aws secret 2024-08-10 15:34:13 +03:00
Pete Matsyburka db22fe7518 fix condition 2024-08-10 15:34:13 +03:00
Pete Matsyburka 0dcd97f49b add verify tool 2024-08-10 15:34:13 +03:00
Pete Matsyburka de14330449 allow to remove page area 2024-08-10 15:34:13 +03:00
Pete Matsyburka 173658138c fix conditions chain 2024-08-10 15:34:13 +03:00
Pete Matsyburka 000f3c72fc fix go to step 2024-08-10 15:34:13 +03:00
Pete Matsyburka 561b16e9fb fix pdf 2024-08-07 17:04:54 +03:00
Pete Matsyburka 507ca49112 adjust ai link 2024-08-07 14:34:32 +03:00
Pete Matsyburka 94d5ab4ace add reason 2024-08-06 00:49:40 +03:00
Pete Matsyburka 91d44a3e31 adjust date format 2024-08-05 19:38:47 +03:00
Pete Matsyburka 478167ea4f add webhook secret 2024-08-05 13:30:04 +03:00
Pete Matsyburka 6b1c3bba19 add testing footer 2024-08-05 10:45:05 +03:00
Pete Matsyburka 73841f751c fix icons 2024-08-04 23:41:32 +03:00
Pete Matsyburka 22dbe3291f adjust compose mount folder 2024-08-04 01:28:22 +03:00
Pete Matsyburka 1dac3fe434 fix cell 2024-08-04 00:13:34 +03:00
Pete Matsyburka d0c49ac04c i18n 2024-08-03 09:26:26 +03:00
Pete Matsyburka 3877dbf83f remove lookbehind regexp 2024-08-02 19:02:58 +03:00
Alex Turchyn d15fd1fb84 allow to view password during registration 2024-08-01 23:31:54 +03:00
Pete Matsyburka 0a105490fe process noreply 2024-08-01 23:22:33 +03:00
Pete Matsyburka 0bd35b526c handle date enter 2024-08-01 18:47:17 +03:00
Pete Matsyburka 3a2910f717 add expire at 2024-08-01 18:34:09 +03:00
Pete Matsyburka c4a91c2b34 extract markdown link 2024-08-01 01:31:25 +03:00
Pete Matsyburka 5643380916 configure page quality 2024-08-01 00:14:33 +03:00
Pete Matsyburka cf6b378ccb adjust payment step 2024-07-31 22:56:18 +03:00
Pete Matsyburka 66bc8cfb5f archive without confirm 2024-07-31 17:57:52 +03:00
Pete Matsyburka 994bd1fe5c dummy script 2024-07-31 10:53:51 +03:00
Pete Matsyburka 12528b6527 fix cell field 2024-07-30 21:18:56 +03:00
Pete Matsyburka d135c93939 fix n+1 2024-07-29 17:23:05 +03:00
Pete Matsyburka 637011d55a remove log 2024-07-29 16:50:25 +03:00
Pete Matsyburka b94e6bf54a adjust font resize 2024-07-26 17:58:00 +03:00
Pete Matsyburka 8f975c2898 fix lang 2024-07-26 17:53:21 +03:00
Pete Matsyburka d25c4fa766 remove log 2024-07-25 15:11:01 +03:00
Pete Matsyburka 43db15b350 add signature id 2024-07-25 09:07:45 +03:00
Pete Matsyburka 09074e2ac0 fix tld 2024-07-24 18:30:21 +03:00
Pete Matsyburka 04295f4540 adjust signature prefill 2024-07-24 09:13:13 +03:00
Pete Matsyburka 11c48a495f update rubocop 2024-07-23 22:42:56 +03:00
Pete Matsyburka 942c961cd1 add it locale 2024-07-22 18:52:40 +03:00
Pete Matsyburka 9193511d5f adjust auto scroll 2024-07-22 17:42:32 +03:00
Pete Matsyburka e0ddf97771 fix rubocop 2024-07-21 17:38:52 +03:00
Pete Matsyburka 2bd249bd5a fix erblint 2024-07-21 15:46:11 +03:00
Pete Matsyburka b72c99dbce add user api 2024-07-21 15:16:55 +03:00
Pete Matsyburka fc0504f91e update rubocop 2024-07-21 15:12:05 +03:00
Alex Turchyn 1da2196b0e add Doorkeeper tables 2024-07-21 15:10:17 +03:00
Pete Matsyburka 4bdb249a2b allow to permanently remove submissions 2024-07-20 22:29:32 +03:00
Pete Matsyburka cb38549480 allow to disable completed email attachments 2024-07-20 21:59:06 +03:00
Pete Matsyburka ee9ee6ee32 adjust attachment create 2024-07-20 21:17:47 +03:00
Pete Matsyburka 748eaaa98d add index on email email event 2024-07-20 09:40:57 +03:00
Pete Matsyburka 9e4ec11e33 adjust signature qr 2024-07-20 09:00:57 +03:00
Pete Matsyburka 93d2b76eae fix form values 2024-07-19 19:30:24 +03:00
Pete Matsyburka 2c8942e888 add form events api 2024-07-19 13:19:56 +03:00
Pete Matsyburka 4424840c81 adjust submissions endpoint 2024-07-19 10:52:16 +03:00
Pete Matsyburka 68392b4e74 add with field placeholder prop 2024-07-19 10:37:01 +03:00
Pete Matsyburka 0b4e345ad6 fix on paste 2024-07-18 10:58:12 +03:00
Pete Matsyburka 1cea5f5a99 fix fields 2024-07-17 22:02:58 +03:00
Pete Matsyburka cdaadbab20 add input mode 2024-07-16 19:41:57 +03:00
Pete Matsyburka d1ec8c6837 use preferences font 2024-07-16 13:18:03 +03:00
Pete Matsyburka cdffa66db1 refactor clone 2024-07-16 08:59:37 +03:00
Pete Matsyburka fca21ab476 fix mm domain 2024-07-15 19:28:57 +03:00
Pete Matsyburka b1d2d92f54 allow to clone template 2024-07-15 15:54:13 +03:00
Pete Matsyburka 10bb9112c7 add with field placeholder option 2024-07-15 14:03:36 +03:00
Pete Matsyburka 58b337a3ee add fields include option 2024-07-15 12:56:02 +03:00
Pete Matsyburka de52f2f5e5 add webhook urls record 2024-07-14 22:53:33 +03:00
Pete Matsyburka f10e941529 adjust field name regexp 2024-07-12 17:44:39 +03:00
Pete Matsyburka 78b1864d7d adjust payment step 2024-07-12 15:25:48 +03:00
Alex Turchyn ab49af9bcb add tool to merge pdf files 2024-07-12 12:25:14 +03:00
Pete Matsyburka 286b8d7067 add heading field 2024-07-12 07:53:09 +03:00
Pete Matsyburka 21c087cba7 fix mark sent 2024-07-11 13:09:35 +03:00
Pete Matsyburka ddd881a52f fix pdf form fields 2024-07-10 21:58:30 +03:00
Pete Matsyburka 12c69578af fix pdf form fields 2024-07-10 17:57:54 +03:00
Pete Matsyburka e96cb5eb8a add blank page button 2024-07-09 23:53:46 +03:00
Pete Matsyburka c01cfe83be add ltv method 2024-07-09 16:41:40 +03:00
Pete Matsyburka b92d1ee38c adjust payment step 2024-07-07 20:33:57 +03:00
Pete Matsyburka b90c67b184 fix cell field font size 2024-07-07 10:22:59 +03:00
Pete Matsyburka edb29977dd skip field description 2024-07-07 09:05:02 +03:00
Alex Turchyn e55c8f251d automatically lower signature font size if text doesn't fit 2024-07-06 09:46:34 +03:00
Pete Matsyburka f1d6bbdc67 fix ci 2024-07-06 09:42:52 +03:00
Pete Matsyburka 8da4c2fa10 adjust link preview 2024-07-05 14:06:37 +03:00
Pete Matsyburka e872fa2497 ruby 3.3.3 2024-07-05 11:45:21 +03:00
Pete Matsyburka 165bfef9b9 remove version from compose file 2024-07-04 22:55:51 +03:00
Pete Matsyburka 8a50c755d2 fix payment field value 2024-07-04 18:25:49 +03:00
Pete Matsyburka 002bc315f6 adjust import 2024-07-04 18:04:36 +03:00
Pete Matsyburka e87522eeab adjust form fields 2024-07-02 22:38:03 +03:00
Pete Matsyburka 377244f948 add form preview 2024-07-02 22:38:02 +03:00
Pete Matsyburka 73423a6f44 fix stamp 2024-07-02 19:11:42 +03:00
Pete Matsyburka d219e3cd7e do not move in preview 2024-07-02 13:17:39 +03:00
Pete Matsyburka 09b0022195 move fields with arrows 2024-07-02 11:41:24 +03:00
Pete Matsyburka 8aac935641 allow to download combined pdf via api 2024-07-01 12:06:05 +03:00
Pete Matsyburka 16e5c13633 fix erblint 2024-07-01 08:05:09 +03:00
Pete Matsyburka fff1831768 add gpt link 2024-06-30 16:21:19 +03:00
Pete Matsyburka f91a318e94 fix drawer 2024-06-29 20:10:28 +03:00
Pete Matsyburka ed06e17b23 adjust field attrs 2024-06-29 11:29:59 +03:00
Pete Matsyburka 63e1be7dfe remove border 2024-06-29 08:52:16 +03:00
Pete Matsyburka 16044612bd fix form fields 2024-06-29 08:44:19 +03:00
Pete Matsyburka 27d8880414 border alert 2024-06-29 00:04:10 +03:00
Pete Matsyburka a0aae6a9de add currencies 2024-06-28 23:34:15 +03:00
Alex Turchyn 749043dfbb process acro form fields 2024-06-28 21:42:39 +03:00
Pete Matsyburka 320fe02e35 remove upload signature button from drawn type 2024-06-28 16:56:51 +03:00
Pete Matsyburka 6f4b9d4a4e double click on builder checkbox 2024-06-28 16:28:37 +03:00
Pete Matsyburka a589cf9471 focus field input 2024-06-27 17:41:08 +03:00
Pete Matsyburka 7fc7283191 copy and paste fields 2024-06-27 15:14:39 +03:00
Pete Matsyburka af03560494 add radio select default value settings 2024-06-27 13:11:48 +03:00
Pete Matsyburka 988d1b93a5 double click to toggle signing date 2024-06-27 12:40:10 +03:00
Pete Matsyburka ab0278198a adjust phone step 2024-06-26 16:18:33 +03:00
Pete Matsyburka fbc649ab59 add extra links 2024-06-26 13:34:47 +03:00
Pete Matsyburka 6b5adc2504 select us date format 2024-06-25 21:49:44 +03:00
Pete Matsyburka 7c4307ef10 fix query 2024-06-25 21:11:48 +03:00
Pete Matsyburka de744e2f6c adjust invite email 2024-06-25 19:05:50 +03:00
Pete Matsyburka c171597c7f adjust max retries 2024-06-25 08:45:38 +03:00
Pete Matsyburka 3034c00bb3 use sidekiq 2024-06-24 21:10:58 +03:00
Pete Matsyburka 9539c476c0 remove legacy tracking param 2024-06-24 14:33:33 +03:00
Pete Matsyburka 4552c72860 add index on external id 2024-06-24 13:27:14 +03:00
Pete Matsyburka 85f8a92721 optimize query 2024-06-23 12:20:01 +03:00
Pete Matsyburka 049b9c78c2 add retries 2024-06-23 09:16:34 +03:00
Pete Matsyburka acf06084bf simplify job params 2024-06-22 10:50:07 +03:00
Pete Matsyburka 61da477733 reorder 2024-06-22 10:50:07 +03:00
Pete Matsyburka fdeec548a3 adjust message 2024-06-22 10:50:07 +03:00
Pete Matsyburka ff0d486831 fix clone template folder 2024-06-22 10:50:07 +03:00
Pete Matsyburka d5bf54ee26 adjust tempalte preferences 2024-06-22 10:50:07 +03:00
Pete Matsyburka 7616774613 use latest alpine 2024-06-21 00:46:11 +03:00
Pete Matsyburka 27c4dc7eca font size in the UI 2024-06-20 22:15:45 +03:00
Pete Matsyburka 4ae834d6f7 skip optional signature 2024-06-20 17:52:40 +03:00
Pete Matsyburka b71a66a082 add font size preference 2024-06-20 16:14:18 +03:00
Pete Matsyburka 80cc38542c fix email attachments 2024-06-18 20:09:58 +03:00
Pete Matsyburka 185aecec42 increase signature size 2024-06-18 17:18:23 +03:00
Pete Matsyburka 8c092eba78 allow to permanently delete submissions 2024-06-18 16:54:15 +03:00
Pete Matsyburka 781ccc701d optimize large files upload 2024-06-16 11:27:24 +03:00
Pete Matsyburka 009e05584a remove submitter signature assoc 2024-06-15 20:14:09 +03:00
Pete Matsyburka f9d52e56a2 prefill signature 2024-06-15 16:23:27 +03:00
Pete Matsyburka 9e66f8412c add extra user fields 2024-06-14 22:59:11 +03:00
Pete Matsyburka 181e42c4b3 refactor 2024-06-14 16:55:20 +03:00
Pete Matsyburka 15e60fc5e7 add emails copy toggle 2024-06-14 12:19:16 +03:00
Pete Matsyburka ee357e58ac fix prefill signature 2024-06-14 10:00:25 +03:00
Pete Matsyburka 0d9a709fa3 disable prefill signature 2024-06-14 09:09:56 +03:00
Pete Matsyburka 7eae09bdb0 assign default values on api complete 2024-06-13 23:45:53 +03:00
Pete Matsyburka e8e32e5d1f add log 2024-06-13 18:05:11 +03:00
Pete Matsyburka a3d54db4c6 adjust list import 2024-06-13 15:20:34 +03:00
Pete Matsyburka 9686ae6250 add field validation 2024-06-13 14:32:49 +03:00
Pete Matsyburka 044195f07b blur on field move 2024-06-13 10:54:10 +03:00
Pete Matsyburka 35f159266a paste raw text 2024-06-13 10:26:36 +03:00
Pete Matsyburka 3b717ae3de double click to edit default value 2024-06-12 22:46:50 +03:00
Pete Matsyburka 2389c3fc40 prefill signature 2024-06-11 19:42:26 +03:00
Pete Matsyburka 92da790e77 fix email tracking params 2024-06-11 16:59:15 +03:00
Pete Matsyburka 14418b93b1 full error log in prod 2024-06-10 17:17:45 +03:00
Pete Matsyburka 1289aff493 parse date using format 2024-06-10 12:08:56 +03:00
Pete Matsyburka 7568f563cb adjust payment step 2024-06-09 23:56:43 +03:00
Pete Matsyburka 5ae56bd50c add field preferences param 2024-06-09 20:25:19 +03:00
Pete Matsyburka c2ded81959 update hmtl lang attr 2024-06-09 16:39:41 +03:00
Pete Matsyburka f2c307ef63 adjust conditions modal 2024-06-09 16:37:28 +03:00
Pete Matsyburka af787887d8 i18n sign in 2024-06-08 23:02:29 +03:00
Pete Matsyburka 54272d83eb add integrations placeholder 2024-06-08 21:30:04 +03:00
Pete Matsyburka 3070fc2074 clear condition values 2024-06-08 12:32:41 +03:00
Pete Matsyburka 6a31d2c722 alert message on missing submitter fields 2024-06-08 10:01:14 +03:00
Pete Matsyburka f26bfe9acc remove button from archived 2024-06-07 21:54:37 +03:00
Pete Matsyburka a729fd6234 fix webhook url on testing 2024-06-06 22:03:15 +03:00
Pete Matsyburka c2051c156e adjust signature step 2024-06-06 17:14:05 +03:00
Pete Matsyburka 3852870c09 add payment field condition 2024-06-05 21:09:08 +03:00
Pete Matsyburka a58253a6a1 add devise emails events 2024-06-05 09:29:00 +03:00
Pete Matsyburka 30c98a970d fix audit trail 2024-06-04 19:34:09 +03:00
Pete Matsyburka 8f2edef09e wrap long email is stamp 2024-06-04 19:31:55 +03:00
Pete Matsyburka 9e465ea47e use field title in audit 2024-06-04 18:33:24 +03:00
Pete Matsyburka cdda55d528 add email events 2024-06-04 17:41:07 +03:00
Pete Matsyburka db302858c4 add template folder name to webhook 2024-06-03 18:13:13 +03:00
Pete Matsyburka b81cc83027 fix signature toggle 2024-06-03 12:31:40 +03:00
Pete Matsyburka 766d653349 fix build 2024-06-03 12:01:09 +03:00
Pete Matsyburka 98c04eac15 normalize client ip 2024-06-03 08:57:14 +03:00
Pete Matsyburka c35dfea154 add log 2024-06-02 21:24:21 +03:00
Pete Matsyburka 37a9a86a1d fix naming 2024-06-02 18:36:59 +03:00
Pete Matsyburka e9a759055b allow to upload user initials 2024-06-02 17:06:09 +03:00
Pete Matsyburka f6c061a57b allow to draw signature on mobile 2024-06-02 16:02:18 +03:00
Pete Matsyburka 91f3d8ed7e adjust meta tags 2024-05-31 22:35:59 +03:00
Pete Matsyburka 3b18502d3d remove onsubmit html event 2024-05-31 14:44:36 +03:00
Pete Matsyburka df8e79683a eager load in test 2024-05-31 13:19:33 +03:00
Alex Turchyn 7080e8ea33 fix js errors 2024-05-31 13:08:02 +03:00
Pete Matsyburka c56f8cb38f fix autocomplete 2024-05-30 23:38:38 +03:00
Pete Matsyburka 1ceac968b0 fix move existing user 2024-05-30 22:18:32 +03:00
Pete Matsyburka 938ee99cfc show full error trace in dev 2024-05-29 16:14:07 +03:00
Pete Matsyburka 2367040e45 add slug to submissions response 2024-05-28 00:25:59 +03:00
Pete Matsyburka df19aabfba adjust users table 2024-05-27 20:36:52 +03:00
Pete Matsyburka f7b5bcd52f add webhooks queue 2024-05-27 14:40:12 +03:00
Pete Matsyburka a249b0a678 fix pdf form flatten fields 2024-05-27 00:47:14 +03:00
Pete Matsyburka 99bc46b40f fix erblint 2024-05-26 22:51:16 +03:00
Pete Matsyburka c1e6f0970d add file types 2024-05-26 22:38:24 +03:00
Pete Matsyburka e8c2316bb2 add users count 2024-05-26 20:51:02 +03:00
Pete Matsyburka 9866e5468c add download combined pdf button 2024-05-26 11:43:47 +03:00
Pete Matsyburka fc60df7974 fix erblint 2024-05-25 16:59:22 +03:00
Pete Matsyburka 08784d28c7 adjust log ip 2024-05-25 16:25:59 +03:00
Pete Matsyburka 0440ea652f increase attachment url ttl 2024-05-25 16:25:56 +03:00
Pete Matsyburka e466562391 use rollbar 2024-05-24 13:35:28 +03:00
Pete Matsyburka b2c8667c53 safe register element 2024-05-23 14:02:48 +03:00
Pete Matsyburka d6893079f7 remove breadcrumbs 2024-05-23 13:39:16 +03:00
Pete Matsyburka ac1d973605 fix loop 2024-05-23 12:45:01 +03:00
Pete Matsyburka 1405fefc85 add sentry 2024-05-23 12:25:25 +03:00
Pete Matsyburka 2a75055fb6 fix pdf parsing 2024-05-22 21:37:23 +03:00
Pete Matsyburka f84fdd51de fix builder areas 2024-05-22 15:39:46 +03:00
Alex Turchyn b38073126e fix dropdown styles 2024-05-22 15:20:04 +03:00
Alex Turchyn c6e681195d upgrade daisyUI to v3.9.4 2024-05-22 13:39:43 +03:00
Pete Matsyburka 53d924be0b fix build 2024-05-20 20:07:17 +03:00
Pete Matsyburka d6cf027d5d fix erblint 2024-05-19 19:44:38 +03:00
Alex Turchyn ec332bab88 flatten pdf for preview images 2024-05-19 19:25:53 +03:00
Alex Turchyn 8b3b90a7e2 replace emojis 2024-05-19 19:12:17 +03:00
Pete Matsyburka 9047e3bed3 add dry run prop 2024-05-18 22:54:26 +03:00
Pete Matsyburka 72b7391245 add documents copy email to template preferences 2024-05-18 22:03:20 +03:00
Pete Matsyburka 2df05f0327 adjust initials height 2024-05-18 17:51:22 +03:00
Pete Matsyburka 95f3eb0e5d display filters 2024-05-18 13:58:21 +03:00
Pete Matsyburka ce8d964ce6 fix ensure result generated 2024-05-17 16:07:08 +03:00
Pete Matsyburka f0d5fc4e2d add submission created webhook event 2024-05-17 13:28:10 +03:00
Pete Matsyburka b4b17d847e add view archived to template page 2024-05-17 12:08:46 +03:00
Alex Turchyn fd537e7ac0 upgrade rack 3 2024-05-16 23:26:30 +03:00
Pete Matsyburka 8c271104e1 single digital signature by default 2024-05-16 23:18:48 +03:00
Pete Matsyburka 51edd60d96 update gem 2024-05-16 15:16:59 +03:00
Pete Matsyburka a265ca1716 remove pdf processor 2024-05-16 13:37:23 +03:00
Pete Matsyburka a773f63a65 remove pdf reader 2024-05-16 11:56:41 +03:00
Pete Matsyburka 576c8a2e24 fix ensure result generated 2024-05-15 23:49:36 +03:00
Pete Matsyburka 5829de9ee4 adjust required fields alert 2024-05-15 11:42:51 +03:00
Pete Matsyburka b202941b79 fix tsa settings 2024-05-15 00:21:52 +03:00
Pete Matsyburka 6f6fb74988 adjust plans settings nav 2024-05-14 23:32:11 +03:00
Pete Matsyburka 0137bdbe4c optimize tsa settings controller 2024-05-12 19:32:09 +03:00
Pete Matsyburka 55d9511c01 add disclosure message 2024-05-12 12:35:20 +03:00
Pete Matsyburka 929d29576d add template webhooks 2024-05-12 11:37:33 +03:00
Pete Matsyburka 6f7128a51c add number format 2024-05-12 11:12:07 +03:00
Pete Matsyburka fc5c4ff2fc adjust reminder options 2024-05-11 16:24:13 +03:00
Pete Matsyburka a81595db2b allow legacy algos 2024-05-10 22:55:21 +03:00
Pete Matsyburka a5615d500d fix missing field steps 2024-05-10 22:24:44 +03:00
Pete Matsyburka da9266befc fix time format 2024-05-10 09:24:09 +03:00
Pete Matsyburka 5b3cffeb73 fix submission archived webhook 2024-05-08 09:53:37 +03:00
Pete Matsyburka 8f7b8a5fdf fix signature size 2024-05-08 09:38:16 +03:00
Pete Matsyburka 6d93116993 fix checkbox add on missclick 2024-05-05 18:18:07 +03:00
Pete Matsyburka a0f4916d3b add field settings dropdown to area 2024-05-05 16:58:19 +03:00
Pete Matsyburka ace897eaf9 update SECURITY.md 2024-05-04 23:15:04 +03:00
Pete Matsyburka b8667c5408 fix audit log on dev 2024-05-04 11:09:48 +03:00
Pete Matsyburka d87d66632e add field-area-active class 2024-05-02 23:33:44 +03:00
Alex Turchyn 3015c2d156 convert BMP images to PNG before uploading in the signing form 2024-05-02 22:10:23 +03:00
Pete Matsyburka 52b9e9ee8e fix underscore field name 2024-05-02 16:33:41 +03:00
Pete Matsyburka 5bfad0271c fix audit log font 2024-05-02 13:50:43 +03:00
Pete Matsyburka 58797891c7 fix trusted cert env 2024-05-02 13:39:47 +03:00
Pete Matsyburka 897750ba1e fix build 2024-05-02 12:43:13 +03:00
Pete Matsyburka 99341e773f refactor sign params 2024-05-01 23:40:09 +03:00
Pete Matsyburka 71319c1548 fix pdf generation 2024-05-01 11:51:40 +03:00
Pete Matsyburka c0a42f6bfc do now show default fields on preview 2024-04-29 15:42:11 +03:00
Pete Matsyburka d5abb46112 fix phone value assignment 2024-04-28 22:09:02 +03:00
Pete Matsyburka 2508b5fc8a adjust template account clone 2024-04-28 16:43:31 +03:00
Pete Matsyburka 4bd1ef0da3 fix params normalize 2024-04-28 16:33:11 +03:00
Pete Matsyburka 4efd748571 fix 2024-04-28 16:32:23 +03:00
Pete Matsyburka 859892d1fd adjust template preferences 2024-04-28 16:26:47 +03:00
Pete Matsyburka 7adeaf93cd only active linked accounts 2024-04-28 15:01:15 +03:00
Pete Matsyburka 5dbe155ecb shorten call 2024-04-28 14:44:33 +03:00
Pete Matsyburka 667d065bee populate linked accounts 2024-04-28 14:27:23 +03:00
Pete Matsyburka bb613059de add archived at to accounts 2024-04-28 13:43:53 +03:00
Pete Matsyburka 1b2dec4c2a adjust create submission resp 2024-04-27 23:08:16 +03:00
Pete Matsyburka 2c2ef97c30 validate params 2024-04-27 21:35:20 +03:00
Pete Matsyburka 6df1d68237 addept value param 2024-04-27 21:35:09 +03:00
Pete Matsyburka 20c9faedff update dep 2024-04-27 20:15:39 +03:00
Pete Matsyburka d931a40ea9 adjust log 2024-04-27 11:11:00 +03:00
Pete Matsyburka 1097b98b85 add log 2024-04-26 23:37:04 +03:00
Pete Matsyburka 2e597fcf43 ruby 3.3.1 2024-04-26 12:39:45 +03:00
Pete Matsyburka bb2fa6a261 add reuse signature prop 2024-04-25 17:51:36 +03:00
Pete Matsyburka 1dc8369487 fix template clone 2024-04-24 21:02:19 +03:00
Pete Matsyburka e6d60f1985 better error message 2024-04-24 13:18:57 +03:00
Pete Matsyburka 82e3cb8a4d fix audit log toggle 2024-04-24 01:19:07 +03:00
Pete Matsyburka aeec0cbf8e phone step langauge 2024-04-23 21:33:40 +03:00
Pete Matsyburka 7a2d1fc6c5 allow to remove personalization configs 2024-04-23 20:47:12 +03:00
Pete Matsyburka 68033ab7d2 fix files link position 2024-04-23 20:09:39 +03:00
Pete Matsyburka 7595c60ea8 remove pattern validation 2024-04-23 15:02:42 +03:00
Pete Matsyburka d9318c04f8 fix builder bg color 2024-04-23 01:57:45 +03:00
Pete Matsyburka 2d76198290 add flatten pdf toggle 2024-04-22 21:34:55 +03:00
Pete Matsyburka 02a7b6ea55 do not allow to skip required field with whitespace 2024-04-22 21:15:57 +03:00
Pete Matsyburka ab57076b8f add class 2024-04-22 20:01:30 +03:00
Pete Matsyburka 82fba47c98 rotate pdf 2024-04-22 19:10:58 +03:00
Pete Matsyburka 5f32e2a5af adjust create event 2024-04-21 17:01:44 +03:00
Pete Matsyburka 70012fbd7c fix form 2024-04-21 16:40:28 +03:00
Pete Matsyburka 668f6ca975 fix cells draw 2024-04-21 00:32:40 +03:00
Pete Matsyburka e78dd301ef fix download ttl 2024-04-20 23:58:43 +03:00
Pete Matsyburka f8ec3daf10 add minimize prop 2024-04-20 18:53:16 +03:00
Pete Matsyburka 75c929639d fix condition last field error 2024-04-20 17:23:28 +03:00
Pete Matsyburka e9c1242b8d verify field only once 2024-04-20 17:07:31 +03:00
Pete Matsyburka e7eb1d4f12 add noindex 2024-04-20 15:47:48 +03:00
Pete Matsyburka abbd3e809a remove sign now from archived 2024-04-20 12:54:14 +03:00
Pete Matsyburka f430b4f5a6 change default email 2024-04-20 11:25:04 +03:00
Pete Matsyburka e65234b4c8 add archived users view 2024-04-19 16:19:02 +03:00
Pete Matsyburka fe04d22c94 fix email normalize 2024-04-19 12:30:10 +03:00
Pete Matsyburka 44a7a5ebdc fix go to last 2024-04-19 10:33:31 +03:00
Pete Matsyburka 119ef0d0c7 add force sso key 2024-04-19 02:04:35 +03:00
Pete Matsyburka cdb8eb7931 fix pdf number of pages 2024-04-18 22:25:07 +03:00
Pete Matsyburka aef176c0cc downgrade net smtp 2024-04-18 18:43:47 +03:00
Pete Matsyburka 0c21b55865 adjust fixed email log 2024-04-18 17:02:53 +03:00
Pete Matsyburka 964b1444a5 adjust completed action 2024-04-18 16:45:09 +03:00
Pete Matsyburka 48de343948 add fix email log 2024-04-18 10:44:45 +03:00
Pete Matsyburka 2640934c15 skip .om tld typo fix 2024-04-18 10:42:54 +03:00
Pete Matsyburka 442dd4d6db add submitters template_id filter 2024-04-18 01:48:14 +03:00
Pete Matsyburka 3168feb3d6 clone template preferences 2024-04-17 21:09:35 +03:00
Pete Matsyburka fbae4933f6 fix multiple bcc addresses 2024-04-17 20:51:34 +03:00
Pete Matsyburka 78bb66c344 adjust wording 2024-04-17 20:24:31 +03:00
Pete Matsyburka 5af65ef74f adjust drawer 2024-04-17 17:27:43 +03:00
Pete Matsyburka 4d354ae9ff do not load storage configs in dev 2024-04-17 16:51:42 +03:00
Pete Matsyburka 1e57c1ace7 add template preferences 2024-04-17 16:09:09 +03:00
Pete Matsyburka fefd138dfd add devise log 2024-04-17 00:12:05 +03:00
Pete Matsyburka 16a77d3536 add log 2024-04-16 14:07:38 +03:00
Pete Matsyburka e03857bdb7 mark required field on mobile 2024-04-16 10:42:11 +03:00
Pete Matsyburka fc679bcd72 add require file links auth toggle 2024-04-15 20:34:00 +03:00
Pete Matsyburka 652b0da021 add fields param 2024-04-15 19:36:16 +03:00
Pete Matsyburka 2d54b0db2f update deps 2024-04-15 18:33:55 +03:00
Pete Matsyburka 7a29894926 add required fields prop 2024-04-15 16:34:44 +03:00
Pete Matsyburka f9ae79dd5b add go to last 2024-04-13 17:41:49 +03:00
Pete Matsyburka cadb78ca35 check on email events 2024-04-13 12:37:48 +03:00
Pete Matsyburka 36a6b7ff1d fix typo 2024-04-13 11:35:04 +03:00
Pete Matsyburka efee46f113 add bulk source 2024-04-12 17:20:00 +03:00
Pete Matsyburka b6639738c3 adjust dockerfile 2024-04-11 22:52:12 +03:00
Alex Turchyn aab07c5f49 add icons 2024-04-11 22:52:11 +03:00
Pete Matsyburka 33c0784bc6 add folder name to submission resp 2024-04-11 16:36:53 +03:00
Pete Matsyburka 6f5ebfc8a6 fix preview completed 2024-04-11 02:45:38 +03:00
Pete Matsyburka 811091ff14 adjust text signature size 2024-04-10 22:24:19 +03:00
Pete Matsyburka 6666f39928 add redacted param 2024-04-10 19:56:35 +03:00
Pete Matsyburka f67db9fe5f add field uuid param 2024-04-10 13:30:27 +03:00
Pete Matsyburka 45f002803e adjust padding 2024-04-10 10:55:51 +03:00
Pete Matsyburka 8c19f90e88 remove debug 2024-04-10 00:44:36 +03:00
Pete Matsyburka a8f5d5cea7 fix typo 2024-04-09 19:08:05 +03:00
Pete Matsyburka 6788071eec add webhook event preferences 2024-04-09 18:11:58 +03:00
Pete Matsyburka 0c690b3033 custom submit button 2024-04-09 01:54:39 +03:00
Pete Matsyburka dcb963b623 add autoscrollFields prop 2024-04-08 18:37:10 +03:00
Pete Matsyburka 04e30dbdd6 add submitter update params 2024-04-08 10:51:18 +03:00
Pete Matsyburka ac6d5a7aed update readme 2024-04-08 01:18:31 +03:00
Pete Matsyburka 3166a31b67 add icon 2024-04-08 01:02:43 +03:00
Pete Matsyburka fc3283eb3a add Korean i18n 2024-04-08 00:33:54 +03:00
Pete Matsyburka d6a96d4aa3 fix base64 unicode 2024-04-07 18:06:26 +03:00
Pete Matsyburka cda349d71c adjust send email views 2024-04-07 17:58:18 +03:00
Pete Matsyburka 87345a3b3d fix rubocop 2024-04-07 11:14:34 +03:00
Pete Matsyburka b791d0699c fix replace button file types 2024-04-06 21:57:35 +03:00
Pete Matsyburka 2cb15f3282 optimize document upload 2024-04-06 21:57:34 +03:00
Pete Matsyburka 8b823ca0d0 do not fix gob tld 2024-04-06 19:41:17 +03:00
Pete Matsyburka f3f08797f7 optimize document upload 2024-04-06 17:08:45 +03:00
Pete Matsyburka cab9672d25 remove direct upload 2024-04-06 15:00:30 +03:00
Pete Matsyburka c8d3b1bace reorder settings 2024-04-06 12:56:05 +03:00
Pete Matsyburka 5ee51b2b57 fix testing account 2024-04-06 01:20:02 +03:00
Pete Matsyburka 525dc503a6 accept default value array 2024-04-06 01:02:24 +03:00
Pete Matsyburka 12cf28792b add uuid to accounts 2024-04-05 19:56:34 +03:00
Pete Matsyburka 33466c1d07 adjust sso 2024-04-05 19:28:57 +03:00
Pete Matsyburka 226ae2172e word break underscore 2024-04-05 19:14:02 +03:00
Pete Matsyburka 8e2700b8b8 break long titles 2024-04-05 16:44:17 +03:00
Pete Matsyburka 753cbd4d27 allow to add custom form completed message 2024-04-05 16:16:30 +03:00
Pete Matsyburka f16405ca70 assign first party i18n name 2024-04-05 02:21:14 +03:00
Pete Matsyburka 23f052c400 add Korean 2024-04-05 02:02:23 +03:00
Pete Matsyburka 90a0a03e15 validate custom certs in the cloud 2024-04-04 14:08:14 +03:00
Pete Matsyburka 2dba8ae366 add submissions dashboard view 2024-04-04 13:45:11 +03:00
Pete Matsyburka 5bb7ad571c display default date only of the signing party 2024-04-03 10:27:19 +03:00
Pete Matsyburka 0cdb7c2c4f fix date normalization 2024-04-02 11:30:52 +03:00
Pete Matsyburka 11bdc522cd add spreadsheet download 2024-04-01 17:56:08 +03:00
Pete Matsyburka 014835f90d adjust download button state 2024-04-01 15:15:55 +03:00
Pete Matsyburka 05e66b60ee fix result generation with annots 2024-04-01 13:16:54 +03:00
Pete Matsyburka 505b185cf2 fix download indicator 2024-03-31 18:31:09 +03:00
Pete Matsyburka 50c32ac6ff replace date value 2024-03-31 18:20:33 +03:00
Pete Matsyburka 878ab25fd4 access account via submission 2024-03-31 17:40:51 +03:00
Pete Matsyburka 1b31772ff6 show default date on the page 2024-03-31 17:34:23 +03:00
Pete Matsyburka d4940234b7 add bulk placeholder 2024-03-30 23:36:40 +02:00
Pete Matsyburka 9bdc5d4863 adjust batch limit 2024-03-30 14:42:42 +02:00
Pete Matsyburka a73fd7929c add smtp timeout 2024-03-30 12:43:15 +02:00
Pete Matsyburka 8269a61751 adjust payment connected 2024-03-29 23:24:51 +02:00
Pete Matsyburka de072d1437 scroll to multiple checkbox field 2024-03-29 21:43:13 +02:00
Pete Matsyburka 5b24793f2b render links with markdown in emails 2024-03-29 21:21:04 +02:00
Pete Matsyburka 660f7ab8f3 password reset neutral on-prem 2024-03-29 14:50:18 +02:00
Pete Matsyburka 6e3fea5fa0 validate email param 2024-03-28 11:25:32 +02:00
Pete Matsyburka 29ecbecbc2 flatten annots 2024-03-27 18:24:41 +02:00
Pete Matsyburka ad3bbe9892 sctoll to radio group 2024-03-27 18:24:35 +02:00
Pete Matsyburka b570ebee69 remember builder selected field type 2024-03-27 14:21:30 +02:00
Pete Matsyburka 207780fa3f clear fields on remove doc 2024-03-27 00:11:10 +02:00
Pete Matsyburka 8aa219433b render blank option checkbox 2024-03-26 19:28:41 +02:00
Pete Matsyburka cb1e9f2be4 do not send completed emails to integration 2024-03-26 18:01:35 +02:00
Pete Matsyburka 202943f58d fix pidfile issue 2024-03-26 01:17:55 +02:00
Pete Matsyburka 6dd15b2091 use letter page size for us 2024-03-25 20:20:38 +02:00
Pete Matsyburka 1c55e4f18b always display default value 2024-03-25 19:16:12 +02:00
Pete Matsyburka 6cc36b1728 allow to update template roles 2024-03-25 17:37:46 +02:00
Pete Matsyburka 37751cd5c1 update template submitters 2024-03-25 16:34:45 +02:00
Pete Matsyburka 80d882cd20 add reply to param 2024-03-24 14:09:28 +02:00
Pete Matsyburka 8bef75e570 move autocomplete controllers 2024-03-23 23:52:50 +02:00
Pete Matsyburka 077eab7005 refactor templates controller 2024-03-23 23:52:49 +02:00
Pete Matsyburka 715c70d422 rename to users 2024-03-23 21:23:59 +02:00
Pete Matsyburka a3c23f9d53 update readme 2024-03-22 17:35:52 +02:00
Pete Matsyburka c1e67a8af0 add submission id var 2024-03-22 13:11:33 +02:00
Pete Matsyburka 2fdc294293 fix annot 2024-03-21 16:25:49 +02:00
Pete Matsyburka 1f49ba069e disable puma pidfile 2024-03-21 15:03:50 +02:00
Pete Matsyburka 43ef0fc0d0 use FORCE_SSL=false 2024-03-21 14:12:11 +02:00
Pete Matsyburka a1a920e536 allow to remove default value 2024-03-21 00:04:53 +02:00
Pete Matsyburka dec33b4331 add extra small field font size 2024-03-20 16:52:53 +02:00
Pete Matsyburka 432f290633 assign template fields on completed via API 2024-03-20 00:13:16 +02:00
Pete Matsyburka cf98616a27 remove log 2024-03-19 13:57:21 +02:00
Pete Matsyburka 1a720cf1ad allow to clone template from testing 2024-03-19 01:44:16 +02:00
Pete Matsyburka 557c6c34f2 search default fields 2024-03-19 01:21:44 +02:00
Pete Matsyburka 943c76505d remove api error log 2024-03-18 23:18:35 +02:00
Pete Matsyburka 66eb83e6a7 allow to search on value 2024-03-18 20:16:26 +02:00
Pete Matsyburka 0844427357 add rollbar queue 2024-03-18 19:35:19 +02:00
Pete Matsyburka 72e9d9ea2d update readme 2024-03-17 23:16:27 +02:00
503 changed files with 26311 additions and 4624 deletions
+2 -1
View File
@@ -8,7 +8,8 @@
},
"rules": {
"vue/no-deprecated-html-element-is": 0,
"vue/no-mutating-props": 0
"vue/no-mutating-props": 0,
"vue/one-component-per-file": 0
},
"parserOptions": {
"ecmaVersion": 2022,
+5 -3
View File
@@ -11,7 +11,7 @@ jobs:
- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2.2
ruby-version: 3.3.4
- name: Cache gems
uses: actions/cache@v1
with:
@@ -35,7 +35,7 @@ jobs:
- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2.2
ruby-version: 3.3.4
- name: Cache gems
uses: actions/cache@v1
with:
@@ -100,13 +100,15 @@ jobs:
- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.2.2
ruby-version: 3.3.4
- name: Set up Node
uses: actions/setup-node@v1
with:
node-version: 20.9.0
- name: Install Chrome
uses: browser-actions/setup-chrome@latest
with:
chrome-version: 125
- name: Cache node_modules
uses: actions/cache@v1
with:
+1
View File
@@ -36,3 +36,4 @@ yarn-debug.log*
/attachments
/docuseal
/ee
dump.rdb
+15 -8
View File
@@ -10,7 +10,8 @@ AllCops:
- node_modules/**/*
- bin/*
- vendor/**/*
TargetRubyVersion: '3.2'
TargetRubyVersion: '3.3'
SuggestExtensions: false
Metrics/BlockLength:
Exclude:
@@ -33,6 +34,7 @@ Metrics/MethodLength:
Max: 30
Exclude:
- 'db/migrate/**'
- 'spec/**/*'
Metrics/CyclomaticComplexity:
Max: 15
@@ -44,25 +46,30 @@ Layout/LineLength:
AllowedPatterns: ['\A\s*#']
Metrics/AbcSize:
Max: 40
Max: 45
Exclude:
- spec/**/*
Metrics/ModuleLength:
Max: 500
Max: 1000
Metrics/ClassLength:
Max: 500
Max: 1000
RSpec/NestedGroups:
Max: 6
RSpec/MultipleExpectations:
Max: 20
Max: 25
RSpec/ExampleLength:
Max: 40
Max: 50
RSpec/MultipleMemoizedHelpers:
Max: 6
Max: 9
Metrics/BlockNesting:
Max: 4
Rails/I18nLocaleTexts:
Enabled: false
@@ -76,5 +83,5 @@ Rails/SkipsModelValidations:
Rails/ApplicationController:
Enabled: false
Capybara/ClickLinkOrButtonStyle:
Rails/Output:
Enabled: false
+24 -11
View File
@@ -1,4 +1,4 @@
FROM ruby:3.2.2-alpine3.18 as fonts
FROM ruby:3.3.4-alpine as fonts
WORKDIR /fonts
@@ -6,7 +6,7 @@ RUN apk --no-cache add fontforge wget && wget https://github.com/satbyy/go-noto-
RUN fontforge -lang=py -c 'font1 = fontforge.open("FreeSans.ttf"); font2 = fontforge.open("NotoSansSymbols2-Regular.ttf"); font1.mergeFonts(font2); font1.generate("FreeSans.ttf")'
FROM ruby:3.2.2-alpine3.18 as webpack
FROM ruby:3.3.4-alpine as webpack
ENV RAILS_ENV=production
ENV NODE_ENV=production
@@ -23,28 +23,41 @@ RUN yarn install --network-timeout 1000000
COPY ./bin/shakapacker ./bin/shakapacker
COPY ./config/webpack ./config/webpack
COPY ./config/shakapacker.yml ./config/shakapacker.yml
COPY ./postcss.config.js ./postcss.config.js ./
COPY ./tailwind.config.js ./tailwind.config.js ./
COPY ./tailwind.form.config.js ./tailwind.form.config.js ./
COPY ./tailwind.application.config.js ./tailwind.application.config.js ./
COPY ./postcss.config.js ./postcss.config.js
COPY ./tailwind.config.js ./tailwind.config.js
COPY ./tailwind.form.config.js ./tailwind.form.config.js
COPY ./tailwind.application.config.js ./tailwind.application.config.js
COPY ./app/javascript ./app/javascript
COPY ./app/views ./app/views
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
FROM ruby:3.2.2-alpine3.18 as app
FROM ruby:3.3.4-alpine as app
ENV RAILS_ENV=production
ENV BUNDLE_WITHOUT="development:test"
ENV PIDFILE=/dev/null
ENV LD_PRELOAD=/lib/libgcompat.so.0
ENV OPENSSL_CONF=/app/openssl_legacy.cnf
WORKDIR /app
RUN apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev vips-poppler poppler-utils vips-heif libc6-compat 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 vips-poppler poppler-utils redis libheif@edge vips-heif gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
RUN echo $'.include = /etc/ssl/openssl.cnf\n\
\n\
[provider_sect]\n\
default = default_sect\n\
legacy = legacy_sect\n\
\n\
[default_sect]\n\
activate = 1\n\
\n\
[legacy_sect]\n\
activate = 1' >> /app/openssl_legacy.cnf
COPY ./Gemfile ./Gemfile.lock ./
RUN apk add --no-cache build-base && bundle update --bundler && bundle install && apk del build-base && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf
RUN apk add --no-cache build-base && bundle install && apk del --no-cache build-base && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf
COPY ./bin ./bin
COPY ./app ./app
@@ -67,4 +80,4 @@ WORKDIR /data/docuseal
ENV WORKDIR=/data/docuseal
EXPOSE 3000
CMD ["/app/bin/rails", "server"]
CMD ["/app/bin/bundle", "exec", "puma", "-C", "/app/config/puma.rb", "--dir", "/app"]
+5 -4
View File
@@ -2,13 +2,15 @@
source 'https://rubygems.org'
ruby '3.2.2'
ruby '3.3.4'
gem 'arabic-letter-connector', require: 'arabic-letter-connector/logic'
gem 'aws-sdk-s3', require: false
gem 'aws-sdk-secretsmanager', require: false
gem 'azure-storage-blob', require: false
gem 'bootsnap', require: false
gem 'cancancan'
gem 'csv'
gem 'devise'
gem 'devise-two-factor'
gem 'dotenv', require: false
@@ -23,11 +25,10 @@ gem 'lograge'
gem 'mysql2', require: false
gem 'oj'
gem 'pagy'
gem 'pdf-reader'
gem 'pg', require: false
gem 'premailer-rails'
gem 'pretender'
gem 'puma'
gem 'puma', require: false
gem 'rack'
gem 'rails'
gem 'rails_autolink'
@@ -37,7 +38,7 @@ gem 'rqrcode'
gem 'ruby-vips'
gem 'rubyXL'
gem 'shakapacker'
gem 'sidekiq', require: ENV.key?('REDIS_URL')
gem 'sidekiq'
gem 'sqlite3', require: false
gem 'strip_attributes'
gem 'turbo-rails'
+258 -255
View File
@@ -1,104 +1,101 @@
GEM
remote: https://rubygems.org/
specs:
Ascii85 (1.1.0)
actioncable (7.1.2)
actionpack (= 7.1.2)
activesupport (= 7.1.2)
actioncable (7.2.1.1)
actionpack (= 7.2.1.1)
activesupport (= 7.2.1.1)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (7.1.2)
actionpack (= 7.1.2)
activejob (= 7.1.2)
activerecord (= 7.1.2)
activestorage (= 7.1.2)
activesupport (= 7.1.2)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.1.2)
actionpack (= 7.1.2)
actionview (= 7.1.2)
activejob (= 7.1.2)
activesupport (= 7.1.2)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
actionmailbox (7.2.1.1)
actionpack (= 7.2.1.1)
activejob (= 7.2.1.1)
activerecord (= 7.2.1.1)
activestorage (= 7.2.1.1)
activesupport (= 7.2.1.1)
mail (>= 2.8.0)
actionmailer (7.2.1.1)
actionpack (= 7.2.1.1)
actionview (= 7.2.1.1)
activejob (= 7.2.1.1)
activesupport (= 7.2.1.1)
mail (>= 2.8.0)
rails-dom-testing (~> 2.2)
actionpack (7.1.2)
actionview (= 7.1.2)
activesupport (= 7.1.2)
actionpack (7.2.1.1)
actionview (= 7.2.1.1)
activesupport (= 7.2.1.1)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4)
rack (>= 2.2.4, < 3.2)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
actiontext (7.1.2)
actionpack (= 7.1.2)
activerecord (= 7.1.2)
activestorage (= 7.1.2)
activesupport (= 7.1.2)
useragent (~> 0.16)
actiontext (7.2.1.1)
actionpack (= 7.2.1.1)
activerecord (= 7.2.1.1)
activestorage (= 7.2.1.1)
activesupport (= 7.2.1.1)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.1.2)
activesupport (= 7.1.2)
actionview (7.2.1.1)
activesupport (= 7.2.1.1)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
activejob (7.1.2)
activesupport (= 7.1.2)
activejob (7.2.1.1)
activesupport (= 7.2.1.1)
globalid (>= 0.3.6)
activemodel (7.1.2)
activesupport (= 7.1.2)
activerecord (7.1.2)
activemodel (= 7.1.2)
activesupport (= 7.1.2)
activemodel (7.2.1.1)
activesupport (= 7.2.1.1)
activerecord (7.2.1.1)
activemodel (= 7.2.1.1)
activesupport (= 7.2.1.1)
timeout (>= 0.4.0)
activestorage (7.1.2)
actionpack (= 7.1.2)
activejob (= 7.1.2)
activerecord (= 7.1.2)
activesupport (= 7.1.2)
activestorage (7.2.1.1)
actionpack (= 7.2.1.1)
activejob (= 7.2.1.1)
activerecord (= 7.2.1.1)
activesupport (= 7.2.1.1)
marcel (~> 1.0)
activesupport (7.1.2)
activesupport (7.2.1.1)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
mutex_m
tzinfo (~> 2.0)
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
afm (0.2.2)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
annotate (3.2.0)
activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
arabic-letter-connector (0.1.1)
ast (2.4.2)
aws-eventstream (1.3.0)
aws-partitions (1.874.0)
aws-sdk-core (3.190.1)
aws-partitions (1.985.0)
aws-sdk-core (3.209.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
aws-sigv4 (~> 1.9)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.75.0)
aws-sdk-core (~> 3, >= 3.188.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.142.0)
aws-sdk-core (~> 3, >= 3.189.0)
aws-sdk-kms (1.94.0)
aws-sdk-core (~> 3, >= 3.207.0)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.167.0)
aws-sdk-core (~> 3, >= 3.207.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
aws-sigv4 (1.8.0)
aws-sigv4 (~> 1.5)
aws-sdk-secretsmanager (1.108.0)
aws-sdk-core (~> 3, >= 3.207.0)
aws-sigv4 (~> 1.5)
aws-sigv4 (1.10.0)
aws-eventstream (~> 1, >= 1.0.2)
azure-storage-blob (2.0.3)
azure-storage-common (~> 2.0)
@@ -110,86 +107,89 @@ GEM
nokogiri (~> 1, >= 1.10.8)
base64 (0.2.0)
bcrypt (3.1.20)
better_html (2.0.2)
better_html (2.1.1)
actionview (>= 6.0)
activesupport (>= 6.0)
ast (~> 2.0)
erubi (~> 1.4)
parser (>= 2.4)
smart_properties
bigdecimal (3.1.5)
bigdecimal (3.1.8)
bindex (0.8.1)
bootsnap (1.17.0)
bootsnap (1.18.4)
msgpack (~> 1.2)
builder (3.2.4)
bullet (7.1.4)
builder (3.3.0)
bullet (7.2.0)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
camertron-eprun (1.1.1)
cancancan (3.5.0)
capybara (3.39.2)
cancancan (3.6.1)
capybara (3.40.0)
addressable
matrix
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
nokogiri (~> 1.11)
rack (>= 1.6.0)
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
childprocess (5.1.0)
logger (~> 1.5)
chunky_png (1.4.0)
cldr-plurals-runtime-rb (1.1.0)
cmdparse (3.0.7)
coderay (1.1.3)
concurrent-ruby (1.2.2)
concurrent-ruby (1.3.4)
connection_pool (2.4.1)
crack (0.4.5)
crack (1.0.0)
bigdecimal
rexml
crass (1.0.6)
css_parser (1.16.0)
css_parser (1.19.0)
addressable
cuprite (0.15)
csv (3.3.0)
cuprite (0.15.1)
capybara (~> 3.0)
ferrum (~> 0.14.0)
ferrum (~> 0.15.0)
date (3.3.4)
debug (1.9.1)
debug (1.9.2)
irb (~> 1.10)
reline (>= 0.3.8)
declarative (0.0.20)
devise (4.9.3)
devise (4.9.4)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
devise-two-factor (5.0.0)
devise-two-factor (6.0.0)
activesupport (~> 7.0)
devise (~> 4.0)
railties (~> 7.0)
rotp (~> 6.0)
diff-lcs (1.5.0)
diff-lcs (1.5.1)
digest-crc (0.6.5)
rake (>= 12.0.0, < 14.0.0)
docile (1.4.0)
dotenv (2.8.1)
drb (2.2.0)
ruby2_keywords
docile (1.4.1)
dotenv (3.1.4)
drb (2.2.1)
email_typo (0.2.3)
erb_lint (0.5.0)
erb_lint (0.6.0)
activesupport
better_html (>= 2.0.1)
parser (>= 2.7.1.4)
rainbow
rubocop
rubocop (>= 1)
smart_properties
erubi (1.12.0)
factory_bot (6.4.4)
erubi (1.13.0)
factory_bot (6.5.0)
activesupport (>= 5.0.0)
factory_bot_rails (6.4.2)
factory_bot_rails (6.4.3)
factory_bot (~> 6.4)
railties (>= 5.0.0)
faker (3.2.2)
faker (3.4.2)
i18n (>= 1.8.11, < 2)
faraday (1.10.3)
faraday (1.10.4)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
@@ -209,92 +209,96 @@ GEM
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (1.0.1)
faraday-net_http (1.0.2)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday_middleware (1.2.0)
faraday_middleware (1.2.1)
faraday (~> 1.0)
ferrum (0.14)
ferrum (0.15)
addressable (~> 2.5)
concurrent-ruby (~> 1.1)
webrick (~> 1.7)
websocket-driver (>= 0.6, < 0.8)
ffi (1.16.3)
websocket-driver (~> 0.7)
ffi (1.17.0)
ffi (1.17.0-aarch64-linux-gnu)
ffi (1.17.0-x86_64-linux-gnu)
geom2d (0.4.1)
globalid (1.2.1)
activesupport (>= 6.1)
google-apis-core (0.11.2)
google-apis-core (0.15.1)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
googleauth (~> 1.9)
httpclient (>= 2.8.3, < 3.a)
mini_mime (~> 1.0)
mutex_m
representable (~> 3.0)
retriable (>= 2.0, < 4.a)
rexml
webrick
google-apis-iamcredentials_v1 (0.17.0)
google-apis-core (>= 0.11.0, < 2.a)
google-apis-storage_v1 (0.29.0)
google-apis-core (>= 0.11.0, < 2.a)
google-cloud-core (1.6.1)
google-apis-iamcredentials_v1 (0.21.0)
google-apis-core (>= 0.15.0, < 2.a)
google-apis-storage_v1 (0.46.0)
google-apis-core (>= 0.15.0, < 2.a)
google-cloud-core (1.7.1)
google-cloud-env (>= 1.0, < 3.a)
google-cloud-errors (~> 1.0)
google-cloud-env (2.1.0)
google-cloud-env (2.2.1)
faraday (>= 1.0, < 3.a)
google-cloud-errors (1.3.1)
google-cloud-storage (1.45.0)
google-cloud-errors (1.4.0)
google-cloud-storage (1.52.0)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.29.0)
google-apis-core (~> 0.13)
google-apis-iamcredentials_v1 (~> 0.18)
google-apis-storage_v1 (~> 0.38)
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
googleauth (~> 1.9)
mini_mime (~> 1.0)
googleauth (1.9.1)
googleauth (1.11.1)
faraday (>= 1.0, < 3.a)
google-cloud-env (~> 2.1)
jwt (>= 1.4, < 3.0)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
hashdiff (1.1.0)
hashery (2.1.2)
hexapdf (0.37.1)
hashdiff (1.1.1)
hexapdf (0.47.0)
cmdparse (~> 3.0, >= 3.0.3)
geom2d (~> 0.4, >= 0.4.1)
openssl (>= 2.2.1)
htmlentities (4.3.4)
httpclient (2.8.3)
i18n (1.14.1)
i18n (1.14.6)
concurrent-ruby (~> 1.0)
image_processing (1.12.2)
image_processing (1.13.0)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
io-console (0.7.1)
irb (1.11.0)
rdoc
reline (>= 0.3.8)
io-console (0.7.2)
irb (1.14.1)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jmespath (1.6.2)
json (2.7.1)
jwt (2.7.1)
json (2.7.2)
jwt (2.9.3)
base64
language_server-protocol (3.17.0.3)
launchy (2.5.2)
launchy (3.0.1)
addressable (~> 2.8)
letter_opener (1.8.1)
launchy (>= 2.2, < 3)
letter_opener_web (2.0.0)
actionmailer (>= 5.2)
letter_opener (~> 1.7)
railties (>= 5.2)
childprocess (~> 5.0)
letter_opener (1.10.0)
launchy (>= 2.2, < 4)
letter_opener_web (3.0.0)
actionmailer (>= 6.1)
letter_opener (~> 1.9)
railties (>= 6.1)
rexml
logger (1.6.1)
lograge (0.14.0)
actionpack (>= 4)
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
loofah (2.22.0)
loofah (2.23.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
@@ -302,53 +306,54 @@ GEM
net-imap
net-pop
net-smtp
marcel (1.0.2)
marcel (1.0.4)
matrix (0.4.2)
method_source (1.0.0)
mini_magick (4.12.0)
method_source (1.1.0)
mini_magick (4.13.2)
mini_mime (1.1.5)
mini_portile2 (2.8.5)
minitest (5.20.0)
msgpack (1.7.2)
mini_portile2 (2.8.8)
minitest (5.25.1)
msgpack (1.7.3)
multi_json (1.15.0)
multipart-post (2.3.0)
multipart-post (2.4.1)
mutex_m (0.2.0)
mysql2 (0.5.5)
net-http-persistent (4.0.2)
mysql2 (0.5.6)
net-http-persistent (4.0.4)
connection_pool (~> 2.2)
net-imap (0.4.9)
net-imap (0.4.17)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
timeout
net-smtp (0.4.0)
net-smtp (0.5.0)
net-protocol
nio4r (2.7.0)
nokogiri (1.15.5)
nio4r (2.7.3)
nokogiri (1.16.8)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
oj (3.16.3)
nokogiri (1.16.8-aarch64-linux)
racc (~> 1.4)
nokogiri (1.16.8-x86_64-linux)
racc (~> 1.4)
oj (3.16.6)
bigdecimal (>= 3.0)
ostruct (>= 0.2)
openssl (3.2.0)
orm_adapter (0.5.0)
os (1.1.4)
pagy (6.2.0)
parallel (1.24.0)
parser (3.2.2.4)
ostruct (0.6.0)
package_json (0.1.0)
pagy (9.1.0)
parallel (1.26.3)
parser (3.3.5.0)
ast (~> 2.4.1)
racc
pdf-reader (2.12.0)
Ascii85 (~> 1.0)
afm (~> 0.2.1)
hashery (~> 2.0)
ruby-rc4
ttfunk
pg (1.5.4)
premailer (1.21.0)
pg (1.5.8)
premailer (1.27.0)
addressable
css_parser (>= 1.12.0)
css_parser (>= 1.19.0)
htmlentities (>= 4.0.0)
premailer-rails (1.12.0)
actionmailer (>= 3)
@@ -359,152 +364,148 @@ GEM
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
pry-rails (0.3.9)
pry (>= 0.10.4)
pry-rails (0.3.11)
pry (>= 0.13.0)
psych (5.1.2)
stringio
public_suffix (5.0.4)
puma (6.4.0)
public_suffix (6.0.1)
puma (6.4.3)
nio4r (~> 2.0)
racc (1.7.3)
rack (2.2.8)
racc (1.8.1)
rack (3.1.8)
rack-proxy (0.7.7)
rack
rack-session (1.0.2)
rack (< 3)
rack-session (2.0.0)
rack (>= 3.0.0)
rack-test (2.1.0)
rack (>= 1.3)
rackup (1.0.0)
rack (< 3)
webrick
rails (7.1.2)
actioncable (= 7.1.2)
actionmailbox (= 7.1.2)
actionmailer (= 7.1.2)
actionpack (= 7.1.2)
actiontext (= 7.1.2)
actionview (= 7.1.2)
activejob (= 7.1.2)
activemodel (= 7.1.2)
activerecord (= 7.1.2)
activestorage (= 7.1.2)
activesupport (= 7.1.2)
rackup (2.1.0)
rack (>= 3)
webrick (~> 1.8)
rails (7.2.1.1)
actioncable (= 7.2.1.1)
actionmailbox (= 7.2.1.1)
actionmailer (= 7.2.1.1)
actionpack (= 7.2.1.1)
actiontext (= 7.2.1.1)
actionview (= 7.2.1.1)
activejob (= 7.2.1.1)
activemodel (= 7.2.1.1)
activerecord (= 7.2.1.1)
activestorage (= 7.2.1.1)
activesupport (= 7.2.1.1)
bundler (>= 1.15.0)
railties (= 7.1.2)
railties (= 7.2.1.1)
rails-dom-testing (2.2.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0)
rails-html-sanitizer (1.6.1)
loofah (~> 2.21)
nokogiri (~> 1.14)
rails-i18n (7.0.8)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
rails-i18n (7.0.9)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
rails_autolink (1.1.8)
actionview (> 3.1)
activesupport (> 3.1)
railties (> 3.1)
railties (7.1.2)
actionpack (= 7.1.2)
activesupport (= 7.1.2)
irb
railties (7.2.1.1)
actionpack (= 7.2.1.1)
activesupport (= 7.2.1.1)
irb (~> 1.13)
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.1.0)
rdoc (6.6.2)
rake (13.2.1)
rdoc (6.7.0)
psych (>= 4.0.0)
redis-client (0.19.1)
redis-client (0.22.2)
connection_pool
regexp_parser (2.8.3)
reline (0.4.1)
regexp_parser (2.9.2)
reline (0.5.10)
io-console (~> 0.5)
representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
request_store (1.5.1)
request_store (1.7.0)
rack (>= 1.4)
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
retriable (3.1.2)
rexml (3.2.6)
rexml (3.3.9)
rotp (6.3.0)
rqrcode (2.2.0)
chunky_png (~> 1.0)
rqrcode_core (~> 1.0)
rqrcode_core (1.2.0)
rspec-core (3.12.2)
rspec-support (~> 3.12.0)
rspec-expectations (3.12.3)
rspec-core (3.13.1)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-mocks (3.12.6)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-rails (6.1.0)
actionpack (>= 6.1)
activesupport (>= 6.1)
railties (>= 6.1)
rspec-core (~> 3.12)
rspec-expectations (~> 3.12)
rspec-mocks (~> 3.12)
rspec-support (~> 3.12)
rspec-support (3.12.1)
rubocop (1.59.0)
rspec-support (~> 3.13.0)
rspec-rails (7.0.1)
actionpack (>= 7.0)
activesupport (>= 7.0)
railties (>= 7.0)
rspec-core (~> 3.13)
rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
rspec-support (3.13.1)
rubocop (1.66.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.2.2.4)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.30.0, < 2.0)
regexp_parser (>= 2.4, < 3.0)
rubocop-ast (>= 1.32.2, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.30.0)
parser (>= 3.2.1.0)
rubocop-capybara (2.19.0)
rubocop (~> 1.41)
rubocop-factory_bot (2.24.0)
rubocop (~> 1.33)
rubocop-performance (1.20.1)
rubocop-ast (1.32.3)
parser (>= 3.3.1.0)
rubocop-performance (1.22.1)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.30.0, < 2.0)
rubocop-rails (2.23.1)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rails (2.26.2)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
rubocop-ast (>= 1.30.0, < 2.0)
rubocop-rspec (2.25.0)
rubocop (~> 1.40)
rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22)
rubocop (>= 1.52.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rspec (3.1.0)
rubocop (~> 1.61)
ruby-progressbar (1.13.0)
ruby-rc4 (0.1.5)
ruby-vips (2.2.0)
ruby-vips (2.2.2)
ffi (~> 1.12)
logger
ruby2_keywords (0.0.5)
rubyXL (3.4.25)
rubyXL (3.4.27)
nokogiri (>= 1.10.8)
rubyzip (>= 1.3.0)
rubyzip (2.3.2)
securerandom (0.3.1)
semantic_range (3.0.0)
shakapacker (7.1.0)
shakapacker (8.0.2)
activesupport (>= 5.2)
package_json
rack-proxy (>= 0.6.1)
railties (>= 5.2)
semantic_range (>= 2.3.0)
sidekiq (7.2.0)
sidekiq (7.3.2)
concurrent-ruby (< 2)
connection_pool (>= 2.3.0)
logger
rack (>= 2.2.4)
redis-client (>= 0.14.0)
signet (0.18.0)
redis-client (>= 0.22.2)
signet (0.19.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
@@ -513,34 +514,34 @@ GEM
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov-html (0.13.1)
simplecov_json_formatter (0.1.4)
smart_properties (1.17.0)
sqlite3 (1.7.0)
sqlite3 (2.1.0)
mini_portile2 (~> 2.8.0)
sqlite3 (1.7.0-arm64-darwin)
stringio (3.1.0)
sqlite3 (2.1.0-aarch64-linux-gnu)
sqlite3 (2.1.0-x86_64-linux-gnu)
stringio (3.1.1)
strip_attributes (1.13.0)
activemodel (>= 3.0, < 8.0)
thor (1.3.0)
thor (1.3.2)
timeout (0.4.1)
trailblazer-option (0.1.2)
ttfunk (1.7.0)
turbo-rails (1.5.0)
turbo-rails (2.0.10)
actionpack (>= 6.0.0)
activejob (>= 6.0.0)
railties (>= 6.0.0)
twitter_cldr (6.12.0)
twitter_cldr (6.12.1)
camertron-eprun
cldr-plurals-runtime-rb (~> 1.1)
tzinfo
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
tzinfo-data (1.2023.4)
tzinfo-data (1.2024.2)
tzinfo (>= 1.0.0)
uber (0.1.0)
unicode-display_width (2.5.0)
unicode-display_width (2.6.0)
uniform_notifier (1.16.0)
useragent (0.16.10)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.1)
@@ -548,32 +549,35 @@ GEM
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webmock (3.19.1)
webmock (3.24.0)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.8.1)
webrick (1.8.2)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.12)
zeitwerk (2.7.0)
PLATFORMS
arm64-darwin-22
aarch64-linux
ruby
x86_64-linux
DEPENDENCIES
annotate
arabic-letter-connector
aws-sdk-s3
aws-sdk-secretsmanager
azure-storage-blob
better_html
bootsnap
bullet
cancancan
capybara
csv
cuprite
debug
devise
@@ -594,7 +598,6 @@ DEPENDENCIES
mysql2
oj
pagy
pdf-reader
pg
premailer-rails
pretender
@@ -625,7 +628,7 @@ DEPENDENCIES
webmock
RUBY VERSION
ruby 3.2.2p53
ruby 3.3.4p94
BUNDLED WITH
2.5.3
+19 -14
View File
@@ -1,6 +1,6 @@
<h1 align="center" style="border-bottom: none">
<div>
<a href="https://www.docuseal.co">
<a href="https://www.docuseal.com">
<img alt="DocuSeal" src="https://github.com/docusealco/docuseal/assets/5418788/c12cd051-81cd-4402-bc3a-92f2cfdc1b06" width="80" />
<br>
</a>
@@ -25,12 +25,12 @@
DocuSeal is an open source platform that provides secure and efficient digital document signing and processing. Create PDF forms to have them filled and signed online on any device with an easy-to-use, mobile-optimized web tool.
</p>
<h2 align="center">
<a href="https://demo.docuseal.co">✨ Live Demo</a>
<a href="https://demo.docuseal.tech">✨ Live Demo</a>
<span>|</span>
<a href="https://docuseal.co/sign_up">☁️ Try in Cloud</a>
<a href="https://docuseal.com/sign_up">☁️ Try in Cloud</a>
</h2>
[![Demo](https://github.com/docusealco/docuseal/assets/5418788/d8703ea3-361a-423f-8bfe-eff1bd9dbe14)](https://demo.docuseal.co)
[![Demo](https://github.com/docusealco/docuseal/assets/5418788/d8703ea3-361a-423f-8bfe-eff1bd9dbe14)](https://demo.docuseal.tech)
## Features
- PDF form fields builder (WYSIWYG)
@@ -42,7 +42,7 @@ DocuSeal is an open source platform that provides secure and efficient digital d
- PDF signature verification
- Users management
- Mobile-optimized
- Signing available in 12 Languages
- 6 UI languages with signing available in 13 languages
- API and Webhooks for integrations
- Easy to deploy in minutes
@@ -51,12 +51,14 @@ DocuSeal is an open source platform that provides secure and efficient digital d
- User roles
- Automated reminders
- Invitation and identify verification via SMS
- Conditional fields and formulas
- Bulk send with CSV, XLSX spreadsheet import
- SSO / SAML
- Template creation with HTML API ([Guide](https://www.docuseal.co/guides/create-pdf-document-fillable-form-with-html-api))
- Template creation with PDF or DOCX and field tags API ([Guide](https://www.docuseal.co/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form))
- Embedded signing form ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue) or [JavaScript](https://www.docuseal.co/docs/embedded))
- Embedded document form builder ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue) or [JavaScript](https://www.docuseal.co/docs/embedded))
- [Learn more](https://www.docuseal.co/pricing)
- Template creation with HTML API ([Guide](https://www.docuseal.com/guides/create-pdf-document-fillable-form-with-html-api))
- Template creation with PDF or DOCX and field tags API ([Guide](https://www.docuseal.com/guides/use-embedded-text-field-tags-in-the-pdf-to-create-a-fillable-form))
- Embedded signing form ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.com/docs/embedded))
- Embedded document form builder ([React](https://github.com/docusealco/docuseal-react), [Vue](https://github.com/docusealco/docuseal-vue), [Angular](https://github.com/docusealco/docuseal-angular) or [JavaScript](https://www.docuseal.com/docs/embedded))
- [Learn more](https://www.docuseal.com/pricing)
## Deploy
@@ -65,9 +67,6 @@ DocuSeal is an open source platform that provides secure and efficient digital d
| [<img alt="Deploy on Heroku" src="https://www.herokucdn.com/deploy/button.svg" height="40">](https://heroku.com/deploy?template=https://github.com/docusealco/docuseal-heroku) | [<img alt="Deploy on Railway" src="https://railway.app/button.svg" height="40">](https://railway.app/template/IGoDnc?referralCode=ruU7JR)|
|**DigitalOcean**|**Render**|
| [<img alt="Deploy on DigitalOcean" src="https://www.deploytodo.com/do-btn-blue.svg" height="40">](https://cloud.digitalocean.com/apps/new?repo=https://github.com/docusealco/docuseal-digitalocean/tree/master&refcode=421d50f53990) | [<img alt="Deploy to Render" src="https://render.com/images/deploy-to-render-button.svg" height="40">](https://render.com/deploy?repo=https://github.com/docusealco/docuseal-render)
|**Koyeb**|**Elestio**|
| [<img alt="Deploy on Koyeb" src="https://www.koyeb.com/static/images/deploy/button.svg" height="40">](https://app.koyeb.com/deploy?name=docuseal&type=docker&image=docker.io/docuseal/docuseal&env[PORT]=8000&env[DATABASE_URL]=CHANGE_ME&env[SECRET_KEY_BASE]=CHANGE_ME&ports=8000;http;/) | [<img alt="Deploy on Elestio" src="https://pub-da36157c854648669813f3f76c526c2b.r2.dev/deploy-on-elestio-black.png">](https://dash.elest.io/deploy?soft=DocuSeal&id=339) |
#### Docker
@@ -94,9 +93,15 @@ sudo HOST=your-domain-name.com docker compose up
At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, Real Estate, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws.
[Book a Meeting](https://www.docuseal.co/contact)
[Book a Meeting](https://www.docuseal.com/contact)
## License
Distributed under the AGPLv3 License. See [LICENSE](https://github.com/docusealco/docuseal/blob/master/LICENSE) for more information.
Unless otherwise noted, all files © 2023 DocuSeal LLC.
## Tools
- [Signature Maker](https://www.docuseal.com/online-signature)
- [Sign Document Online](https://www.docuseal.com/sign-documents-online)
- [Fill PDF Online](https://www.docuseal.com/fill-pdf)
+10 -1
View File
@@ -1,5 +1,14 @@
# Reporting a Vulnerability
If you come across any security concern or vulnarability, please report the information via email to security@docuseal.co instead of opening a GitHub issue. We will promptly respond and will collaborate with you to validate the issue, and resolve it ASAP.
If you come across any security concern or vulnarability, please report the information via email to security@docuseal.com instead of opening a GitHub issue. We will promptly respond and will collaborate with you to validate the issue, and resolve it ASAP.
**We have a bug bounty program to reward security researchers.**
Out of scope vulnerabilities:
- CSRF
- DNSSEC, CAA, CSP headers
- DNS or email security related
- Rate Limiting
Note: We reserve the right to classify any reported vulnerability as out of scope for the bug bounty program.
+10 -1
View File
@@ -8,8 +8,17 @@ class AccountConfigsController < ApplicationController
AccountConfig::ALLOW_TYPED_SIGNATURE,
AccountConfig::FORCE_MFA,
AccountConfig::ALLOW_TO_RESUBMIT,
AccountConfig::ALLOW_TO_DECLINE_KEY,
AccountConfig::FORM_PREFILL_SIGNATURE_KEY,
AccountConfig::ESIGNING_PREFERENCE_KEY,
AccountConfig::FORM_WITH_CONFETTI_KEY
AccountConfig::FORM_WITH_CONFETTI_KEY,
AccountConfig::DOWNLOAD_LINKS_AUTH_KEY,
AccountConfig::FORCE_SSO_AUTH_KEY,
AccountConfig::FLATTEN_RESULT_PDF_KEY,
AccountConfig::WITH_SIGNATURE_ID,
AccountConfig::COMBINE_PDF_RESULT_KEY,
AccountConfig::REQUIRE_SIGNING_REASON_KEY,
AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY
].freeze
InvalidKey = Class.new(StandardError)
+23 -17
View File
@@ -4,10 +4,11 @@ class AccountsController < ApplicationController
LOCALE_OPTIONS = {
'en-US' => 'English (United States)',
'en-GB' => 'English (United Kingdom)',
'fr-FR' => 'French (France)',
'es-ES' => 'Spanish (Spain)',
'pt-PT' => 'Portuguese (Portugal)',
'de-DE' => 'German (Germany)'
'fr-FR' => 'Français',
'es-ES' => 'Español',
'pt-PT' => 'Português',
'de-DE' => 'Deutsch',
'it-IT' => 'Italiano'
}.freeze
before_action :load_account
@@ -18,21 +19,25 @@ class AccountsController < ApplicationController
def update
current_account.update!(account_params)
@encrypted_config = EncryptedConfig.find_or_initialize_by(account: current_account,
key: EncryptedConfig::APP_URL_KEY)
@encrypted_config.assign_attributes(app_url_params)
unless Docuseal.multitenant?
@encrypted_config = EncryptedConfig.find_or_initialize_by(account: current_account,
key: EncryptedConfig::APP_URL_KEY)
@encrypted_config.assign_attributes(app_url_params)
unless URI.parse(@encrypted_config.value.to_s).class.in?([URI::HTTP, URI::HTTPS])
@encrypted_config.errors.add(:value, 'should be a valid URL')
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'))
return render :show, status: :unprocessable_entity
return render :show, status: :unprocessable_entity
end
@encrypted_config.save!
Docuseal.refresh_default_url_options!
end
@encrypted_config.save!
Docuseal.refresh_default_url_options!
redirect_to settings_account_path, notice: 'Account information has been updated'
with_locale do
redirect_to settings_account_path, notice: I18n.t('account_information_has_been_updated')
end
rescue ActiveRecord::RecordInvalid
render :show, status: :unprocessable_entity
end
@@ -42,11 +47,12 @@ class AccountsController < ApplicationController
true_user.update!(locked_at: Time.current)
# rubocop:disable Layout/LineLength
render turbo_stream: turbo_stream.replace(
:account_delete_button,
html: helpers.tag.p('Your account removal request will be processed within 2 weeks. ' \
'Please contact us if you want to keep your account.')
html: helpers.tag.p(I18n.t('your_account_removal_request_will_be_processed_within_2_weeks_please_contact_us_if_you_want_to_keep_your_account'))
)
# rubocop:enable Layout/LineLength
end
private
@@ -8,6 +8,7 @@ module Api
skip_authorization_check
before_action :set_cors_headers
before_action :set_noindex_headers
def show
blob_uuid, purp, exp = ApplicationRecord.signed_id_verifier.verified(params[:signed_uuid])
@@ -20,6 +21,8 @@ module Api
blob = ActiveStorage::Blob.find_by!(uuid: blob_uuid)
authorization_check!(blob) if exp.blank?
if request.headers['Range'].present?
send_blob_byte_range_data blob, request.headers['Range']
else
@@ -31,5 +34,22 @@ module Api
end
end
end
private
def authorization_check!(blob)
attachment = blob.attachments.take
is_authorized = attachment.name.in?(%w[logo preview_images]) ||
(current_user && attachment.record.account.id == current_user.account_id) ||
!attachment.record.account.account_configs
.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
return if is_authorized
Rollbar.error('Blob aunauthorized') if defined?(Rollbar)
raise CanCan::AccessDenied
end
end
end
@@ -7,6 +7,9 @@ module Api
skip_before_action :authenticate_user!
skip_authorization_check
before_action :set_cors_headers
before_action :set_noindex_headers
# rubocop:disable Metrics
def show
Rollbar.info('Blob legacy') if defined?(Rollbar)
+51 -15
View File
@@ -25,11 +25,9 @@ module Api
render json: { error: 'Too many requests' }, status: :too_many_requests
end
if Rails.env.production?
unless Rails.env.development?
rescue_from CanCan::AccessDenied do |e|
Rollbar.warning(e) if defined?(Rollbar)
render json: { error: e.message }, status: :forbidden
render json: { error: access_denied_error_message(e) }, status: :forbidden
end
rescue_from JSON::ParserError do |e|
@@ -41,31 +39,69 @@ module Api
private
def paginate(relation)
result = relation.order(id: :desc)
def access_denied_error_message(error)
return 'Not authorized' if request.headers['X-Auth-Token'].blank?
return 'Not authorized' unless error.subject.is_a?(ActiveRecord::Base)
return 'Not authorized' unless error.subject.respond_to?(:account_id)
linked_account_record_exists =
if current_user.account.testing?
current_user.account.linked_account_accounts.where(account_type: 'testing')
.exists?(account_id: error.subject.account_id)
else
current_user.account.testing_accounts.exists?(id: error.subject.account_id)
end
return 'Not authorized' unless linked_account_record_exists
object_name = error.subject.model_name.human
id = error.subject.id
if current_user.account.testing?
"#{object_name} #{id} not found using testing API key; Use production API key to " \
"access production #{object_name.downcase.pluralize}."
else
"#{object_name} #{id} not found using production API key; Use testing API key to " \
"access testing #{object_name.downcase.pluralize}."
end
end
def paginate(relation, field: :id)
result = relation.order(field => :desc)
.limit([params.fetch(:limit, DEFAULT_LIMIT).to_i, MAX_LIMIT].min)
result = result.where(id: ...params[:after].to_i) if params[:after].present?
result = result.where(id: (params[:before].to_i + 1)...) if params[:before].present?
if field == :id
result = result.where(id: ...params[:after].to_i) if params[:after].present?
result = result.where(id: (params[:before].to_i + 1)...) if params[:before].present?
else
result = result.where(field => ...params[:after]) if params[:after].present?
result = result.where(field => (params[:before] + 1)...) if params[:before].present?
end
result
end
def authenticate_user!
@current_user ||=
if request.headers['X-Auth-Token'].present?
sha256 = Digest::SHA256.hexdigest(request.headers['X-Auth-Token'])
User.joins(:access_token).active.find_by(access_token: { sha256: })
end
render json: { error: 'Not authenticated' }, status: :unauthorized unless current_user
end
def current_user
super || @current_user ||=
if request.headers['X-Auth-Token'].present?
sha256 = Digest::SHA256.hexdigest(request.headers['X-Auth-Token'])
User.joins(:access_token).active.find_by(access_token: { sha256: })
end
end
def current_account
current_user&.account
end
def set_noindex_headers
headers['X-Robots-Tag'] = 'noindex'
end
def set_cors_headers
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, PATCH, DELETE, OPTIONS'
+25 -4
View File
@@ -1,16 +1,37 @@
# frozen_string_literal: true
module Api
class AttachmentsController < ApiBaseController
skip_before_action :authenticate_user!
skip_authorization_check
class AttachmentsController < ActionController::API
include ActionController::Cookies
include ActiveStorage::SetCurrent
COOKIE_STORE_LIMIT = 10
def create
submitter = Submitter.find_by!(slug: params[:submitter_slug])
attachment = Submitters.create_attachment!(submitter, params)
render json: attachment.as_json(only: %i[uuid], methods: %i[url filename content_type])
if params[:remember_signature] == 'true' && submitter.email.present?
cookies.encrypted[:signature_uuids] = build_new_cookie_signatures_json(submitter, attachment)
end
render json: attachment.as_json(only: %i[uuid created_at], methods: %i[url filename content_type])
end
def build_new_cookie_signatures_json(submitter, attachment)
values =
begin
JSON.parse(cookies.encrypted[:signature_uuids].presence || '{}')
rescue JSON::ParserError
{}
end
values[submitter.email] = attachment.uuid
values = values.to_a.last(COOKIE_STORE_LIMIT).to_h if values.size > COOKIE_STORE_LIMIT
values.to_json
end
end
end
@@ -0,0 +1,36 @@
# frozen_string_literal: true
module Api
class FormEventsController < ApiBaseController
load_and_authorize_resource :submitter, parent: false
def index
submitters = @submitters.where.not(completed_at: nil)
params[:after] = Time.zone.at(params[:after].to_i) if params[:after].present?
params[:before] = Time.zone.at(params[:before].to_i) if params[:before].present?
submitters = paginate(
submitters.preload(template: :folder, submission: [:submitters, { audit_trail_attachment: :blob,
combined_document_attachment: :blob }],
documents_attachments: :blob, attachments_attachments: :blob),
field: :completed_at
)
render json: {
data: submitters.map do |s|
{
event_type: 'form.completed',
timestamp: s.completed_at,
data: Submitters::SerializeForWebhook.call(s)
}
end,
pagination: {
count: submitters.size,
next: submitters.last&.completed_at&.to_i,
prev: submitters.first&.completed_at&.to_i
}
}
end
end
end
@@ -0,0 +1,45 @@
# frozen_string_literal: true
module Api
class SubmissionDocumentsController < ApiBaseController
load_and_authorize_resource :submission
def index
documents =
if @submission.submitters.all?(&:completed_at?)
last_submitter = @submission.submitters.max_by(&:completed_at)
if last_submitter.documents_attachments.blank?
last_submitter.documents_attachments = Submissions::EnsureResultGenerated.call(submitter)
end
last_submitter.documents_attachments
else
values_hash = Submissions::GeneratePreviewAttachments.build_values_hash(@submission)
if @submission.preview_documents.present? &&
@submission.preview_documents.all? { |s| s.metadata['values_hash'] == values_hash }
@submission.preview_documents
else
ApplicationRecord.no_touching do
@submission.preview_documents.each(&:destroy)
end
Submissions::GeneratePreviewAttachments.call(@submission, values_hash:)
end
end
ActiveRecord::Associations::Preloader.new(
records: documents,
associations: [:blob]
).call
render json: {
id: @submission.id,
documents: documents.map do |attachment|
{ name: attachment.filename.base, url: ActiveStorage::Blob.proxy_url(attachment.blob) }
end
}
end
end
end
@@ -0,0 +1,52 @@
# frozen_string_literal: true
module Api
class SubmissionEventsController < ApiBaseController
load_and_authorize_resource :submission, parent: false
def index
submissions = build_completed_query(@submissions)
params[:after] = Time.zone.at(params[:after].to_i) if params[:after].present?
params[:before] = Time.zone.at(params[:before].to_i) if params[:before].present?
submissions = paginate(submissions.preload(
:created_by_user, :submission_events,
template: :folder,
submitters: { documents_attachments: :blob, attachments_attachments: :blob },
audit_trail_attachment: :blob
),
field: :completed_at)
render json: {
data: submissions.map do |s|
{
event_type: 'submission.completed',
timestamp: s.completed_at,
data: Submissions::SerializeForApi.call(s, s.submitters)
}
end,
pagination: {
count: submissions.size,
next: submissions.last&.completed_at&.to_i,
prev: submissions.first&.completed_at&.to_i
}
}
end
private
def build_completed_query(submissions)
submissions = submissions.where(
Submitter.where(completed_at: nil).where(
Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])
).select(1).arel.exists.not
)
submissions.joins(:submitters)
.group(:id)
.select(Submission.arel_table[Arel.star],
Submitter.arel_table[:completed_at].maximum.as('completed_at'))
end
end
end
+55 -55
View File
@@ -17,11 +17,16 @@ module Api
submissions = submissions.joins(template: :folder).where(folder: { name: params[:template_folder] })
end
submissions = paginate(submissions.preload(:created_by_user, :template, :submitters,
submissions = paginate(submissions.preload(:created_by_user, :submitters,
template: :folder,
combined_document_attachment: :blob,
audit_trail_attachment: :blob))
render json: {
data: submissions.as_json(serialize_params),
data: submissions.map do |s|
Submissions::SerializeForApi.call(s, s.submitters, params,
with_events: false, with_documents: false, with_values: false)
end,
pagination: {
count: submissions.size,
next: submissions.last&.id,
@@ -43,33 +48,11 @@ module Api
@submission.audit_trail_attachment = Submissions::GenerateAuditTrail.call(@submission)
end
serialized_submitters = submitters.map { |submitter| Submitters::SerializeForApi.call(submitter) }
json = @submission.as_json(
serialize_params.deep_merge(
include: { submission_events: { only: %i[id submitter_id event_type event_timestamp] } }
)
)
if submitters.all?(&:completed_at?)
last_submitter = submitters.max_by(&:completed_at)
json[:documents] = serialized_submitters.find { |e| e['id'] == last_submitter.id }['documents']
json[:status] = 'completed'
json[:completed_at] = last_submitter.completed_at
else
json[:documents] = []
json[:status] = 'pending'
json[:completed_at] = nil
end
json[:submitters] = serialized_submitters
render json:
render json: Submissions::SerializeForApi.call(@submission, submitters, params)
end
def create
Params::SubmissionCreateValidator.call(params, dry_run: !Docuseal.multitenant?)
Params::SubmissionCreateValidator.call(params)
return render json: { error: 'Template not found' }, status: :unprocessable_entity if @template.nil?
@@ -84,29 +67,60 @@ module Api
submissions = create_submissions(@template, params)
Submissions.send_signature_requests(submissions)
submissions.each do |submission|
if submission.submitters.all?(&:completed_at?) && submission.submitters.last
ProcessSubmitterCompletionJob.perform_later(submission.submitters.last)
WebhookUrls.for_account_id(@template.account_id, 'submission.created').each do |webhook_url|
submissions.each do |submission|
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submission.id,
'webhook_url_id' => webhook_url.id)
end
end
render json: submissions.flat_map(&:submitters)
rescue Submitters::NormalizeValues::BaseError => e
Submissions.send_signature_requests(submissions)
submissions.each do |submission|
submission.submitters.each do |submitter|
next unless submitter.completed_at?
ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id, 'send_invitation_email' => false)
end
end
render json: build_create_json(submissions)
rescue Submitters::NormalizeValues::BaseError, Submissions::CreateFromSubmitters::BaseError,
DownloadUtils::UnableToDownload => e
Rollbar.warning(e) if defined?(Rollbar)
render json: { error: e.message }, status: :unprocessable_entity
end
def destroy
@submission.update!(archived_at: Time.current)
if params[:permanently] == 'true'
@submission.destroy!
else
@submission.update!(archived_at: Time.current)
render json: @submission.as_json(only: %i[id], methods: %i[archived_at])
WebhookUrls.for_account_id(@submission.account_id, 'submission.archived').each do |webhook_url|
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id,
'webhook_url_id' => webhook_url.id)
end
end
render json: @submission.as_json(only: %i[id archived_at])
end
private
def build_create_json(submissions)
json = submissions.flat_map do |submission|
submission.submitters.map do |s|
Submitters::SerializeForApi.call(s, with_documents: false, with_urls: true, params:)
end
end
json = { submitters: json } if request.path.ends_with?('/init')
json
end
def create_submissions(template, params)
is_send_email = !params[:send_email].in?(['false', false])
@@ -126,7 +140,6 @@ module Api
template:,
user: current_user,
source: :api,
mark_as_sent: is_send_email,
submitters_order: params[:submitters_order] || params[:order] || 'preserved',
submissions_attrs:,
params:
@@ -144,31 +157,18 @@ module Api
end
end
def serialize_params
{
only: %i[id source submitters_order created_at updated_at archived_at],
methods: %i[audit_log_url],
include: {
submitters: { only: %i[id slug uuid name email phone
completed_at opened_at sent_at
created_at updated_at external_id metadata],
methods: %i[status application_key] },
template: { only: %i[id name created_at updated_at] },
created_by_user: { only: %i[id email first_name last_name] }
}
}
end
def submissions_params
permitted_attrs = [
:send_email, :send_sms, :bcc_completed, :completed_redirect_url,
:send_email, :send_sms, :bcc_completed, :completed_redirect_url, :reply_to, :go_to_last,
:expire_at,
{
message: %i[subject body],
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
:completed, :phone, :application_key, :external_id,
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
fields: [%i[name default_value title description
readonly validation_pattern invalid_message]] }]]
fields: [:name, :uuid, :default_value, :value, :title, :description,
:readonly, :validation_pattern, :invalid_message,
{ default_value: [], value: [], preferences: {} }] }]]
}
]
@@ -13,7 +13,10 @@ module Api
SubmissionEvents.create_with_tracking_data(submitter, 'view_form', request)
SendFormViewedWebhookRequestJob.perform_later(submitter)
WebhookUrls.for_account_id(submitter.account_id, 'form.viewed').each do |webhook_url|
SendFormViewedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
'webhook_url_id' => webhook_url.id)
end
render json: {}
end
@@ -1,35 +0,0 @@
# frozen_string_literal: true
module Api
class SubmittersAutocompleteController < ApiBaseController
load_and_authorize_resource :submitter, parent: false
SELECT_COLUMNS = %w[email phone name].freeze
LIMIT = 100
def index
submitters = search_submitters(@submitters)
values = submitters.limit(LIMIT).group(SELECT_COLUMNS.join(', ')).pluck(SELECT_COLUMNS.join(', '))
attrs = values.map { |row| SELECT_COLUMNS.zip(row).to_h }
attrs = attrs.uniq { |e| e[params[:field]] } if params[:field].present?
render json: attrs
end
private
def search_submitters(submitters)
if SELECT_COLUMNS.include?(params[:field])
column = Submitter.arel_table[params[:field].to_sym]
term = "#{params[:q].downcase}%"
submitters.where(column.matches(term, false, true))
else
Submitters.search(submitters, params[:q])
end
end
end
end
+77 -14
View File
@@ -10,6 +10,11 @@ module Api
submitters = submitters.where(external_id: params[:application_key]) if params[:application_key].present?
submitters = submitters.where(external_id: params[:external_id]) if params[:external_id].present?
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
if params[:template_id].present?
submitters = submitters.joins(:submission).where(submission: { template_id: params[:template_id] })
end
submitters = maybe_filder_by_completed_at(submitters, params)
submitters = paginate(
@@ -18,7 +23,9 @@ module Api
)
render json: {
data: submitters.map { |s| Submitters::SerializeForApi.call(s, with_template: true, with_events: true) },
data: submitters.map do |s|
Submitters::SerializeForApi.call(s, with_template: true, with_events: true, params:)
end,
pagination: {
count: submitters.size,
next: submitters.last&.id,
@@ -30,7 +37,7 @@ module Api
def show
Submissions::EnsureResultGenerated.call(@submitter) if @submitter.completed_at?
render json: Submitters::SerializeForApi.call(@submitter, with_template: true, with_events: true)
render json: Submitters::SerializeForApi.call(@submitter, with_template: true, with_events: true, params:)
end
def update
@@ -61,22 +68,31 @@ module Api
end
if @submitter.completed_at?
ProcessSubmitterCompletionJob.perform_later(@submitter)
ProcessSubmitterCompletionJob.perform_async('submitter_id' => @submitter.id)
elsif normalized_params[:send_email] || normalized_params[:send_sms]
Submitters.send_signature_requests([@submitter])
end
render json: Submitters::SerializeForApi.call(@submitter, with_template: false, with_events: false)
render json: Submitters::SerializeForApi.call(@submitter, with_template: false,
with_urls: true,
with_events: false,
params:)
rescue Submitters::NormalizeValues::BaseError, DownloadUtils::UnableToDownload => e
Rollbar.warning(e) if defined?(Rollbar)
render json: { error: e.message }, status: :unprocessable_entity
end
def submitter_params
submitter_params = params.key?(:submitter) ? params.require(:submitter) : params
submitter_params.permit(
:send_email, :send_sms, :uuid, :name, :email, :role,
:completed, :phone, :application_key, :external_id,
:send_email, :send_sms, :reply_to, :completed_redirect_url, :uuid, :name, :email, :role,
:completed, :phone, :application_key, :external_id, :go_to_last,
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
fields: [%i[name default_value readonly validation_pattern invalid_message]] }
fields: [[:name, :uuid, :default_value, :value,
:readonly, :validation_pattern, :invalid_message,
{ default_value: [], value: [], preferences: {} }]] }
)
end
@@ -95,20 +111,59 @@ module Api
end
def assign_submitter_attrs(submitter, attrs)
submitter.email = Submissions.normalize_email(attrs[:email]) if attrs.key?(:email)
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '') if attrs.key?(:phone)
submitter.values = submitter.values.merge(attrs[:values].to_unsafe_h) if attrs[:values].present?
submitter.completed_at = attrs[:completed] ? Time.current : submitter.completed_at
submitter.external_id = attrs[:application_key] if attrs.key?(:application_key)
submitter.external_id = attrs[:external_id] if attrs.key?(:external_id)
submitter.metadata = attrs[:metadata] if attrs.key?(:metadata)
values = attrs[:values]&.to_unsafe_h || {}
assign_submission_fields(submitter.submission)
phone_field_uuid = submitter.submission.template_fields.find do |f|
values[f['uuid']].present? && f['type'] == 'phone'
end&.dig('uuid')
submitter.email = Submissions.normalize_email(attrs[:email]) if attrs.key?(:email)
submitter.name = attrs[:name] if attrs.key?(:name)
if attrs.key?(:phone)
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '')
elsif values[phone_field_uuid].present?
submitter.phone = values[phone_field_uuid].to_s.gsub(/[^0-9+]/, '')
end
values = values.except(phone_field_uuid)
submitter.values = submitter.values.merge(values) if values.present?
submitter.metadata = attrs[:metadata] if attrs.key?(:metadata)
maybe_assign_completed_attributes(submitter, attrs)
assign_external_id(submitter, attrs)
assign_preferences(submitter, attrs)
submitter
end
def maybe_assign_completed_attributes(submitter, attrs)
submitter.completed_at = attrs[:completed] ? Time.current : submitter.completed_at
if attrs[:completed]
submitter.values = Submitters::SubmitValues.merge_default_values(submitter)
submitter.values = Submitters::SubmitValues.merge_formula_values(submitter)
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
submitter.values = submitter.values.transform_values do |v|
v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v
end
end
submitter
end
def assign_external_id(submitter, attrs)
submitter.external_id = attrs[:application_key] if attrs.key?(:application_key)
submitter.external_id = attrs[:external_id] if attrs.key?(:external_id)
submitter
end
def assign_submission_fields(submission)
submission.template_fields ||= submission.template.fields
submission.template_schema ||= submission.template.schema
@@ -124,6 +179,14 @@ module Api
end
submitter.preferences['send_sms'] = submitter_preferences['send_sms'] if submitter_preferences.key?('send_sms')
submitter.preferences['reply_to'] = submitter_preferences['reply_to'] if submitter_preferences.key?('reply_to')
if submitter_preferences.key?('go_to_last')
submitter.preferences['go_to_last'] = submitter_preferences['go_to_last']
end
if submitter_preferences.key?('completed_redirect_url')
submitter.preferences['completed_redirect_url'] = submitter_preferences['completed_redirect_url']
end
return unless submitter_preferences.key?('email_message_uuid')
@@ -1,16 +0,0 @@
# frozen_string_literal: true
module Api
class TemplateFoldersAutocompleteController < ApiBaseController
load_and_authorize_resource :template_folder, parent: false
LIMIT = 100
def index
template_folders = @template_folders.joins(:templates).where(templates: { archived_at: nil }).distinct
template_folders = TemplateFolders.search(template_folders, params[:q]).limit(LIMIT)
render json: template_folders.as_json(only: %i[name archived_at])
end
end
end
@@ -7,6 +7,11 @@ module Api
def create
authorize!(:manage, @template)
ActiveRecord::Associations::Preloader.new(
records: [@template],
associations: [schema_documents: :preview_images_attachments]
).call
cloned_template = Templates::Clone.call(
@template,
author: current_user,
@@ -18,9 +23,14 @@ module Api
cloned_template.source = :api
cloned_template.save!
Templates::CloneAttachments.call(template: cloned_template, original_template: @template)
schema_documents = Templates::CloneAttachments.call(template: cloned_template, original_template: @template)
render json: Templates::SerializeForApi.call(cloned_template)
WebhookUrls.for_account_id(cloned_template.account_id, 'template.created').each do |webhook_url|
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => cloned_template.id,
'webhook_url_id' => webhook_url.id)
end
render json: Templates::SerializeForApi.call(cloned_template, schema_documents)
end
end
end
+25 -10
View File
@@ -18,7 +18,7 @@ module Api
preview_image_attachments =
ActiveStorage::Attachment.joins(:blob)
.where(blob: { filename: '0.jpg' })
.where(blob: { filename: ['0.png', '0.jpg'] })
.where(record_id: schema_documents.map(&:id),
record_type: 'ActiveStorage::Attachment',
name: :preview_images)
@@ -49,6 +49,14 @@ module Api
@template.folder = TemplateFolders.find_or_create_by_name(current_user, folder_name)
end
Array.wrap(params[:roles].presence || params.dig(:template, :roles).presence).each_with_index do |role, index|
if (item = @template.submitters[index])
item['name'] = role
else
@template.submitters << { 'name' => role, 'uuid' => SecureRandom.uuid }
end
end
archived = params.key?(:archived) ? params[:archived] : params.dig(:template, :archived)
if archived.in?([true, false])
@@ -57,11 +65,16 @@ module Api
@template.update!(template_params)
WebhookUrls.for_account_id(@template.account_id, 'template.updated').each do |webhook_url|
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => @template.id,
'webhook_url_id' => webhook_url.id)
end
render json: @template.as_json(only: %i[id updated_at])
end
def destroy
if params[:permanently] == 'true' && !Docuseal.multitenant?
if params[:permanently] == 'true'
@template.destroy!
else
@template.update!(archived_at: Time.current)
@@ -74,7 +87,7 @@ module Api
def filter_templates(templates, params)
templates = Templates.search(templates, params[:q])
templates = params[:archived] ? templates.archived : templates.active
templates = params[:archived].in?(['true', true]) ? templates.archived : templates.active
templates = templates.where(external_id: params[:application_key]) if params[:application_key].present?
templates = templates.where(external_id: params[:external_id]) if params[:external_id].present?
templates = templates.joins(:folder).where(folder: { name: params[:folder] }) if params[:folder].present?
@@ -83,24 +96,26 @@ module Api
end
def template_params
permit_params = [
permitted_params = [
:name,
{ schema: [%i[attachment_uuid name]],
submitters: [%i[name uuid]],
:external_id,
{
submitters: [%i[name uuid is_requester invite_by_uuid linked_to_uuid email]],
fields: [[:uuid, :submitter_uuid, :name, :type,
:required, :readonly, :default_value,
:title, :description,
{ preferences: {},
conditions: [%i[field_uuid value action]],
conditions: [%i[field_uuid value action operation]],
options: [%i[value uuid]],
validation: %i[message pattern],
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]] }
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]]
}
]
if params.key?(:template)
params.require(:template).permit(*permit_params)
params.require(:template).permit(permitted_params)
else
params.permit(*permit_params)
params.permit(permitted_params)
end
end
end
@@ -1,29 +0,0 @@
# frozen_string_literal: true
module Api
class TemplatesDocumentsController < ApiBaseController
load_and_authorize_resource :template
def create
return head :unprocessable_entity if params[:blobs].blank? && params[:files].blank?
documents = Templates::CreateAttachments.call(@template, params)
schema = documents.map do |doc|
{ attachment_uuid: doc.uuid, name: doc.filename.base }
end
render json: {
schema:,
documents: documents.as_json(
methods: %i[metadata signed_uuid],
include: {
preview_images: { methods: %i[url metadata filename] }
}
)
}
rescue Templates::CreateAttachments::PdfEncrypted
render json: { error: 'PDF encrypted' }, status: :unprocessable_entity
end
end
end
+39
View File
@@ -0,0 +1,39 @@
# frozen_string_literal: true
module Api
class ToolsController < ApiBaseController
skip_authorization_check
def merge
files = params[:files] || []
return render json: { error: 'Files are required' }, status: :unprocessable_entity if files.blank?
return render json: { error: 'At least 2 files are required' }, status: :unprocessable_entity if files.size < 2
render json: {
data: Base64.encode64(PdfUtils.merge(files.map { |base64| StringIO.new(Base64.decode64(base64)) }).string)
}
end
def verify
file = Base64.decode64(params[:file])
pdf = HexaPDF::Document.new(io: StringIO.new(file))
trusted_certs = Accounts.load_trusted_certs(current_account)
is_checksum_found = CompletedDocument.exists?(sha256: Base64.urlsafe_encode64(Digest::SHA256.digest(file)))
render json: {
checksum_status: is_checksum_found ? 'verified' : 'not_found',
signatures: pdf.signatures.map do |sig|
{
verification_result: sig.verify(trusted_certs:).messages,
signer_name: sig.signer_name,
signing_reason: sig.signing_reason,
signing_time: sig.signing_time,
signature_type: sig.signature_type
}
end
}
end
end
end
+11
View File
@@ -0,0 +1,11 @@
# frozen_string_literal: true
module Api
class UsersController < ApiBaseController
authorize_resource :current_user
def show
render json: current_user.as_json(only: %i[id first_name last_name email])
end
end
end
+1 -1
View File
@@ -12,6 +12,6 @@ class ApiSettingsController < ApplicationController
current_user.access_token.save!
redirect_back(fallback_location: settings_api_index_path, notice: 'API token as been updated.')
redirect_back(fallback_location: settings_api_index_path, notice: I18n.t('api_token_has_been_updated'))
end
end
+31 -2
View File
@@ -8,12 +8,14 @@ class ApplicationController < ActionController::Base
check_authorization unless: :devise_controller?
around_action :with_locale
before_action :sign_in_for_demo, if: -> { Docuseal.demo? }
before_action :maybe_redirect_to_setup, unless: :signed_in?
before_action :authenticate_user!, unless: :devise_controller?
helper_method :button_title,
:current_account,
:form_link_host,
:svg_icon
impersonates :user, with: ->(uuid) { User.find_by(uuid:) }
@@ -37,6 +39,10 @@ class ApplicationController < ActionController::Base
end
def default_url_options
if request.domain == 'docuseal.com'
return { host: 'docuseal.com', protocol: ENV['FORCE_SSL'].present? ? 'https' : 'http' }
end
Docuseal.default_url_options
end
@@ -51,8 +57,20 @@ class ApplicationController < ActionController::Base
private
def with_locale(&)
return yield unless current_account
locale = params[:lang].presence if Rails.env.development?
locale ||= current_account.locale
I18n.with_locale(locale, &)
end
def with_browser_locale(&)
locale = request.env['HTTP_ACCEPT_LANGUAGE'].to_s[BROWSER_LOCALE_REGEXP].to_s
return yield if I18n.locale != :'en-US' && I18n.locale != :en
locale = params[:lang].presence
locale ||= request.env['HTTP_ACCEPT_LANGUAGE'].to_s[BROWSER_LOCALE_REGEXP].to_s
locale =
if locale.starts_with?('en-') && locale != 'en-US'
@@ -78,7 +96,8 @@ class ApplicationController < ActionController::Base
redirect_to setup_index_path unless User.exists?
end
def button_title(title: 'Submit', disabled_with: 'Submitting', title_class: '', icon: nil, icon_disabled: nil)
def button_title(title: I18n.t('submit'), disabled_with: I18n.t('submitting'), title_class: '', icon: nil,
icon_disabled: nil)
render_to_string(partial: 'shared/button_title',
locals: { title:, disabled_with:, title_class:, icon:, icon_disabled: })
end
@@ -86,4 +105,14 @@ class ApplicationController < ActionController::Base
def svg_icon(icon_name, class: '')
render_to_string(partial: "icons/#{icon_name}", locals: { class: })
end
def form_link_host
Docuseal.default_url_options[:host]
end
def maybe_redirect_com
return if request.domain != 'docuseal.co'
redirect_to request.url.gsub('.co/', '.com/'), allow_other_host: true, status: :moved_permanently
end
end
@@ -9,6 +9,8 @@ class ConsoleRedirectController < ApplicationController
params[:redir] = Docuseal.multitenant? ? "#{Docuseal::CONSOLE_URL}/plans" : "#{Docuseal::CONSOLE_URL}/on_premise"
end
params[:redir] = "#{Docuseal::CONSOLE_URL}/manage" if request.path == '/manage'
return redirect_to(new_user_session_path({ redir: params[:redir] }.compact)) if true_user.blank?
auth = JsonWebToken.encode(uuid: true_user.uuid,
+5 -53
View File
@@ -7,66 +7,18 @@ class DashboardController < ApplicationController
before_action :maybe_render_landing
before_action :maybe_redirect_mfa_setup
load_and_authorize_resource :template_folder, parent: false
load_and_authorize_resource :template, parent: false
SHOW_TEMPLATES_FOLDERS_THRESHOLD = 9
TEMPLATES_PER_PAGE = 12
FOLDERS_PER_PAGE = 18
skip_authorization_check
def index
@template_folders = filter_template_folders(@template_folders)
@pagy, @template_folders = pagy(
@template_folders,
items: FOLDERS_PER_PAGE,
page: @template_folders.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD ? params[:page] : 1
)
if @pagy.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD
@templates = @templates.none
if cookies.permanent[:dashboard_view] == 'submissions'
SubmissionsDashboardController.dispatch(:index, request, response)
else
@template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME }
@templates = filter_templates(@templates)
items =
if @template_folders.size < 4
TEMPLATES_PER_PAGE
else
(@template_folders.size < 7 ? 9 : 6)
end
@pagy, @templates = pagy(@templates, items:)
TemplatesDashboardController.dispatch(:index, request, response)
end
end
private
def filter_template_folders(template_folders)
rel = template_folders.joins(:active_templates)
.order(id: :desc)
.distinct
TemplateFolders.search(rel, params[:q])
end
def filter_templates(templates)
rel = templates.active.preload(:author).order(id: :desc)
if params[:q].blank?
if Docuseal.multitenant? && !current_account.testing?
rel = rel.where(folder_id: current_account.default_template_folder.id)
else
shared_template_ids =
TemplateSharing.where(account_id: [current_account.id, TemplateSharing::ALL_ID]).select(:template_id)
rel = rel.where(folder_id: current_account.default_template_folder.id).or(rel.where(id: shared_template_ids))
end
end
Templates.search(rel, params[:q])
end
def maybe_redirect_product_url
return if !Docuseal.multitenant? || signed_in?
@@ -81,7 +33,7 @@ class DashboardController < ApplicationController
account_id: current_user.account_id,
key: AccountConfig::FORCE_MFA)
redirect_to mfa_setup_path, notice: 'Setup 2FA to continue'
redirect_to mfa_setup_path, notice: I18n.t('setup_2fa_to_continue')
end
def maybe_render_landing
@@ -9,9 +9,9 @@ class EmailSmtpSettingsController < ApplicationController
def create
if @encrypted_config.update(email_configs)
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email']).deliver_now!
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email'] || current_user.email).deliver_now!
redirect_to settings_email_index_path, notice: 'Changes have been saved'
redirect_to settings_email_index_path, notice: I18n.t('changes_have_been_saved')
else
render :index, status: :unprocessable_entity
end
@@ -0,0 +1,39 @@
# frozen_string_literal: true
class EmbedScriptsController < ActionController::Metal
DUMMY_SCRIPT = <<~JAVASCRIPT.freeze
const DummyBuilder = class extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<div style="text-align: center; padding: 20px; font-family: Arial, sans-serif;">
<h2>Upgrade to Pro</h2>
<p>Unlock embedded components by upgrading to Pro</p>
<div style="margin-top: 40px;">
<a href="#{Docuseal::CONSOLE_URL}/on_premise" target="_blank" style="padding: 15px 25px; background-color: #222; color: white; text-decoration: none; border-radius: 5px; font-size: 16px; cursor: pointer;">
Learn More
</a>
</div>
</div>
`;
}
};
const DummyForm = class extends DummyBuilder {};
if (!window.customElements.get('docuseal-builder')) {
window.customElements.define('docuseal-builder', DummyBuilder);
}
if (!window.customElements.get('docuseal-form')) {
window.customElements.define('docuseal-form', DummyForm);
}
JAVASCRIPT
def show
headers['Content-Type'] = 'application/javascript'
self.response_body = DUMMY_SCRIPT
self.status = 200
end
end
+11 -2
View File
@@ -2,7 +2,7 @@
class ErrorsController < ActionController::Base
ENTERPRISE_FEATURE_MESSAGE =
'This feature is available in Pro Edition: https://www.docuseal.co/pricing'
'This feature is available in Pro Edition: https://www.docuseal.com/pricing'
ENTERPRISE_PATHS = [
'/templates/html',
@@ -15,6 +15,11 @@ class ErrorsController < ActionController::Base
'/api/templates/docx'
].freeze
SAFE_ERROR_MESSAGE_CLASSES = [
ActionDispatch::Http::Parameters::ParseError,
JSON::ParserError
].freeze
def show
if request.original_fullpath.in?(ENTERPRISE_PATHS) && error_status_code == 404
return render json: { status: 404, message: ENTERPRISE_FEATURE_MESSAGE }, status: :not_found
@@ -24,7 +29,11 @@ class ErrorsController < ActionController::Base
f.json do
set_cors_headers
render json: { status: error_status_code }, status: error_status_code
exception = request.env['action_dispatch.exception']
error = exception.message if exception.class.in?(SAFE_ERROR_MESSAGE_CLASSES)
render json: { status: error_status_code, error: }.compact, status: error_status_code
end
f.html { render error_status_code.to_s, status: error_status_code }
+22 -12
View File
@@ -11,6 +11,8 @@ class EsignSettingsController < ApplicationController
end
end
prepend_before_action :maybe_redirect_com, only: %i[show]
before_action :load_encrypted_config
authorize_resource :encrypted_config, parent: false, only: %i[new create]
authorize_resource :encrypted_config, only: %i[update destroy show]
@@ -21,9 +23,9 @@ class EsignSettingsController < ApplicationController
default_pkcs = GenerateCertificate.load_pkcs(cert_data) if cert_data['cert'].present?
custom_pkcs_list = (cert_data['custom'] || []).map do |e|
{ 'pkcs' => OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password'].to_s),
'name' => e['name'],
'status' => e['status'] }
pkcs = e['data'].present? ? OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password'].to_s) : nil
{ 'pkcs' => pkcs, 'name' => e['name'], 'status' => e['status'] }
end
@pkcs_list = [
@@ -48,7 +50,7 @@ class EsignSettingsController < ApplicationController
if (@encrypted_config.value && @encrypted_config.value['custom']&.any? { |e| e['name'] == @cert_record.name }) ||
@cert_record.name == DEFAULT_CERT_NAME
@cert_record.errors.add(:name, 'already exists')
@cert_record.errors.add(:name, I18n.t('already_exists'))
return render turbo_stream: turbo_stream.replace(:modal, template: 'esign_settings/new'),
status: :unprocessable_entity
@@ -56,22 +58,30 @@ class EsignSettingsController < ApplicationController
save_new_cert!(@encrypted_config, @cert_record)
redirect_to settings_esign_path, notice: 'Certificate has been successfully added!'
rescue OpenSSL::PKCS12::PKCS12Error
@cert_record.errors.add(:password, "is invalid. Make sure you're uploading a valid .p12 file")
redirect_to settings_esign_path, notice: I18n.t('certificate_has_been_successfully_added')
rescue OpenSSL::PKCS12::PKCS12Error => e
Rollbar.error(e) if defined?(Rollbar)
@cert_record.errors.add(:password, e.message)
render turbo_stream: turbo_stream.replace(:modal, template: 'esign_settings/new'), status: :unprocessable_entity
end
def update
@encrypted_config.value['custom'].each { |e| e['status'] = 'validate' }
@encrypted_config.value['custom'].to_a.each { |e| e['status'] = 'validate' }
custom_cert_data = @encrypted_config.value['custom'].find { |e| e['name'] == params[:name] }
custom_cert_data['status'] = 'default' if custom_cert_data
custom_cert_data = @encrypted_config.value['custom'].to_a.find { |e| e['name'] == params[:name] }
if custom_cert_data
custom_cert_data['status'] = 'default'
elsif params[:name] == Docuseal::AATL_CERT_NAME
@encrypted_config.value['custom'] ||= []
@encrypted_config.value['custom'] << { 'name' => params[:name], 'status' => 'default' }
end
@encrypted_config.save!
redirect_to settings_esign_path, notice: 'Default certificate has been selected'
redirect_to settings_esign_path, notice: I18n.t('default_certificate_has_been_selected')
end
def destroy
@@ -79,7 +89,7 @@ class EsignSettingsController < ApplicationController
@encrypted_config.save!
redirect_to settings_esign_path, notice: 'Certificate has been removed'
redirect_to settings_esign_path, notice: I18n.t('certificate_has_been_removed')
end
private
+5 -5
View File
@@ -18,11 +18,11 @@ class MfaSetupController < ApplicationController
current_user.otp_required_for_login = true
current_user.save!
redirect_to settings_profile_index_path, notice: '2FA has been configured'
redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_configured')
else
@provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Docuseal.product_name)
@error_message = '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
end
@@ -32,9 +32,9 @@ class MfaSetupController < ApplicationController
if current_user.validate_and_consume_otp!(params[:otp_attempt])
current_user.update!(otp_required_for_login: false, otp_secret: nil)
redirect_to settings_profile_index_path, notice: '2FA has been removed'
redirect_to settings_profile_index_path, notice: I18n.t('2fa_has_been_removed')
else
@error_message = 'Code is invalid'
@error_message = I18n.t('code_is_invalid')
render turbo_stream: turbo_stream.replace(:modal, template: 'mfa_setup/edit'), status: :unprocessable_entity
end
@@ -43,7 +43,7 @@ class MfaSetupController < ApplicationController
private
def set_provision_url
return redirect_to root_path, alert: '2FA has been set up already' if current_user.otp_required_for_login
return redirect_to root_path, alert: I18n.t('2fa_has_been_set_up_already') if current_user.otp_required_for_login
current_user.otp_secret ||= User.generate_otp_secret
@@ -13,9 +13,9 @@ class NotificationsSettingsController < ApplicationController
def create
if @account_config.value.present? ? @account_config.save : @account_config.delete
redirect_back fallback_location: settings_notifications_path, notice: 'Changes have been saved'
redirect_back fallback_location: settings_notifications_path, notice: I18n.t('changes_have_been_saved')
else
redirect_back fallback_location: settings_notifications_path, alert: 'Unable to save'
redirect_back fallback_location: settings_notifications_path, alert: I18n.t('unable_to_save')
end
end
@@ -35,12 +35,12 @@ class NotificationsSettingsController < ApplicationController
def load_reminder_config
@reminder_config =
AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::SUBMITTER_REMAILERS)
AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::SUBMITTER_REMINDERS)
end
def email_config_params
params.require(:account_config).permit!.tap do |attrs|
attrs[:key] = nil unless attrs[:key].in?([AccountConfig::BCC_EMAILS, AccountConfig::SUBMITTER_REMAILERS])
attrs[:key] = nil unless attrs[:key].in?([AccountConfig::BCC_EMAILS, AccountConfig::SUBMITTER_REMINDERS])
end
end
end
+6
View File
@@ -5,6 +5,12 @@ class PasswordsController < Devise::PasswordsController
attribute :user
end
def create
super do |resource|
resource.errors.clear unless Docuseal.multitenant?
end
end
def update
super do |resource|
Current.user = resource
@@ -5,7 +5,9 @@ class PersonalizationSettingsController < ApplicationController
AccountConfig::FORM_COMPLETED_BUTTON_KEY,
AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY,
AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY,
AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY
AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY,
AccountConfig::FORM_COMPLETED_MESSAGE_KEY,
*(Docuseal.multitenant? ? [] : [AccountConfig::POLICY_LINKS_KEY])
].freeze
InvalidKey = Class.new(StandardError)
@@ -17,13 +19,19 @@ class PersonalizationSettingsController < ApplicationController
end
def create
if @account_config.value.is_a?(Hash)
@account_config.value = @account_config.value.reject do |_, v|
v.blank? && v != false
end
end
if @account_config.value != false && @account_config.value.blank?
@account_config.destroy!
else
@account_config.save!
end
redirect_back(fallback_location: settings_personalization_path, notice: 'Settings have been saved.')
redirect_back(fallback_location: settings_personalization_path, notice: I18n.t('settings_have_been_saved'))
end
private
@@ -12,7 +12,8 @@ class PreviewDocumentPageController < ActionController::API
return head :not_found unless attachment
preview_image = attachment.preview_images.joins(:blob).find_by(blob: { filename: "#{params[:id]}#{FORMAT}" })
preview_image = attachment.preview_images.joins(:blob)
.find_by(blob: { filename: ["#{params[:id]}.png", "#{params[:id]}.jpg"] })
return redirect_to preview_image.url, allow_other_host: true if preview_image
+2 -2
View File
@@ -9,7 +9,7 @@ class ProfileController < ApplicationController
def update_contact
if current_user.update(contact_params)
redirect_to settings_profile_index_path, notice: 'Contact information has been updated'
redirect_to settings_profile_index_path, notice: I18n.t('contact_information_has_been_update')
else
render :index, status: :unprocessable_entity
end
@@ -18,7 +18,7 @@ class ProfileController < ApplicationController
def update_password
if current_user.update(password_params)
bypass_sign_in(current_user)
redirect_to settings_profile_index_path, notice: 'Password has been changed'
redirect_to settings_profile_index_path, notice: I18n.t('password_has_been_changed')
else
render :index, status: :unprocessable_entity
end
@@ -7,6 +7,8 @@ class SendSubmissionEmailController < ApplicationController
skip_before_action :verify_authenticity_token
skip_authorization_check
SEND_DURATION = 30.minutes
def success; end
def create
@@ -23,7 +25,10 @@ class SendSubmissionEmailController < ApplicationController
RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes)
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later!
unless EmailEvent.exists?(tag: :submitter_documents_copy, email: @submitter.email, emailable: @submitter,
event_type: :send, created_at: SEND_DURATION.ago..Time.current)
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later!
end
respond_to do |f|
f.html { redirect_to success_send_submission_email_index_path }
+5 -1
View File
@@ -3,12 +3,16 @@
class SessionsController < Devise::SessionsController
before_action :configure_permitted_parameters
around_action :with_browser_locale
def create
email = sign_in_params[:email].to_s.downcase
if Docuseal.multitenant? && !User.exists?(email:)
Rollbar.warning('Sign in new user') if defined?(Rollbar)
return redirect_to new_registration_path(sign_up: true, user: sign_in_params.slice(:email)),
notice: 'Create a new account'
notice: I18n.t('create_a_new_account')
end
if User.exists?(email:, otp_required_for_login: true) && sign_in_params[:otp_attempt].blank?
+4 -4
View File
@@ -21,7 +21,7 @@ class SetupController < ApplicationController
@encrypted_config = EncryptedConfig.new(encrypted_config_params)
unless URI.parse(encrypted_config_params[:value].to_s).class.in?([URI::HTTP, URI::HTTPS])
@encrypted_config.errors.add(:value, 'should be a valid URL')
@encrypted_config.errors.add(:value, I18n.t('should_be_a_valid_url'))
return render :index, status: :unprocessable_entity
end
@@ -56,7 +56,7 @@ class SetupController < ApplicationController
def account_params
return {} unless params[:account]
params.require(:account).permit(:name, :timezone)
params.require(:account).permit(:name, :timezone, :locale)
end
def encrypted_config_params
@@ -66,10 +66,10 @@ class SetupController < ApplicationController
end
def redirect_to_root_if_signed
redirect_to root_path, notice: 'You are already signed in'
redirect_to root_path, notice: I18n.t('you_are_already_signed_in')
end
def ensure_first_user_not_created!
redirect_to new_user_session_path, notice: 'Please sign in.' if User.exists?
redirect_to new_user_session_path, notice: I18n.t('please_sign_in') if User.exists?
end
end
@@ -3,7 +3,6 @@
class SsoSettingsController < ApplicationController
before_action :load_encrypted_config
authorize_resource :encrypted_config, only: :index
authorize_resource :encrypted_config, parent: false, except: :index
def index; end
+36 -12
View File
@@ -7,33 +7,43 @@ class StartFormController < ApplicationController
skip_authorization_check
around_action :with_browser_locale, only: %i[show completed]
before_action :maybe_redirect_com, only: %i[show completed]
before_action :load_template
def show
@submitter = @template.submissions.new(account_id: @template.account_id)
.submitters.new(uuid: @template.submitters.first['uuid'])
.submitters.new(uuid: (filter_undefined_submitters(@template).first ||
@template.submitters.first)['uuid'])
end
def update
return redirect_to start_form_path(@template.slug) if @template.archived_at?
@submitter = Submitter.where(submission: @template.submissions.where(archived_at: nil))
.order(id: :desc)
.then { |rel| params[:resubmit].present? ? rel.where(completed_at: nil) : rel }
.find_or_initialize_by(**submitter_params.compact_blank)
@submitter = find_or_initialize_submitter(@template, submitter_params)
if @submitter.completed_at?
redirect_to start_form_completed_path(@template.slug, email: submitter_params[:email])
else
if @template.submitters.to_a.size > 1 && @submitter.new_record?
@error_message = 'Not found'
if filter_undefined_submitters(@template).size > 1 && @submitter.new_record?
@error_message = I18n.t('not_found')
return render :show
end
assign_submission_attributes(@submitter, @template) if @submitter.new_record?
if (is_new_record = @submitter.new_record?)
assign_submission_attributes(@submitter, @template)
Submissions::AssignDefinedSubmitters.call(@submitter.submission)
end
if @submitter.save
if is_new_record
WebhookUrls.for_account_id(@submitter.account_id, 'submission.created').each do |webhook_url|
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => @submitter.submission_id,
'webhook_url_id' => webhook_url.id)
end
end
redirect_to submit_form_path(@submitter.slug)
else
render :show
@@ -49,14 +59,21 @@ class StartFormController < ApplicationController
private
def find_or_initialize_submitter(template, submitter_params)
Submitter.where(submission: template.submissions.where(expire_at: Time.current..)
.or(template.submissions.where(expire_at: nil)).where(archived_at: nil))
.order(id: :desc)
.where(declined_at: nil)
.then { |rel| params[:resubmit].present? ? rel.where(completed_at: nil) : rel }
.find_or_initialize_by(**submitter_params.compact_blank)
end
def assign_submission_attributes(submitter, template)
resubmit_submitter =
if params[:resubmit].present?
Submitter.where(submission: @template.submissions).find_by(slug: params[:resubmit])
end
(Submitter.where(submission: template.submissions).find_by(slug: params[:resubmit]) if params[:resubmit].present?)
submitter.assign_attributes(
uuid: template.submitters.first['uuid'],
uuid: (filter_undefined_submitters(template).first || @template.submitters.first)['uuid'],
ip: request.remote_ip,
ua: request.user_agent,
values: resubmit_submitter&.preferences&.fetch('default_values', nil) || {},
@@ -73,11 +90,18 @@ class StartFormController < ApplicationController
submitter.submission ||= Submission.new(template:,
account_id: template.account_id,
template_submitters: template.submitters,
submitters: [submitter],
source: :link)
submitter.account_id = submitter.submission.account_id
submitter
end
def filter_undefined_submitters(template)
Templates.filter_undefined_submitters(template)
end
def submitter_params
params.require(:submitter).permit(:email, :phone, :name).tap do |attrs|
attrs[:email] = Submissions.normalize_email(attrs[:email])
@@ -11,7 +11,7 @@ class StorageSettingsController < ApplicationController
if @encrypted_config.update(storage_configs)
LoadActiveStorageConfigs.reload
redirect_to settings_storage_index_path, notice: 'Changes have been saved'
redirect_to settings_storage_index_path, notice: I18n.t('changes_have_been_saved')
else
render :index, status: :unprocessable_entity
end
@@ -0,0 +1,25 @@
# frozen_string_literal: true
class SubmissionsArchivedController < ApplicationController
load_and_authorize_resource :submission, parent: false
def index
@submissions = @submissions.joins(:template)
@submissions = @submissions.where.not(archived_at: nil)
.or(@submissions.where.not(templates: { archived_at: nil }))
.preload(:created_by_user, template: :author)
@submissions = @submissions.preload(:template_accesses) unless current_user.role.in?(%w[admin superadmin])
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
@submissions = Submissions::Filter.call(@submissions, current_user, params)
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
else
@submissions.order(id: :desc)
end
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
end
end
+46 -4
View File
@@ -6,9 +6,18 @@ class SubmissionsController < ApplicationController
load_and_authorize_resource :submission, only: %i[show destroy]
prepend_before_action :maybe_redirect_com, only: %i[show]
def show
@submission = Submissions.preload_with_pages(@submission)
unless @submission.submitters.all?(&:completed_at?)
ActiveRecord::Associations::Preloader.new(
records: [@submission],
associations: [submitters: :start_form_submission_events]
).call
end
render :show, layout: 'plain'
end
@@ -19,6 +28,8 @@ class SubmissionsController < ApplicationController
def create
authorize!(:create, Submission)
save_template_message(@template, params) if params[:save_message] == '1'
if params[:is_custom_message] != '1'
params.delete(:subject)
params.delete(:body)
@@ -37,24 +48,55 @@ class SubmissionsController < ApplicationController
user: current_user,
source: :invite,
submitters_order: params[:preserve_order] == '1' ? 'preserved' : 'random',
mark_as_sent: params[:send_email] == '1',
submissions_attrs: submissions_params[:submission].to_h.values,
params: params.merge('send_completed_email' => true))
end
enqueue_submission_created_webhooks(@template, submissions)
Submissions.send_signature_requests(submissions)
redirect_to template_path(@template), notice: 'New recipients have been added'
redirect_to template_path(@template), notice: I18n.t('new_recipients_have_been_added')
end
def destroy
@submission.update!(archived_at: Time.current)
notice =
if params[:permanently].present?
@submission.destroy!
redirect_back(fallback_location: template_path(@submission.template), notice: 'Submission has been archived')
I18n.t('submission_has_been_removed')
else
@submission.update!(archived_at: Time.current)
WebhookUrls.for_account_id(@submission.account_id, 'submission.archived').each do |webhook_url|
SendSubmissionArchivedWebhookRequestJob.perform_async('submission_id' => @submission.id,
'webhook_url_id' => webhook_url.id)
end
I18n.t('submission_has_been_archived')
end
redirect_back(fallback_location: template_path(@submission.template), notice:)
end
private
def save_template_message(template, params)
template.preferences['request_email_subject'] = params[:subject] if params[:subject].present?
template.preferences['request_email_body'] = params[:body] if params[:body].present?
template.save!
end
def enqueue_submission_created_webhooks(template, submissions)
WebhookUrls.for_account_id(template.account_id, 'submission.created').each do |webhook_url|
submissions.each do |submission|
SendSubmissionCreatedWebhookRequestJob.perform_async('submission_id' => submission.id,
'webhook_url_id' => webhook_url.id)
end
end
end
def submissions_params
params.permit(submission: { submitters: [%i[uuid email phone name]] })
end
@@ -0,0 +1,29 @@
# frozen_string_literal: true
class SubmissionsDashboardController < ApplicationController
load_and_authorize_resource :submission, parent: false
def index
@submissions = @submissions.joins(:template)
@submissions = @submissions.where(archived_at: nil)
.where(templates: { archived_at: nil })
.preload(:created_by_user, template: :author)
@submissions = @submissions.preload(:template_accesses) unless current_user.role.in?(%w[admin superadmin])
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
@submissions = Submissions::Filter.call(@submissions, current_user, params)
@submissions = @submissions.pending if params[:status] == 'pending'
@submissions = @submissions.completed if params[:status] == 'completed'
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
else
@submissions.order(id: :desc)
end
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
end
end
@@ -16,13 +16,16 @@ class SubmissionsDebugController < ApplicationController
render 'submit_form/show'
end
f.pdf do
if params[:audit]
Submissions::GenerateAuditTrail.call(@submitter.submission)
else
Submissions::GenerateResultAttachments.call(@submitter)
end
result =
if params[:audit]
Submissions::GenerateAuditTrail.call(@submitter.submission)
elsif params[:combined]
Submissions::GenerateCombinedAttachment.call(@submitter)
else
Submissions::GenerateResultAttachments.call(@submitter)
end
send_data ActiveStorage::Attachment.where(name: params[:audit] ? :audit_trail : :documents).last.download,
send_data Array.wrap(result).first.download,
filename: 'debug.pdf',
disposition: 'inline',
type: 'application/pdf'
@@ -33,7 +33,17 @@ class SubmissionsDownloadController < ApplicationController
return head :not_found
end
render json: build_urls(last_submitter)
if params[:combined] == 'true'
url = build_combined_url(submitter)
if url
render json: [url]
else
head :not_found
end
else
render json: build_urls(last_submitter)
end
end
private
@@ -43,8 +53,32 @@ class SubmissionsDownloadController < ApplicationController
end
def build_urls(submitter)
filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value
Submitters.select_attachments_for_download(submitter).map do |attachment|
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.minutes.from_now.to_i)
ActiveStorage::Blob.proxy_url(
attachment.blob,
expires_at: FILES_TTL.from_now.to_i,
filename: Submitters.build_document_filename(submitter, attachment.blob, filename_format)
)
end
end
def build_combined_url(submitter)
return if submitter.submission.submitters.exists?(completed_at: nil)
return if submitter.submission.submitters.order(:completed_at).last != submitter
attachment = submitter.submission.combined_document_attachment
attachment ||= Submissions::GenerateCombinedAttachment.call(submitter)
filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value
ActiveStorage::Blob.proxy_url(
attachment.blob,
expires_at: FILES_TTL.from_now.to_i,
filename: Submitters.build_document_filename(submitter, attachment.blob, filename_format)
)
end
end
@@ -0,0 +1,17 @@
# frozen_string_literal: true
class SubmissionsFiltersController < ApplicationController
ALLOWED_NAMES = %w[
author
completed_at
created_at
].freeze
skip_authorization_check
def show
return head :not_found unless ALLOWED_NAMES.include?(params[:name])
render params[:name]
end
end
@@ -4,6 +4,8 @@ class SubmissionsPreviewController < ApplicationController
skip_before_action :authenticate_user!
skip_authorization_check
prepend_before_action :maybe_redirect_com, only: %i[show completed]
TTL = 40.minutes
def show
@@ -18,8 +20,8 @@ class SubmissionsPreviewController < ApplicationController
@submission ||= Submission.find_by!(slug: params[:slug])
if !@submission.submitters.all?(&:completed_at?) && current_user.blank?
raise ActionController::RoutingError, 'Not Found'
if @submission.account.archived_at? || (!@submission.submitters.all?(&:completed_at?) && current_user.blank?)
raise ActionController::RoutingError, I18n.t('not_found')
end
if !submission_valid_ttl?(@submission) && !signature_valid
@@ -0,0 +1,11 @@
# frozen_string_literal: true
class SubmissionsUnarchiveController < ApplicationController
load_and_authorize_resource :submission
def create
@submission.update!(archived_at: nil)
redirect_to submission_path(@submission), notice: I18n.t('submission_has_been_unarchived')
end
end
+46 -21
View File
@@ -7,46 +7,64 @@ class SubmitFormController < ApplicationController
skip_before_action :authenticate_user!
skip_authorization_check
PRELOAD_ALL_PAGES_AMOUNT = 200
CONFIG_KEYS = [].freeze
def show
@submitter = Submitter.find_by!(slug: params[:slug])
submission = @submitter.submission
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
return render :archived if submission.template.archived_at? ||
submission.archived_at? ||
@submitter.account.archived_at?
return render :expired if submission.expired?
return render :declined if @submitter.declined_at?
return render :awaiting if submission.template.preferences['submitters_order'] == 'preserved' &&
!Submitters.current_submitter_order?(@submitter)
ActiveRecord::Associations::Preloader.new(
records: [@submitter],
associations: [submission: [:template, { template_schema_documents: :blob }]]
).call
total_pages =
@submitter.submission.template_schema_documents.sum { |e| e.metadata.dig('pdf', 'number_of_pages').to_i }
if total_pages < PRELOAD_ALL_PAGES_AMOUNT
ActiveRecord::Associations::Preloader.new(
records: @submitter.submission.template_schema_documents,
associations: [:blob, { preview_images_attachments: :blob }]
).call
end
Submitters.preload_with_pages(@submitter)
Submitters::MaybeUpdateDefaultValues.call(@submitter, current_user)
cookies[:submitter_sid] = @submitter.signed_id
@attachments_index = build_attachments_index(submission)
render(@submitter.submission.template.archived_at? || @submitter.submission.archived_at? ? :archived : :show)
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
return unless @form_configs[:prefill_signature]
if (user_signature = UserConfigs.load_signature(current_user))
@signature_attachment = ActiveStorage::Attachment.find_or_create_by!(
blob_id: user_signature.blob_id,
name: 'attachments',
record: @submitter
)
end
@signature_attachment ||=
Submitters::MaybeAssignDefaultBrowserSignature.call(@submitter, params, cookies, @attachments_index.values)
@attachments_index[@signature_attachment.uuid] = @signature_attachment if @signature_attachment
end
def update
submitter = Submitter.find_by!(slug: params[:slug])
if submitter.completed_at?
return render json: { error: 'Form has been completed already.' }, status: :unprocessable_entity
return render json: { error: I18n.t('form_has_been_completed_already') }, status: :unprocessable_entity
end
if submitter.template.archived_at? || submitter.submission.archived_at?
Rollbar.info("Archived template: #{submitter.template.id}") if defined?(Rollbar)
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_entity
end
return render json: { error: 'Form has been archived.' }, status: :unprocessable_entity
if submitter.submission.expired?
return render json: { error: I18n.t('form_has_been_expired') }, status: :unprocessable_entity
end
if submitter.declined_at?
return render json: { error: I18n.t('form_has_been_declined') },
status: :unprocessable_entity
end
Submitters::SubmitValues.call(submitter, params, request)
@@ -57,8 +75,15 @@ class SubmitFormController < ApplicationController
end
def completed
@submitter = Submitter.find_by!(slug: params[:submit_form_slug])
@submitter = Submitter.completed.find_by!(slug: params[:submit_form_slug])
end
def success; end
private
def build_attachments_index(submission)
ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
.preload(:blob).index_by(&:uuid)
end
end
@@ -0,0 +1,35 @@
# frozen_string_literal: true
class SubmitFormDeclineController < ApplicationController
skip_before_action :authenticate_user!
skip_authorization_check
def create
submitter = Submitter.find_by!(slug: params[:submit_form_slug])
return redirect_to submit_form_path(submitter.slug) if submitter.declined_at? ||
submitter.completed_at? ||
submitter.submission.archived_at? ||
submitter.submission.expired? ||
submitter.submission.template.archived_at?
ApplicationRecord.transaction do
submitter.update!(declined_at: Time.current)
SubmissionEvents.create_with_tracking_data(submitter, 'decline_form', request, { reason: params[:reason] })
end
user = submitter.submission.created_by_user || submitter.template.author
if user.user_configs.find_by(key: UserConfig::RECEIVE_DECLINED_EMAIL)&.value != false
SubmitterMailer.declined_email(submitter, user).deliver_later!
end
WebhookUrls.for_account_id(submitter.account_id, 'form.declined').each do |webhook_url|
SendFormDeclinedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
'webhook_url_id' => webhook_url.id)
end
redirect_to submit_form_path(submitter.slug)
end
end
@@ -0,0 +1,37 @@
# frozen_string_literal: true
class SubmitFormDownloadController < ApplicationController
skip_before_action :authenticate_user!
skip_authorization_check
FILES_TTL = 5.minutes
def index
submitter = Submitter.find_by!(slug: params[:submit_form_slug])
return redirect_to submitter_download_index_path(submitter.slug) if submitter.completed_at?
return head :unprocessable_entity if submitter.declined_at? ||
submitter.submission.archived_at? ||
submitter.submission.expired? ||
submitter.submission.template.archived_at?
last_completed_submitter = submitter.submission.submitters
.where.not(id: submitter.id)
.where.not(completed_at: nil)
.max_by(&:completed_at)
attachments =
if last_completed_submitter
Submitters.select_attachments_for_download(last_completed_submitter)
else
submitter.submission.template.schema_documents.preload(:blob)
end
urls = attachments.map do |attachment|
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.from_now.to_i)
end
render json: urls
end
end
@@ -0,0 +1,21 @@
# frozen_string_literal: true
class SubmitFormDrawSignatureController < ApplicationController
layout false
around_action :with_browser_locale, only: %i[show]
skip_before_action :authenticate_user!
skip_authorization_check
def show
@submitter = Submitter.find_by!(slug: params[:slug])
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
if @submitter.submission.template.archived_at? || @submitter.submission.archived_at?
return redirect_to submit_form_path(@submitter.slug)
end
render :show
end
end
@@ -0,0 +1,58 @@
# frozen_string_literal: true
class SubmitFormInviteController < ApplicationController
skip_before_action :authenticate_user!
skip_authorization_check
def create
submitter = Submitter.find_by!(slug: params[:submit_form_slug])
return head :unprocessable_entity unless can_invite?(submitter)
invite_submitters = filter_invite_submitters(submitter)
ApplicationRecord.transaction do
invite_submitters.each do |item|
attrs = submitters_attributes.find { |e| e[:uuid] == item['uuid'] }
next unless attrs
submitter.submission.submitters.create!(**attrs, account_id: submitter.account_id)
SubmissionEvents.create_with_tracking_data(submitter, 'invite_party', request, { uuid: submitter.uuid })
end
submitter.submission.update!(submitters_order: :preserved)
end
submitter.submission.submitters.reload
if invite_submitters.all? { |s| submitter.submission.submitters.any? { |e| e.uuid == s['uuid'] } }
Submitters::SubmitValues.call(submitter, ActionController::Parameters.new(completed: 'true'), request)
head :ok
else
head :unprocessable_entity
end
end
private
def can_invite?(submitter)
!submitter.declined_at? &&
!submitter.completed_at? &&
!submitter.submission.archived_at? &&
!submitter.submission.expired? &&
!submitter.submission.template.archived_at?
end
def filter_invite_submitters(submitter)
(submitter.submission.template_submitters || submitter.submission.template.submitters).select do |s|
s['invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] }
end
end
def submitters_attributes
params.require(:submission).permit(submitters: [%i[uuid email]]).fetch(:submitters, [])
end
end
@@ -0,0 +1,23 @@
# frozen_string_literal: true
class SubmitFormValuesController < ApplicationController
skip_before_action :authenticate_user!
skip_authorization_check
def index
submitter = Submitter.find_by!(slug: params[:submit_form_slug])
return render json: {} if submitter.completed_at? || submitter.declined_at?
return render json: {} if submitter.submission.template.archived_at? ||
submitter.submission.archived_at? ||
submitter.submission.expired?
value = submitter.values[params['field_uuid']]
attachment = submitter.attachments.where(created_at: params[:after]..).find_by(uuid: value) if value.present?
render json: {
value:,
attachment: attachment&.as_json(only: %i[uuid created_at], methods: %i[url filename content_type])
}, head: :ok
end
end
@@ -0,0 +1,34 @@
# frozen_string_literal: true
class SubmittersAutocompleteController < ApplicationController
load_and_authorize_resource :submitter, parent: false
SELECT_COLUMNS = %w[email phone name].freeze
LIMIT = 100
def index
submitters = search_submitters(@submitters)
arel_columns = SELECT_COLUMNS.map { |col| Submitter.arel_table[col] }
values = submitters.limit(LIMIT).group(arel_columns).pluck(arel_columns)
attrs = values.map { |row| SELECT_COLUMNS.zip(row).to_h }
attrs = attrs.uniq { |e| e[params[:field]] } if params[:field].present?
render json: attrs
end
private
def search_submitters(submitters)
if SELECT_COLUMNS.include?(params[:field])
column = Submitter.arel_table[params[:field].to_sym]
term = "#{params[:q].downcase}%"
submitters.where(column.matches(term))
else
Submitters.search(submitters, params[:q])
end
end
end
+80
View File
@@ -0,0 +1,80 @@
# frozen_string_literal: true
class SubmittersController < ApplicationController
load_and_authorize_resource :submitter, only: %i[edit update]
def edit
@submitter_email_message =
if @submitter.preferences['email_message_uuid'].present?
@submitter.account.email_messages.find_by(uuid: @submitter.preferences['email_message_uuid'])
end
end
def update
submission = @submitter.submission
if @submitter.submission_events.exists?(event_type: 'start_form') || submission.archived_at? || submission.expired?
return redirect_back fallback_location: submission_path(submission), alert: I18n.t('submitter_cannot_be_updated')
end
if submitter_params.values.all?(&:blank?)
return redirect_back fallback_location: submission_path(submission),
alert: I18n.t('at_least_one_field_must_be_filled')
end
if params[:is_custom_message] != '1'
params.delete(:subject)
params.delete(:body)
end
submitter_preferences = Submitters.normalize_preferences(@submitter.account, current_user, params)
if submitter_preferences.key?('email_message_uuid')
@submitter.preferences['email_message_uuid'] = submitter_preferences['email_message_uuid']
end
assign_submitter_attrs(@submitter, submitter_params)
if @submitter.save
maybe_resend_email_sms(@submitter, params)
redirect_back fallback_location: submission_path(submission), notice: I18n.t('changes_have_been_saved')
else
redirect_back fallback_location: submission_path(submission), alert: I18n.t('unable_to_save')
end
end
private
def maybe_resend_email_sms(submitter, params)
if params[:send_email] == '1' && submitter.email.present?
is_sent_recently = Docuseal.multitenant? &&
EmailEvent.exists?(email: submitter.email,
tag: 'submitter_invitation',
emailable: submitter,
event_type: 'send',
created_at: 4.hours.ago..Time.current)
SendSubmitterInvitationEmailJob.perform_async('submitter_id' => submitter.id) unless is_sent_recently
end
return if submitter.phone.blank?
return unless params[:send_sms] == '1'
SendSubmitterInvitationSmsJob.perform_async('submitter_id' => submitter.id)
end
def assign_submitter_attrs(submitter, attrs)
submitter.phone = attrs[:phone].to_s.gsub(/[^0-9+]/, '') if attrs.key?(:phone)
submitter.email = Submissions.normalize_email(attrs[:email]) if attrs.key?(:email)
submitter.name = attrs[:name] if attrs.key?(:name)
submitter
end
def submitter_params
params.require(:submitter).permit(:email, :name, :phone).transform_values(&:strip)
end
end
@@ -6,16 +6,18 @@ class SubmittersSendEmailController < ApplicationController
def create
if Docuseal.multitenant? && SubmissionEvent.exists?(submitter: @submitter,
event_type: 'send_email',
created_at: 24.hours.ago..Time.current)
created_at: 10.hours.ago..Time.current)
Rollbar.warning("Already sent: #{@submitter.id}") if defined?(Rollbar)
return redirect_back(fallback_location: submission_path(@submitter.submission),
alert: 'Email has been sent already.')
alert: I18n.t('email_has_been_sent_already'))
end
SendSubmitterInvitationEmailJob.perform_later('submitter_id' => @submitter.id)
SendSubmitterInvitationEmailJob.perform_async('submitter_id' => @submitter.id)
@submitter.sent_at ||= Time.current
@submitter.save!
redirect_back(fallback_location: submission_path(@submitter.submission), notice: 'Email has been sent')
redirect_back(fallback_location: submission_path(@submitter.submission), notice: I18n.t('email_has_been_sent'))
end
end
@@ -0,0 +1,33 @@
# frozen_string_literal: true
class TemplateDocumentsController < ApplicationController
load_and_authorize_resource :template
def create
if params[:blobs].blank? && params[:files].blank?
return render json: { error: I18n.t('file_is_missing') }, status: :unprocessable_entity
end
old_fields_hash = @template.fields.hash
documents = Templates::CreateAttachments.call(@template, params, extract_fields: true)
schema = documents.map do |doc|
{ attachment_uuid: doc.uuid, name: doc.filename.base }
end
render json: {
schema:,
fields: old_fields_hash == @template.fields.hash ? nil : @template.fields,
submitters: old_fields_hash == @template.fields.hash ? nil : @template.submitters,
documents: documents.as_json(
methods: %i[metadata signed_uuid],
include: {
preview_images: { methods: %i[url metadata filename] }
}
)
}
rescue Templates::CreateAttachments::PdfEncrypted
render json: { error: 'PDF encrypted', status: 'pdf_encrypted' }, status: :unprocessable_entity
end
end
@@ -0,0 +1,14 @@
# frozen_string_literal: true
class TemplateFoldersAutocompleteController < ApplicationController
load_and_authorize_resource :template_folder, parent: false
LIMIT = 100
def index
template_folders = @template_folders.joins(:templates).where(templates: { archived_at: nil }).distinct
template_folders = TemplateFolders.search(template_folders, params[:q]).limit(LIMIT)
render json: template_folders.as_json(only: %i[name archived_at])
end
end
@@ -4,10 +4,10 @@ class TemplateFoldersController < ApplicationController
load_and_authorize_resource :template_folder
def show
@templates = @template_folder.templates.active.preload(:author).order(id: :desc)
@templates = @template_folder.templates.active.preload(:author, :template_accesses).order(id: :desc)
@templates = Templates.search(@templates, params[:q])
@pagy, @templates = pagy(@templates, items: 12)
@pagy, @templates = pagy(@templates, limit: 12)
end
def edit; end
@@ -15,9 +15,9 @@ class TemplateFoldersController < ApplicationController
def update
if @template_folder != current_account.default_template_folder &&
@template_folder.update(template_folder_params)
redirect_to folder_path(@template_folder), notice: 'Folder name has been updated'
redirect_to folder_path(@template_folder), notice: I18n.t('folder_name_has_been_updated')
else
redirect_to folder_path(@template_folder), alert: 'Unable to rename folder'
redirect_to folder_path(@template_folder), alert: I18n.t('unable_to_rename_folder')
end
end
@@ -4,9 +4,9 @@ class TemplatesArchivedController < ApplicationController
load_and_authorize_resource :template, parent: false
def index
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder).order(id: :desc)
@templates = @templates.where.not(archived_at: nil).preload(:author, :folder, :template_accesses).order(id: :desc)
@templates = Templates.search(@templates, params[:q])
@pagy, @templates = pagy(@templates, items: 12)
@pagy, @templates = pagy(@templates, limit: 12)
end
end
@@ -6,9 +6,16 @@ class TemplatesArchivedSubmissionsController < ApplicationController
def index
@submissions = @submissions.where.not(archived_at: nil)
@submissions = Submissions.search(@submissions, params[:q])
@submissions = Submissions.search(@submissions, params[:q], search_values: true)
@submissions = Submissions::Filter.call(@submissions, current_user, params)
@pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc))
@submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
@submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
else
@submissions.order(id: :desc)
end
@pagy, @submissions = pagy(@submissions.preload(submitters: :start_form_submission_events))
rescue ActiveRecord::RecordNotFound
redirect_to root_path
end
+62 -13
View File
@@ -8,26 +8,35 @@ class TemplatesController < ApplicationController
def show
submissions = @template.submissions.accessible_by(current_ability)
submissions = submissions.active if @template.archived_at.blank?
submissions = Submissions.search(submissions, params[:q])
submissions = Submissions.search(submissions, params[:q], search_values: true)
submissions = Submissions::Filter.call(submissions, current_user, params)
@base_submissions = submissions
submissions = submissions.pending if params[:status] == 'pending'
submissions = submissions.completed if params[:status] == 'completed'
@pagy, @submissions = pagy(submissions.preload(:submitters).order(id: :desc))
submissions = if params[:completed_at_from].present? || params[:completed_at_to].present?
submissions.order(Submitter.arel_table[:completed_at].maximum.desc)
else
submissions.order(id: :desc)
end
submissions = submissions.preload(:template_accesses) unless current_user.role.in?(%w[admin superadmin])
@pagy, @submissions = pagy(submissions.preload(submitters: :start_form_submission_events))
rescue ActiveRecord::RecordNotFound
redirect_to root_path
end
def new
@template.name = "#{@base_template.name} (Clone)" if @base_template
@template.name = "#{@base_template.name} (#{I18n.t('clone')})" if @base_template
end
def edit
ActiveRecord::Associations::Preloader.new(
records: [@template],
associations: [schema_documents: { preview_images_attachments: :blob }]
associations: [schema_documents: [:blob, { preview_images_attachments: :blob }]]
).call
@template_data =
@@ -43,6 +52,11 @@ class TemplatesController < ApplicationController
def create
if @base_template
ActiveRecord::Associations::Preloader.new(
records: [@base_template],
associations: [schema_documents: :preview_images_attachments]
).call
@template = Templates::Clone.call(@base_template, author: current_user,
name: params.dig(:template, :name),
folder_name: params[:folder_name])
@@ -53,7 +67,7 @@ class TemplatesController < ApplicationController
if params[:account_id].present? && authorized_clone_account_id?(params[:account_id])
@template.account_id = params[:account_id]
@template.folder = @template.account.default_template_folder
@template.folder = @template.account.default_template_folder if @template.account_id != current_account.id
else
@template.account = current_account
end
@@ -61,24 +75,32 @@ class TemplatesController < ApplicationController
if @template.save
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
enqueue_template_created_webhooks(@template)
maybe_redirect_to_template(@template)
else
render turbo_stream: turbo_stream.replace(:modal, template: 'templates/new'), status: :unprocessable_entity
end
end
def update
@template.update!(template_params)
enqueue_template_updated_webhooks(@template)
head :ok
end
def destroy
notice =
if params[:permanently].present?
@template.destroy!
Rollbar.info("Remove template: #{@template.id}") if defined?(Rollbar)
'Template has been removed.'
I18n.t('template_has_been_removed')
else
@template.update!(archived_at: Time.current)
'Template has been archived.'
I18n.t('template_has_been_archived')
end
redirect_back(fallback_location: root_path, notice:)
@@ -86,20 +108,47 @@ class TemplatesController < ApplicationController
private
def template_params
params.require(:template).permit(
:name,
{ schema: [[:attachment_uuid, :name, { conditions: [%i[field_uuid value action operation]] }]],
submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid email]],
fields: [[:uuid, :submitter_uuid, :name, :type,
:required, :readonly, :default_value,
:title, :description,
{ preferences: {},
conditions: [%i[field_uuid value action operation]],
options: [%i[value uuid]],
validation: %i[message pattern],
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]] }
)
end
def authorized_clone_account_id?(account_id)
true_user.account_id.to_s == account_id.to_s || true_user.account.linked_accounts.exists?(id: account_id)
true_user.account_id.to_s == account_id.to_s ||
true_user.account.linked_accounts.accessible_by(current_ability).exists?(id: account_id)
end
def maybe_redirect_to_template(template)
if template.account == current_account
redirect_to(edit_template_path(@template))
else
redirect_back(fallback_location: root_path, notice: 'Template has been clonned')
redirect_back(fallback_location: root_path, notice: I18n.t('template_has_been_cloned'))
end
end
def template_params
params.require(:template).permit(:name)
def enqueue_template_created_webhooks(template)
WebhookUrls.for_account_id(template.account_id, 'template.created').each do |webhook_url|
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => template.id,
'webhook_url_id' => webhook_url.id)
end
end
def enqueue_template_updated_webhooks(template)
WebhookUrls.for_account_id(template.account_id, 'template.updated').each do |webhook_url|
SendTemplateUpdatedWebhookRequestJob.perform_async('template_id' => template.id,
'webhook_url_id' => webhook_url.id)
end
end
def load_base_template
@@ -0,0 +1,63 @@
# frozen_string_literal: true
class TemplatesDashboardController < ApplicationController
load_and_authorize_resource :template_folder, parent: false
load_and_authorize_resource :template, parent: false
SHOW_TEMPLATES_FOLDERS_THRESHOLD = 9
TEMPLATES_PER_PAGE = 12
FOLDERS_PER_PAGE = 18
def index
@template_folders = filter_template_folders(@template_folders)
@pagy, @template_folders = pagy(
@template_folders,
items: FOLDERS_PER_PAGE,
page: @template_folders.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD ? params[:page] : 1
)
if @pagy.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD
@templates = @templates.none
else
@template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME }
@templates = filter_templates(@templates)
limit =
if @template_folders.size < 4
TEMPLATES_PER_PAGE
else
(@template_folders.size < 7 ? 9 : 6)
end
@pagy, @templates = pagy(@templates, limit:)
end
end
private
def filter_template_folders(template_folders)
rel = template_folders.joins(:active_templates)
.order(id: :desc)
.distinct
TemplateFolders.search(rel, params[:q])
end
def filter_templates(templates)
rel = templates.active.preload(:author, :template_accesses).order(id: :desc)
if params[:q].blank?
if Docuseal.multitenant? && !current_account.testing?
rel = rel.where(folder_id: current_account.default_template_folder.id)
else
shared_template_ids =
TemplateSharing.where(account_id: [current_account.id, TemplateSharing::ALL_ID]).select(:template_id)
rel = rel.where(folder_id: current_account.default_template_folder.id).or(rel.where(id: shared_template_ids))
end
end
Templates.search(rel, params[:q])
end
end
@@ -0,0 +1,33 @@
# frozen_string_literal: true
class TemplatesDebugController < ApplicationController
load_and_authorize_resource :template
def show
attachment = @template.documents.first
pdf = HexaPDF::Document.new(io: StringIO.new(attachment.download))
fields = Templates::FindAcroFields.call(pdf, attachment)
attachment.metadata['pdf'] ||= {}
attachment.metadata['pdf']['fields'] = fields
@template.update!(fields: Templates::ProcessDocument.normalize_attachment_fields(@template, [attachment]))
ActiveRecord::Associations::Preloader.new(
records: [@template],
associations: [schema_documents: { preview_images_attachments: :blob }]
).call
@template_data =
@template.as_json.merge(
documents: @template.schema_documents.as_json(
methods: %i[metadata signed_uuid],
include: { preview_images: { methods: %i[url metadata filename] } }
)
).to_json
render 'templates/edit', layout: 'plain'
end
end
@@ -9,9 +9,9 @@ class TemplatesFoldersController < ApplicationController
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:name])
if @template.save
redirect_back(fallback_location: template_path(@template), notice: 'Document template has been moved')
redirect_back(fallback_location: template_path(@template), notice: I18n.t('document_template_has_been_moved'))
else
redirect_back(fallback_location: template_path(@template), notice: 'Unable to move template into folder')
redirect_back(fallback_location: template_path(@template), notice: I18n.t('unable_to_move_template_into_folder'))
end
end
@@ -0,0 +1,23 @@
# frozen_string_literal: true
class TemplatesFormPreviewController < ApplicationController
layout 'form'
load_and_authorize_resource :template
def show
@submitter = Submitter.new(uuid: params[:uuid] || @template.submitters.first['uuid'],
account: current_account,
submission: @template.submissions.new(template_submitters: @template.submitters,
account: current_account))
@submitter.submission.submitters = @template.submitters.map { |item| Submitter.new(uuid: item['uuid']) }
Submitters.preload_with_pages(@submitter)
@attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments)
.preload(:blob).index_by(&:uuid)
@form_configs = Submitters::FormConfigs.call(@submitter)
end
end
@@ -0,0 +1,44 @@
# frozen_string_literal: true
class TemplatesPreferencesController < ApplicationController
load_and_authorize_resource :template
def show; end
def create
authorize!(:update, @template)
@template.preferences = @template.preferences.merge(template_params[:preferences])
@template.preferences = @template.preferences.reject { |_, v| (v.is_a?(String) || v.is_a?(Hash)) && v.blank? }
@template.save!
head :ok
end
private
def template_params
params.require(:template).permit(
preferences: %i[bcc_completed request_email_subject request_email_body
documents_copy_email_subject documents_copy_email_body
documents_copy_email_enabled documents_copy_email_attach_audit
documents_copy_email_attach_documents
completed_notification_email_attach_documents
completed_redirect_url
submitters_order
completed_notification_email_subject completed_notification_email_body
completed_notification_email_enabled completed_notification_email_attach_audit] +
[completed_message: %i[title body]]
).tap do |attrs|
attrs[:preferences] = attrs[:preferences].transform_values do |value|
if %w[true false].include?(value)
value == 'true'
elsif value.respond_to?(:compact_blank)
value.compact_blank
else
value
end
end
end
end
end
@@ -0,0 +1,54 @@
# frozen_string_literal: true
class TemplatesRecipientsController < ApplicationController
load_and_authorize_resource :template
def create
authorize!(:update, @template)
@template.submitters =
submitters_params.map { |s| s.reject { |_, v| v.is_a?(String) && v.blank? } }
@template.save!
render json: { submitters: @template.submitters }
end
private
def submitters_params
params.require(:template).permit(
submitters: [%i[name uuid is_requester invite_by_uuid linked_to_uuid email option]]
).fetch(:submitters, {}).values.filter_map do |s|
next if s[:uuid].blank?
if s[:is_requester] == '1' && s[:invite_by_uuid].blank?
s[:is_requester] = true
else
s.delete(:is_requester)
end
s.delete(:invite_by_uuid) if s[:invite_by_uuid].blank?
option = s.delete(:option)
if option.present?
case option
when 'is_requester'
s[:is_requester] = true
when 'not_set'
s.delete(:is_requester)
s.delete(:email)
s.delete(:linked_to_uuid)
s.delete(:invite_by_uuid)
when /\Alinked_to_(.*)\z/
s[:linked_to_uuid] = ::Regexp.last_match(-1)
when /\Ainvite_by_(.*)\z/
s[:invite_by_uuid] = ::Regexp.last_match(-1)
end
end
s
end
end
end
@@ -6,6 +6,6 @@ class TemplatesRestoreController < ApplicationController
def create
@template.update!(archived_at: nil)
redirect_to template_path(@template), notice: 'Template has been unarchived'
redirect_to template_path(@template), notice: I18n.t('template_has_been_unarchived')
end
end
+21 -10
View File
@@ -12,19 +12,28 @@ class TemplatesUploadsController < ApplicationController
save_template!(@template, url_params)
documents = Templates::CreateAttachments.call(@template, url_params || params)
documents = Templates::CreateAttachments.call(@template, url_params || params, extract_fields: true)
schema = documents.map { |doc| { attachment_uuid: doc.uuid, name: doc.filename.base } }
if @template.fields.blank?
@template.fields = Templates::ProcessDocument.normalize_attachment_fields(@template, documents)
schema.each { |item| item['pending_fields'] = true } if @template.fields.present?
end
@template.update!(schema:)
enqueue_template_created_webhooks(@template)
redirect_to edit_template_path(@template)
rescue Templates::CreateAttachments::PdfEncrypted
render turbo_stream: turbo_stream.append(params[:form_id], html: helpers.tag.prompt_password)
rescue StandardError => e
Rollbar.error(e) if defined?(Rollbar)
redirect_to root_path, alert: 'Unable to upload file'
raise if Rails.env.local?
redirect_to root_path, alert: I18n.t('unable_to_update_file')
end
private
@@ -43,23 +52,25 @@ class TemplatesUploadsController < ApplicationController
def create_file_params_from_url
tempfile = Tempfile.new
tempfile.binmode
tempfile.write(conn.get(Addressable::URI.parse(params[:url]).display_uri.to_s).body)
tempfile.write(DownloadUtils.call(params[:url]).body)
tempfile.rewind
filename = URI.decode_www_form_component(params[:filename]) if params[:filename].present?
filename ||= File.basename(URI.decode_www_form_component(params[:url]))
file = ActionDispatch::Http::UploadedFile.new(
tempfile:,
filename: File.basename(
URI.decode_www_form_component(params[:filename].presence || params[:url])
),
filename:,
type: Marcel::MimeType.for(tempfile)
)
{ files: [file] }
end
def conn
Faraday.new do |faraday|
faraday.response :follow_redirects
def enqueue_template_created_webhooks(template)
WebhookUrls.for_account_id(template.account_id, 'template.created').each do |webhook_url|
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => template.id,
'webhook_url_id' => webhook_url.id)
end
end
end
@@ -4,9 +4,8 @@ class TestingApiSettingsController < ApplicationController
def index
authorize!(:manage, current_user.access_token)
@webhook_config =
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
@webhook_url = current_account.webhook_urls.first_or_initialize
authorize!(:manage, @webhook_config)
authorize!(:manage, @webhook_url)
end
end
+22 -14
View File
@@ -1,38 +1,46 @@
# frozen_string_literal: true
class TimestampServerController < ApplicationController
HASH_ALGORITHM = 'SHA256'
before_action :build_encrypted_config
authorize_resource :encrypted_config
TimestampError = Class.new(StandardError)
def create
return head :not_found if Docuseal.multitenant?
test_timeserver_url(@encrypted_config.value) if @encrypted_config.value.present?
if @encrypted_config.value.present? ? @encrypted_config.save : @encrypted_config.delete
redirect_back fallback_location: settings_notifications_path, notice: 'Changes have been saved'
redirect_back fallback_location: settings_notifications_path, notice: I18n.t('changes_have_been_saved')
else
redirect_back fallback_location: settings_notifications_path, alert: 'Unable to save'
redirect_back fallback_location: settings_notifications_path, alert: I18n.t('unable_to_save')
end
rescue HexaPDF::Error, SocketError, Submissions::TimestampHandler::TimestampError, OpenSSL::Timestamp::TimestampError
redirect_back fallback_location: settings_notifications_path, alert: 'Invalid Timeserver'
rescue SocketError, TimestampError, OpenSSL::Timestamp::TimestampError
redirect_back fallback_location: settings_notifications_path, alert: t('invalid_timeserver')
end
private
def test_timeserver_url(url)
pdf = HexaPDF::Document.new
pdf.pages.add
req = OpenSSL::Timestamp::Request.new
req.algorithm = HASH_ALGORITHM
req.message_imprint = OpenSSL::Digest.digest(HASH_ALGORITHM, 'test')
pkcs = Accounts.load_signing_pkcs(current_account)
uri = Addressable::URI.parse(url)
pdf.sign(StringIO.new,
reason: 'Test',
certificate: pkcs.certificate,
key: pkcs.key,
signature_size: 10_000,
certificate_chain: pkcs.ca_certs || [],
timestamp_handler: Submissions::TimestampHandler.new(tsa_url: url))
conn = Faraday.new(uri.origin) do |c|
c.basic_auth(uri.user, uri.password) if uri.password.present?
end
response = conn.post(uri.path, req.to_der,
'content-type' => 'application/timestamp-query')
raise TimestampError if response.status != 200 || response.body.blank?
response
end
def load_encrypted_config
@@ -0,0 +1,43 @@
# frozen_string_literal: true
class UserInitialsController < ApplicationController
before_action :load_user_config
authorize_resource :user_config
def edit; end
def update
file = params[:file]
return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_initials') if file.blank?
blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
filename: file.original_filename,
content_type: file.content_type)
attachment = ActiveStorage::Attachment.create!(
blob:,
name: 'initials',
record: current_user
)
if @user_config.update(value: attachment.uuid)
redirect_to settings_profile_index_path, notice: I18n.t('initials_has_been_saved')
else
redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_initials')
end
end
def destroy
@user_config.destroy
redirect_to settings_profile_index_path, notice: I18n.t('initials_has_been_removed')
end
private
def load_user_config
@user_config =
UserConfig.find_or_initialize_by(user: current_user, key: UserConfig::INITIALS_KEY)
end
end
@@ -9,7 +9,7 @@ class UserSignaturesController < ApplicationController
def update
file = params[:file]
return redirect_to settings_profile_index_path, notice: 'Unable to save signature' if file.blank?
return redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_signature') if file.blank?
blob = ActiveStorage::Blob.create_and_upload!(io: file.open,
filename: file.original_filename,
@@ -22,16 +22,16 @@ class UserSignaturesController < ApplicationController
)
if @user_config.update(value: attachment.uuid)
redirect_to settings_profile_index_path, notice: 'Signature has been saved'
redirect_to settings_profile_index_path, notice: I18n.t('signature_has_been_saved')
else
redirect_to settings_profile_index_path, notice: 'Unable to save signature'
redirect_to settings_profile_index_path, notice: I18n.t('unable_to_save_signature')
end
end
def destroy
@user_config.destroy
redirect_to settings_profile_index_path, notice: 'Signature has been removed'
redirect_to settings_profile_index_path, notice: I18n.t('signature_has_been_removed')
end
private
+45 -11
View File
@@ -1,13 +1,22 @@
# frozen_string_literal: true
class UsersController < ApplicationController
load_and_authorize_resource :user, only: %i[index edit new update destroy]
load_and_authorize_resource :user, only: %i[index edit update destroy]
before_action :build_user, only: :create
authorize_resource :user, only: :create
before_action :build_user, only: %i[new create]
authorize_resource :user, only: %i[new create]
def index
@pagy, @users = pagy(@users.active.order(id: :desc))
@users =
if params[:status] == 'archived'
@users.archived.where.not(role: 'integration')
elsif params[:status] == 'integration'
@users.active.where(role: 'integration')
else
@users.active.where.not(role: 'integration')
end
@pagy, @users = pagy(@users.where(account: current_account).order(id: :desc))
end
def new; end
@@ -15,23 +24,43 @@ class UsersController < ApplicationController
def edit; end
def create
existing_user = User.accessible_by(current_ability).find_by(email: @user.email)
if existing_user
existing_user.archived_at = nil
existing_user.assign_attributes(user_params)
existing_user.account = current_account
@user = existing_user
end
@user.role = User::ADMIN_ROLE unless role_valid?(@user.role)
if @user.save
UserMailer.invitation_email(@user).deliver_later!
redirect_to settings_users_path, notice: 'User has been invited'
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_invited')
else
render turbo_stream: turbo_stream.replace(:modal, template: 'users/new'), status: :unprocessable_entity
end
end
def update
return redirect_to settings_users_path, notice: 'Unable to update user.' if Docuseal.demo?
return redirect_to settings_users_path, notice: I18n.t('unable_to_update_user') if Docuseal.demo?
attrs = user_params.compact_blank
attrs = user_params.compact_blank.merge(user_params.slice(:archived_at))
attrs.delete(:role) if !role_valid?(attrs[:role]) || current_user == @user
if params.dig(:user, :account_id).present?
account = Account.accessible_by(current_ability).find(params[:user][:account_id])
authorize!(:manage, account)
@user.account = account
end
if @user.update(attrs)
redirect_to settings_users_path, notice: 'User has been updated'
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_updated')
else
render turbo_stream: turbo_stream.replace(:modal, template: 'users/edit'), status: :unprocessable_entity
end
@@ -39,12 +68,12 @@ class UsersController < ApplicationController
def destroy
if Docuseal.demo? || @user.id == current_user.id
return redirect_to settings_users_path, notice: 'Unable to remove user'
return redirect_to settings_users_path, notice: I18n.t('unable_to_remove_user')
end
@user.update!(archived_at: Time.current)
redirect_to settings_users_path, notice: 'User has been removed'
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_removed')
end
private
@@ -65,6 +94,11 @@ class UsersController < ApplicationController
end
def user_params
params.require(:user).permit(:email, :first_name, :last_name, :password, :role)
if params.key?(:user)
params.require(:user).permit(:email, :first_name, :last_name, :password,
:role, :archived_at, :account_id)
else
{}
end
end
end
@@ -9,26 +9,11 @@ class VerifyPdfSignatureController < ApplicationController
HexaPDF::Document.new(io: file.open)
end
cert_data = if Docuseal.multitenant?
Docuseal::CERTS
else
EncryptedConfig.find_by(key: EncryptedConfig::ESIGN_CERTS_KEY)&.value || {}
end
default_pkcs = GenerateCertificate.load_pkcs(cert_data)
custom_certs = cert_data.fetch('custom', []).map do |e|
OpenSSL::PKCS12.new(Base64.urlsafe_decode64(e['data']), e['password'].to_s)
end
trusted_certs = [default_pkcs.certificate,
*default_pkcs.ca_certs,
*custom_certs.map(&:certificate),
*custom_certs.flat_map(&:ca_certs).compact]
trusted_certs = Accounts.load_trusted_certs(current_account)
render turbo_stream: turbo_stream.replace('result', partial: 'result',
locals: { pdfs:, files: params[:files], trusted_certs: })
rescue HexaPDF::MalformedPDFError
render turbo_stream: turbo_stream.replace('result', html: helpers.tag.div('Invalid PDF', id: 'result'))
render turbo_stream: turbo_stream.replace('result', html: helpers.tag.div(I18n.t('invalid_pdf'), id: 'result'))
end
end
@@ -0,0 +1,22 @@
# frozen_string_literal: true
class WebhookPreferencesController < ApplicationController
load_and_authorize_resource :webhook_url, parent: false
def update
webhook_preferences_params[:events].each do |event, val|
@webhook_url.events.delete(event) if val == '0'
@webhook_url.events.push(event) if val == '1' && @webhook_url.events.exclude?(event)
end
@webhook_url.save!
head :ok
end
private
def webhook_preferences_params
params.require(:webhook_url).permit(events: {})
end
end
@@ -0,0 +1,22 @@
# frozen_string_literal: true
class WebhookSecretController < ApplicationController
load_and_authorize_resource :webhook_url, parent: false
def show; end
def update
@webhook_url.update!(secret: {
webhook_secret_params[:key] => webhook_secret_params[:value]
}.compact_blank)
redirect_back(fallback_location: settings_webhook_path(@webhook_url),
notice: I18n.t('webhook_secret_has_been_saved'))
end
private
def webhook_secret_params
params.require(:webhook_url).permit(secret: %i[key value]).fetch(:secret, {})
end
end
+38 -13
View File
@@ -1,35 +1,60 @@
# frozen_string_literal: true
class WebhookSettingsController < ApplicationController
before_action :load_encrypted_config
authorize_resource :encrypted_config, parent: false
load_and_authorize_resource :webhook_url, parent: false, only: %i[index show new create update destroy]
load_and_authorize_resource :webhook_url, only: %i[resend], id_param: :webhook_id
def index
@webhook_urls = @webhook_urls.order(id: :desc)
@webhook_url = @webhook_urls.first_or_initialize
render @webhook_urls.size > 1 ? 'index' : 'show'
end
def show; end
def new; end
def create
@encrypted_config.assign_attributes(encrypted_config_params)
@webhook_url.save!
@encrypted_config.value.present? ? @encrypted_config.save! : @encrypted_config.delete
redirect_back(fallback_location: settings_webhooks_path, notice: 'Webhook URL has been saved.')
redirect_to settings_webhooks_path, notice: I18n.t('webhook_url_has_been_saved')
end
def update
@webhook_url.update!(update_params)
redirect_back(fallback_location: settings_webhook_path(@webhook_url),
notice: I18n.t('webhook_url_has_been_updated'))
end
def destroy
@webhook_url.destroy!
redirect_to settings_webhooks_path, notice: I18n.t('webhook_url_has_been_deleted')
end
def resend
submitter = current_account.submitters.where.not(completed_at: nil).order(:id).last
SendFormCompletedWebhookRequestJob.perform_later(submitter)
if submitter.blank? || @webhook_url.blank?
return redirect_back(fallback_location: settings_webhooks_path,
alert: I18n.t('unable_to_resend_webhook_request'))
end
redirect_back(fallback_location: settings_webhooks_path, notice: 'Webhook request has been sent.')
SendFormCompletedWebhookRequestJob.perform_async('submitter_id' => submitter.id,
'webhook_url_id' => @webhook_url.id)
redirect_back(fallback_location: settings_webhooks_path, notice: I18n.t('webhook_request_has_been_sent'))
end
private
def load_encrypted_config
@encrypted_config =
current_account.encrypted_configs.find_or_initialize_by(key: EncryptedConfig::WEBHOOK_URL_KEY)
def create_params
params.require(:webhook_url).permit(:url, events: []).reverse_merge(events: [])
end
def encrypted_config_params
params.require(:encrypted_config).permit(:value)
def update_params
params.require(:webhook_url).permit(:url)
end
end
+81 -21
View File
@@ -3,8 +3,10 @@ import { encodeMethodIntoRequestBody } from '@hotwired/turbo-rails/app/javascrip
import { createApp, reactive } from 'vue'
import TemplateBuilder from './template_builder/builder'
import ImportList from './template_builder/import_list'
import ToggleVisible from './elements/toggle_visible'
import ToggleCookies from './elements/toggle_cookies'
import DisableHidden from './elements/disable_hidden'
import TurboModal from './elements/turbo_modal'
import FileDropzone from './elements/file_dropzone'
@@ -21,6 +23,14 @@ import SignatureForm from './elements/signature_form'
import SubmitForm from './elements/submit_form'
import PromptPassword from './elements/prompt_password'
import EmailsTextarea from './elements/emails_textarea'
import ToggleOnSubmit from './elements/toggle_on_submit'
import PasswordInput from './elements/password_input'
import SearchInput from './elements/search_input'
import ToggleAttribute from './elements/toggle_attribute'
import LinkedInput from './elements/linked_input'
import CheckboxGroup from './elements/checkbox_group'
import MaskedInput from './elements/masked_input'
import SetDateButton from './elements/set_date_button'
import * as TurboInstantClick from './lib/turbo_instant_click'
@@ -38,24 +48,6 @@ document.addEventListener('keyup', (e) => {
}
})
window.customElements.define('toggle-visible', ToggleVisible)
window.customElements.define('disable-hidden', DisableHidden)
window.customElements.define('turbo-modal', TurboModal)
window.customElements.define('file-dropzone', FileDropzone)
window.customElements.define('menu-active', MenuActive)
window.customElements.define('clipboard-copy', ClipboardCopy)
window.customElements.define('dynamic-list', DynamicList)
window.customElements.define('download-button', DownloadButton)
window.customElements.define('set-origin-url', SetOriginUrl)
window.customElements.define('set-timezone', SetTimezone)
window.customElements.define('autoresize-textarea', AutoresizeTextarea)
window.customElements.define('submitters-autocomplete', SubmittersAutocomplete)
window.customElements.define('folder-autocomplete', FolderAutocomplete)
window.customElements.define('signature-form', SignatureForm)
window.customElements.define('submit-form', SubmitForm)
window.customElements.define('prompt-password', PromptPassword)
window.customElements.define('emails-textarea', EmailsTextarea)
document.addEventListener('turbo:before-fetch-request', encodeMethodIntoRequestBody)
document.addEventListener('turbo:submit-end', async (event) => {
const resp = event.detail?.formSubmission?.result?.fetchResponse?.response
@@ -79,8 +71,39 @@ document.addEventListener('turbo:submit-end', async (event) => {
URL.revokeObjectURL(url)
})
window.customElements.define('template-builder', class extends HTMLElement {
const safeRegisterElement = (name, element, options = {}) => !window.customElements.get(name) && window.customElements.define(name, element, options)
safeRegisterElement('toggle-visible', ToggleVisible)
safeRegisterElement('disable-hidden', DisableHidden)
safeRegisterElement('turbo-modal', TurboModal)
safeRegisterElement('file-dropzone', FileDropzone)
safeRegisterElement('menu-active', MenuActive)
safeRegisterElement('clipboard-copy', ClipboardCopy)
safeRegisterElement('dynamic-list', DynamicList)
safeRegisterElement('download-button', DownloadButton)
safeRegisterElement('set-origin-url', SetOriginUrl)
safeRegisterElement('set-timezone', SetTimezone)
safeRegisterElement('autoresize-textarea', AutoresizeTextarea)
safeRegisterElement('submitters-autocomplete', SubmittersAutocomplete)
safeRegisterElement('folder-autocomplete', FolderAutocomplete)
safeRegisterElement('signature-form', SignatureForm)
safeRegisterElement('submit-form', SubmitForm)
safeRegisterElement('prompt-password', PromptPassword)
safeRegisterElement('emails-textarea', EmailsTextarea)
safeRegisterElement('toggle-cookies', ToggleCookies)
safeRegisterElement('toggle-on-submit', ToggleOnSubmit)
safeRegisterElement('password-input', PasswordInput)
safeRegisterElement('search-input', SearchInput)
safeRegisterElement('toggle-attribute', ToggleAttribute)
safeRegisterElement('linked-input', LinkedInput)
safeRegisterElement('checkbox-group', CheckboxGroup)
safeRegisterElement('masked-input', MaskedInput)
safeRegisterElement('set-date-button', SetDateButton)
safeRegisterElement('template-builder', class extends HTMLElement {
connectedCallback () {
document.addEventListener('turbo:submit-end', this.onSubmit)
this.appElem = document.createElement('div')
this.appElem.classList.add('md:h-screen')
@@ -88,15 +111,52 @@ window.customElements.define('template-builder', class extends HTMLElement {
this.app = createApp(TemplateBuilder, {
template: reactive(JSON.parse(this.dataset.template)),
backgroundColor: '#faf7f5',
locale: this.dataset.locale,
withPhone: this.dataset.withPhone === 'true',
withVerification: ['true', 'false'].includes(this.dataset.withVerification) ? this.dataset.withVerification === 'true' : null,
withLogo: this.dataset.withLogo !== 'false',
editable: this.dataset.editable !== 'false',
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
withPayment: this.dataset.withPayment === 'true',
isPaymentConnected: this.dataset.isPaymentConnected === 'true',
withFormula: this.dataset.withFormula === 'true',
withSendButton: this.dataset.withSendButton !== 'false',
withSignYourselfButton: this.dataset.withSignYourselfButton !== 'false',
withConditions: this.dataset.withConditions === 'true',
currencies: (this.dataset.currencies || '').split(',').filter(Boolean),
acceptFileTypes: this.dataset.acceptFileTypes,
isDirectUpload: this.dataset.isDirectUpload === 'true'
acceptFileTypes: this.dataset.acceptFileTypes
})
this.component = this.app.mount(this.appElem)
this.appendChild(this.appElem)
}
onSubmit = (e) => {
if (e.detail.success && e.detail?.formSubmission?.formElement?.id === 'submitters_form') {
e.detail.fetchResponse.response.json().then((data) => {
this.component.template.submitters = data.submitters
})
}
}
disconnectedCallback () {
document.removeEventListener('turbo:submit-end', this.onSubmit)
this.app?.unmount()
this.appElem?.remove()
}
})
safeRegisterElement('import-list', class extends HTMLElement {
connectedCallback () {
this.appElem = document.createElement('div')
this.app = createApp(ImportList, {
template: JSON.parse(this.dataset.template),
multitenant: this.dataset.multitenant === 'true',
authenticityToken: document.querySelector('meta[name="csrf-token"]')?.content,
i18n: JSON.parse(this.dataset.i18n || '{}')
})
this.app.mount(this.appElem)
+24 -6
View File
@@ -83,6 +83,11 @@ button[disabled] .enabled {
bottom: auto;
}
.tooltip-pre:before {
white-space: pre;
text-align: left;
}
.tooltip-bottom-end:after {
transform: translateX(-25%);
border-color: transparent transparent var(--tooltip-color) transparent;
@@ -92,10 +97,27 @@ button[disabled] .enabled {
bottom: auto;
}
.tooltip-bottom-start:before {
transform: translateX(-31%);
top: var(--tooltip-offset);
left: 100%;
right: auto;
bottom: auto;
}
.tooltip-bottom-start:after {
transform: translateX(-25%);
border-color: transparent transparent var(--tooltip-color) transparent;
top: var(--tooltip-tail-offset);
left: 50%;
right: auto;
bottom: auto;
}
.autocomplete {
background: white;
z-index: 1000;
font: 14px/22px "-apple-system", BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font: 16px/25px "-apple-system", BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
overflow: auto;
box-sizing: border-box;
@apply border border-base-300 mt-1 rounded-md;
@@ -106,7 +128,7 @@ button[disabled] .enabled {
}
.autocomplete > div {
@apply px-2 py-1 font-normal text-sm;
@apply px-2 py-1.5 font-normal;
}
.autocomplete .group {
@@ -125,7 +147,3 @@ button[disabled] .enabled {
outline-offset: 3px;
outline-color: hsl(var(--bc) / 0.2);
}
select:required:invalid {
color: gray !important;
}
+118
View File
@@ -0,0 +1,118 @@
import SignaturePad from 'signature_pad'
import { cropCanvasAndExportToPNG } from './submission_form/crop_canvas'
window.customElements.define('draw-signature', class extends HTMLElement {
connectedCallback () {
const scale = 3
this.canvas.width = this.canvas.parentNode.clientWidth * scale
this.canvas.height = this.canvas.parentNode.clientHeight * scale
this.canvas.getContext('2d').scale(scale, scale)
this.pad = new SignaturePad(this.canvas)
if (this.dataset.color) {
this.pad.penColor = this.dataset.color
}
this.pad.addEventListener('endStroke', () => {
this.updateSubmitButtonVisibility()
})
this.clearButton.addEventListener('click', (e) => {
e.preventDefault()
this.clearSignaturePad()
})
this.form.addEventListener('submit', (e) => {
e.preventDefault()
this.submitButton.disabled = true
this.submitImage().then((data) => {
this.valueInput.value = data.uuid
return fetch(this.form.action, {
method: 'PUT',
body: new FormData(this.form)
}).then((response) => {
this.form.classList.add('hidden')
this.success.classList.remove('hidden')
return response
})
}).finally(() => {
this.submitButton.disabled = false
})
})
}
clearSignaturePad () {
this.pad.clear()
this.updateSubmitButtonVisibility()
}
updateSubmitButtonVisibility () {
if (this.pad.isEmpty()) {
this.submitButton.style.display = 'none'
this.placeholderButton.style.display = 'block'
} else {
this.submitButton.style.display = 'block'
this.placeholderButton.style.display = 'none'
}
}
async submitImage () {
return new Promise((resolve, reject) => {
cropCanvasAndExportToPNG(this.canvas, { errorOnTooSmall: true }).then(async (blob) => {
const file = new File([blob], 'signature.png', { type: 'image/png' })
const formData = new FormData()
formData.append('file', file)
formData.append('submitter_slug', this.dataset.slug)
formData.append('name', 'attachments')
formData.append('remember_signature', 'true')
return fetch('/api/attachments', {
method: 'POST',
body: formData
}).then((resp) => resp.json()).then((attachment) => {
return resolve(attachment)
})
}).catch((error) => {
return reject(error)
})
})
}
get submitButton () {
return this.querySelector('button[type="submit"]')
}
get clearButton () {
return this.querySelector('button[aria-label="Clear"]')
}
get placeholderButton () {
return this.querySelector('button[disabled]')
}
get canvas () {
return this.querySelector('canvas')
}
get valueInput () {
return this.querySelector('input[name^="values"]')
}
get form () {
return this.querySelector('form')
}
get success () {
return this.querySelector('#success')
}
})
@@ -7,7 +7,7 @@ export default class extends HTMLElement {
resize () {
if (this.textarea.clientHeight < this.textarea.scrollHeight) {
this.textarea.style.height = `${this.textarea.scrollHeight}px`
this.textarea.style.height = `${this.textarea.scrollHeight + 5}px`
}
}
+15
View File
@@ -0,0 +1,15 @@
export default class extends HTMLElement {
connectedCallback () {
this.items.forEach((item) => {
item.addEventListener('change', (e) => {
this.items.forEach((item) => {
item.checked = item === e.target && e.target.checked
})
})
})
}
get items () {
return this.querySelectorAll('input[type="checkbox"]')
}
}
+13 -8
View File
@@ -17,13 +17,18 @@ export default targetable(class extends HTMLElement {
this.toggleState()
fetch(this.dataset.src).then((response) => response.json()).then((urls) => {
const isSafariIos = /iPhone|iPad|iPod/i.test(navigator.userAgent)
fetch(this.dataset.src).then(async (response) => {
if (response.ok) {
const urls = await response.json()
const isSafariIos = /iPhone|iPad|iPod/i.test(navigator.userAgent)
if (isSafariIos && urls.length > 1) {
this.downloadSafariIos(urls)
if (isSafariIos && urls.length > 1) {
this.downloadSafariIos(urls)
} else {
this.downloadUrls(urls)
}
} else {
this.downloadUrls(urls)
alert('Failed to download files')
}
})
}
@@ -48,9 +53,9 @@ export default targetable(class extends HTMLElement {
fileRequests.reduce(
(prevPromise, request) => prevPromise.then(() => request()),
Promise.resolve()
)
this.toggleState()
).finally(() => {
this.toggleState()
})
}
downloadSafariIos (urls) {
+3 -57
View File
@@ -9,10 +9,6 @@ export default actionable(targetable(class extends HTMLElement {
]
connectedCallback () {
if (this.dataset.isDirectUpload === 'true') {
import('@rails/activestorage')
}
this.addEventListener('drop', this.onDrop)
this.addEventListener('dragover', (e) => e.preventDefault())
@@ -48,61 +44,11 @@ export default actionable(targetable(class extends HTMLElement {
this.classList.toggle('opacity-50')
}
async uploadFiles (files) {
uploadFiles () {
this.toggleLoading()
if (this.dataset.isDirectUpload === 'true') {
const { DirectUpload } = await import('@rails/activestorage')
await Promise.all(
Array.from(files).map(async (file) => {
const upload = new DirectUpload(
file,
'/direct_uploads',
this.input
)
return new Promise((resolve, reject) => {
upload.create((error, blob) => {
if (error) {
console.error(error)
return reject(error)
} else {
return resolve(blob)
}
})
}).catch((error) => {
console.error(error)
})
})
).then((blobs) => {
if (this.dataset.submitOnUpload) {
this.querySelectorAll('[name="blob_signed_ids[]"]').forEach((e) => e.remove())
}
blobs.forEach((blob) => {
const input = document.createElement('input')
input.type = 'hidden'
input.name = 'blob_signed_ids[]'
input.value = blob.signed_id
this.append(input)
})
if (this.dataset.submitOnUpload === 'true') {
this.closest('form').querySelector('button[type="submit"]').click()
}
}).finally(() => {
if (this.dataset.submitOnUpload !== 'true') {
this.toggleLoading()
}
})
} else {
if (this.dataset.submitOnUpload) {
this.closest('form').querySelector('button[type="submit"]').click()
}
if (this.dataset.submitOnUpload) {
this.closest('form').querySelector('button[type="submit"]').click()
}
}
}))
@@ -20,7 +20,7 @@ export default class extends HTMLElement {
fetch = (text, resolve) => {
const queryParams = new URLSearchParams({ q: text })
fetch('/api/template_folders_autocomplete?' + queryParams).then(async (resp) => {
fetch('/template_folders_autocomplete?' + queryParams).then(async (resp) => {
const items = await resp.json()
resolve(items)
+31
View File
@@ -0,0 +1,31 @@
export default class extends HTMLElement {
connectedCallback () {
if (this.target) {
this.input.value = this.target.value
this.target.addEventListener('input', (e) => {
this.input.value = e.target.value
})
this.target.addEventListener('linked-input.update', (e) => {
this.input.value = e.target.value
})
}
}
get input () {
return this.querySelector('input')
}
get target () {
if (this.dataset.targetId) {
const listItem = this.closest('[data-targets="dynamic-list.items"]')
if (listItem) {
return listItem.querySelector(`#${this.dataset.targetId}`)
} else {
return document.getElementById(this.dataset.targetId)
}
}
}
}
+18
View File
@@ -0,0 +1,18 @@
export default class extends HTMLElement {
connectedCallback () {
const maskedToken = this.input.value
this.input.addEventListener('focus', () => {
this.input.value = this.dataset.token
this.input.select()
})
this.input.addEventListener('focusout', () => {
this.input.value = maskedToken
})
}
get input () {
return this.querySelector('input')
}
}
+35
View File
@@ -0,0 +1,35 @@
import { target, targetable } from '@github/catalyst/lib/targetable'
export default targetable(class extends HTMLElement {
static [target.static] = [
'visiblePasswordIcon',
'hiddenPasswordIcon',
'passwordInput',
'togglePasswordVisibility'
]
connectedCallback () {
this.togglePasswordVisibility.addEventListener('click', this.handleTogglePasswordVisibility)
document.addEventListener('turbo:submit-start', this.setInitialPasswordType)
}
disconnectedCallback () {
this.togglePasswordVisibility.removeEventListener('click', this.handleTogglePasswordVisibility)
document.removeEventListener('turbo:submit-start', this.setInitialPasswordType)
}
handleTogglePasswordVisibility = () => {
this.passwordInput.type = this.passwordInput.type === 'password' ? 'text' : 'password'
this.toggleIcon()
}
setInitialPasswordType = () => {
this.passwordInput.type = 'password'
this.toggleIcon()
}
toggleIcon = () => {
this.visiblePasswordIcon.classList.toggle('hidden', this.passwordInput.type === 'password')
this.hiddenPasswordIcon.classList.toggle('hidden', this.passwordInput.type === 'text')
}
})
+25
View File
@@ -0,0 +1,25 @@
export default class extends HTMLElement {
connectedCallback () {
this.input.addEventListener('focus', () => {
if (this.title) {
this.title.classList.add('hidden', 'md:block')
this.input.classList.add('w-60')
}
})
this.input.addEventListener('blur', (e) => {
if (this.title && !e.target.value) {
this.title.classList.remove('hidden')
this.input.classList.remove('w-60')
}
})
}
get input () {
return this.querySelector('input')
}
get title () {
return document.querySelector(this.dataset.title)
}
}

Some files were not shown because too many files have changed in this diff Show More