Compare commits

..

1513 Commits

Author SHA1 Message Date
Alex Turchyn 861daaedf4 Merge from docusealco/wip 2026-03-09 10:40:29 +02:00
Pete Matsyburka d8430f4e63 fix migration 2026-03-09 09:02:56 +02:00
Pete Matsyburka dd44864485 fix field position 2026-03-06 11:37:53 +02:00
Pete Matsyburka b3bb29f383 fix brakeman 2026-03-05 21:42:55 +02:00
Pete Matsyburka b4d137c0de remove flash 2026-03-05 12:30:26 +02:00
Alex Turchyn 62a969d8fe add MCP support 2026-03-05 12:17:14 +02:00
Pete Matsyburka 961f09e092 set dynamic fields 2026-03-05 10:08:29 +02:00
Pete Matsyburka b2dfa83f18 upload json 2026-03-04 12:27:15 +02:00
Pete Matsyburka 37196ff89f add dynamic documents 2026-03-04 10:13:30 +02:00
Pete Matsyburka b50d982497 smtp timeout env vars 2026-03-03 22:49:55 +02:00
Pete Matsyburka 4a484aca69 hide storage nav if configured with env 2026-03-03 22:01:44 +02:00
Pete Matsyburka 0bf37d571a update color 2026-03-03 18:46:06 +02:00
Alex Turchyn d4e94ac2bc Merge from docusealco/wip 2026-03-02 17:26:04 +02:00
Pete Matsyburka 75bc789bb1 add reply to 2026-03-02 14:47:37 +02:00
Pete Matsyburka bc231d5bf1 add reply to 2026-03-02 14:34:16 +02:00
Alex Turchyn ab9008b3be fix brakeman SQL injection warning 2026-03-01 16:14:15 +02:00
Pete Matsyburka 65491936c3 fix archived export 2026-02-26 17:20:05 +02:00
Pete Matsyburka e589998840 add field type class 2026-02-26 11:08:26 +02:00
Pete Matsyburka 0afba8d11e adjust with prefillable 2026-02-26 11:03:08 +02:00
Pete Matsyburka f622ed168f fix autocomplete 2026-02-25 13:01:03 +02:00
Pete Matsyburka 5a31df40d8 add field settings classes 2026-02-25 11:02:00 +02:00
Pete Matsyburka db3b80c96c fix clone 2026-02-25 11:02:00 +02:00
Pete Matsyburka bd28a761d0 adjust bmp loader 2026-02-25 11:02:00 +02:00
Pete Matsyburka 4f2830a34d increase image resolution 2026-02-25 11:02:00 +02:00
Pete Matsyburka a5716cd518 show html textarea 2026-02-25 11:02:00 +02:00
Pete Matsyburka d035525c7a additional terms 2026-02-25 11:02:00 +02:00
Alex Turchyn 13fa87c449 Merge from docusealco/wip 2026-02-23 13:56:04 +02:00
Pete Matsyburka 246ccfb049 fix highlight 2026-02-23 08:31:38 +02:00
Pete Matsyburka cbffb1e5b8 cloudflare r2 fix 2026-02-22 20:13:19 +02:00
Pete Matsyburka c4b007411d radio select in formula 2026-02-22 19:54:06 +02:00
Pete Matsyburka 04264fe937 fix reason field 2026-02-22 12:15:44 +02:00
Pete Matsyburka 0c37ad12b7 add variables key 2026-02-22 09:46:04 +02:00
Pete Matsyburka 8c5298ed2e refactor link tooltip 2026-02-22 09:28:30 +02:00
Pete Matsyburka 262118e047 update deps 2026-02-22 07:37:05 +02:00
Pete Matsyburka e5b63ea2b0 fix multi party email form 2026-02-21 17:31:04 +02:00
Pete Matsyburka 746757d402 rtl in editor 2026-02-21 16:38:25 +02:00
Pete Matsyburka e6e640328b use br for email content 2026-02-21 16:20:48 +02:00
Pete Matsyburka 845782a69c link input width 2026-02-21 15:48:59 +02:00
Pete Matsyburka fc6baa1b3b adjust autolink 2026-02-21 15:48:58 +02:00
Pete Matsyburka 47822ecc15 remove autolink 2026-02-21 14:55:11 +02:00
Pete Matsyburka 3d0c7f1118 markdown in preferences 2026-02-21 14:44:19 +02:00
Pete Matsyburka e9c8e4d325 adjust editor 2026-02-21 14:32:10 +02:00
Alex Turchyn 39cc82ce0c add markdown editor 2026-02-21 11:15:09 +02:00
Pete Matsyburka 451f83421a fix cell render 2026-02-21 10:49:05 +02:00
Pete Matsyburka 5073f2eefb adjsut sign yourself 2026-02-21 10:17:58 +02:00
Pete Matsyburka 231fff5508 canvas blocked message 2026-02-21 09:53:03 +02:00
Pete Matsyburka f1d146eca3 fix typos 2026-02-20 22:39:03 +02:00
Pete Matsyburka b6635fcc4f change user only when root 2026-02-20 22:25:12 +02:00
Pete Matsyburka fb5e13ee4c adjust recipients form 2026-02-20 18:26:10 +02:00
Pete Matsyburka ba84741a64 invite party via field 2026-02-20 12:41:45 +02:00
Pete Matsyburka 65c275ac17 rename 2026-02-20 10:16:06 +02:00
Pete Matsyburka 825322d489 adjust for custom domain 2026-02-20 09:14:36 +02:00
Pete Matsyburka 739e2abdf8 vips size limit 2026-02-19 14:24:05 +02:00
Pete Matsyburka 1d2394e31e size limit 2026-02-19 14:17:59 +02:00
Pete Matsyburka 1b41af798d update 2026-02-17 19:42:47 +02:00
Pete Matsyburka 118f4a231b detailed time format 2026-02-17 18:09:34 +02:00
Pete Matsyburka e48652f425 fix rubocop 2026-02-17 10:29:00 +02:00
Pete Matsyburka 848f01edf8 add spec 2026-02-17 10:19:29 +02:00
Pete Matsyburka 43fbc42770 fix reason field 2026-02-17 10:04:11 +02:00
Pete Matsyburka 2c736a0eed validate readonly field 2026-02-17 08:53:13 +02:00
Alex Turchyn 33ca930055 Merge from docusealco/wip
Wip
2026-02-16 15:08:07 +02:00
Pete Matsyburka 3b396f8421 fix redirect 2026-02-16 10:00:51 +02:00
Pete Matsyburka aa77e3a8d3 adjust port check 2026-02-16 08:08:41 +02:00
Pete Matsyburka 75316d8d87 fix draw custom field 2026-02-15 22:02:00 +02:00
Pete Matsyburka a999109a5c add port check 2026-02-15 12:18:53 +02:00
Pete Matsyburka bdd33c7d6b fix spec 2026-02-15 09:53:31 +02:00
Pete Matsyburka 61c5ee22a0 hide form credentials 2026-02-15 09:11:40 +02:00
Pete Matsyburka 12c5b909e0 use cancan 2026-02-14 19:13:05 +02:00
Pete Matsyburka 871ef6dda6 add fetch options 2026-02-14 18:50:48 +02:00
Pete Matsyburka 40052a2d7c use query_params 2026-02-14 17:42:08 +02:00
Pete Matsyburka 347be0137d refactor 2fa 2026-02-14 17:35:10 +02:00
Pete Matsyburka fe6baba8bf fix erb lint 2026-02-14 12:10:21 +02:00
Pete Matsyburka ed8c313bd4 timestamp controller multitenant 2026-02-14 12:02:37 +02:00
Pete Matsyburka ca0acb34d6 use url for open modal 2026-02-14 12:02:31 +02:00
Pete Matsyburka 34ea639c25 escape wildcard query 2026-02-14 12:02:25 +02:00
Pete Matsyburka 680ab9dbed raise invalid param 2026-02-14 10:18:58 +02:00
Pete Matsyburka 9377766e52 fix url download 2026-02-14 09:08:52 +02:00
Pete Matsyburka 7a2c37454e update favicon 2026-02-12 15:56:54 +02:00
Pete Matsyburka 8f6f418f54 fix font size 2026-02-12 14:27:15 +02:00
Pete Matsyburka 9b7745c565 hide resend on archived 2026-02-11 17:32:04 +02:00
Alex Turchyn a389e8cc38 Merge from docusealco/wip 2026-02-11 16:49:32 +02:00
Pete Matsyburka 2056301f70 remember send tab selection 2026-02-11 13:14:09 +02:00
Pete Matsyburka 93babe3660 update logo 2026-02-11 10:01:12 +02:00
Pete Matsyburka 4aef2d8eb0 update deps 2026-02-10 09:02:37 +02:00
Pete Matsyburka c5ec2ed230 fix document condition 2026-02-05 11:56:22 +02:00
Pete Matsyburka a156d5f6cc increase webhook timeout 2026-02-05 08:42:53 +02:00
Pete Matsyburka f84674cddd adjust field focus 2026-02-04 17:05:12 +02:00
Pete Matsyburka b7b62c197a fix date field 2026-02-04 16:56:35 +02:00
Pete Matsyburka f5350eac36 prefill typed signature and initials from name 2026-02-04 16:48:40 +02:00
Pete Matsyburka ee6228db15 only required field prop 2026-02-04 10:51:11 +02:00
Pete Matsyburka 3e434c0878 fix 2026-02-04 00:47:34 +02:00
Pete Matsyburka 7c5372a8e2 fix pdf sign 2026-02-02 22:04:43 +02:00
Alex Turchyn 6ab68cc36b Merge from docusealco/wip 2026-02-02 14:13:54 +02:00
Pete Matsyburka ee2400a5c5 touch signature attachment 2026-02-02 12:46:34 +02:00
Pete Matsyburka 80be5d2cf5 sandbox 2026-02-01 08:10:02 +02:00
Pete Matsyburka 76cf44a31b fix 2026-01-31 22:17:11 +02:00
Pete Matsyburka eb63113227 use trilogy mysql 2026-01-31 22:15:17 +02:00
Pete Matsyburka b7045fb3d9 fix azure proxy 2026-01-31 13:07:42 +02:00
Pete Matsyburka 11a5b5842d starttls label 2026-01-31 11:41:52 +02:00
Pete Matsyburka 362994afd5 adjust i18n 2026-01-31 10:26:09 +02:00
Pete Matsyburka d7682f4ebd add number conditions 2026-01-31 09:00:24 +02:00
Pete Matsyburka 56d4fe7fa9 add white color 2026-01-31 07:58:12 +02:00
Pete Matsyburka 93a852ac14 fix save modal 2026-01-30 21:45:55 +02:00
Pete Matsyburka 964db0ab08 fix menu 2026-01-30 21:03:56 +02:00
Pete Matsyburka 8547295da7 border color 2026-01-30 20:53:47 +02:00
Pete Matsyburka c75050fa46 adjust kba 2026-01-30 18:17:47 +02:00
Pete Matsyburka 65372c52aa fix order 2026-01-30 17:42:50 +02:00
Alex Turchyn 12c767b7d3 add more options to context menu 2026-01-30 16:36:50 +02:00
Pete Matsyburka 2fc5e9cfb3 format cells 2026-01-30 11:21:47 +02:00
Pete Matsyburka bccc78ee65 optimize builder 2026-01-30 10:23:51 +02:00
Pete Matsyburka c0793f3ce7 cell font settings 2026-01-29 21:43:01 +02:00
Pete Matsyburka a8dd8699a3 adjust kba 2026-01-29 15:55:15 +02:00
Pete Matsyburka 91bb25e8b6 fix reload 2026-01-29 15:33:24 +02:00
Pete Matsyburka c3f4480cdc add submitter index 2026-01-29 13:01:44 +02:00
Pete Matsyburka 33e226861e add SMTP_SSL_VERIFY env 2026-01-29 11:34:37 +02:00
Pete Matsyburka 4f5acbec9d readonly radio 2026-01-28 21:36:48 +02:00
Pete Matsyburka d2c9bbf6ab optimize conditions 2026-01-28 13:24:05 +02:00
Pete Matsyburka b5e57e1f2f add include fields 2026-01-28 11:49:35 +02:00
Pete Matsyburka c82c8b2272 fix race condition 2026-01-28 08:50:44 +02:00
Pete Matsyburka 43f1b598a7 adjust smtp noverify 2026-01-28 08:27:25 +02:00
Pete Matsyburka 5081e387d0 fix undefined 2026-01-27 20:13:26 +02:00
Pete Matsyburka 1e5f55c1d6 fix Point is invalid 2026-01-27 20:07:57 +02:00
Alex Turchyn aa38773be3 Merge from docusealco/wip 2026-01-27 18:39:04 +02:00
Pete Matsyburka 96530bd809 fix race condition 2026-01-27 17:06:30 +02:00
Pete Matsyburka 127f95a9d0 fix typo 2026-01-27 12:31:06 +02:00
Pete Matsyburka cf31e12348 adjust smtp configs 2026-01-27 10:57:04 +02:00
Pete Matsyburka 4adaa531b7 fix pagination 2026-01-27 10:34:18 +02:00
Pete Matsyburka c7659563b8 fix puma warnings 2026-01-26 20:23:11 +02:00
Alex Turchyn 7b5494468f update rails to 8.1.2 2026-01-26 18:22:47 +02:00
Pete Matsyburka d12c67fd54 with force open 2026-01-26 18:00:46 +02:00
Pete Matsyburka e20eacb5ee center on click 2026-01-26 12:35:53 +02:00
Pete Matsyburka 98055f0762 add can parse polyfill 2026-01-26 12:17:57 +02:00
Pete Matsyburka a46c42c3a0 fix resize warning 2026-01-26 12:06:41 +02:00
Pete Matsyburka c3d1503361 cleanup 2026-01-26 11:44:12 +02:00
Pete Matsyburka 65b47d27cd adjust form 2026-01-26 11:38:33 +02:00
Pete Matsyburka 968c07f8be include idcode 2026-01-26 11:13:01 +02:00
Pete Matsyburka 91814ca105 custom fields 2026-01-26 11:12:55 +02:00
Pete Matsyburka 9128f24270 fix synology acl 2026-01-24 18:01:49 +02:00
Pete Matsyburka 264897bbd5 audit with sender 2026-01-23 12:12:37 +02:00
Pete Matsyburka 67d59ff1ba fix invite by order 2026-01-22 21:20:34 +02:00
Pete Matsyburka 81eee56e1e i18n 2026-01-21 20:14:53 +02:00
Pete Matsyburka 92ea8af786 fix options drag 2026-01-21 16:02:20 +02:00
Pete Matsyburka df0616f77e paste options 2026-01-21 15:42:38 +02:00
Pete Matsyburka a337dff85c change model 2026-01-21 10:23:26 +02:00
Pete Matsyburka 48e349c1d2 fix signature pad when resize 2026-01-20 17:24:05 +02:00
Pete Matsyburka df9fa0fec7 fix scroll into field 2026-01-20 15:27:35 +02:00
Alex Turchyn b3e72f0726 Merge from docusealco/wip 2026-01-19 15:40:56 +02:00
Pete Matsyburka 700ebc4727 render reason 2026-01-19 13:56:17 +02:00
Pete Matsyburka 745a18efca fix select mode draw 2026-01-19 11:57:09 +02:00
Pete Matsyburka b4a8147ff0 fix radio group paste 2026-01-19 11:51:00 +02:00
Pete Matsyburka 0543c25054 detect fields in context menu 2026-01-19 09:23:04 +02:00
Pete Matsyburka b1a0afea66 fix menu 2026-01-18 10:29:15 +02:00
Pete Matsyburka 43c41e2557 add select fields mode 2026-01-18 09:52:43 +02:00
Pete Matsyburka bc8eb33f05 Revert "hide area name menu on desktop"
This reverts commit 5212773d0a.
2026-01-17 19:42:27 +02:00
Pete Matsyburka d1cebc92f7 fix eslint 2026-01-16 12:38:29 +02:00
Pete Matsyburka bf02cf4058 fix paste 2026-01-16 12:25:50 +02:00
Pete Matsyburka 5212773d0a hide area name menu on desktop 2026-01-16 12:18:17 +02:00
Alex Turchyn bf53204a41 add context menu to template builder 2026-01-16 12:07:59 +02:00
Pete Matsyburka 092a19966c adjust condition 2026-01-15 17:34:05 +02:00
Pete Matsyburka 6060a2b798 fix icon 2026-01-14 21:58:30 +02:00
Pete Matsyburka ce12af68b2 fix required 2026-01-14 14:32:00 +02:00
Pete Matsyburka c05f8d47aa require 2fa 2026-01-14 12:44:27 +02:00
Pete Matsyburka f8be19c20e pg 18 2026-01-13 20:44:52 +02:00
Pete Matsyburka 2fe32a2145 show version 2026-01-13 12:25:08 +02:00
Pete Matsyburka 794ea4d0e8 remove expired from pending filter 2026-01-13 12:07:39 +02:00
Pete Matsyburka 307b0f5afd fix build 2026-01-13 09:44:58 +02:00
Alex Turchyn 9011e3f4b4 Merge from docusealco/wip 2026-01-12 13:25:54 +02:00
Pete Matsyburka 0b45ce5c0c update model 2026-01-12 12:05:54 +02:00
Pete Matsyburka a8ab93a4cc adjust sort 2026-01-12 11:27:19 +02:00
Pete Matsyburka d1768be636 secure env 2026-01-12 11:27:17 +02:00
Pete Matsyburka b2cd7c82a7 insert new field at page position 2026-01-11 14:45:42 +02:00
Pete Matsyburka b88d99afe0 merge preview docs 2026-01-11 11:09:00 +02:00
Pete Matsyburka 4523d38551 direct links option 2026-01-09 22:05:37 +02:00
Pete Matsyburka c4ba892559 refactor clone 2026-01-08 09:34:45 +02:00
Pete Matsyburka 40f8093e00 adjust onnx 2026-01-07 14:50:27 +02:00
Pete Matsyburka 4375459856 add nmm 2026-01-07 12:19:05 +02:00
Pete Matsyburka b3eb8a70bf fix submitter uuid 2026-01-07 00:01:47 +02:00
Pete Matsyburka 6245a487b6 add kba countdown 2026-01-06 11:24:30 +02:00
Pete Matsyburka a89f4c1b10 adjust trim 2026-01-06 10:22:34 +02:00
Pete Matsyburka 7df729b8b5 containment threshold 2026-01-06 09:04:25 +02:00
Pete Matsyburka f1db7c7e82 adjust trim 2026-01-06 08:54:29 +02:00
Alex Turchyn 00af69cbf2 Merge from docusealco/wip 2026-01-05 21:46:19 +02:00
Pete Matsyburka c6819a19bd adjust inference 2026-01-05 18:36:50 +02:00
Pete Matsyburka f5cbcb3d24 adjust kba 2026-01-05 13:45:41 +02:00
Pete Matsyburka 6ca75c53ef fix condition 2026-01-05 12:46:43 +02:00
Pete Matsyburka e0ec443e10 fix mysql migration 2026-01-05 12:34:50 +02:00
Pete Matsyburka 9eed2fc2ef fix 2026-01-05 12:21:34 +02:00
Pete Matsyburka b9c6d3ff9f model v2 2026-01-05 09:18:38 +02:00
Pete Matsyburka 03c25a89b2 fix save 2026-01-04 13:16:57 +02:00
Pete Matsyburka f5b4a0c5ab inference v2 2026-01-03 16:55:05 +02:00
Pete Matsyburka b10ed46ccc add kba 2026-01-02 17:02:45 +02:00
Pete Matsyburka c55036a1b1 update gem 2025-12-31 23:27:41 +02:00
Pete Matsyburka 962a5dade5 add preview url 2025-12-31 22:32:27 +02:00
Pete Matsyburka 369a09a6df fix upload 2025-12-26 12:13:30 +02:00
Pete Matsyburka e9a39e2571 add s3 endpoint variable 2025-12-25 14:03:57 +02:00
Pete Matsyburka 1cf9cc18f1 do not retry 2025-12-23 10:17:19 +02:00
Pete Matsyburka 1984d4ec8d fix sync 2025-12-22 20:31:00 +02:00
Alex Turchyn fa99b4ebdc Merge from docusealco/wip 2025-12-22 02:27:47 +02:00
Pete Matsyburka 83c793a2a5 reorder field options 2025-12-21 16:12:57 +02:00
Pete Matsyburka d078727070 recipient fields condition 2025-12-21 13:19:16 +02:00
Pete Matsyburka 0d8e2c5ff0 adjust signature size 2025-12-20 17:12:15 +02:00
Pete Matsyburka ea062d7494 update gem 2025-12-19 12:26:01 +02:00
Pete Matsyburka d525e0597a recipient fields config 2025-12-19 12:26:00 +02:00
Pete Matsyburka 964b5d4e74 validate webhook 2025-12-18 12:45:33 +02:00
Pete Matsyburka abf17eb09f localhost list 2025-12-18 12:12:40 +02:00
Pete Matsyburka 178809dad7 fix field render 2025-12-16 21:54:36 +02:00
Pete Matsyburka 881d189715 fix phone 2025-12-16 12:34:18 +02:00
Pete Matsyburka 48a8973910 add with field labels 2025-12-16 11:38:37 +02:00
Alex Turchyn ae891932c5 Merge from docusealco/wip 2025-12-15 13:19:09 +02:00
Alex Turchyn a8b5e00814 show field boxes on the submissions page 2025-12-15 12:05:37 +02:00
Alex Turchyn e98432d7e4 update docs 2025-12-15 11:11:00 +02:00
Pete Matsyburka 29ff58ed98 fix drawing 2025-12-15 10:22:27 +02:00
Pete Matsyburka a621575ffc 11k 2025-12-14 22:24:07 +02:00
Alex Turchyn d7c7efc203 update brakeman ignore rules 2025-12-12 21:44:54 +02:00
Pete Matsyburka 4f013ca927 do not combine with verification 2025-12-12 20:39:18 +02:00
Pete Matsyburka d9823feef7 fix option 2025-12-12 09:22:42 +02:00
Pete Matsyburka 676d775544 fix generation 2025-12-12 09:14:23 +02:00
Pete Matsyburka 840ad17258 fix locale 2025-12-11 11:11:46 +02:00
Alex Turchyn ce0cd09b70 add translations 2025-12-11 10:28:35 +02:00
Alex Turchyn bb752fbc00 update translations 2025-12-11 10:28:07 +02:00
Pete Matsyburka c71fb04404 fix mysql migration 2025-12-10 13:43:34 +02:00
Pete Matsyburka e856cf8787 validate redirect url 2025-12-10 09:13:55 +02:00
Pete Matsyburka 71aae65bc6 add require email 2fa param 2025-12-09 11:46:57 +02:00
Pete Matsyburka 7a33444f9b add invite_by 2025-12-09 11:39:15 +02:00
Pete Matsyburka e12ec9d986 fix build 2025-12-08 16:26:45 +02:00
Alex Turchyn 94d1fb7dcb Merge from docusealco/wip 2025-12-08 15:31:19 +02:00
Alex Turchyn 6e43a42274 add email 2FA 2025-12-08 14:51:49 +02:00
Pete Matsyburka 0e0aa0259e add validate prop 2025-12-07 15:31:20 +02:00
Pete Matsyburka 75ab6d39ef adjust unarchive 2025-12-07 14:57:21 +02:00
Pete Matsyburka 6f84d84f96 use newer model 2025-12-05 15:17:50 +02:00
Pete Matsyburka cf46593e9b fix required checbox 2025-12-04 21:41:36 +02:00
Pete Matsyburka 1808346348 fix build 2025-12-04 15:38:55 +02:00
Pete Matsyburka ea59a68499 adjust active job log 2025-12-04 14:54:55 +02:00
Pete Matsyburka cba2d02990 skip webhook not found 2025-12-03 13:20:52 +02:00
Pete Matsyburka e92fc0d3e1 adjust mobile field types 2025-12-03 10:33:22 +02:00
Pete Matsyburka f1eaa77092 password reset i18n 2025-12-02 14:25:45 +02:00
Alex Turchyn 5934bdec55 use set_reset_password_token 2025-12-02 14:00:51 +02:00
Alex Turchyn 7a7d3c6ee3 add translations for reset password instructions email 2025-12-02 14:00:51 +02:00
Pete Matsyburka cb2f58c04c fix i18n 2025-12-02 13:31:35 +02:00
Pete Matsyburka a1c117f1ca add i18n 2025-12-02 12:50:58 +02:00
Alex Turchyn 1aacd98460 Merge from docusealco/wip 2025-12-01 17:09:47 +02:00
Pete Matsyburka 840162c9ca fix sort 2025-12-01 10:37:58 +02:00
Pete Matsyburka 3519b0e009 fix typo 2025-12-01 10:15:23 +02:00
Pete Matsyburka 53fae56c51 fix regexp field types 2025-12-01 10:09:27 +02:00
Pete Matsyburka f4552dbaa8 skip reconfirmation 2025-11-30 16:30:56 +02:00
Pete Matsyburka dfab4c331c send confirmation 2025-11-30 16:04:30 +02:00
Pete Matsyburka d2a0937038 fix pdf text emoji 2025-11-30 14:37:05 +02:00
Pete Matsyburka fa1ef44b22 add tabs 2025-11-30 12:41:16 +02:00
Pete Matsyburka c1a5c91299 adjust detection 2025-11-30 11:11:08 +02:00
Pete Matsyburka e6280e8f5a optimize text nodes 2025-11-29 18:28:10 +02:00
Pete Matsyburka fd2ba57325 adjust invitation 2025-11-29 10:34:13 +02:00
Pete Matsyburka dc178ef03f adjust router 2025-11-28 16:15:57 +02:00
Alex Turchyn 68936e10c5 add confirmation columns 2025-11-28 15:57:36 +02:00
Pete Matsyburka 41f16e7d39 adjust detection 2025-11-27 17:00:26 +02:00
Pete Matsyburka 453ebbf90a adjust start form 2025-11-27 12:23:45 +02:00
Pete Matsyburka 7ca290c080 adjust detection 2025-11-25 13:43:35 +02:00
Alex Turchyn dd5b6a6aa6 Merge from docusealco/wip 2025-11-24 17:43:59 +02:00
Pete Matsyburka bf2ebb995b add tz info 2025-11-24 15:46:50 +02:00
Alex Turchyn 3e929758fb add the reports page 2025-11-24 15:46:04 +02:00
Pete Matsyburka 4a53116586 adjust links 2025-11-24 12:28:13 +02:00
Pete Matsyburka 2d1981acfa adjust billing link 2025-11-24 11:13:00 +02:00
Pete Matsyburka c610df269f adjust populate 2025-11-22 13:02:58 +02:00
Pete Matsyburka 96dac635f5 add us timezones 2025-11-21 17:50:04 +02:00
Pete Matsyburka abd579103d add index 2025-11-21 13:41:51 +02:00
Pete Matsyburka 7ed27bb413 populate columns 2025-11-21 12:33:43 +02:00
Pete Matsyburka 180c542a9f add index 2025-11-21 11:45:19 +02:00
Pete Matsyburka 89613113de set is first completed submitter 2025-11-21 11:31:02 +02:00
Pete Matsyburka 3102900776 add is first to completed submitters 2025-11-21 11:27:28 +02:00
Pete Matsyburka 1a68053f4e set submission event account id 2025-11-21 11:18:28 +02:00
Pete Matsyburka 5d4a032763 add submission event account id 2025-11-21 11:17:20 +02:00
Pete Matsyburka 7481d27822 add tsa timeout 2025-11-18 22:17:05 +02:00
Alex Turchyn 22afd01fd2 Merge from docusealco/wip 2025-11-17 13:20:44 +02:00
Pete Matsyburka fd9eb09dd6 use can? 2025-11-17 10:21:38 +02:00
Alex Turchyn c53ed7f984 add custom invitation reminder emails 2025-11-17 10:05:52 +02:00
Pete Matsyburka 1d87e210be optimize detect fields 2025-11-16 19:05:54 +02:00
Pete Matsyburka 5fec952b8c adjust detections 2025-11-15 19:48:42 +02:00
Pete Matsyburka c4fbcc103a autocorrect pdf 2025-11-15 10:46:54 +02:00
Pete Matsyburka 9333a1ba45 fix fields error alert 2025-11-14 16:35:02 +02:00
Pete Matsyburka de5cd92dba adjust send email 2025-11-13 19:36:06 +02:00
Pete Matsyburka 83e32bec9f fix preferences form 2025-11-13 10:34:52 +02:00
Pete Matsyburka 4603efc04e refactor toggles form 2025-11-13 09:50:26 +02:00
Alex Turchyn 6c73c82a27 add ability to reset custom template emails to default 2025-11-13 08:56:09 +02:00
Pete Matsyburka 8e71d9f9e9 skip email 2025-11-13 08:29:27 +02:00
Pete Matsyburka 80f60a5d88 re-invite archived user 2025-11-12 20:33:52 +02:00
Pete Matsyburka 2c33ec382d fix device icon 2025-11-12 16:48:33 +02:00
Pete Matsyburka 884865cffc adjust size 2025-11-12 16:39:29 +02:00
Alex Turchyn 9537fe8c91 update constants 2025-11-12 16:33:51 +02:00
Alex Turchyn c8fe36a2b6 add desktop/mobile/tablet badges to events log 2025-11-12 16:33:51 +02:00
Pete Matsyburka 7ec3282b56 i18n 2025-11-11 17:34:50 +02:00
Pete Matsyburka ab8c41a520 add signature reasons param 2025-11-11 13:42:01 +02:00
Pete Matsyburka 4c1c70ff04 fix audit generation 2025-11-11 12:08:16 +02:00
Pete Matsyburka d7a6e80bb1 html escape font signature 2025-11-11 11:11:07 +02:00
Alex Turchyn f87ef670d1 Merge from docusealco/wip 2025-11-10 13:08:28 +02:00
Pete Matsyburka daba2505bc fix detection 2025-11-09 21:33:47 +02:00
Pete Matsyburka 71528eda45 map page nodes 2025-11-09 08:51:00 +02:00
Pete Matsyburka 269e2beeaa fix preview 2025-11-07 10:20:41 +02:00
Pete Matsyburka 72dd2c6bc5 add template download 2025-11-05 12:07:11 +02:00
Pete Matsyburka 9758f7a15f adjust detection 2025-11-04 16:17:13 +02:00
Alex Turchyn 2521eb5a4a Merge from docusealco/wip 2025-11-03 20:16:45 +02:00
Pete Matsyburka bf6afc5e61 adjust detection 2025-11-03 19:24:09 +02:00
Pete Matsyburka 291d869843 adjust detect 2025-11-03 09:49:25 +02:00
Pete Matsyburka cdfecb2a8f fix role validation 2025-11-03 09:17:50 +02:00
Pete Matsyburka c0df8e0ec3 close doc 2025-11-02 08:57:58 +02:00
Pete Matsyburka a21f7d1653 fix padding 2025-11-02 08:13:40 +02:00
Pete Matsyburka 79e2514416 fix model name 2025-11-02 08:11:31 +02:00
Pete Matsyburka 80a7385183 fix redirect 2025-11-02 08:09:54 +02:00
Pete Matsyburka 1c8a7b6a7c detect fields 2025-11-02 08:09:54 +02:00
Pete Matsyburka b46ced2b6f add log 2025-10-31 10:26:10 +02:00
Pete Matsyburka 5ae479644e focus on canvas click 2025-10-31 10:18:39 +02:00
Pete Matsyburka cb66eb1293 fix redirect 2025-10-31 10:10:19 +02:00
Pete Matsyburka b4d5a2cad4 dangerous extension error 2025-10-30 08:47:01 +02:00
Pete Matsyburka 32691b0c0d add background 2025-10-29 10:10:04 +02:00
Pete Matsyburka 9163f744a3 remove sign up button 2025-10-27 20:03:33 +02:00
Pete Matsyburka 23ecce51a3 hide archived completed page 2025-10-22 22:03:39 +03:00
Pete Matsyburka 747ffa8cf1 adjust signature ID size 2025-10-15 13:38:53 +03:00
Alex Turchyn 4e230899ed Merge from docusealco/wip 2025-10-13 13:50:54 +03:00
Pete Matsyburka aff9f753c7 adjust style 2025-10-11 18:20:59 +03:00
Pete Matsyburka 1127fac500 update gem 2025-10-11 11:09:24 +03:00
Pete Matsyburka 7707e1cd2b adjust style 2025-10-11 10:58:09 +03:00
Pete Matsyburka 30274f7f1a fix sync button 2025-10-10 23:03:19 +03:00
Pete Matsyburka a5da6cc781 fix i18n 2025-10-10 14:04:27 +03:00
Pete Matsyburka 201e4010c5 fix german i18n 2025-10-10 13:15:41 +03:00
Pete Matsyburka b9bbdbffae fix french i18n 2025-10-10 11:01:01 +03:00
Alex Turchyn 9e8e1996e8 google drive file picker 2025-10-09 16:43:11 +03:00
Pete Matsyburka b99235397a update gem 2025-10-07 21:15:58 +03:00
Pete Matsyburka c0fd06daf3 adjust i18n 2025-10-07 13:57:36 +03:00
Pete Matsyburka 90536294df adjust i18n 2025-10-07 09:34:39 +03:00
Pete Matsyburka f8e9787675 fix edit email detailed 2025-10-07 08:59:01 +03:00
Pete Matsyburka bf9b785946 adjust i18n 2025-10-07 08:45:40 +03:00
Pete Matsyburka dab3a6f031 fix typo 2025-10-06 21:45:46 +03:00
Pete Matsyburka 88466193c5 fix i18n 2025-10-06 20:05:14 +03:00
Pete Matsyburka c2f7601dad update readme 2025-10-06 18:44:29 +03:00
Pete Matsyburka 189f7b2d7b add dutch langauge 2025-10-06 17:43:51 +03:00
Pete Matsyburka 5d87160ee9 fix rubocop 2025-10-04 18:33:37 +03:00
Pete Matsyburka 0cdcdb8898 update rubocop 2025-10-01 13:31:40 +03:00
Pete Matsyburka c20619b5c8 adjust completed email delivery 2025-09-30 13:49:45 +03:00
Alex Turchyn 93f84abce4 Merge from docusealco/wip 2025-09-29 13:14:29 +03:00
Pete Matsyburka 3c6a32caba payment link 2025-09-29 10:23:11 +03:00
Pete Matsyburka 72c08e5fdb fix settings 2025-09-29 08:59:36 +03:00
Pete Matsyburka 022d80b5ce adjust reminders 2025-09-29 07:31:46 +03:00
Pete Matsyburka f48da51802 10k stars 2025-09-27 16:12:00 +03:00
Pete Matsyburka 709d04b33f toggle submit element 2025-09-27 11:40:16 +03:00
Pete Matsyburka 3a251dd063 development url ttl 2025-09-27 11:08:14 +03:00
Pete Matsyburka 9cc92b59b3 add strikethrough 2025-09-27 10:40:29 +03:00
Pete Matsyburka e0469b10a7 hide mobile folder upload 2025-09-26 09:35:32 +03:00
Pete Matsyburka 39eb67b162 refactor csp 2025-09-26 08:20:03 +03:00
Pete Matsyburka 8d9bea3b0f adjust date format 2025-09-25 20:32:41 +03:00
Pete Matsyburka 7015d8dde8 fix csp 2025-09-25 18:30:07 +03:00
Pete Matsyburka 6b54072cb5 check formula field condition 2025-09-25 14:00:51 +03:00
Pete Matsyburka b898708306 fix formula condition 2025-09-25 13:12:36 +03:00
Pete Matsyburka 1b1cf36839 fix verification step 2025-09-25 10:40:33 +03:00
Pete Matsyburka 18b89edc19 fix search input 2025-09-25 09:01:04 +03:00
Pete Matsyburka 70c7ef4247 adjust reminders 2025-09-25 08:47:31 +03:00
Pete Matsyburka a8196709be remove csp meta tag 2025-09-24 23:23:42 +03:00
Pete Matsyburka a9d249ccf0 fix variables schema 2025-09-24 19:51:37 +03:00
Pete Matsyburka cd83d918e5 fix csp 2025-09-24 15:45:04 +03:00
Pete Matsyburka b64a84a362 add csp 2025-09-24 12:14:18 +03:00
Pete Matsyburka f4426a8ee0 adjust payment title 2025-09-23 10:05:51 +03:00
Pete Matsyburka df8c44d2ce quantity formula 2025-09-23 09:03:43 +03:00
Pete Matsyburka 65ce3d4822 add use direct file links 2025-09-22 17:52:24 +03:00
Alex Turchyn e5b1d94579 Merge from docusealco/wip 2025-09-22 13:11:28 +03:00
Pete Matsyburka 0b4754ca24 update email events index 2025-09-22 08:40:12 +03:00
Pete Matsyburka b3dc8d55be fix submission name 2025-09-19 21:36:05 +03:00
Pete Matsyburka e5ab7667b9 adjust verification step 2025-09-18 14:15:16 +03:00
Pete Matsyburka 2a4e6435f1 adjust api settings 2025-09-18 10:24:38 +03:00
Pete Matsyburka 804431c44a adjust account preferences 2025-09-18 10:05:04 +03:00
Alex Turchyn f3b26bf7a0 add tooltips to account preferences 2025-09-18 09:26:21 +03:00
Pete Matsyburka aa27d90017 update gem 2025-09-18 08:48:48 +03:00
Pete Matsyburka 0fa8cf2dd0 fix result generatrion 2025-09-18 08:33:34 +03:00
Pete Matsyburka 634b5c37ba fix form fields import 2025-09-17 13:26:58 +03:00
Pete Matsyburka 6e16f81fc6 add folder filter 2025-09-16 16:19:36 +03:00
Pete Matsyburka ffc4024f70 rubocop 2025-09-16 13:03:02 +03:00
Alex Turchyn a6981185b2 Merge from docusealco/wip 2025-09-15 13:13:58 +03:00
Pete Matsyburka 0361fabb66 fix combined download 2025-09-15 11:39:43 +03:00
Pete Matsyburka 04e7f101be add lock events 2025-09-15 10:10:39 +03:00
Pete Matsyburka 86d680cfdf fix variables schema 2025-09-15 08:57:54 +03:00
Pete Matsyburka 7bc2152057 expire disk storage urls 2025-09-13 18:10:47 +03:00
Pete Matsyburka 74ed9a5040 update docs 2025-09-13 11:23:34 +03:00
Alex Turchyn 30115fc0dc adjust login OTP form 2025-09-13 09:34:50 +03:00
Pete Matsyburka 8e8e5e0e29 add template variables 2025-09-12 18:22:45 +03:00
Alex Turchyn b58ce1b571 add translations 2025-09-10 08:25:36 +03:00
Pete Matsyburka df5c4e4210 fix email 2fa resubmit 2025-09-09 19:29:09 +03:00
Pete Matsyburka ba2e0999f1 add with file links option 2025-09-09 09:53:21 +03:00
Pete Matsyburka a1f5dd08c7 fix typo 2025-09-08 14:42:25 +03:00
Alex Turchyn c1123fef63 Merge from docusealco/wip 2025-09-08 13:05:04 +03:00
Pete Matsyburka 4dc6149530 add reset password link 2025-09-06 14:46:51 +03:00
Pete Matsyburka 1245ff2cce disable edit password 2025-09-06 10:41:51 +03:00
Pete Matsyburka 284204fd78 refactor, use invalid class 2025-09-06 10:18:01 +03:00
Alex Turchyn f7be74eb73 improve user password reset 2025-09-06 09:21:59 +03:00
Pete Matsyburka aeea619059 skip annotation 2025-09-05 15:30:58 +03:00
Pete Matsyburka ea2b7f20e7 count segments 2025-09-05 14:59:20 +03:00
Alex Turchyn f40f1d25de reveal API key with user password 2025-09-05 09:58:16 +03:00
Pete Matsyburka c04bb2d7cf allow to disable user 2fa 2025-09-04 21:19:43 +03:00
Pete Matsyburka 4c1ccd65bf fix condition field value 2025-09-04 18:16:00 +03:00
Pete Matsyburka 54e064b0fc fix fields sort 2025-09-04 16:13:29 +03:00
Pete Matsyburka 9a8b72883c fix relation 2025-09-03 12:50:16 +03:00
Pete Matsyburka 5d75ee2e47 filter export 2025-09-02 21:17:25 +03:00
Pete Matsyburka 7357fa4871 adjust settings nav links 2025-09-01 18:34:23 +03:00
Pete Matsyburka f6850b5427 add completed submitters verification method 2025-09-01 14:35:56 +03:00
Pete Matsyburka 1e9a181e60 fix typo 2025-09-01 14:01:40 +03:00
Pete Matsyburka 7224a1ccec adjust expire link 2025-09-01 13:45:28 +03:00
Alex Turchyn 9b935a8180 Merge from docusealco/wip 2025-09-01 12:47:09 +03:00
Pete Matsyburka c4a693846e expire download links 2025-09-01 11:59:02 +03:00
Pete Matsyburka 02c7cdcd97 fix rotate 2025-08-28 17:43:39 +03:00
Pete Matsyburka 36bee92e8e fix condition remove 2025-08-28 10:31:19 +03:00
Pete Matsyburka 6542804f44 fix markdown 2025-08-27 22:01:49 +03:00
Pete Matsyburka d805fd614c private template placeholder 2025-08-26 12:38:41 +03:00
Alex Turchyn 21299dc65f Merge pull from docusealco/wip 2025-08-25 12:37:42 +03:00
Pete Matsyburka 0df0946c2a fix markdown 2025-08-25 10:11:36 +03:00
Pete Matsyburka 1bb38d50a1 adjust formula font 2025-08-24 18:16:43 +03:00
Pete Matsyburka 242987cb41 icon size 2025-08-24 14:25:36 +03:00
Pete Matsyburka 5456abde54 adjust date input 2025-08-24 10:08:47 +03:00
Pete Matsyburka 4a07427440 adjust paste radio 2025-08-24 09:27:51 +03:00
Pete Matsyburka cf60dae2ff fix modal title 2025-08-23 21:46:51 +03:00
Pete Matsyburka 659997c6c1 add 2 options by default 2025-08-23 21:33:46 +03:00
Pete Matsyburka 389a47a152 expand options 2025-08-23 21:16:07 +03:00
Pete Matsyburka b6b81ca487 editable readonly custom field 2025-08-23 20:52:57 +03:00
Pete Matsyburka 719c1786f1 set default value 2025-08-23 20:16:14 +03:00
Pete Matsyburka 4685bac4b3 fix serializers 2025-08-23 11:28:26 +03:00
Pete Matsyburka a05275cc08 update json 2025-08-23 10:41:49 +03:00
Pete Matsyburka 1abf1b1658 fix formula 2025-08-22 18:11:09 +03:00
Pete Matsyburka 8893dc5698 pwa orientation any 2025-08-22 18:11:05 +03:00
Pete Matsyburka f8467b2da1 nested formula 2025-08-21 14:36:49 +03:00
Pete Matsyburka f39b8b000e use csv safe 2025-08-21 13:58:53 +03:00
Pete Matsyburka 961036fa31 update widget 2025-08-21 10:22:06 +03:00
Pete Matsyburka 8b9056894e sanitize url 2025-08-20 21:31:15 +03:00
Pete Matsyburka 7f979e9396 add number validation 2025-08-20 18:32:20 +03:00
Pete Matsyburka 8db1192d0e update hexapdf 2025-08-19 16:34:59 +03:00
Pete Matsyburka 3bd515951a stamp without completed 2025-08-19 12:40:45 +03:00
Pete Matsyburka 2bd5b9676b add with verification 2025-08-19 12:25:23 +03:00
Alex Turchyn 993698ec01 Merge from docusealco/wip 2025-08-18 12:33:13 +03:00
Pete Matsyburka fcdc44ddbe update rails 2025-08-14 09:44:04 +03:00
Pete Matsyburka 0cb6b0ae12 fix zip 2025-08-13 11:18:49 +03:00
Pete Matsyburka 0388927c5b fix readonly date 2025-08-12 22:33:38 +03:00
Pete Matsyburka 2c8f5d2129 debug file 2025-08-12 20:01:52 +03:00
Pete Matsyburka 2bffbf83c6 handle zip upload 2025-08-12 13:07:16 +03:00
Pete Matsyburka ec01529014 show readonly fields 2025-08-11 16:58:14 +03:00
Pete Matsyburka ea1abf5662 fix test email 2025-08-08 16:33:19 +03:00
Pete Matsyburka fcd5263af1 add hour minute variable 2025-08-07 11:33:11 +03:00
Pete Matsyburka 1fa8616dbc fix submitters order 2025-08-04 14:46:46 +03:00
Alex Turchyn f627277b45 Merge from docusealco/wip 2025-08-04 12:59:10 +03:00
Pete Matsyburka 809c3ea270 fix template 2fa email 2025-07-31 19:23:27 +03:00
Pete Matsyburka 9caa9d79d4 fix tooltip 2025-07-31 13:32:28 +03:00
Pete Matsyburka a8f1c1c323 add new signature formats 2025-07-31 13:13:31 +03:00
Pete Matsyburka 59fbea5c5f fix migration 2025-07-30 18:45:28 +03:00
Pete Matsyburka d3273d879a add order ui 2025-07-30 10:22:38 +03:00
Pete Matsyburka ea227e82c9 force smtp auto 2025-07-29 22:49:20 +03:00
Pete Matsyburka 676b330aa3 add submitters order param 2025-07-29 17:47:47 +03:00
Pete Matsyburka 8d3295e125 better signature validation 2025-07-29 13:51:11 +03:00
Pete Matsyburka 8cb74e0bcc fix build 2025-07-28 16:55:21 +03:00
Alex Turchyn 26749a4dbc Merge from docusealco/wip 2025-07-28 12:44:35 +03:00
Pete Matsyburka 14915d37b4 fix build 2025-07-28 09:02:17 +03:00
Pete Matsyburka 16d6d58efb fix typo 2025-07-27 10:42:56 +03:00
Pete Matsyburka b2b97a313a prefillable fields 2025-07-27 10:27:12 +03:00
Pete Matsyburka 2974e6a64f remove unused posthog 2025-07-25 09:41:24 +03:00
Pete Matsyburka dae34d0aa5 remove multiple signatures toggle 2025-07-24 15:13:39 +03:00
Alex Turchyn e9d0671184 remove duplicate translations 2025-07-24 14:39:28 +03:00
Alex Turchyn 897e33bf6a simplify shared links modal UI 2025-07-24 10:13:34 +03:00
Alex Turchyn f37e93a409 remove duplicate translations 2025-07-24 09:43:17 +03:00
Alex Turchyn 561ac63c93 email editor 2025-07-24 09:41:57 +03:00
Pete Matsyburka 149e4be07a do not send invitation if completed 2025-07-23 10:20:21 +03:00
Pete Matsyburka eb98dc9e06 adjust folder form 2025-07-22 15:18:48 +03:00
Pete Matsyburka ce7d965358 update 2025-07-22 10:24:40 +03:00
Pete Matsyburka bb73024859 folder name to string 2025-07-22 09:49:58 +03:00
Pete Matsyburka 631a442e2a fix builder file drop modal close 2025-07-21 16:43:03 +03:00
Pete Matsyburka 20a082ae8e adjust image field validation 2025-07-21 16:26:08 +03:00
Alex Turchyn 154d706be1 Merge from docusealco/wip 2025-07-21 13:38:05 +03:00
Alex Turchyn aaae257c75 group initial and signature images in audit logs 2025-07-20 19:56:10 +03:00
Pete Matsyburka ec944a2283 add subfolders 2025-07-20 08:28:19 +03:00
Pete Matsyburka 69d63ecffe with signature id reason 2025-07-18 11:08:22 +03:00
Pete Matsyburka 06a1e2992d fix redirect url 2025-07-17 18:33:24 +03:00
Pete Matsyburka ec9293bac0 with signature id 2025-07-17 10:54:44 +03:00
Pete Matsyburka 8bf727c43c adjust font auto 2025-07-15 09:19:49 +03:00
Pete Matsyburka 708b8afdf5 optimize 2025-07-14 17:13:04 +03:00
Pete Matsyburka 6f7b9b1ebf code highligh 2025-07-14 13:43:26 +03:00
Pete Matsyburka 4f48c4fd9f fix gh font 2025-07-14 13:36:34 +03:00
Pete Matsyburka 9dac90e6cc cleanup 2025-07-14 13:29:39 +03:00
Alex Turchyn 0741a6f485 Merge from docusealco/wip 2025-07-14 13:26:14 +03:00
Pete Matsyburka 1f338dfd2b adjust font size 2025-07-14 12:53:15 +03:00
Pete Matsyburka 0310c26edd fix safari template folder drag&drop 2025-07-12 12:35:19 +03:00
Pete Matsyburka e43570d4c1 adjust nav 2025-07-12 11:51:10 +03:00
Pete Matsyburka d7142c2935 adjust setup nav 2025-07-12 11:16:14 +03:00
Pete Matsyburka fa6dc04ef1 add gh tooltip 2025-07-12 10:57:07 +03:00
Pete Matsyburka c5ec7a4b37 image step validation 2025-07-11 21:52:55 +03:00
Pete Matsyburka bd3d643318 simplify nav 2025-07-11 21:24:00 +03:00
Pete Matsyburka 1ae635963c update eid 2025-07-11 19:54:03 +03:00
Alex Turchyn a61c3e798d shared link 2fa 2025-07-11 09:30:24 +03:00
Pete Matsyburka 931addedbe adjust submitter index 2025-07-10 23:10:40 +03:00
Pete Matsyburka 9aa4a547cf with submitter timezone 2025-07-10 20:56:34 +03:00
Pete Matsyburka 82a7df5e3f add pdf/a-3b 2025-07-10 12:58:19 +03:00
Pete Matsyburka 0ff87383d0 fix selfsign 2025-07-08 16:48:57 +03:00
Pete Matsyburka ea3dec443f refactor 2025-07-07 15:40:23 +03:00
Alex Turchyn d7a895b98b Merge from docusealco/wip 2025-07-07 15:36:18 +03:00
Pete Matsyburka b90b7b7845 add select field font 2025-07-07 10:57:21 +03:00
Pete Matsyburka 3d05d75f2d fix search 2025-07-07 07:36:27 +03:00
Pete Matsyburka e701881377 store error body 2025-07-05 16:36:55 +03:00
Pete Matsyburka 4615e3e48f refresh webhook status 2025-07-05 15:39:42 +03:00
Alex Turchyn 988a5361a6 add webhook events 2025-07-04 22:28:01 +03:00
Pete Matsyburka 1b60b42428 text size 2025-07-04 13:33:52 +03:00
Pete Matsyburka 7b74487214 add length validation 2025-07-04 13:03:22 +03:00
Pete Matsyburka 91d98ed267 rotate pages 2025-07-04 09:47:52 +03:00
Pete Matsyburka 24fbb7de06 require phone 2fa 2025-07-03 10:20:30 +03:00
Pete Matsyburka 91df5b2407 optimize image blank 2025-07-02 17:44:18 +03:00
Pete Matsyburka eef2d9d599 use union all 2025-07-01 09:16:50 +03:00
Pete Matsyburka 8ad70b7e74 adjust folder query 2025-07-01 09:02:40 +03:00
Pete Matsyburka 8b775103c7 fix spec 2025-07-01 08:35:25 +03:00
Pete Matsyburka 8c2475aac1 add gh action timouts 2025-07-01 08:05:42 +03:00
Pete Matsyburka d3236ae4a1 adjust sort order 2025-06-30 23:57:49 +03:00
Pete Matsyburka 8fd9af3a6a fix ci 2025-06-30 13:11:21 +03:00
Alex Turchyn 4f94858901 Merge from docusealco/wip 2025-06-30 12:56:05 +03:00
Pete Matsyburka d0ac4ad222 fix sqlite remove template 2025-06-30 11:44:55 +03:00
Pete Matsyburka 10e4476f4a fix signature prefill 2025-06-30 11:41:07 +03:00
Pete Matsyburka 74411d883a adjust link default parties 2025-06-29 12:31:41 +03:00
Alex Turchyn 18ee2d5316 add shared link form fields preferences 2025-06-28 23:06:25 +03:00
Pete Matsyburka b4f80422eb update doc 2025-06-28 13:30:05 +03:00
Pete Matsyburka 7fe9ab7601 update params 2025-06-28 10:37:10 +03:00
Pete Matsyburka 13ad614e6b fix rubocop 2025-06-28 10:17:50 +03:00
Alex Turchyn 7b1b4bcf2c show shared link enable button on disabled link page 2025-06-28 09:58:22 +03:00
Alex Turchyn a6715d4034 add meta description translations 2025-06-27 23:25:52 +03:00
Pete Matsyburka 254995c6c6 fix missing font variant 2025-06-27 14:45:29 +03:00
Pete Matsyburka 0e33e23d1f fix prefill condition 2025-06-27 12:49:25 +03:00
Pete Matsyburka 57503eefd2 adjust search index 2025-06-27 12:08:57 +03:00
Pete Matsyburka cd477c880c render image as file 2025-06-26 21:50:12 +03:00
Pete Matsyburka bf2f5b24dc update docs 2025-06-26 17:19:53 +03:00
Pete Matsyburka 7eba4865bc fix signature remember 2025-06-26 11:28:38 +03:00
Pete Matsyburka 64cd24a24d validate blank images 2025-06-26 11:04:25 +03:00
Pete Matsyburka de5acd0949 fix one-off 2025-06-26 10:05:16 +03:00
Pete Matsyburka 6ebbc3edac pdfium read text 2025-06-26 09:51:09 +03:00
Pete Matsyburka 2020f6c136 make shared templates searchable 2025-06-26 07:47:29 +03:00
Pete Matsyburka ee4acd5a82 validate signature image 2025-06-25 22:31:19 +03:00
Pete Matsyburka 2e487ac936 fix timezone selector 2025-06-25 21:36:03 +03:00
Pete Matsyburka dee5fc7c07 fix preview 2025-06-25 16:34:25 +03:00
Pete Matsyburka e130584fec fix timezone selector 2025-06-25 09:38:52 +03:00
Pete Matsyburka ade5c4832f fix bulk table mappings 2025-06-24 12:49:52 +03:00
Pete Matsyburka 33b14d5278 fix sqlite query 2025-06-23 20:34:30 +03:00
Pete Matsyburka 48e73c2c81 fix png quality 2025-06-23 14:53:35 +03:00
Pete Matsyburka 894d98e92b update widget version 2025-06-23 13:33:44 +03:00
Alex Turchyn 14c84eff93 Merge from docusealco/wip 2025-06-23 13:19:06 +03:00
Pete Matsyburka 01d982b093 embed preview toggle 2025-06-22 14:54:12 +03:00
Pete Matsyburka 35852ef6ab add onprem search reindex 2025-06-21 23:04:11 +03:00
Pete Matsyburka 47b235dc5f fix search 2025-06-21 22:17:54 +03:00
Pete Matsyburka 0e11d2a581 fix tour link 2025-06-21 17:11:26 +03:00
Pete Matsyburka e57e11f206 optimize query 2025-06-21 14:58:20 +03:00
Alex Turchyn 51f639065c add translations 2025-06-20 21:14:22 +03:00
Pete Matsyburka 6c1d4b466f adjust server selector 2025-06-20 13:41:42 +03:00
Pete Matsyburka b940c597dc fix pdfium error 2025-06-20 02:16:31 +03:00
Pete Matsyburka 1ce67bcb39 adjust folder autocomplete 2025-06-19 22:49:47 +03:00
Pete Matsyburka 5050a67d08 add datenow type 2025-06-19 19:58:22 +03:00
Pete Matsyburka 76321cef3c optimize autocomplete 2025-06-19 16:06:35 +03:00
Pete Matsyburka 8f3a203994 markdown link 2025-06-19 12:45:44 +03:00
Pete Matsyburka ea24562861 fix time format 2025-06-19 12:25:35 +03:00
Pete Matsyburka 4a888d048e fix empty file generate 2025-06-19 12:06:08 +03:00
Alex Turchyn 36331bb993 show found submissions when searching on templates 2025-06-19 12:05:43 +03:00
Pete Matsyburka 6f0e7c0ca8 skip autocomplete 2025-06-18 23:53:36 +03:00
Pete Matsyburka 2491b7b7e3 adjust indexes 2025-06-18 11:56:42 +03:00
Pete Matsyburka 7965c17f46 fix serializer 2025-06-17 18:03:28 +03:00
Pete Matsyburka 5519459519 refactor 2025-06-17 12:42:57 +03:00
Pete Matsyburka d8bf2cc101 add index 2025-06-17 11:03:40 +03:00
Pete Matsyburka 2aece98d67 add pro routes 2025-06-16 22:37:50 +03:00
Pete Matsyburka f602497255 require all submitters 2025-06-16 22:17:14 +03:00
Pete Matsyburka 11db68cdc7 fix merge 2025-06-16 18:55:23 +03:00
Pete Matsyburka bed98f4344 optimize query 2025-06-16 17:02:05 +03:00
Pete Matsyburka 055f7e2bc1 Merge from docusealco/wip 2025-06-15 23:53:35 +03:00
Pete Matsyburka dae3b4b6b5 add completed at index 2025-06-15 12:26:11 +03:00
Pete Matsyburka f2888a08dc fix query 2025-06-14 08:53:45 +03:00
Pete Matsyburka 95627c5963 add audit timezone 2025-06-13 22:37:04 +03:00
Pete Matsyburka 4bb3721db0 add submitter timezone 2025-06-13 19:15:05 +03:00
Pete Matsyburka b12111383b fix update oneoff 2025-06-11 23:39:19 +03:00
Pete Matsyburka 263c0ff939 fix migration 2025-06-11 18:50:01 +03:00
Pete Matsyburka efc8ba6b0a fix merge 2025-06-11 17:55:10 +03:00
Pete Matsyburka 4c530b3f88 fix query 2025-06-11 12:10:24 +03:00
Pete Matsyburka f6358c9d47 fix brakeman 2025-06-11 09:32:43 +03:00
Pete Matsyburka b3baea2f6f optimize query 2025-06-11 09:28:10 +03:00
Pete Matsyburka c0594a8b74 fix brakeman 2025-06-11 09:16:57 +03:00
Pete Matsyburka 1403401b03 permit field required 2025-06-10 11:26:12 +03:00
Pete Matsyburka 700184f776 add ngram 2025-06-10 09:19:13 +03:00
Pete Matsyburka b6a1a3ee4d fix archived 2025-06-09 21:46:26 +03:00
Pete Matsyburka 8cbf5802b5 fix signature upload 2025-06-09 17:06:20 +03:00
Pete Matsyburka ab9706afef fix template fields 2025-06-09 14:20:17 +03:00
Pete Matsyburka f07f9d8cf9 fix oneoff edit 2025-06-09 14:20:17 +03:00
Pete Matsyburka 591caecb92 fix redirect 2025-06-09 14:20:17 +03:00
Pete Matsyburka ef4c2b7213 update gem 2025-06-08 16:54:59 +03:00
Pete Matsyburka 18999255a9 remove constant 2025-06-08 13:34:41 +03:00
Pete Matsyburka 0a2a1de6b9 brakeman ignore 2025-06-07 21:28:01 +03:00
Pete Matsyburka 1b857b6eb3 fix preview 2025-06-07 21:12:37 +03:00
Pete Matsyburka e5e6b11c95 fix image name 2025-06-07 16:20:57 +03:00
Pete Matsyburka 3ec3f58d55 fix mysql migration 2025-06-07 09:55:45 +03:00
Pete Matsyburka 3bfce862ec fix reindex 2025-06-07 09:52:26 +03:00
Pete Matsyburka c01b67f7ec index template 2025-06-06 23:14:17 +03:00
Pete Matsyburka ea0efe9314 fix form cells 2025-06-06 22:10:00 +03:00
Pete Matsyburka 28b583691f partial download toggle 2025-06-06 16:28:53 +03:00
Pete Matsyburka 33811945be fulltext search 2025-06-06 16:03:16 +03:00
Pete Matsyburka 9d1860f038 fix specs 2025-06-06 08:47:56 +03:00
Pete Matsyburka f03166b4ea one-off submission 2025-06-06 08:47:56 +03:00
Pete Matsyburka c59d948d41 add stamp to all pages 2025-06-04 08:44:55 +03:00
Pete Matsyburka 57d63dd2f2 fix empty option condition 2025-06-03 22:29:14 +03:00
Alex Turchyn c88715daec Merge from docusealco/wip 2025-06-03 12:22:34 +03:00
Pete Matsyburka 0375dad7c9 smaller audit initial 2025-06-03 10:13:49 +03:00
Pete Matsyburka de3f83db96 use metadata lang 2025-06-03 09:52:46 +03:00
Pete Matsyburka a7bb96464c completed action shared link 2025-06-01 22:42:44 +03:00
Pete Matsyburka 1d48fe9621 fix spec 2025-06-01 15:51:43 +03:00
Pete Matsyburka 9544bb3d76 Revert "shared link true default"
This reverts commit e41fe5fbca.
2025-06-01 15:23:20 +03:00
Pete Matsyburka 4e94664008 refactor start form 2025-06-01 15:23:19 +03:00
Pete Matsyburka e41fe5fbca shared link true default 2025-06-01 13:21:11 +03:00
Pete Matsyburka 24d713dec6 resubmit shared template 2025-06-01 12:27:36 +03:00
Pete Matsyburka f464e1c46a add log 2025-06-01 12:22:16 +03:00
Pete Matsyburka 85d52d1f28 refactor policy 2025-06-01 11:38:08 +03:00
Alex Turchyn e77b5fa2c9 add optional shared link to templates 2025-06-01 11:07:52 +03:00
Alex Turchyn c91b4a765b add Privacy Policy and eSignature Disclosure links 2025-06-01 09:20:42 +03:00
Pete Matsyburka 2a031c14c8 fix js error 2025-05-30 16:58:20 +03:00
Pete Matsyburka f7dfc0b2ed add email event date index 2025-05-30 11:12:33 +03:00
Pete Matsyburka 3b894f5281 fix 2025-05-30 10:36:08 +03:00
Alex Turchyn 9645332e48 add countless pagination 2025-05-30 09:12:42 +03:00
Pete Matsyburka 5670390b37 fix signature reason 2025-05-29 17:31:09 +03:00
Pete Matsyburka 260bd6d5d5 fix signature field size 2025-05-29 17:24:03 +03:00
Alex Turchyn fa0733f480 improve user email validation 2025-05-29 14:24:17 +03:00
Pete Matsyburka 8b9678a434 fix heif 2025-05-28 23:01:34 +03:00
Pete Matsyburka 43669bf095 do not prefill optional signature 2025-05-28 21:51:17 +03:00
Pete Matsyburka 2ef8df3f0c optimize query 2025-05-28 10:23:36 +03:00
Alex Turchyn 1c9d15aa7e add translations for pagination 2025-05-28 10:06:06 +03:00
Alex Turchyn 9fcd8955d3 add 'field-area-active-label' theme class 2025-05-27 21:10:58 +03:00
Pete Matsyburka 5e44ba49b8 refactor 2025-05-26 22:26:14 +03:00
Pete Matsyburka ac1f90e711 do not email smtp settings 2025-05-26 14:46:33 +03:00
Alex Turchyn c6eae66127 Merge from docusealco/wip 2025-05-26 13:26:19 +03:00
Pete Matsyburka 08aa902e07 set from user id 2025-05-26 11:58:47 +03:00
Pete Matsyburka 1690337699 fix pdf image result 2025-05-26 10:11:01 +03:00
Pete Matsyburka f78cf8cc6e pdfium 2025-05-26 10:01:27 +03:00
Pete Matsyburka 343118d00d fix bmp 2025-05-24 11:54:18 +03:00
Alex Turchyn a3bd37c9a2 Improve error message when does not match in completed form 2025-05-24 11:25:49 +03:00
Alex Turchyn 8bc13a5900 fix german translations 2025-05-23 10:52:47 +03:00
Pete Matsyburka 21138c1d02 remove log 2025-05-22 15:41:19 +03:00
Alex Turchyn 38674a9fc9 fix accidental checkbox addition 2025-05-21 22:18:25 +03:00
Pete Matsyburka a7ed0b86a4 dot rspec 2025-05-21 10:20:43 +03:00
Pete Matsyburka 91a96f06c8 remove account config action 2025-05-20 22:50:43 +03:00
Alex Turchyn 23631b75f2 add template_accesses factory 2025-05-20 19:36:45 +03:00
Pete Matsyburka 0fb9c2bbf6 fix gmail normalize 2025-05-20 19:32:23 +03:00
Pete Matsyburka 14fa8fc8e7 fix expired 2025-05-20 11:18:08 +03:00
Pete Matsyburka 047c192988 fix typo 2025-05-20 09:49:17 +03:00
Pete Matsyburka bfa244c8fa add ico and bmp support 2025-05-20 09:49:16 +03:00
Alex Turchyn 019222ae40 Merge from docusealco/wip 2025-05-19 13:07:42 +03:00
Pete Matsyburka 8a10852fe2 docs 2025-05-18 17:38:51 +03:00
Pete Matsyburka 01232aed5a add account access 2025-05-18 15:16:53 +03:00
Alex Turchyn 82f3ff4824 fix narrow signatures 2025-05-18 09:44:55 +03:00
Pete Matsyburka 7be3f5a931 fix android 2025-05-16 18:28:55 +03:00
Pete Matsyburka 9d013a7384 fix email normalize 2025-05-15 22:16:06 +03:00
Alex Turchyn 0c71d7bff8 fix EU server selection note 2025-05-15 18:05:07 +03:00
Pete Matsyburka fdd030652e adjust border 2025-05-15 14:39:25 +03:00
Pete Matsyburka f2bf1461f0 adjust selector 2025-05-15 13:50:02 +03:00
Alex Turchyn fc6f796f30 adjust server selector 2025-05-15 13:16:43 +03:00
Alex Turchyn 5f44806287 adjust dropoze border style 2025-05-15 13:16:40 +03:00
Pete Matsyburka 318b65af18 fix i18n 2025-05-14 21:28:52 +03:00
Pete Matsyburka 91b182b04a adjust email validate 2025-05-14 20:40:59 +03:00
Pete Matsyburka 15b886d4ff update reminder durations 2025-05-14 20:22:34 +03:00
Pete Matsyburka 0122a237e3 remove request helper 2025-05-14 13:21:57 +03:00
Alex Turchyn 2dca7d8a8e add builder 'replace' button specs 2025-05-14 13:06:03 +03:00
Pete Matsyburka 87265e2f22 fix prefill same name field 2025-05-14 13:04:35 +03:00
Pete Matsyburka 3166defa1a add submitter value var 2025-05-13 12:27:27 +03:00
Pete Matsyburka 93ba17f42e expire at webhook 2025-05-12 18:23:17 +03:00
Pete Matsyburka 751be1dae5 fix template filter 2025-05-12 15:38:13 +03:00
Pete Matsyburka 3f8d2831e2 allow double dash email 2025-05-12 13:22:11 +03:00
Alex Turchyn 4d8942803e Merge from docusealco/wip 2025-05-12 12:31:27 +03:00
Pete Matsyburka 46d9bb2c87 fix optimize query 2025-05-12 11:17:26 +03:00
Pete Matsyburka 34433c0575 adjust conditions 2025-05-12 11:05:48 +03:00
Pete Matsyburka 544306eac4 fix replace 2025-05-11 18:02:24 +03:00
Pete Matsyburka 0eeebfd3cf clear cache 2025-05-10 20:03:35 +03:00
Pete Matsyburka 050224a10d fix refactor expire at 2025-05-10 17:39:53 +03:00
Alex Turchyn 7b6fc7d06f add expiration duration to template preferences 2025-05-10 12:12:15 +03:00
Pete Matsyburka 7fb148c85c drag folders 2025-05-10 12:11:57 +03:00
Alex Turchyn dffa59fecd add translations 2025-05-09 22:20:20 +03:00
Pete Matsyburka 24fe2a1192 fix refactor dropzone 2025-05-09 22:12:20 +03:00
Alex Turchyn 97b8ac4444 add builder dashboard dropzone 2025-05-09 21:35:31 +03:00
Pete Matsyburka e34a763dd9 fix field id 2025-05-09 10:04:22 +03:00
Pete Matsyburka 298b1d3e1e update gem 2025-05-08 22:41:25 +03:00
Pete Matsyburka d9978519cc adjust qes link 2025-05-08 12:32:10 +03:00
Pete Matsyburka b4a68cf79e adjust field types prop 2025-05-08 12:22:18 +03:00
Pete Matsyburka 5663e60f92 fix log 2025-05-06 22:18:53 +03:00
Pete Matsyburka 19e8835809 fix timestamp path 2025-05-06 18:44:29 +03:00
Pete Matsyburka 330fae927f skip bounce from audit 2025-05-06 16:48:50 +03:00
Pete Matsyburka c5a5212947 disable archived template actions 2025-05-06 15:50:58 +03:00
Pete Matsyburka 21d81e38bf add email events 2025-05-06 15:10:02 +03:00
Pete Matsyburka 5b1a4ebffd improve log 2025-05-03 16:30:11 +03:00
Pete Matsyburka 5f5dabd0e5 advanced formats 2025-05-02 19:17:19 +03:00
Pete Matsyburka ff46993de6 fix spec 2025-05-02 12:54:00 +03:00
Pete Matsyburka 7676db2196 hex colors 2025-05-02 12:36:33 +03:00
Alex Turchyn 3ef0d28ffe adjust app tour 2025-05-02 10:27:57 +03:00
Pete Matsyburka 231177f057 add log 2025-05-01 00:14:04 +03:00
Pete Matsyburka e5f7739952 use sha 256 for tsa 2025-04-30 23:48:03 +03:00
Pete Matsyburka 4a8e2a82e5 tsa fallback 2025-04-30 22:58:50 +03:00
Pete Matsyburka 6efe8027f1 disable multiple sign 2025-04-30 16:54:56 +03:00
Pete Matsyburka 96bb4eb6e5 fix standalone radio 2025-04-29 20:15:32 +03:00
Pete Matsyburka 38e68814cf fix color 2025-04-29 18:02:19 +03:00
Alex Turchyn 786c80bee7 Merge pull from docusealco/wip 2025-04-28 12:46:30 +03:00
Pete Matsyburka 98dcce299e resend 2025-04-28 08:59:41 +03:00
Pete Matsyburka cbff242a5d adjust condition fields 2025-04-25 12:14:49 +03:00
Pete Matsyburka 1876b3efba update readme 2025-04-24 19:44:49 +03:00
Alex Turchyn 51091f05f0 add Japanese language to the signing form 2025-04-24 19:38:46 +03:00
Pete Matsyburka d76eee2ae4 add valign 2025-04-24 19:33:26 +03:00
Pete Matsyburka d7bfa96d57 hide order on mobile 2025-04-24 12:59:31 +03:00
Pete Matsyburka be60608211 fix remove listener 2025-04-23 21:24:18 +03:00
Pete Matsyburka 4e5abb3c09 fix query 2025-04-23 12:30:06 +03:00
Pete Matsyburka 7fee82ded2 fix order queries 2025-04-23 10:58:40 +03:00
Alex Turchyn e1cd62ff40 add folders/templates sorting 2025-04-22 21:16:17 +03:00
Pete Matsyburka b498291b62 update gem 2025-04-22 16:20:35 +03:00
Pete Matsyburka def3009846 fix cert disabled 2025-04-22 15:52:03 +03:00
Pete Matsyburka 9d43fd9aa5 fix fields 2025-04-18 18:18:57 +03:00
Pete Matsyburka 22a21fb037 fix field focus 2025-04-18 13:02:17 +03:00
Pete Matsyburka 2a9c375273 adjust header 2025-04-18 12:57:43 +03:00
Alex Turchyn 847e70ec1b improve signing form header 2025-04-18 12:16:11 +03:00
Pete Matsyburka 7964d6e0f0 improve signature validation 2025-04-18 11:34:10 +03:00
Pete Matsyburka d0259791bf add field value email variable 2025-04-16 21:19:13 +03:00
Pete Matsyburka a9dcaeccbc fix erblint 2025-04-16 11:35:24 +03:00
Pete Matsyburka 501b91a603 removed email 2025-04-16 11:35:20 +03:00
Pete Matsyburka 2b5f772feb update ai link 2025-04-15 21:40:22 +03:00
Pete Matsyburka 7cada0af8b older devises 2025-04-15 19:58:31 +03:00
Pete Matsyburka 67be785e69 add slug filter 2025-04-15 13:22:02 +03:00
Pete Matsyburka d1cfc64adc adjust api response 2025-04-14 12:14:24 +03:00
Pete Matsyburka 4e5157d1ee add text valign 2025-04-11 16:25:32 +03:00
Pete Matsyburka 5e27cc36c6 fix default font 2025-04-10 13:22:10 +03:00
Pete Matsyburka 6e81a3be3f fix i18n 2025-04-10 11:51:40 +03:00
Pete Matsyburka 858b2f33fd ensure unique recipients 2025-04-10 00:56:16 +03:00
Alex Turchyn 6adcc87b2b add translations 2025-04-09 20:45:17 +03:00
Alex Turchyn e1d5a5b6a6 prevent an infinite loop of field conditions 2025-04-09 14:04:42 +03:00
Pete Matsyburka f090e79002 fix required 2025-04-09 13:51:43 +03:00
Pete Matsyburka 56b32e35a2 make last name optional 2025-04-08 19:12:04 +03:00
Pete Matsyburka b40c27cdbf fix specs 2025-04-08 16:21:45 +03:00
Pete Matsyburka c4505e67f0 require 2fa placeholder 2025-04-08 15:24:17 +03:00
Alex Turchyn d0799b8cc1 Merge from docusealco/wip 2025-04-07 12:58:13 +03:00
Pete Matsyburka 889fcad859 fix drop position 2025-04-05 12:06:29 +03:00
Pete Matsyburka c133fef99d ruby 3.4.2 2025-04-04 17:20:52 +03:00
Alex Turchyn cfdc978a2a add app tour 2025-04-04 11:40:55 +03:00
Pete Matsyburka bff1651966 fix long font preview 2025-04-03 17:59:05 +03:00
Alex Turchyn b97854f2fe improve account deletion confirmation message 2025-04-03 12:56:48 +03:00
Alex Turchyn 7a4629bf79 improve drag'n'drop in the builder 2025-04-02 16:51:39 +03:00
Pete Matsyburka 2573d8cb19 fix n+1 2025-04-01 10:23:31 +03:00
Pete Matsyburka dd559e90f1 add archived filter 2025-04-01 10:23:30 +03:00
Alex Turchyn e5db61e358 add API specs for completed submitter 2025-03-31 22:40:05 +03:00
Pete Matsyburka 69376c0faf fix acro form fields 2025-03-31 21:50:04 +03:00
Alex Turchyn 1f48be135c Merge from docusealco/wip 2025-03-31 13:02:51 +03:00
Pete Matsyburka 1cae12b728 adjust validation 2025-03-29 23:15:45 +02:00
Alex Turchyn 86d2cd93f8 fix signature uploading 2025-03-29 15:44:19 +02:00
Alex Turchyn b0110d9340 update signature validation 2025-03-29 14:50:26 +02:00
Alex Turchyn 7e70f4fb14 update translations 2025-03-29 13:37:21 +02:00
Pete Matsyburka 28e3d65e63 improve required field handling 2025-03-28 22:42:27 +02:00
Pete Matsyburka f1a2b71b59 fix smtp 2025-03-27 19:50:09 +02:00
Pete Matsyburka 47c7270911 enforce signing order 2025-03-27 19:23:20 +02:00
Pete Matsyburka 4e1fa336a2 add personalization options 2025-03-27 19:23:19 +02:00
Pete Matsyburka 244bc32136 fix preview url 2025-03-27 00:52:35 +02:00
Pete Matsyburka 42c30bdfd0 ensure generated doc completed 2025-03-26 13:14:59 +02:00
Alex Turchyn 0abf48ddba update preview image 2025-03-26 13:14:07 +02:00
Alex Turchyn ef05f030de Merge from docusealco/wip 2025-03-24 14:03:04 +02:00
Pete Matsyburka b8c36ff09c update gems 2025-03-24 09:48:37 +02:00
Pete Matsyburka 1ba8a9a374 fix pdf fields extract 2025-03-24 09:44:10 +02:00
Pete Matsyburka 661e9b69fb do not validate values param 2025-03-21 13:42:20 +02:00
Pete Matsyburka a1aeabccff fix scroll 2025-03-20 21:07:28 +02:00
Pete Matsyburka 0e5e0e7772 fix pdf upload 2025-03-19 17:57:46 +02:00
Alex Turchyn 2a74fdd095 add field area classes 2025-03-19 15:33:02 +02:00
Alex Turchyn ac426e55b2 add missing CSS class for custom fields 2025-03-18 22:24:54 +02:00
Pete Matsyburka ad2b35673a fix class 2025-03-18 20:31:00 +02:00
Pete Matsyburka af1e8fa6f2 adjust i18n 2025-03-18 17:34:31 +02:00
Pete Matsyburka 5800cb6c5a fix default value condition 2025-03-18 17:34:31 +02:00
Alex Turchyn 64d3360e82 add ui classes 2025-03-18 17:34:11 +02:00
Alex Turchyn 3ae98bf03e improve multiple submitters error message 2025-03-18 17:31:40 +02:00
Alex Turchyn 65e81d90f4 fix acro radio fields 2025-03-17 22:38:42 +02:00
Pete Matsyburka 4fb9637e2d add merge roles param 2025-03-17 13:30:07 +02:00
Pete Matsyburka 6b3726dbd5 update gem 2025-03-15 15:01:46 +02:00
Pete Matsyburka c5b0f2c76c fix require value validation 2025-03-13 19:21:28 +02:00
Pete Matsyburka ea18f9e28a fix combined pdf 2025-03-13 14:21:53 +02:00
Pete Matsyburka d229c75978 fix draw field 2025-03-12 19:14:00 +02:00
Pete Matsyburka ecbe3c3e4f PRESIGNED_URLS_EXPIRE_MINUTES 2025-03-12 14:24:33 +02:00
Pete Matsyburka 38a5cb968b extract pdf date fields 2025-03-12 10:23:24 +02:00
Pete Matsyburka a30ee9e961 fix perform at 2025-03-11 11:28:22 +02:00
Pete Matsyburka eba27004dc font format date number 2025-03-11 10:51:04 +02:00
Pete Matsyburka 09849acd2f add submission.expired webhook 2025-03-10 22:53:56 +02:00
Pete Matsyburka 19e9047a39 reduce max attempts 2025-03-10 15:54:21 +02:00
Pete Matsyburka 82b6bdd9ad update gem 2025-03-10 15:05:09 +02:00
Alex Turchyn 5764b4dac1 Merge from docusealco/wip 2025-03-10 13:13:01 +02:00
Pete Matsyburka 3affd67d24 add font field preferences 2025-03-09 12:46:28 +02:00
Pete Matsyburka d5a98a0f9e web concurrency auto 2025-03-08 16:11:11 +02:00
Pete Matsyburka 9629cc7226 make remember configurable 2025-03-05 20:44:32 +02:00
Pete Matsyburka 06415ad869 add routes hook 2025-03-05 18:29:39 +02:00
Pete Matsyburka 3f872da435 fix mask 2025-03-05 18:07:38 +02:00
Pete Matsyburka bc1d5a9825 add font type 2025-03-05 12:12:16 +02:00
Pete Matsyburka 72c8ed9a00 update gem 2025-03-04 22:17:01 +02:00
Pete Matsyburka 3fec433846 fix builder editable 2025-03-04 19:46:55 +02:00
Pete Matsyburka 3b572702d7 add mask size 2025-03-04 01:02:18 +02:00
Alex Turchyn febdc0b48d add specs for masked fields 2025-03-03 22:43:27 +02:00
Pete Matsyburka 8773bb2f62 disable cfr toggle 2025-03-03 13:21:10 +02:00
Pete Matsyburka 55ad70fabc fix builder default date format 2025-03-03 11:24:38 +02:00
Pete Matsyburka 23dcd62672 add field mask 2025-03-03 00:18:56 +02:00
Alex Turchyn 42371d7ce3 Merge from docusealco/wip 2025-03-02 15:37:12 +02:00
Pete Matsyburka 4116746be3 destroy email events 2025-02-28 12:12:02 +02:00
Pete Matsyburka b2b2856ac0 handle required pdf fields 2025-02-27 21:37:32 +02:00
Alex Turchyn b65bca213f add brakeman to CI 2025-02-27 18:43:57 +02:00
Pete Matsyburka 7a72e2cbb7 allow http 2025-02-27 10:59:32 +02:00
Pete Matsyburka 3a73398a1e fix email 2025-02-27 10:01:33 +02:00
Pete Matsyburka cce20d9230 fix rubocop 2025-02-25 14:39:11 +02:00
Pete Matsyburka 8b72c91951 add console audit tables 2025-02-25 13:16:11 +02:00
Pete Matsyburka b0d8e47a75 fix annotate model 2025-02-25 13:10:33 +02:00
Pete Matsyburka 85824faa2b https webhooks 2025-02-25 10:33:16 +02:00
Pete Matsyburka 8a507339af add brakeman 2025-02-25 00:54:05 +02:00
Pete Matsyburka 4636849118 fix permitted params 2025-02-24 23:59:30 +02:00
Pete Matsyburka cc52cae9fc fix not found page 2025-02-24 14:19:48 +02:00
Alex Turchyn b6bb4d2c8b Merge from docusealco/wip 2025-02-24 14:05:38 +02:00
Pete Matsyburka 5032676bd2 fix url download 2025-02-24 13:24:16 +02:00
Pete Matsyburka 146b37f8cd fix clone 2025-02-21 22:21:31 +02:00
Pete Matsyburka 47b3a2d09e multiple recipient emails 2025-02-21 21:49:33 +02:00
Pete Matsyburka f2306b334b update gem 2025-02-20 13:02:58 +02:00
Pete Matsyburka 3affa65571 fix formula condition 2025-02-20 12:13:52 +02:00
Pete Matsyburka 6f3be16826 fix dockerfile 2025-02-18 16:50:48 +02:00
Pete Matsyburka 07ce76537b use ubuntu arm 2025-02-18 16:50:01 +02:00
Pete Matsyburka a1fb38b8cb add email variables 2025-02-18 12:18:59 +02:00
Alex Turchyn 1b52a99662 Merge from docusealco/wip 2025-02-17 22:55:09 +02:00
Pete Matsyburka f5facfa6b9 use ubuntu latest build 2025-02-17 22:49:14 +02:00
Pete Matsyburka 21c6144dc6 fix document controller 2025-02-17 18:45:52 +02:00
Pete Matsyburka 2c155f0e42 use ip to reopen 2025-02-17 12:46:44 +02:00
Alex Turchyn 33cc0a8ab5 Merge from docusealco/wip 2025-02-17 12:36:04 +02:00
Pete Matsyburka 1e65a7e52b fix combined 2025-02-14 22:28:59 +02:00
Pete Matsyburka 14e2dbe1aa detailed party email form 2025-02-14 18:12:15 +02:00
Pete Matsyburka a823a6ac56 fix template clone 2025-02-14 18:12:09 +02:00
Pete Matsyburka cfc4e001ba refactor annots 2025-02-14 12:33:49 +02:00
Pete Matsyburka 496cc855f6 refactor 2025-02-14 11:00:12 +02:00
Pete Matsyburka bfb2db07a5 fix events modal 2025-02-13 16:46:57 +02:00
Pete Matsyburka 9f4f868246 use erb_lint 2025-02-13 13:19:33 +02:00
Pete Matsyburka f91f02f786 remove seats info 2025-02-13 00:10:43 +02:00
Pete Matsyburka 1da72e3e7a update gem 2025-02-12 21:31:56 +02:00
Pete Matsyburka 2e2c0e087d skip missing font 2025-02-12 20:38:46 +02:00
Pete Matsyburka 0c72f58ab0 fix combined 2025-02-12 20:38:46 +02:00
Alex Turchyn d9d969cb79 add translations 2025-02-12 14:31:49 +02:00
Pete Matsyburka 8d685d1f73 adjust event log 2025-02-11 22:37:47 +02:00
Alex Turchyn e406620044 add 'Event Log' modal 2025-02-11 22:23:51 +02:00
Pete Matsyburka a315674421 update gem 2025-02-11 11:18:15 +02:00
Alex Turchyn 8d953f210f add attachment count attribute to template factory 2025-02-11 11:16:32 +02:00
Alex Turchyn 7439ef07ad Merge from docusealco/wip 2025-02-10 13:08:17 +02:00
Pete Matsyburka fc4b4de6ed customize template email per party 2025-02-09 14:13:04 +02:00
Alex Turchyn 96e4733f87 add submissions filtering by 'sent' and 'opened' statuses 2025-02-07 20:20:37 +02:00
Pete Matsyburka 8b6731a289 show logo 2025-02-07 14:48:04 +02:00
Pete Matsyburka f93ac68e9c adjust field drag 2025-02-07 14:39:23 +02:00
Alex Turchyn 439df4879c add stamp text logo 2025-02-07 14:15:35 +02:00
Alex Turchyn 83b6ce8574 fix logos 2025-02-07 14:15:18 +02:00
Pete Matsyburka 6ea07bc13b show minimize on mobile if with description 2025-02-06 13:49:09 +02:00
Alex Turchyn a15624690b show unauthorized message in test environment 2025-02-06 13:41:16 +02:00
Pete Matsyburka 974bb85fb7 rescue malformed PDF 2025-02-06 12:55:27 +02:00
Pete Matsyburka 5dc80601b2 courier font 2025-02-05 18:17:55 +02:00
Pete Matsyburka df2e53bc40 font mono 2025-02-05 16:13:31 +02:00
Alex Turchyn 6e685bbc32 Merge from docusealco/wip 2025-02-05 13:44:10 +02:00
Pete Matsyburka 0b3ca0de96 fix ci 2025-02-04 19:37:42 +02:00
Pete Matsyburka aed3f1f141 fix build 2025-02-04 18:47:15 +02:00
Pete Matsyburka 1a7981e919 update actions 2025-02-04 16:55:24 +02:00
Pete Matsyburka 4add805371 fix field align 2025-02-04 16:19:49 +02:00
Alex Turchyn 66c18317b7 Merge from docusealco/wip 2025-02-04 16:11:47 +02:00
Pete Matsyburka 9edbb885b4 use arm runner 2025-02-04 15:51:31 +02:00
Alex Turchyn c67c80d6bc Merge from docusealco/wip 2025-02-04 13:44:17 +02:00
Pete Matsyburka 0399531e0c html image 2025-02-03 18:38:52 +02:00
Pete Matsyburka 54426556d9 field conditions 2025-02-02 21:36:38 +02:00
Alex Turchyn fa74ee1755 add Progressive Web App 2025-02-02 14:31:42 +02:00
Pete Matsyburka eb53af12c4 adjust form 2025-02-02 12:36:42 +02:00
Pete Matsyburka 38efcb99c0 fix verification 2025-01-31 21:32:15 +02:00
Pete Matsyburka a92d914674 adjust form 2025-01-31 20:11:57 +02:00
Pete Matsyburka 219a698aa5 adjust i18n 2025-01-31 15:26:53 +02:00
Alex Turchyn 5bed6448dc make custom fields titles smaller on mobile devices 2025-01-31 15:19:04 +02:00
Alex Turchyn 6d2a8ca3d3 update translations 2025-01-31 14:43:06 +02:00
Alex Turchyn bfb2e0043d update email attribution footer for Test Mode 2025-01-31 14:43:06 +02:00
Pete Matsyburka 922101b12c fix dashboard toggle 2025-01-31 12:03:52 +02:00
Pete Matsyburka 9ff41a0c4a scroll on mobile field click 2025-01-29 16:08:09 +02:00
Pete Matsyburka 071f187605 fix template conditions 2025-01-29 00:56:14 +02:00
Pete Matsyburka ab6cd0e89c adjust send email job 2025-01-28 18:52:12 +02:00
Pete Matsyburka 97c0dad7ff add with fields search 2025-01-28 14:13:06 +02:00
Pete Matsyburka bc0917a190 fix sign in date 2025-01-28 11:16:24 +02:00
Alex Turchyn 5718acecff add translations 2025-01-28 11:15:33 +02:00
Alex Turchyn 395d43dbeb Merge from docusealco/wip 2025-01-27 13:21:32 +02:00
Pete Matsyburka 37de5e0d0d add resubmit from dashboard 2025-01-25 20:22:48 +02:00
Pete Matsyburka 44e848a1f0 fix heading move 2025-01-25 00:07:20 +02:00
Pete Matsyburka f6d59d731c fix email typo 2025-01-24 13:32:12 +02:00
Pete Matsyburka 87b039e709 fix submitter names 2025-01-23 22:21:01 +02:00
Pete Matsyburka e0c6a4e08b fix submission preview page 2025-01-23 17:11:50 +02:00
Pete Matsyburka 80b06550c2 adjust template link 2025-01-23 11:39:43 +02:00
Pete Matsyburka ec8e35ed7a fix email typo 2025-01-22 23:22:34 +02:00
Alex Turchyn 4dc325a6bb change open/read timeout for webhook requests 2025-01-22 20:58:47 +02:00
Pete Matsyburka d52c57df72 adjust i18n 2025-01-22 11:49:04 +02:00
Alex Turchyn 0cb2557447 add warning message to navbar 2025-01-21 13:10:27 +02:00
Pete Matsyburka cd05c1fd19 search field by title 2025-01-20 23:08:19 +02:00
Pete Matsyburka 5458e82b6e increase parties limit 2025-01-20 21:01:13 +02:00
Pete Matsyburka c59c58624b convert icon to png 2025-01-20 14:46:43 +02:00
Alex Turchyn 2da059d440 Merge from docusealco/wip 2025-01-20 12:48:34 +02:00
Pete Matsyburka b0aabd9e79 add signed option in filename 2025-01-19 16:16:00 +02:00
Pete Matsyburka f1cd60dfad error message 2025-01-19 16:02:40 +02:00
Alex Turchyn a264a8522a add compliances section 2025-01-18 16:39:20 +02:00
Pete Matsyburka 794e339425 add verification settings 2025-01-18 16:35:07 +02:00
Pete Matsyburka 9aaf61e658 add number currency format 2025-01-17 21:08:13 +02:00
Pete Matsyburka 37faa7ece6 do not remove condition on replace 2025-01-17 17:11:10 +02:00
Pete Matsyburka 666e3fde4c fix document remove 2025-01-17 17:06:37 +02:00
Pete Matsyburka 995e040a4e Revert "do not remove condition on replace"
This reverts commit c24ae1751e.
2025-01-17 16:55:11 +02:00
Pete Matsyburka 185ac2cbee refactor 2025-01-17 15:52:49 +02:00
Pete Matsyburka a4e372ff5c fix android scroll 2025-01-17 01:49:36 +02:00
Pete Matsyburka b4834c7674 fix filename date 2025-01-16 16:30:06 +02:00
Pete Matsyburka c24ae1751e do not remove condition on replace 2025-01-16 01:05:06 +02:00
Pete Matsyburka 9bd0ad70c4 add default field options 2025-01-16 00:40:22 +02:00
Pete Matsyburka f210f1d8bf Revert "fix scrolling to fields on Android devices"
This reverts commit e3f3895aac.
2025-01-15 13:57:30 +02:00
Alex Turchyn 92b61992f2 fix vertical scrolling for mobile devices 2025-01-15 02:52:31 +02:00
Alex Turchyn e3f3895aac fix scrolling to fields on Android devices 2025-01-14 23:23:09 +02:00
Alex Turchyn c696890cf5 fix dropdowns in Firefox 2025-01-14 01:57:20 +02:00
Alex Turchyn 288d204d8e Merge from docusealco/wip 2025-01-13 13:00:50 +02:00
Pete Matsyburka 5a96f4f1eb adjust ios mobile 2025-01-11 14:02:04 +02:00
Alex Turchyn 5ff4f9889e add user reviews 2025-01-11 13:09:38 +02:00
Alex Turchyn 78fb93fd02 fix detection of unknown mobile IOS devices 2025-01-11 13:06:21 +02:00
Pete Matsyburka 3c83d459bd add document page attributes 2025-01-10 20:33:23 +02:00
Pete Matsyburka 6cdb8e7132 fix draw cursor 2025-01-10 20:33:16 +02:00
Pete Matsyburka e2b93cbcab fix colors 2025-01-10 15:12:09 +02:00
Pete Matsyburka a59cc7c44e verification redirect 2025-01-10 11:50:54 +02:00
Pete Matsyburka 1984b99e04 fix formula with conditions 2025-01-09 22:25:46 +02:00
Pete Matsyburka fe3e8fba3e fix readonly formula values 2025-01-09 12:26:56 +02:00
Pete Matsyburka 97ecdf2ff0 fix invite submitters api order 2025-01-09 01:09:26 +02:00
Alex Turchyn e4c62087d0 fix translations 2025-01-08 18:17:46 +02:00
Pete Matsyburka 1f9faddfd0 add reply to 2025-01-07 20:38:56 +02:00
Pete Matsyburka 6dff6483bf fix default field title 2025-01-07 17:30:21 +02:00
Pete Matsyburka 2e43eb189f fix define submitters order 2025-01-07 13:04:59 +02:00
Alex Turchyn cee471bfa5 Merge from docusealco/wip 2025-01-06 12:18:51 +02:00
Pete Matsyburka 81521dee81 add ability to reorder fields 2025-01-04 14:20:47 +02:00
Pete Matsyburka 3cac62d0fd fix typo 2025-01-04 01:42:25 +02:00
Pete Matsyburka 24ed3f2e7c filter submissions api 2025-01-04 01:31:54 +02:00
Alex Turchyn 02ad620c9f add translations 2025-01-04 01:10:22 +02:00
Alex Turchyn 9051d5a835 add a text hint under user roles 2025-01-03 18:39:50 +02:00
Alex Turchyn 0abd270f56 update translations 2025-01-03 01:09:29 +02:00
Pete Matsyburka fde9affd70 fix send to next 2025-01-02 23:32:24 +02:00
Pete Matsyburka 4edee2971a superadmin file download 2025-01-02 22:42:08 +02:00
Pete Matsyburka 313d7f8408 adjust response 2025-01-02 22:42:02 +02:00
Alex Turchyn db60810272 add email OTP 2025-01-01 14:38:24 +02:00
Pete Matsyburka b3507c1a3a adjust test toggle size 2024-12-31 19:57:41 +02:00
Alex Turchyn 19b5a07bac Add 'Test mode' switch to the profile dropdown 2024-12-31 19:36:14 +02:00
Pete Matsyburka 616aced4e4 fix spec 2024-12-30 21:38:02 +02:00
Alex Turchyn cc218fc0b0 Merge from docusealco/wip 2024-12-30 13:32:14 +02:00
Pete Matsyburka c59f2a022a increase retries 2024-12-28 16:58:20 +02:00
Pete Matsyburka 2a240cebeb allow to move fields between pages 2024-12-28 14:03:09 +02:00
Pete Matsyburka b950b1c63e ruby 3.4.1 2024-12-27 01:02:45 +02:00
Pete Matsyburka 4ad0b04b7f remove sleep 2024-12-25 12:51:41 +02:00
Pete Matsyburka 8edf6405b0 adjust param validation 2024-12-24 00:29:25 +02:00
Pete Matsyburka 8e3caae849 render headings 2024-12-23 22:58:24 +02:00
Alex Turchyn ac9a5c9556 Merge from docusealco/wip 2024-12-23 14:04:15 +02:00
Alex Turchyn 20d09f6dff add countdown for resend code 2024-12-23 01:06:05 +02:00
Pete Matsyburka 333a758688 heading value 2024-12-22 20:44:58 +02:00
Pete Matsyburka 2c8b4d5816 fix typo 2024-12-22 18:33:07 +02:00
Pete Matsyburka 9ce822bce8 add user tokens 2024-12-22 11:33:15 +02:00
Pete Matsyburka 0a382a688c remove schema rb 2024-12-22 01:11:25 +02:00
Pete Matsyburka bd3008a35a fix sidekiq 2024-12-22 00:51:38 +02:00
Alex Turchyn e9bd11cb2e update rails to v8.0.1 2024-12-22 00:04:08 +02:00
Pete Matsyburka ca429bc445 adjust default name 2024-12-20 16:00:38 +02:00
Pete Matsyburka 21a0c54843 fix 2024-12-20 13:58:32 +02:00
Pete Matsyburka 2f41d13009 add order fields 2024-12-20 13:58:12 +02:00
Alex Turchyn f69dcf77de set field alignment from PDF form field preferences 2024-12-20 13:58:00 +02:00
Pete Matsyburka 32596d738c optional invite 2024-12-19 17:33:24 +02:00
Pete Matsyburka e7e5d0e776 add signature formats 2024-12-19 00:42:35 +02:00
Alex Turchyn bb3b38e76f Merge from docusealco/wip 2024-12-18 21:30:59 +02:00
Alex Turchyn bc4c21f804 fix CI 2024-12-18 21:11:07 +02:00
Pete Matsyburka 2fb5de4fa9 fix submission schema 2024-12-18 16:53:35 +02:00
Pete Matsyburka 2e58b2e37b disable certs 2024-12-17 23:25:04 +02:00
Alex Turchyn 780f5512f4 Merge from docusealco/wip 2024-12-16 18:24:32 +02:00
Pete Matsyburka 2cf4908d53 fix filter 2024-12-13 23:19:38 +02:00
Pete Matsyburka 4a9b898713 edit required field 2024-12-13 21:24:30 +02:00
Alex Turchyn bb552d13d4 don't raise validation error if the message is empty 2024-12-13 20:55:37 +02:00
Pete Matsyburka 15d06f4fb0 add status filters 2024-12-13 20:46:26 +02:00
Pete Matsyburka 35a2aba887 adjust permanently remove 2024-12-13 13:31:54 +02:00
Pete Matsyburka 48a82b1d49 add import image 2024-12-13 12:48:44 +02:00
Pete Matsyburka b8453e14c4 add version 2024-12-13 12:04:45 +02:00
Pete Matsyburka 29fbdfbe2f cells align 2024-12-13 00:24:29 +02:00
Pete Matsyburka 5340af0bc6 remove reason: 2024-12-12 19:16:24 +02:00
Pete Matsyburka 1881b6c50f add sidekiq queue 2024-12-12 18:38:14 +02:00
Alex Turchyn b6a2aae970 restrict user invites 2024-12-12 14:04:09 +02:00
Pete Matsyburka b317094192 custom field width height 2024-12-11 19:32:29 +02:00
Pete Matsyburka ab1f4cf297 refactor clone 2024-12-10 23:25:53 +02:00
Pete Matsyburka d3033c072a update document name on clone 2024-12-10 22:27:37 +02:00
Pete Matsyburka f380200e03 add clone document params 2024-12-10 17:42:49 +02:00
Pete Matsyburka a4928268ea fix folder templates 2024-12-09 21:48:47 +02:00
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
805 changed files with 112975 additions and 7001 deletions
+58
View File
@@ -0,0 +1,58 @@
---
:position: before
:position_in_additional_file_patterns: before
:position_in_class: before
:position_in_factory: before
:position_in_fixture: before
:position_in_routes: before
:position_in_serializer: before
:position_in_test: before
:classified_sort: true
:exclude_controllers: true
:exclude_factories: true
:exclude_fixtures: false
:exclude_helpers: true
:exclude_scaffolds: true
:exclude_serializers: false
:exclude_sti_subclasses: false
:exclude_tests: false
:force: false
:format_markdown: false
:format_rdoc: false
:format_yard: false
:frozen: false
:ignore_model_sub_dir: false
:ignore_unknown_models: false
:include_version: false
:show_check_constraints: false
:show_complete_foreign_keys: false
:show_foreign_keys: true
:show_indexes: true
:simple_indexes: false
:sort: false
:timestamp: false
:trace: false
:with_comment: true
:with_column_comments: true
:with_table_comments: true
:active_admin: false
:command:
:debug: false
:hide_default_column_types: ''
:hide_limit_column_types: ''
:ignore_columns:
:ignore_routes:
:models: true
:routes: false
:skip_on_db_migrate: false
:target_action: :do_annotations
:wrapper:
:wrapper_close:
:wrapper_open:
:classes_default_to_s: []
:additional_file_patterns: []
:model_dir:
- app/models
:require: []
:root_dir:
- ''
View File
+53 -16
View File
@@ -6,14 +6,15 @@ jobs:
rubocop:
name: Rubocop
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.1
ruby-version: 4.0.1
- name: Cache gems
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
@@ -30,14 +31,15 @@ jobs:
erblint:
name: Erblint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.1
ruby-version: 4.0.1
- name: Cache gems
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
@@ -49,21 +51,22 @@ jobs:
bundle config path vendor/bundle
bundle install --jobs 4 --retry 4
- name: Run Erblint
run: bundle exec erblint ./app
run: bundle exec erb_lint ./app
eslint:
name: ESLint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v1
with:
node-version: 20.9.0
node-version: 20.19.0
- name: Cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
- uses: actions/cache@v4
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
@@ -77,9 +80,38 @@ jobs:
run: |
./node_modules/eslint/bin/eslint.js "app/javascript/**/*.js"
brakeman:
name: Brakeman
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 4.0.1
- name: Cache gems
uses: actions/cache@v4
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-
- name: Install gems
run: |
gem install bundler
bundle config path vendor/bundle
bundle install --jobs 4 --retry 4
yarn install
sudo apt-get update
sudo apt-get install libvips
- name: Run Brakeman
run: bundle exec brakeman -q --exit-on-warn
rspec:
name: RSpec
runs-on: ubuntu-latest
timeout-minutes: 10
services:
postgres:
@@ -96,24 +128,26 @@ jobs:
--health-retries 5
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Install Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3.1
ruby-version: 4.0.1
- name: Set up Node
uses: actions/setup-node@v1
with:
node-version: 20.9.0
node-version: 20.19.0
- name: Install Chrome
uses: browser-actions/setup-chrome@latest
with:
chrome-version: 125
- name: Cache node_modules
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
- name: Cache gems
uses: actions/cache@v1
uses: actions/cache@v4
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
@@ -128,7 +162,10 @@ jobs:
bundle install --jobs 4 --retry 4
yarn install
sudo apt-get update
sudo apt-get install libvips
sudo apt-get install -y libvips
wget -O pdfium-linux.tgz "https://github.com/docusealco/pdfium-binaries/releases/latest/download/pdfium-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz"
sudo tar -xzf pdfium-linux.tgz --strip-components=1 -C /usr/lib lib/libpdfium.so
rm -f pdfium-linux.tgz
- name: Run
env:
RAILS_ENV: test
+10 -11
View File
@@ -7,7 +7,8 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04-arm
timeout-minutes: 30
steps:
- name: Checkout code
@@ -15,32 +16,30 @@ jobs:
with:
submodules: recursive
-
name: Docker meta
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
docuseal/docuseal
tags: |
type=semver,pattern={{version}}
images: docuseal/docuseal
tags: type=semver,pattern={{version}}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Create .version file
run: echo ${{ github.ref_name }} > .version
- name: Login to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
uses: docker/build-push-action@v6
with:
context: .
push: true
+2
View File
@@ -36,3 +36,5 @@ yarn-debug.log*
/attachments
/docuseal
/ee
dump.rdb
*.onnx
+1
View File
@@ -0,0 +1 @@
--require rails_helper
+42 -7
View File
@@ -1,4 +1,4 @@
require:
plugins:
- rubocop-performance
- rubocop-rails
- rubocop-rspec
@@ -10,7 +10,8 @@ AllCops:
- node_modules/**/*
- bin/*
- vendor/**/*
TargetRubyVersion: '3.3'
TargetRubyVersion: '4.0'
SuggestExtensions: false
Metrics/BlockLength:
Exclude:
@@ -33,6 +34,7 @@ Metrics/MethodLength:
Max: 30
Exclude:
- 'db/migrate/**'
- 'spec/**/*'
Metrics/CyclomaticComplexity:
Max: 15
@@ -40,28 +42,48 @@ Metrics/CyclomaticComplexity:
Metrics/PerceivedComplexity:
Max: 15
Style/MultipleComparison:
Enabled: false
Style/NumericPredicate:
Enabled: false
Style/MinMaxComparison:
Enabled: false
Naming/PredicateMethod:
Enabled: false
Layout/LineLength:
AllowedPatterns: ['\A\s*#']
Metrics/AbcSize:
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: 500
RSpec/MultipleMemoizedHelpers:
Max: 15
RSpec/AnyInstance:
Enabled: false
Metrics/BlockNesting:
Max: 6
Rails/I18nLocaleTexts:
@@ -76,5 +98,18 @@ Rails/SkipsModelValidations:
Rails/ApplicationController:
Enabled: false
Capybara/ClickLinkOrButtonStyle:
Rails/Output:
Enabled: false
Rails/StrongParametersExpect:
Enabled: false
Rails/RedirectBackOrTo:
Enabled: false
Rails/UnknownEnv:
Environments:
- development
- test
- production
- local
+55 -21
View File
@@ -1,12 +1,22 @@
FROM ruby:3.3.1-alpine3.18 as fonts
FROM ruby:4.0.1-alpine AS download
WORKDIR /fonts
RUN apk --no-cache add fontforge wget && wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Regular.ttf && wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Bold.ttf && wget https://github.com/impallari/DancingScript/raw/master/fonts/DancingScript-Regular.otf && wget https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansSymbols2/hinted/ttf/NotoSansSymbols2-Regular.ttf && wget https://github.com/Maxattax97/gnu-freefont/raw/master/ttf/FreeSans.ttf && wget https://github.com/impallari/DancingScript/raw/master/OFL.txt
RUN apk --no-cache add fontforge wget && \
wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Regular.ttf && \
wget https://github.com/satbyy/go-noto-universal/releases/download/v7.0/GoNotoKurrent-Bold.ttf && \
wget https://github.com/impallari/DancingScript/raw/master/fonts/DancingScript-Regular.otf && \
wget https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansSymbols2/hinted/ttf/NotoSansSymbols2-Regular.ttf && \
wget https://github.com/Maxattax97/gnu-freefont/raw/master/ttf/FreeSans.ttf && \
wget https://github.com/impallari/DancingScript/raw/master/OFL.txt && \
wget -O /model.onnx "https://github.com/docusealco/fields-detection/releases/download/2.0.0/model_704_int8.onnx" && \
wget -O pdfium-linux.tgz "https://github.com/docusealco/pdfium-binaries/releases/latest/download/pdfium-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz" && \
mkdir -p /pdfium-linux && \
tar -xzf pdfium-linux.tgz -C /pdfium-linux
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.3.1-alpine3.18 as webpack
FROM ruby:4.0.1-alpine AS webpack
ENV RAILS_ENV=production
ENV NODE_ENV=production
@@ -26,43 +36,67 @@ 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.dynamic.config.js ./tailwind.dynamic.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.3.1-alpine3.18 as app
FROM ruby:4.0.1-alpine AS app
ENV RAILS_ENV=production
ENV BUNDLE_WITHOUT="development:test"
ENV LD_PRELOAD=/lib/libgcompat.so.0
ENV OPENSSL_CONF=/etc/openssl_legacy.cnf
ENV VIPS_MAX_COORD=15000
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 apk add --no-cache sqlite-dev libpq-dev vips-dev yaml-dev redis libheif vips-heif gcompat ttf-freefont onnxruntime && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
COPY ./Gemfile ./Gemfile.lock ./
RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal
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 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' >> /etc/openssl_legacy.cnf
COPY ./bin ./bin
COPY ./app ./app
COPY ./config ./config
COPY ./db ./db
COPY ./log ./log
COPY ./lib ./lib
COPY ./public ./public
COPY ./tmp ./tmp
COPY LICENSE README.md Rakefile config.ru .version ./
COPY --chown=docuseal:docuseal ./Gemfile ./Gemfile.lock ./
COPY --from=fonts /fonts/GoNotoKurrent-Regular.ttf /fonts/GoNotoKurrent-Bold.ttf /fonts/DancingScript-Regular.otf /fonts/OFL.txt /fonts
COPY --from=fonts /fonts/FreeSans.ttf /usr/share/fonts/freefont
COPY --from=webpack /app/public/packs ./public/packs
RUN apk add --no-cache build-base git && bundle install && apk del --no-cache build-base git && 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 && ln -sf /usr/lib/libonnxruntime.so.1 $(ruby -e "print Dir[Gem::Specification.find_by_name('onnxruntime').gem_dir + '/vendor/*.so'].first")
RUN ln -s /fonts /app/public/fonts
RUN bundle exec bootsnap precompile --gemfile app/ lib/
COPY --chown=docuseal:docuseal ./bin ./bin
COPY --chown=docuseal:docuseal ./app ./app
COPY --chown=docuseal:docuseal ./config ./config
COPY --chown=docuseal:docuseal ./db/migrate ./db/migrate
COPY --chown=docuseal:docuseal ./log ./log
COPY --chown=docuseal:docuseal ./lib ./lib
COPY --chown=docuseal:docuseal ./public ./public
COPY --chown=docuseal:docuseal ./tmp ./tmp
COPY --chown=docuseal:docuseal LICENSE LICENSE_ADDITIONAL_TERMS README.md Rakefile config.ru .version ./
COPY --chown=docuseal:docuseal .version ./public/version
COPY --chown=docuseal:docuseal --from=download /fonts/GoNotoKurrent-Regular.ttf /fonts/GoNotoKurrent-Bold.ttf /fonts/DancingScript-Regular.otf /fonts/OFL.txt /fonts
COPY --from=download /fonts/FreeSans.ttf /usr/share/fonts/freefont
COPY --from=download /pdfium-linux/lib/libpdfium.so /usr/lib/libpdfium.so
COPY --from=download /pdfium-linux/licenses/pdfium.txt /usr/lib/libpdfium-LICENSE.txt
COPY --chown=docuseal:docuseal --from=download /model.onnx /app/tmp/model.onnx
COPY --chown=docuseal:docuseal --from=webpack /app/public/packs ./public/packs
RUN ln -s /fonts /app/public/fonts && \
bundle exec bootsnap precompile -j 1 --gemfile app/ lib/ && \
chown -R docuseal:docuseal /app/tmp/cache
WORKDIR /data/docuseal
ENV HOME=/home/docuseal
ENV WORKDIR=/data/docuseal
EXPOSE 3000
+18 -13
View File
@@ -2,14 +2,16 @@
source 'https://rubygems.org'
ruby '3.3.1'
ruby '4.0.1'
gem 'arabic-letter-connector', require: 'arabic-letter-connector/logic'
gem 'arabic-letter-connector', require: false
gem 'aws-sdk-s3', require: false
gem 'azure-storage-blob', require: false
gem 'aws-sdk-secretsmanager', require: false
gem 'azure-blob', require: false
gem 'bootsnap', require: false
gem 'cancancan'
gem 'csv'
gem 'csv', require: false
gem 'csv-safe', require: false
gem 'devise'
gem 'devise-two-factor'
gem 'dotenv', require: false
@@ -19,34 +21,34 @@ gem 'faraday-follow_redirects'
gem 'google-cloud-storage', require: false
gem 'hexapdf'
gem 'image_processing'
gem 'jwt'
gem 'jwt', require: false
gem 'lograge'
gem 'mysql2', require: false
gem 'numo-narray-alt', require: false
gem 'oj'
gem 'onnxruntime', require: false
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'
gem 'rails-i18n'
gem 'rotp'
gem 'rqrcode'
gem 'rouge', require: false
gem 'rqrcode', require: false
gem 'ruby-vips'
gem 'rubyXL'
gem 'rubyXL', require: false
gem 'shakapacker'
gem 'sidekiq', require: ENV.key?('REDIS_URL')
gem 'sidekiq'
gem 'sqlite3', require: false
gem 'strip_attributes'
gem 'trilogy', github: 'trilogy-libraries/trilogy', glob: 'contrib/ruby/*.gemspec', require: false
gem 'turbo-rails'
gem 'twitter_cldr', require: false
gem 'tzinfo-data'
group :development, :test do
gem 'annotate'
gem 'better_html'
gem 'bullet'
gem 'debug'
@@ -63,6 +65,9 @@ group :development, :test do
end
group :development do
gem 'annotaterb'
gem 'brakeman', require: false
gem 'foreman', require: false
gem 'letter_opener_web'
gem 'web-console'
end
+393 -354
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
Additional Terms
In accordance with Section 7(b) of the GNU Affero General Public License,
a covered work must retain the original DocuSeal attribution in interactive
user interfaces.
+17 -17
View File
@@ -1,7 +1,7 @@
<h1 align="center" style="border-bottom: none">
<div>
<a href="https://www.docuseal.co">
<img alt="DocuSeal" src="https://github.com/docusealco/docuseal/assets/5418788/c12cd051-81cd-4402-bc3a-92f2cfdc1b06" width="80" />
<a href="https://www.docuseal.com">
<img alt="DocuSeal" src="https://github.com/user-attachments/assets/38b45682-ffa4-4919-abde-d2d422325c44" width="80" />
<br>
</a>
DocuSeal
@@ -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 13 languages
- 7 UI languages with signing available in 14 languages
- API and Webhooks for integrations
- Easy to deploy in minutes
@@ -54,11 +54,11 @@ DocuSeal is an open source platform that provides secure and efficient digital d
- 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), [Angular](https://github.com/docusealco/docuseal-angular) 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), [Angular](https://github.com/docusealco/docuseal-angular) 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
@@ -93,15 +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.
Distributed under the AGPLv3 License with Section 7(b) Additional Terms. See [LICENSE](https://github.com/docusealco/docuseal/blob/master/LICENSE) and [LICENSE_ADDITIONAL_TERMS](https://github.com/docusealco/docuseal/blob/master/LICENSE_ADDITIONAL_TERMS) for more information.
Unless otherwise noted, all files © 2023-2026 DocuSeal LLC.
## Tools
- [Signature Maker](https://www.docuseal.co/online-signature)
- [Sign Document Online](https://www.docuseal.co/sign-documents-online)
- [Fill PDF Online](https://www.docuseal.co/fill-pdf)
- [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.
+24 -4
View File
@@ -1,18 +1,30 @@
# frozen_string_literal: true
class AccountConfigsController < ApplicationController
before_action :load_account_config
authorize_resource :account_config
before_action :load_account_config, only: :create
authorize_resource :account_config, only: :create
load_and_authorize_resource :account_config, only: :destroy
ALLOWED_KEYS = [
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::DOWNLOAD_LINKS_AUTH_KEY,
AccountConfig::DOWNLOAD_LINKS_EXPIRE_KEY,
AccountConfig::FORCE_SSO_AUTH_KEY,
AccountConfig::FLATTEN_RESULT_PDF_KEY
AccountConfig::FLATTEN_RESULT_PDF_KEY,
AccountConfig::ENFORCE_SIGNING_ORDER_KEY,
AccountConfig::WITH_FILE_LINKS_KEY,
AccountConfig::WITH_SIGNATURE_ID,
AccountConfig::COMBINE_PDF_RESULT_KEY,
AccountConfig::REQUIRE_SIGNING_REASON_KEY,
AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY,
AccountConfig::ENABLE_MCP_KEY
].freeze
InvalidKey = Class.new(StandardError)
@@ -23,6 +35,14 @@ class AccountConfigsController < ApplicationController
head :ok
end
def destroy
raise InvalidKey unless ALLOWED_KEYS.include?(@account_config.key)
@account_config.destroy!
redirect_back(fallback_location: root_path)
end
private
def load_account_config
@@ -33,7 +53,7 @@ class AccountConfigsController < ApplicationController
end
def account_config_params
params.required(:account_config).permit!.tap do |attrs|
params.required(:account_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs|
attrs[:value] = attrs[:value] == '1' if attrs[:value].in?(%w[1 0])
end
end
@@ -0,0 +1,33 @@
# frozen_string_literal: true
class AccountCustomFieldsController < ApplicationController
before_action :load_account_config, only: :create
def create
authorize!(:create, Template)
@account_config.update!(account_config_params)
render json: @account_config.value
end
private
def load_account_config
@account_config =
AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::TEMPLATE_CUSTOM_FIELDS_KEY)
end
def account_config_params
params.permit(
value: [[:uuid, :name, :type,
:required, :readonly, :default_value,
:title, :description,
{ preferences: {},
default_value: [],
options: [%i[value uuid]],
validation: %i[message pattern min max step],
areas: [%i[x y w h cell_w option_uuid]] }]]
)
end
end
+28 -19
View File
@@ -4,10 +4,12 @@ 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',
'nl-NL' => 'Nederlands'
}.freeze
before_action :load_account
@@ -18,35 +20,42 @@ 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_content
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
render :show, status: :unprocessable_content
end
def destroy
authorize!(:manage, current_account)
true_user.update!(locked_at: Time.current)
true_user.skip_reconfirmation!
true_user.update!(locked_at: Time.current, email: true_user.email.sub('@', '+removed@'))
true_user.account.update!(archived_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_months_please_contact_us_if_you_want_to_keep_your_account'))
)
# rubocop:enable Layout/LineLength
end
private
@@ -13,7 +13,7 @@ module Api
def show
blob_uuid, purp, exp = ApplicationRecord.signed_id_verifier.verified(params[:signed_uuid])
if blob_uuid.blank? || (purp.present? && purp != 'blob') || (exp && exp < Time.current.to_i)
if blob_uuid.blank? || purp != 'blob'
Rollbar.error('Blob not found') if defined?(Rollbar)
return head :not_found
@@ -21,7 +21,12 @@ module Api
blob = ActiveStorage::Blob.find_by!(uuid: blob_uuid)
authorization_check!(blob) if exp.blank?
attachment = blob.attachments.take
@record = attachment.record
@record = @record.record if @record.is_a?(ActiveStorage::Attachment)
authorization_check!(attachment, @record, exp)
if request.headers['Range'].present?
send_blob_byte_range_data blob, request.headers['Range']
@@ -37,17 +42,22 @@ module Api
private
def authorization_check!(blob)
is_authorized =
blob.attachments.all? do |a|
a.name.in?(%w[logo preview_images]) ||
(current_user && a.record.account.id == current_user.account_id) ||
!a.record.account.account_configs.find_or_initialize_by(key: AccountConfig::DOWNLOAD_LINKS_AUTH_KEY).value
end
def authorization_check!(attachment, record, exp)
return if attachment.name == 'logo'
return if exp.to_i >= Time.current.to_i
return if current_user && current_ability.can?(:read, record)
return if is_authorized
if exp.blank?
configs = record.account.account_configs.where(key: [AccountConfig::DOWNLOAD_LINKS_AUTH_KEY,
AccountConfig::DOWNLOAD_LINKS_EXPIRE_KEY])
Rollbar.error('Blob aunauthorized') if defined?(Rollbar)
require_auth = configs.any? { |c| c.key == AccountConfig::DOWNLOAD_LINKS_AUTH_KEY && c.value }
require_ttl = configs.none? { |c| c.key == AccountConfig::DOWNLOAD_LINKS_EXPIRE_KEY && c.value == false }
return if !require_ttl && !require_auth
end
Rollbar.error('Blob unauthorized') if defined?(Rollbar)
raise CanCan::AccessDenied
end
+41 -9
View File
@@ -3,7 +3,7 @@
module Api
class ApiBaseController < ActionController::API
include ActiveStorage::SetCurrent
include Pagy::Backend
include Pagy::Method
DEFAULT_LIMIT = 10
MAX_LIMIT = 100
@@ -16,7 +16,7 @@ module Api
check_authorization
rescue_from Params::BaseValidator::InvalidParameterError do |e|
render json: { error: e.message }, status: :unprocessable_entity
render json: { error: e.message }, status: :unprocessable_content
end
rescue_from RateLimit::LimitApproached do |e|
@@ -25,26 +25,58 @@ 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|
render json: { error: e.message }, status: :forbidden
render json: { error: access_denied_error_message(e) }, status: :forbidden
end
rescue_from JSON::ParserError do |e|
Rollbar.warning(e) if defined?(Rollbar)
render json: { error: "JSON parse error: #{e.message}" }, status: :unprocessable_entity
render json: { error: "JSON parse error: #{e.message}" }, status: :unprocessable_content
end
end
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
+46 -4
View File
@@ -1,16 +1,58 @@
# 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])
if params[:type].in?(%w[initials signature])
image = Vips::Image.new_from_file(params[:file].path)
if ImageUtils.blank?(image)
Rollbar.error("Empty signature: #{submitter.id}") if defined?(Rollbar)
return render json: { error: "#{params[:type]} is empty" }, status: :unprocessable_content
end
if ImageUtils.error?(image)
Rollbar.error("Error signature: #{submitter.id}") if defined?(Rollbar)
return render json: { error: "#{params[:type]} error, try to sign on another device" },
status: :unprocessable_content
end
end
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])
rescue Submitters::MaliciousFileExtension => e
Rollbar.error(e) if defined?(Rollbar)
render json: { error: e.message }, status: :unprocessable_content
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,39 @@
# 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: :parent_folder },
submission: [:submitters, { audit_trail_attachment: :blob,
combined_document_attachment: :blob }],
documents_attachments: :blob, attachments_attachments: :blob),
field: :completed_at
)
expires_at = Accounts.link_expires_at(current_account)
render json: {
data: submitters.map do |s|
{
event_type: 'form.completed',
timestamp: s.completed_at,
data: Submitters::SerializeForWebhook.call(s, expires_at:)
}
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,75 @@
# frozen_string_literal: true
module Api
class SubmissionDocumentsController < ApiBaseController
load_and_authorize_resource :submission
def index
is_merge = params[:merge] == 'true' &&
(@submission.schema_documents || @submission.template.schema_documents).size > 1
documents =
if @submission.submitters.all?(&:completed_at?)
build_completed_documents(@submission, merge: is_merge)
else
build_preview_documents(@submission, merge: is_merge)
end
ActiveRecord::Associations::Preloader.new(records: documents, associations: [:blob]).call
expires_at = Accounts.link_expires_at(current_account)
render json: {
id: @submission.id,
documents: documents.map do |attachment|
{ name: attachment.filename.base, url: ActiveStorage::Blob.proxy_url(attachment.blob, expires_at:) }
end
}
end
private
def build_completed_documents(submission, merge: false)
last_submitter = submission.submitters.max_by(&:completed_at)
if merge
if submission.merged_document_attachment.blank?
submission.merged_document_attachment =
Submissions::GenerateCombinedAttachment.call(last_submitter, with_audit: false)
end
[submission.merged_document_attachment]
else
if last_submitter.documents_attachments.blank?
last_submitter.documents_attachments = Submissions::EnsureResultGenerated.call(last_submitter)
end
last_submitter.documents_attachments
end
end
def build_preview_documents(submission, merge: false)
values_hash = Submissions::GeneratePreviewAttachments.build_values_hash(submission)
if merge
if submission.preview_merged_document_attachment.present? &&
submission.preview_merged_document_attachment.metadata['values_hash'] == values_hash
[submission.preview_merged_document_attachment]
else
ApplicationRecord.no_touching { submission.preview_merged_document_attachment&.destroy }
Submissions::GeneratePreviewAttachments.call(submission, values_hash:, merge: true)
end
elsif 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
end
end
@@ -0,0 +1,55 @@
# 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,
combined_document_attachment: :blob
),
field: :completed_at)
expires_at = Accounts.link_expires_at(current_account)
render json: {
data: submissions.map do |s|
{
event_type: 'submission.completed',
timestamp: s.completed_at,
data: Submissions::SerializeForApi.call(s, s.submitters, expires_at:)
}
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
+86 -70
View File
@@ -10,19 +10,21 @@ module Api
end
def index
submissions = Submissions.search(@submissions, params[:q])
submissions = submissions.where(template_id: params[:template_id]) if params[:template_id].present?
if params[:template_folder].present?
submissions = submissions.joins(template: :folder).where(folder: { name: params[:template_folder] })
end
submissions = Submissions.search(current_user, @submissions, params[:q])
submissions = filter_submissions(submissions, params)
submissions = paginate(submissions.preload(:created_by_user, :submitters,
template: :folder,
template: { folder: :parent_folder },
combined_document_attachment: :blob,
audit_trail_attachment: :blob))
expires_at = Accounts.link_expires_at(current_account)
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, expires_at:)
end,
pagination: {
count: submissions.size,
next: submissions.last&.id,
@@ -41,43 +43,21 @@ module Api
end
if @submission.audit_trail_attachment.blank? && submitters.all?(&:completed_at?)
@submission.audit_trail_attachment = Submissions::GenerateAuditTrail.call(@submission)
@submission.audit_trail_attachment = Submissions::EnsureAuditGenerated.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)
return render json: { error: 'Template not found' }, status: :unprocessable_entity if @template.nil?
return render json: { error: 'Template not found' }, status: :unprocessable_content if @template.nil?
if @template.fields.blank?
Rollbar.warning("Template does not contain fields: #{@template.id}") if defined?(Rollbar)
return render json: { error: 'Template does not contain fields' }, status: :unprocessable_entity
return render json: { error: 'Template does not contain fields' }, status: :unprocessable_content
end
params[:send_email] = true unless params.key?(:send_email)
@@ -85,42 +65,89 @@ module Api
submissions = create_submissions(@template, params)
WebhookUrls.enqueue_events(submissions, 'submission.created')
Submissions.send_signature_requests(submissions)
submissions.each do |submission|
if submission.submitters.all?(&:completed_at?) && submission.submitters.last
ProcessSubmitterCompletionJob.perform_later(submission.submitters.last)
submission.submitters.each do |submitter|
next unless submitter.completed_at?
ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id, 'send_invitation_email' => false)
end
end
json = submissions.flat_map do |submission|
submission.submitters.map do |s|
Submitters::SerializeForApi.call(s, with_documents: false, with_urls: true)
end
end
SearchEntries.enqueue_reindex(submissions)
render json:
rescue Submitters::NormalizeValues::BaseError => e
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
render json: { error: e.message }, status: :unprocessable_content
end
def destroy
@submission.update!(archived_at: Time.current)
if params[:permanently].in?(['true', true])
@submission.destroy!
else
@submission.update!(archived_at: Time.current)
SendSubmissionArchivedWebhookRequestJob.perform_later(@submission)
WebhookUrls.enqueue_events(@submission, 'submission.archived')
end
render json: @submission.as_json(only: %i[id], methods: %i[archived_at])
render json: @submission.as_json(only: %i[id archived_at])
end
private
def filter_submissions(submissions, params)
submissions = submissions.where(template_id: params[:template_id]) if params[:template_id].present?
submissions = submissions.where(slug: params[:slug]) if params[:slug].present?
if params[:template_folder].present?
folders =
TemplateFolders.filter_by_full_name(TemplateFolder.accessible_by(current_ability), params[:template_folder])
submissions = submissions.joins(:template).where(template: { folder_id: folders.pluck(:id) })
end
if params.key?(:archived)
submissions = params[:archived].in?(['true', true]) ? submissions.archived : submissions.active
end
Submissions::Filter.call(submissions, current_user, params)
end
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
if request.path.ends_with?('/init')
json =
if submissions.size == 1
{
id: submissions.first.id,
submitters: json,
expire_at: submissions.first.expire_at,
created_at: submissions.first.created_at
}
else
{ submitters: json }
end
end
json
end
def create_submissions(template, params)
is_send_email = !params[:send_email].in?(['false', false])
if (emails = (params[:emails] || params[:email]).presence) &&
(params[:submission].blank? && params[:submitters].blank?)
params[:submission].blank? && params[:submitters].blank?
Submissions.create_from_emails(template:,
user: current_user,
source: :api,
@@ -135,7 +162,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:
@@ -146,40 +172,30 @@ module Api
Submissions::NormalizeParamUtils.save_default_value_attachments!(attachments, submitters)
submitters.each do |submitter|
SubmissionEvents.create_with_tracking_data(submitter, 'api_complete_form', request) if submitter.completed_at?
if submitter.completed_at?
Submitters::SubmitValues.maybe_invite_via_field(submitter, request)
SubmissionEvents.create_with_tracking_data(submitter, 'api_complete_form', request)
end
end
submissions
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 external_id created_at updated_at],
methods: %i[folder_name] },
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, :reply_to, :go_to_last,
:require_phone_2fa, :require_email_2fa, :expire_at, :name,
{
variables: {},
message: %i[subject body],
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
:require_phone_2fa, :require_email_2fa, :order, :index, :invite_by,
{ metadata: {}, values: {}, roles: [], readonly_fields: [], message: %i[subject body],
fields: [:name, :uuid, :default_value, :value, :title, :description,
:readonly, :redacted, :validation_pattern, :invalid_message,
{ default_value: [], value: [] }] }]]
:readonly, :required, :validation_pattern, :invalid_message,
{ default_value: [], value: [], preferences: {}, validation: {} }] }]]
}
]
@@ -189,7 +205,7 @@ module Api
key = params.key?(:submission) ? :submission : :submissions
params.permit(
key => [permitted_attrs]
{ key => [permitted_attrs] }, { key => permitted_attrs }
).fetch(key, [])
end
end
@@ -6,10 +6,10 @@ module Api
skip_authorization_check
def create
submitter = Submitter.find_by!(slug: params[:submitter_slug])
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
if params[:t] == SubmissionEvents.build_tracking_param(submitter, 'click_email')
SubmissionEvents.create_with_tracking_data(submitter, 'click_email', request)
if params[:t] == SubmissionEvents.build_tracking_param(@submitter, 'click_email')
SubmissionEvents.create_with_tracking_data(@submitter, 'click_email', request)
end
render json: {}
@@ -6,14 +6,14 @@ module Api
skip_authorization_check
def create
submitter = Submitter.find_by!(slug: params[:submitter_slug])
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
submitter.opened_at = Time.current
submitter.save
@submitter.opened_at = Time.current
@submitter.save
SubmissionEvents.create_with_tracking_data(submitter, 'view_form', request)
SubmissionEvents.create_with_tracking_data(@submitter, 'view_form', request)
SendFormViewedWebhookRequestJob.perform_later(submitter)
WebhookUrls.enqueue_events(@submitter, 'form.viewed')
render json: {}
end
+105 -35
View File
@@ -5,25 +5,21 @@ module Api
load_and_authorize_resource :submitter
def index
submitters = Submitters.search(@submitters, params[:q])
submitters = Submitters.search(current_user, @submitters, params[:q])
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 = filter_submitters(submitters, params)
submitters = paginate(
submitters.preload(:template, :submission, :submission_events,
documents_attachments: :blob, attachments_attachments: :blob)
)
expires_at = Accounts.link_expires_at(current_account)
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:, expires_at:)
end,
pagination: {
count: submitters.size,
next: submitters.last&.id,
@@ -35,22 +31,25 @@ 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
# rubocop:disable Metrics/MethodLength
def update
if @submitter.completed_at?
return render json: { error: 'Submitter has already completed the submission.' }, status: :unprocessable_entity
return render json: { error: 'Submitter has already completed the submission.' }, status: :unprocessable_content
end
role = @submitter.submission.template_submitters.find { |e| e['uuid'] == @submitter.uuid }['name']
submission = @submitter.submission
role = submission.template_submitters.find { |e| e['uuid'] == @submitter.uuid }['name']
normalized_params, new_attachments =
Submissions::NormalizeParamUtils.normalize_submitter_params!(submitter_params.merge(role:), @submitter.template,
for_submitter: @submitter)
normalized_params, new_attachments = Submissions::NormalizeParamUtils.normalize_submitter_params!(
submitter_params.merge(role:),
@submitter.template || Template.new(submitters: submission.template_submitters, account: @submitter.account),
for_submitter: @submitter
)
Submissions::CreateFromSubmitters.maybe_set_template_fields(@submitter.submission,
[normalized_params],
Submissions::CreateFromSubmitters.maybe_set_template_fields(submission, [normalized_params],
default_submitter_uuid: @submitter.uuid)
assign_submitter_attrs(@submitter, normalized_params)
@@ -62,30 +61,39 @@ module Api
@submitter.submission.save!
SubmissionEvents.create_with_tracking_data(@submitter, 'api_complete_form', request) if @submitter.completed_at?
if @submitter.completed_at?
Submitters::SubmitValues.maybe_invite_via_field(@submitter, request)
SubmissionEvents.create_with_tracking_data(@submitter, 'api_complete_form', request)
end
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_urls: true,
with_events: false)
SearchEntries.enqueue_reindex(@submitter)
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_content
end
# rubocop:enable Metrics/MethodLength
def submitter_params
submitter_params = params.key?(:submitter) ? params.require(:submitter) : params
submitter_params.permit(
:send_email, :send_sms, :reply_to, :completed_redirect_url, :uuid, :name, :email, :role,
:completed, :phone, :application_key, :external_id, :go_to_last,
:completed, :phone, :application_key, :external_id, :go_to_last, :require_phone_2fa, :require_email_2fa,
{ metadata: {}, values: {}, readonly_fields: [], message: %i[subject body],
fields: [[:name, :uuid, :default_value, :value,
:readonly, :redacted, :validation_pattern, :invalid_message,
{ default_value: [], value: [] }]] }
fields: [[:name, :uuid, :default_value, :value, :required,
:readonly, :validation_pattern, :invalid_message,
{ default_value: [], value: [], preferences: {} }]] }
)
end
@@ -104,20 +112,78 @@ 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.maybe_remove_condition_values(submitter)
formula_values = Submitters::SubmitValues.build_formula_values(submitter)
if formula_values.present?
submitter.values = submitter.values.merge(formula_values)
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
end
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 filter_submitters(submitters, params)
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(slug: params[:slug]) if params[:slug].present?
submitters = submitters.where(submission_id: params[:submission_id]) if params[:submission_id].present?
if params[:template_id].present?
submitters = submitters.joins(:submission).where(submissions: { template_id: params[:template_id] })
end
maybe_filder_by_completed_at(submitters, params)
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
@@ -134,6 +200,10 @@ module Api
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?('require_phone_2fa')
submitter.preferences['require_phone_2fa'] = submitter_preferences['require_phone_2fa']
end
if submitter_preferences.key?('go_to_last')
submitter.preferences['go_to_last'] = submitter_preferences['go_to_last']
end
@@ -5,7 +5,12 @@ module Api
load_and_authorize_resource :template
def create
authorize!(:manage, @template)
authorize!(:create, @template)
ActiveRecord::Associations::Preloader.new(
records: [@template],
associations: [schema_documents: :preview_images_attachments]
).call
cloned_template = Templates::Clone.call(
@template,
@@ -16,11 +21,20 @@ module Api
)
cloned_template.source = :api
schema_documents = Templates::CloneAttachments.call(template: cloned_template,
original_template: @template,
documents: params[:documents])
Templates.maybe_assign_access(cloned_template)
cloned_template.save!
Templates::CloneAttachments.call(template: cloned_template, original_template: @template)
WebhookUrls.enqueue_events(cloned_template, 'template.created')
render json: Templates::SerializeForApi.call(cloned_template)
SearchEntries.enqueue_reindex(cloned_template)
render json: Templates::SerializeForApi.call(cloned_template, schema_documents:)
end
end
end
+31 -16
View File
@@ -7,7 +7,7 @@ module Api
def index
templates = filter_templates(@templates, params)
templates = paginate(templates.preload(:author, :folder))
templates = paginate(templates.preload(:author, folder: :parent_folder))
schema_documents =
ActiveStorage::Attachment.where(record_id: templates.map(&:id),
@@ -18,19 +18,20 @@ 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)
.preload(:blob)
expires_at = Accounts.link_expires_at(current_account)
render json: {
data: templates.map do |t|
Templates::SerializeForApi.call(
t,
schema_documents.select { |e| e.record_id == t.id },
preview_image_attachments
)
Templates::SerializeForApi.call(t,
schema_documents: schema_documents.select { |e| e.record_id == t.id },
preview_image_attachments:,
expires_at:)
end,
pagination: {
count: templates.size,
@@ -65,11 +66,15 @@ module Api
@template.update!(template_params)
SearchEntries.enqueue_reindex(@template)
WebhookUrls.enqueue_events(@template, 'template.updated')
render json: @template.as_json(only: %i[id updated_at])
end
def destroy
if params[:permanently] == 'true' && !Docuseal.multitenant?
if params[:permanently].in?(['true', true])
@template.destroy!
else
@template.update!(archived_at: Time.current)
@@ -81,11 +86,17 @@ module Api
private
def filter_templates(templates, params)
templates = Templates.search(templates, params[:q])
templates = params[:archived] ? templates.archived : templates.active
templates = Templates.search(current_user, templates, params[:q])
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?
templates = templates.where(slug: params[:slug]) if params[:slug].present?
if params[:folder].present?
folders = TemplateFolders.filter_by_full_name(TemplateFolder.accessible_by(current_ability), params[:folder])
templates = templates.where(folder_id: folders.pluck(:id))
end
templates
end
@@ -93,16 +104,20 @@ module Api
def template_params
permitted_params = [
:name,
:external_id,
:shared_link,
{
submitters: [%i[name uuid]],
submitters: [%i[name uuid is_requester invite_by_uuid invite_via_field_uuid
optional_invite_by_uuid linked_to_uuid email order]],
fields: [[:uuid, :submitter_uuid, :name, :type,
:required, :readonly, :default_value,
:title, :description,
:title, :description, :prefillable,
{ preferences: {},
conditions: [%i[field_uuid value action]],
default_value: [],
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]] }]]
validation: %i[message pattern min max step],
areas: [%i[uuid x y w h cell_w attachment_uuid option_uuid page]] }]]
}
]
+41
View File
@@ -0,0 +1,41 @@
# 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_content if files.blank?
return render json: { error: 'At least 2 files are required' }, status: :unprocessable_content 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
}
rescue HexaPDF::MalformedPDFError
render json: { error: 'Malformed PDF' }, status: :unprocessable_content
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
+62 -5
View File
@@ -4,21 +4,26 @@ class ApplicationController < ActionController::Base
BROWSER_LOCALE_REGEXP = /\A\w{2}(?:-\w{2})?/
include ActiveStorage::SetCurrent
include Pagy::Backend
include Pagy::Method
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?
before_action :set_csp, if: -> { request.get? && !request.headers['HTTP_X_TURBO'] }
helper_method :button_title,
:current_account,
:true_ability,
:form_link_host,
:svg_icon
impersonates :user, with: ->(uuid) { User.find_by(uuid:) }
rescue_from Pagy::OverflowError do
rescue_from Pagy::RangeError do
redirect_to request.path
end
@@ -28,7 +33,7 @@ class ApplicationController < ActionController::Base
redirect_to request.referer, alert: 'Too many requests', status: :too_many_requests
end
if Rails.env.production?
if Rails.env.production? || Rails.env.test?
rescue_from CanCan::AccessDenied do |e|
Rollbar.warning(e) if defined?(Rollbar)
@@ -49,10 +54,30 @@ class ApplicationController < ActionController::Base
request.session[:impersonated_user_id] = user.uuid
end
def pagy_auto(collection, **keyword_args)
if current_ability.can?(:manage, :countless)
pagy(:countless, collection, **keyword_args)
else
pagy(collection, **keyword_args)
end
end
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'
@@ -74,11 +99,16 @@ class ApplicationController < ActionController::Base
current_user&.account
end
def true_ability
@true_ability ||= Ability.new(true_user)
end
def maybe_redirect_to_setup
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 +116,31 @@ 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
def set_csp
request.content_security_policy = current_content_security_policy.tap do |policy|
policy.default_src :self
policy.script_src :self
policy.style_src :self, :unsafe_inline
policy.img_src :self, :https, :http, :blob, :data
policy.font_src :self, :https, :http, :blob, :data
policy.manifest_src :self
policy.media_src :self
policy.frame_src :self
policy.worker_src :self, :blob
policy.connect_src :self
policy.directives['connect-src'] << 'ws:' if Rails.env.development?
end
end
end
@@ -6,17 +6,21 @@ class ConsoleRedirectController < ApplicationController
def index
if request.path == '/upgrade'
params[:redir] = Docuseal.multitenant? ? "#{Docuseal::CONSOLE_URL}/plans" : "#{Docuseal::CONSOLE_URL}/on_premise"
params[:redir] = Docuseal.multitenant? ? "#{Docuseal::CONSOLE_URL}/plans" : "#{Docuseal::CONSOLE_URL}/on_premises"
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,
scope: :console,
exp: 1.minute.from_now.to_i)
path = Addressable::URI.parse(params[:redir]).path if params[:redir].to_s.starts_with?(Docuseal::CONSOLE_URL)
redir_uri = Addressable::URI.parse(params[:redir])
path = redir_uri.path if params[:redir].to_s.starts_with?(Docuseal::CONSOLE_URL)
redirect_to("#{Docuseal::CONSOLE_URL}#{path}?#{{ auth: }.to_query}", allow_other_host: true)
redirect_to "#{Docuseal::CONSOLE_URL}#{path}?#{{ **redir_uri&.query_values, 'auth' => auth }.to_query}",
allow_other_host: true
end
end
+1 -1
View File
@@ -33,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,16 +9,18 @@ class EmailSmtpSettingsController < ApplicationController
def create
if @encrypted_config.update(email_configs)
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email']).deliver_now!
unless Docuseal.multitenant?
SettingsMailer.smtp_successful_setup(@encrypted_config.value['from_email'] || current_user.email).deliver_now!
end
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
render :index, status: :unprocessable_content
end
rescue StandardError => e
flash[:alert] = e.message
render :index, status: :unprocessable_entity
render :index, status: :unprocessable_content
end
private
@@ -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_premises" 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
+5 -1
View File
@@ -2,11 +2,15 @@
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 = [
'/submissions/html',
'/api/submissions/html',
'/templates/html',
'/api/templates/html',
'/submissions/pdf',
'/api/submissions/pdf',
'/templates/pdf',
'/api/templates/pdf',
'/templates/doc',
+24 -14
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,30 +50,38 @@ 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
status: :unprocessable_content
end
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)
render turbo_stream: turbo_stream.replace(:modal, template: 'esign_settings/new'), status: :unprocessable_entity
@cert_record.errors.add(:password, e.message)
render turbo_stream: turbo_stream.replace(:modal, template: 'esign_settings/new'), status: :unprocessable_content
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
@@ -1,4 +1,11 @@
# frozen_string_literal: true
class InvitationsController < Devise::PasswordsController
def update
super do |resource|
resource.confirmed_at ||= Time.current if resource.errors.empty?
PasswordsController::Current.user = resource
end
end
end
+58
View File
@@ -0,0 +1,58 @@
# frozen_string_literal: true
class McpController < ActionController::API
before_action :authenticate_user!
before_action :verify_mcp_enabled!
before_action do
authorize!(:manage, :mcp)
end
def call
return head :ok if request.raw_post.blank?
body = JSON.parse(request.raw_post)
result = Mcp::HandleRequest.call(body, current_user, current_ability)
if result
render json: result
else
head :accepted
end
rescue CanCan::AccessDenied
render json: { jsonrpc: '2.0', id: nil, error: { code: -32_603, message: 'Forbidden' } }, status: :forbidden
rescue JSON::ParserError
render json: { jsonrpc: '2.0', id: nil, error: { code: -32_700, message: 'Parse error' } }, status: :bad_request
end
private
def authenticate_user!
render json: { error: 'Not authenticated' }, status: :unauthorized unless current_user
end
def verify_mcp_enabled!
return if Docuseal.multitenant?
return if AccountConfig.exists?(account_id: current_user.account_id,
key: AccountConfig::ENABLE_MCP_KEY,
value: true)
render json: { error: 'MCP is disabled' }, status: :forbidden
end
def current_user
@current_user ||= user_from_api_key
end
def user_from_api_key
token = request.headers['Authorization'].to_s[/\ABearer\s+(.+)\z/, 1]
return if token.blank?
sha256 = Digest::SHA256.hexdigest(token)
User.joins(:mcp_tokens).active.find_by(mcp_tokens: { sha256:, archived_at: nil })
end
end
@@ -0,0 +1,37 @@
# frozen_string_literal: true
class McpSettingsController < ApplicationController
load_and_authorize_resource :mcp_token, parent: false
before_action do
authorize!(:manage, :mcp)
end
def index
@mcp_tokens = @mcp_tokens.active.order(id: :desc)
end
def create
@mcp_token = current_user.mcp_tokens.new(mcp_token_params)
if @mcp_token.save
@mcp_tokens = [@mcp_token]
render :index, status: :created
else
render turbo_stream: turbo_stream.replace(:modal, template: 'mcp_settings/new'), status: :unprocessable_content
end
end
def destroy
@mcp_token.update!(archived_at: Time.current)
redirect_back fallback_location: settings_mcp_index_path, notice: I18n.t('mcp_token_has_been_removed')
end
private
def mcp_token_params
params.require(:mcp_token).permit(:name)
end
end
+7 -7
View File
@@ -18,13 +18,13 @@ 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
render turbo_stream: turbo_stream.replace(:mfa_form, partial: 'mfa_setup/form'), status: :unprocessable_content
end
end
@@ -32,18 +32,18 @@ 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
render turbo_stream: turbo_stream.replace(:modal, template: 'mfa_setup/edit'), status: :unprocessable_content
end
end
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
@@ -39,7 +39,7 @@ class NotificationsSettingsController < ApplicationController
end
def email_config_params
params.require(:account_config).permit!.tap do |attrs|
params.require(:account_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs|
attrs[:key] = nil unless attrs[:key].in?([AccountConfig::BCC_EMAILS, AccountConfig::SUBMITTER_REMINDERS])
end
end
+12
View File
@@ -1,6 +1,12 @@
# frozen_string_literal: true
class PasswordsController < Devise::PasswordsController
# rubocop:disable Rails/LexicallyScopedActionFilter
skip_before_action :require_no_authentication, only: %i[edit update]
# rubocop:enable Rails/LexicallyScopedActionFilter
around_action :with_browser_locale
class Current < ActiveSupport::CurrentAttributes
attribute :user
end
@@ -16,4 +22,10 @@ class PasswordsController < Devise::PasswordsController
Current.user = resource
end
end
private
def after_resetting_password_path_for(_)
new_session_path(resource_name)
end
end
@@ -4,9 +4,11 @@ class PersonalizationSettingsController < ApplicationController
ALLOWED_KEYS = [
AccountConfig::FORM_COMPLETED_BUTTON_KEY,
AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY,
AccountConfig::SUBMITTER_INVITATION_REMINDER_EMAIL_KEY,
AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY,
AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY,
AccountConfig::FORM_COMPLETED_MESSAGE_KEY
AccountConfig::FORM_COMPLETED_MESSAGE_KEY,
*(Docuseal.multitenant? ? [] : [AccountConfig::POLICY_LINKS_KEY])
].freeze
InvalidKey = Class.new(StandardError)
@@ -30,7 +32,7 @@ class PersonalizationSettingsController < ApplicationController
@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
@@ -49,7 +51,7 @@ class PersonalizationSettingsController < ApplicationController
end
def account_config_params
attrs = params.require(:account_config).permit!
attrs = params.require(:account_config).permit(:key, :value, { value: {} }, { value: [] })
return attrs if attrs[:value].is_a?(String)
@@ -6,13 +6,24 @@ class PreviewDocumentPageController < ActionController::API
FORMAT = Templates::ProcessDocument::FORMAT
def show
attachment_uuid = ApplicationRecord.signed_id_verifier.verified(params[:signed_uuid], purpose: :attachment)
result_data =
ApplicationRecord.signed_id_verifier.verified(params[:signed_key], purpose: :attachment)
attachment = ActiveStorage::Attachment.find_by(uuid: attachment_uuid) if attachment_uuid
attachment =
if result_data.is_a?(Array) && result_data.compact_blank.size == 2
attachment_id, attachment_uuid = result_data
ActiveStorage::Attachment.find_by(id: attachment_id, uuid: attachment_uuid)
elsif result_data
ActiveStorage::Attachment.find_by(uuid: result_data)
end
return head :not_found unless attachment
preview_image = attachment.preview_images.joins(:blob).find_by(blob: { filename: "#{params[:id]}#{FORMAT}" })
@template = attachment.record
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
+13 -6
View File
@@ -9,18 +9,25 @@ class ProfileController < ApplicationController
def update_contact
if current_user.update(contact_params)
redirect_to settings_profile_index_path, notice: 'Contact information has been updated'
if current_user.try(:pending_reconfirmation?) && current_user.previous_changes.key?(:unconfirmed_email)
SendConfirmationInstructionsJob.perform_async('user_id' => current_user.id)
redirect_to settings_profile_index_path,
notice: I18n.t('a_confirmation_email_has_been_sent_to_the_new_email_address')
else
redirect_to settings_profile_index_path, notice: I18n.t('contact_information_has_been_update')
end
else
render :index, status: :unprocessable_entity
render :index, status: :unprocessable_content
end
end
def update_password
if current_user.update(password_params)
if current_user.update_with_password(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
render :index, status: :unprocessable_content
end
end
@@ -31,6 +38,6 @@ class ProfileController < ApplicationController
end
def password_params
params.require(:user).permit(:password, :password_confirmation)
params.require(:user).permit(:password, :password_confirmation, :current_password)
end
end
+4
View File
@@ -0,0 +1,4 @@
# frozen_string_literal: true
class PwaController < ActionController::Base
end
@@ -0,0 +1,21 @@
# frozen_string_literal: true
class RevealAccessTokenController < ApplicationController
def show
authorize!(:manage, current_user.access_token)
end
def create
authorize!(:manage, current_user.access_token)
if current_user.valid_password?(params[:password])
render turbo_stream: turbo_stream.replace(:access_token_container,
partial: 'reveal_access_token/access_token',
locals: { token: current_user.access_token.token })
else
render turbo_stream: turbo_stream.replace(:modal, template: 'reveal_access_token/show',
locals: { error_message: I18n.t('wrong_password') }),
status: :unprocessable_content
end
end
end
@@ -0,0 +1,17 @@
# frozen_string_literal: true
class SearchEntriesReindexController < ApplicationController
def create
authorize!(:manage, EncryptedConfig)
ReindexAllSearchEntriesJob.perform_async
AccountConfig.find_or_initialize_by(account_id: Account.minimum(:id), key: :fulltext_search)
.update!(value: true)
Docuseal.instance_variable_set(:@fulltext_search, nil)
redirect_back(fallback_location: settings_account_path,
notice: "Started building search index. Visit #{root_url}jobs/busy to check progress.")
end
end
@@ -7,27 +7,43 @@ class SendSubmissionEmailController < ApplicationController
skip_before_action :verify_authenticity_token
skip_authorization_check
def success; end
SEND_DURATION = 30.minutes
def create
@submitter =
if params[:template_slug]
Submitter.joins(submission: :template).find_by!(email: params[:email].to_s.downcase,
template: { slug: params[:template_slug] })
elsif params[:submission_slug]
Submitter.joins(:submission).find_by!(email: params[:email].to_s.downcase,
submission: { slug: params[:submission_slug] })
else
Submitter.find_by!(slug: params[:submitter_slug])
if params[:template_slug]
template = Template.find_by!(slug: params[:template_slug])
@submitter =
Submitter.completed.where(submission: template.submissions).find_by!(email: params[:email].to_s.downcase)
elsif params[:submission_slug]
submission = Submission.find_by(slug: params[:submission_slug])
if submission
@submitter = Submitter.completed.find_by(submission: submission, email: params[:email].to_s.downcase)
end
return redirect_to submissions_preview_completed_path(params[:submission_slug], status: :error) unless @submitter
else
@submitter = Submitter.completed.find_by!(slug: params[:submitter_slug])
end
RateLimit.call("send-email-#{@submitter.id}", limit: 2, ttl: 5.minutes)
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later!
SubmitterMailer.documents_copy_email(@submitter, sig: true).deliver_later! if can_send?(@submitter)
respond_to do |f|
f.html { redirect_to success_send_submission_email_index_path }
f.html { render :success }
f.json { head :ok }
end
end
private
def can_send?(submitter)
return false if submitter.account.archived_at?
return false if EmailEvent.exists?(tag: :submitter_documents_copy, email: submitter.email, emailable: submitter,
event_type: :send, created_at: SEND_DURATION.ago..Time.current)
true
end
end
+4 -2
View File
@@ -3,6 +3,8 @@
class SessionsController < Devise::SessionsController
before_action :configure_permitted_parameters
around_action :with_browser_locale
def create
email = sign_in_params[:email].to_s.downcase
@@ -10,11 +12,11 @@ class SessionsController < Devise::SessionsController
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?
return render :otp, locals: { resource: User.new(sign_in_params) }, status: :unprocessable_entity
return render :otp, locals: { resource: User.new(sign_in_params) }, status: :unprocessable_content
end
super
+8 -7
View File
@@ -21,12 +21,12 @@ 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
return render :index, status: :unprocessable_content
end
return render :index, status: :unprocessable_entity unless @account.valid?
return render :index, status: :unprocessable_content unless @account.valid?
if @user.save
encrypted_configs = [
@@ -34,6 +34,7 @@ class SetupController < ApplicationController
{ key: EncryptedConfig::ESIGN_CERTS_KEY, value: GenerateCertificate.call.transform_values(&:to_pem) }
]
@account.encrypted_configs.create!(encrypted_configs)
@account.account_configs.create!(key: :fulltext_search, value: true) if SearchEntry.table_exists?
Docuseal.refresh_default_url_options!
@@ -41,7 +42,7 @@ class SetupController < ApplicationController
redirect_to newsletter_path
else
render :index, status: :unprocessable_entity
render :index, status: :unprocessable_content
end
end
@@ -56,7 +57,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 +67,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
+183 -29
View File
@@ -6,66 +6,161 @@ class StartFormController < ApplicationController
skip_before_action :authenticate_user!
skip_authorization_check
around_action :with_browser_locale, only: %i[show completed]
around_action :with_browser_locale, only: %i[show update completed]
before_action :maybe_redirect_com, only: %i[show completed]
before_action :load_resubmit_submitter, only: :update
before_action :load_template
before_action :authorize_start!, only: :update
COOKIES_TTL = 12.hours
COOKIES_DEFAULTS = { httponly: true, secure: Rails.env.production? }.freeze
def show
@submitter = @template.submissions.new(account_id: @template.account_id)
.submitters.new(uuid: @template.submitters.first['uuid'])
if @template.preferences['require_phone_2fa'] || @template.preferences['require_email_2fa']
raise ActionController::RoutingError, I18n.t('not_found')
end
if @template.shared_link?
@submitter = @template.submissions.new(account_id: @template.account_id)
.submitters.new(account_id: @template.account_id,
uuid: (filter_undefined_submitters(@template).first ||
@template.submitters.first)['uuid'])
render :email_verification if params[:email_verification]
else
Rollbar.warning("Not shared template: #{@template.id}") if defined?(Rollbar)
return render :private if current_user && current_ability.can?(:read, @template)
raise ActionController::RoutingError, I18n.t('not_found')
end
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])
redirect_to start_form_completed_path(@template.slug, submitter_params.compact_blank)
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 = multiple_submitters_error_message
return render :show
return render :show, status: :unprocessable_content
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)
else
@submitter.assign_attributes(ip: request.remote_ip, ua: request.user_agent)
end
if @template.preferences['shared_link_2fa'] == true
handle_require_2fa(@submitter, is_new_record:)
elsif @submitter.errors.blank? && @submitter.save
enqueue_new_submitter_jobs(@submitter) if is_new_record
if @submitter.save
redirect_to submit_form_path(@submitter.slug)
else
render :show
render :show, status: :unprocessable_content
end
end
end
def completed
return redirect_to start_form_path(@template.slug) if !@template.shared_link? || @template.archived_at?
submitter_params = params.permit(:name, :email, :phone).tap do |attrs|
attrs[:email] = Submissions.normalize_email(attrs[:email])
end
required_fields = @template.preferences.fetch('link_form_fields', ['email'])
required_params = required_fields.index_with { |key| submitter_params[key] }
raise ActionController::RoutingError, I18n.t('not_found') if required_params.any? { |_, v| v.blank? } ||
required_params.except('name').compact_blank.blank?
@submitter = Submitter.where(submission: @template.submissions)
.where.not(completed_at: nil)
.find_by!(email: params[:email])
.find_by!(required_params.except('name'))
end
private
def assign_submission_attributes(submitter, template)
resubmit_submitter =
if params[:resubmit].present?
Submitter.where(submission: @template.submissions).find_by(slug: params[:resubmit])
end
def enqueue_new_submitter_jobs(submitter)
WebhookUrls.enqueue_events(submitter.submission, 'submission.created')
SearchEntries.enqueue_reindex(submitter)
return unless submitter.submission.expire_at?
ProcessSubmissionExpiredJob.perform_at(submitter.submission.expire_at, 'submission_id' => submitter.submission_id)
end
def load_resubmit_submitter
@resubmit_submitter =
if params[:resubmit].present? && !params[:resubmit].in?([true, 'true'])
Submitter.find_by(slug: params[:resubmit])
end
end
def authorize_start!
return redirect_to start_form_path(@template.slug) if @template.archived_at?
return if @resubmit_submitter
return if @template.shared_link? || (current_user && current_ability.can?(:read, @template))
Rollbar.warning("Not shared template: #{@template.id}") if defined?(Rollbar)
redirect_to start_form_path(@template.slug)
end
def find_or_initialize_submitter(template, submitter_params)
required_fields = template.preferences.fetch('link_form_fields', ['email'])
required_params = required_fields.index_with { |key| submitter_params[key] }
find_params = required_params.except('name')
submitter = Submitter.new if find_params.compact_blank.blank?
submitter ||=
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)
.where(external_id: nil)
.where(template.preferences['shared_link_2fa'] == true ? {} : { ip: [nil, request.remote_ip] })
.then { |rel| params[:resubmit].present? || params[:selfsign].present? ? rel.where(completed_at: nil) : rel }
.find_or_initialize_by(find_params)
submitter.name = required_params['name'] if submitter.new_record?
unless @resubmit_submitter
required_params.each do |key, value|
submitter.errors.add(key.to_sym, :blank) if value.blank?
end
end
submitter
end
def assign_submission_attributes(submitter, template)
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) || {},
preferences: resubmit_submitter&.preferences.presence || { 'send_email' => true },
metadata: resubmit_submitter&.metadata.presence || {}
values: @resubmit_submitter&.preferences&.fetch('default_values', nil) || {},
preferences: @resubmit_submitter&.preferences.presence || { 'send_email' => true },
metadata: @resubmit_submitter&.metadata.presence || {}
)
submitter.assign_attributes(@resubmit_submitter.slice(:name, :email, :phone)) if @resubmit_submitter
if submitter.values.present?
resubmit_submitter.attachments.each do |attachment|
@resubmit_submitter.attachments.each do |attachment|
submitter.attachments << attachment.dup if submitter.values.value?(attachment.uuid)
end
end
@@ -73,20 +168,79 @@ class StartFormController < ApplicationController
submitter.submission ||= Submission.new(template:,
account_id: template.account_id,
template_submitters: template.submitters,
expire_at: Templates.build_default_expire_at(template),
submitters: [submitter],
source: :link)
Submissions::CreateFromSubmitters.maybe_set_dynamic_documents(submitter.submission)
submitter.account_id = submitter.submission.account_id
submitter
end
def filter_undefined_submitters(template)
Templates.filter_undefined_submitters(template.submitters)
end
def submitter_params
return { 'email' => current_user.email, 'name' => current_user.full_name } if params[:selfsign]
return @resubmit_submitter.slice(:name, :phone, :email) if @resubmit_submitter.present?
params.require(:submitter).permit(:email, :phone, :name).tap do |attrs|
attrs[:email] = Submissions.normalize_email(attrs[:email])
end
end
def load_template
slug = params[:slug] || params[:start_form_slug]
@template =
if @resubmit_submitter
@resubmit_submitter.template
else
Template.find_by!(slug: params[:slug] || params[:start_form_slug])
end
end
@template = Template.find_by!(slug:)
def multiple_submitters_error_message
if current_user&.account_id == @template.account_id
helpers.t('this_submission_has_multiple_signers_which_prevents_the_use_of_a_sharing_link_html')
else
I18n.t('not_found')
end
end
def handle_require_2fa(submitter, is_new_record:)
return render :show, status: :unprocessable_content if submitter.errors.present?
is_otp_verified = Submitters.verify_link_otp!(params[:one_time_code], submitter)
if cookies.encrypted[:email_2fa_slug] == submitter.slug || is_otp_verified
if submitter.save
enqueue_new_submitter_jobs(submitter) if is_new_record
if is_otp_verified
SubmissionEvents.create_with_tracking_data(submitter, 'email_verified', request)
cookies.encrypted[:email_2fa_slug] =
{ value: submitter.slug, expires: COOKIES_TTL.from_now, **COOKIES_DEFAULTS }
end
redirect_to submit_form_path(submitter.slug)
else
render :show, status: :unprocessable_content
end
else
Submitters.send_shared_link_email_verification_code(submitter, request:)
render :email_verification
end
rescue Submitters::UnableToSendCode, Submitters::InvalidOtp => e
redirect_to start_form_path(submitter.submission.template.slug,
params: submitter_params.merge(email_verification: true)),
alert: e.message
rescue RateLimit::LimitApproached
redirect_to start_form_path(submitter.submission.template.slug,
params: submitter_params.merge(email_verification: true)),
alert: I18n.t(:too_many_attempts)
end
end
@@ -0,0 +1,31 @@
# frozen_string_literal: true
class StartFormEmail2faSendController < ApplicationController
around_action :with_browser_locale
skip_before_action :authenticate_user!
skip_authorization_check
def create
@template = Template.find_by!(slug: params[:slug])
@submitter = @template.submissions.new(account_id: @template.account_id)
.submitters.new(**submitter_params, account_id: @template.account_id)
Submitters.send_shared_link_email_verification_code(@submitter, request:)
redir_params = { notice: I18n.t(:code_has_been_resent) } if params[:resend]
redirect_to start_form_path(@template.slug, params: submitter_params.merge(email_verification: true)),
**redir_params
rescue Submitters::UnableToSendCode => e
redirect_to start_form_path(@template.slug, params: submitter_params.merge(email_verification: true)),
alert: e.message
end
private
def submitter_params
params.require(:submitter).permit(:name, :email, :phone)
end
end
@@ -11,9 +11,9 @@ 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
render :index, status: :unprocessable_content
end
end
@@ -0,0 +1,27 @@
# frozen_string_literal: true
class SubmissionEventsController < ApplicationController
SUBMISSION_EVENT_ICONS = {
'view_form' => 'eye',
'start_form' => 'player_play',
'complete_form' => 'check',
'send_email' => 'mail_forward',
'click_email' => 'hand_click',
'api_complete_form' => 'check',
'send_reminder_email' => 'mail_forward',
'send_2fa_sms' => '2fa',
'send_2fa_email' => '2fa',
'send_sms' => 'send',
'phone_verified' => 'phone_check',
'email_verified' => 'email_check',
'click_sms' => 'hand_click',
'decline_form' => 'x',
'start_verification' => 'player_play',
'complete_verification' => 'check',
'invite_party' => 'user_plus'
}.freeze
load_and_authorize_resource :submission
def index; end
end
@@ -4,12 +4,20 @@ class SubmissionsArchivedController < ApplicationController
load_and_authorize_resource :submission, parent: false
def index
@submissions = @submissions.joins(:template)
@submissions = @submissions.left_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.search(@submissions, params[:q], search_template: true)
.preload(:template_accesses, :created_by_user, template: :author)
@pagy, @submissions = pagy(@submissions.preload(:submitters).order(id: :desc))
@submissions = Submissions.search(current_user, @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_auto(@submissions.preload(submitters: :start_form_submission_events))
end
end
+60 -18
View File
@@ -6,9 +6,30 @@ class SubmissionsController < ApplicationController
load_and_authorize_resource :submission, only: %i[show destroy]
prepend_before_action :maybe_redirect_com, only: %i[show]
before_action only: :create do
authorize!(:create, Submission)
end
FIELD_ICONS = {
'text' => 'text_size', 'signature' => 'writing_sign', 'date' => 'calendar_event',
'number' => 'square_number_1', 'image' => 'photo', 'initials' => 'letter_case_upper',
'file' => 'paperclip', 'select' => 'select', 'checkbox' => 'checkbox', 'radio' => 'circle_dot',
'stamp' => 'rubber_stamp', 'cells' => 'columns_3', 'multiple' => 'checks', 'phone' => 'phone_check',
'payment' => 'credit_card', 'verification' => 'id', 'kba' => 'user_scan'
}.freeze
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
@@ -17,14 +38,9 @@ class SubmissionsController < ApplicationController
end
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)
end
[params.delete(:subject), params.delete(:body)] if params[:is_custom_message] != '1'
submissions =
if params[:emails].present?
@@ -35,30 +51,56 @@ class SubmissionsController < ApplicationController
emails: params[:emails],
params: params.merge('send_completed_email' => true))
else
Submissions.create_from_submitters(template: @template,
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))
create_submissions(@template, submissions_params, params)
end
WebhookUrls.enqueue_events(submissions, 'submission.created')
Submissions.send_signature_requests(submissions)
redirect_to template_path(@template), notice: 'New recipients have been added'
SearchEntries.enqueue_reindex(submissions)
redirect_to template_path(@template), notice: I18n.t('new_recipients_have_been_added')
rescue Submissions::CreateFromSubmitters::BaseError => e
render turbo_stream: turbo_stream.replace(:submitters_error, partial: 'submissions/error',
locals: { error: e.message }),
status: :unprocessable_content
end
def destroy
@submission.update!(archived_at: Time.current)
notice =
if params[:permanently].in?(['true', true])
@submission.destroy!
SendSubmissionArchivedWebhookRequestJob.perform_later(@submission)
I18n.t('submission_has_been_removed')
else
@submission.update!(archived_at: Time.current)
redirect_back(fallback_location: template_path(@submission.template), notice: 'Submission has been archived')
WebhookUrls.enqueue_events(@submission, 'submission.archived')
I18n.t('submission_has_been_archived')
end
redirect_back(fallback_location: @submission.template_id ? template_path(@submission.template) : root_path, notice:)
end
private
def create_submissions(template, submissions_params, params)
submissions_attrs = submissions_params[:submission].to_h.values
submissions_attrs, _, new_fields =
Submissions::NormalizeParamUtils.normalize_submissions_params!(submissions_attrs, template, add_fields: true)
Submissions.create_from_submitters(template: template,
user: current_user,
source: :invite,
submitters_order: params[:preserve_order] == '1' ? 'preserved' : 'random',
submissions_attrs:,
new_fields:,
params: params.merge('send_completed_email' => true))
end
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?
@@ -67,7 +109,7 @@ class SubmissionsController < ApplicationController
end
def submissions_params
params.permit(submission: { submitters: [%i[uuid email phone name]] })
params.permit(submission: { submitters: [:uuid, :email, :phone, :name, { values: {} }] })
end
def load_template
@@ -4,17 +4,21 @@ class SubmissionsDashboardController < ApplicationController
load_and_authorize_resource :submission, parent: false
def index
@submissions = @submissions.joins(:template)
@submissions = @submissions.left_joins(:template)
@submissions = @submissions.where(archived_at: nil)
.where(templates: { archived_at: nil })
.preload(:created_by_user, template: :author)
.preload(:template_accesses, :created_by_user, template: :author)
@submissions = Submissions.search(@submissions, params[:q], search_template: true)
@submissions = Submissions.search(current_user, @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).order(id: :desc))
@pagy, @submissions = pagy_auto(@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'
@@ -8,43 +8,85 @@ class SubmissionsDownloadController < ApplicationController
FILES_TTL = 5.minutes
def index
submitter = Submitter.find_signed(params[:sig], purpose: :download_completed) if params[:sig].present?
@submitter = Submitter.find_signed(params[:sig], purpose: :download_completed) if params[:sig].present?
signature_valid =
if submitter&.slug == params[:submitter_slug]
if @submitter&.slug == params[:submitter_slug]
true
else
submitter = nil
@submitter = nil
end
submitter ||= Submitter.find_by!(slug: params[:submitter_slug])
@submitter ||= Submitter.find_by!(slug: params[:submitter_slug])
Submissions::EnsureResultGenerated.call(submitter)
Submissions::EnsureResultGenerated.call(@submitter)
last_submitter = submitter.submission.submitters.where.not(completed_at: nil).order(:completed_at).last
last_submitter = @submitter.submission.submitters.where.not(completed_at: nil).order(:completed_at).last
return head :not_found unless last_submitter
Submissions::EnsureResultGenerated.call(last_submitter)
return head :not_found unless last_submitter.completed_at?
if !signature_valid && !current_user_submitter?(last_submitter)
return head :not_found unless Submitters::AuthorizedForForm.call(@submitter, current_user, request)
if last_submitter.completed_at < TTL.ago && !signature_valid && !current_user_submitter?(last_submitter)
Rollbar.info("TTL: #{last_submitter.id}") if defined?(Rollbar)
if last_submitter.completed_at < TTL.ago
Rollbar.info("TTL: #{last_submitter.id}") if defined?(Rollbar)
return head :not_found
return head :not_found
end
end
render json: build_urls(last_submitter)
if params[:combined] == 'true'
respond_with_combined(last_submitter)
else
render json: build_urls(last_submitter)
end
end
private
def respond_with_combined(submitter)
url = build_combined_url(submitter)
if url
render json: [url]
else
head :not_found
end
end
def current_user_submitter?(submitter)
current_user && current_user.account.submitters.exists?(id: submitter.id)
current_user && current_ability.can?(:read, submitter)
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.from_now.to_i)
ActiveStorage::Blob.proxy_path(
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::EnsureCombinedGenerated.call(submitter)
filename_format = AccountConfig.find_or_initialize_by(account_id: submitter.account_id,
key: AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY)&.value
ActiveStorage::Blob.proxy_path(
attachment.blob,
expires_at: FILES_TTL.from_now.to_i,
filename: Submitters.build_document_filename(submitter, attachment.blob, filename_format)
)
end
end
@@ -5,16 +5,22 @@ class SubmissionsExportController < ApplicationController
load_and_authorize_resource :submission, through: :template, parent: false, only: :index
def index
submissions = @submissions.active
.preload(submitters: { documents_attachments: :blob,
attachments_attachments: :blob })
.order(id: :asc)
submissions = params[:archived] == 'true' ? @submissions.archived : @submissions.active
submissions = submissions.preload(submitters: { documents_attachments: :blob,
attachments_attachments: :blob })
.order(id: :asc)
submissions = Submissions.search(current_user, submissions, params[:q], search_values: true)
submissions = Submissions::Filter.call(submissions, current_user, params)
expires_at = Accounts.link_expires_at(current_account)
if params[:format] == 'csv'
send_data Submissions::GenerateExportFiles.call(submissions, format: params[:format]),
send_data Submissions::GenerateExportFiles.call(submissions, format: params[:format], expires_at:),
filename: "#{@template.name}.csv"
elsif params[:format] == 'xlsx'
send_data Submissions::GenerateExportFiles.call(submissions, format: params[:format]),
send_data Submissions::GenerateExportFiles.call(submissions, format: params[:format], expires_at:),
filename: "#{@template.name}.xlsx"
end
end
@@ -0,0 +1,19 @@
# frozen_string_literal: true
class SubmissionsFiltersController < ApplicationController
ALLOWED_NAMES = %w[
author
folder
completed_at
status
created_at
].freeze
skip_authorization_check
def show
return head :not_found unless ALLOWED_NAMES.include?(params[:name])
render params[:name]
end
end
@@ -1,9 +1,12 @@
# frozen_string_literal: true
class SubmissionsPreviewController < ApplicationController
around_action :with_browser_locale
skip_before_action :authenticate_user!
skip_authorization_check
prepend_before_action :maybe_redirect_com, only: %i[show completed]
TTL = 40.minutes
def show
@@ -18,11 +21,14 @@ class SubmissionsPreviewController < ApplicationController
@submission ||= Submission.find_by!(slug: params[:slug])
if !@submission.submitters.all?(&:completed_at?) && current_user.blank?
raise ActionController::RoutingError, 'Not Found'
raise ActionController::RoutingError, I18n.t('not_found') if @submission.account.archived_at?
if !@submission.submitters.all?(&:completed_at?) && !signature_valid &&
(!current_user || !current_ability.can?(:read, @submission))
raise ActionController::RoutingError, I18n.t('not_found')
end
if !submission_valid_ttl?(@submission) && !signature_valid
if use_signature?(@submission) && !signature_valid
Rollbar.info("TTL: #{@submission.id}") if defined?(Rollbar)
return redirect_to submissions_preview_completed_path(@submission.slug)
@@ -36,14 +42,27 @@ class SubmissionsPreviewController < ApplicationController
def completed
@submission = Submission.find_by!(slug: params[:submissions_preview_slug])
raise ActionController::RoutingError, I18n.t('not_found') if @submission.account.archived_at?
@template = @submission.template
render :completed, layout: 'form'
end
private
def submission_valid_ttl?(submission)
return true if current_user && current_user.account.submissions.exists?(id: submission.id)
def use_signature?(submission)
return false if current_user && can?(:read, submission)
return true if submission.submitters.any? do |e|
e.preferences['require_phone_2fa'] || e.preferences['require_email_2fa']
end
return true if submission.template&.preferences&.dig('require_phone_2fa')
return true if submission.template&.preferences&.dig('require_email_2fa')
!submission_valid_ttl?(submission)
end
def submission_valid_ttl?(submission)
last_submitter = submission.submitters.select(&:completed_at?).max_by(&:completed_at)
last_submitter && last_submitter.completed_at > TTL.ago
@@ -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
+83 -26
View File
@@ -7,56 +7,113 @@ class SubmitFormController < ApplicationController
skip_before_action :authenticate_user!
skip_authorization_check
PRELOAD_ALL_PAGES_AMOUNT = 200
before_action :load_submitter, only: %i[show update completed]
before_action :maybe_render_locked_page, only: :show
before_action :maybe_require_link_2fa, only: %i[show]
CONFIG_KEYS = [].freeze
def show
@submitter = Submitter.find_by!(slug: params[:slug])
submission = @submitter.submission
return render :email_2fa unless Submitters::AuthorizedForForm.pass_email_2fa?(@submitter, request)
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
ActiveRecord::Associations::Preloader.new(
records: [@submitter],
associations: [submission: [:template, { template_schema_documents: :blob }]]
).call
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
total_pages =
@submitter.submission.template_schema_documents.sum { |e| e.metadata.dig('pdf', 'number_of_pages').to_i }
return render :awaiting if (@form_configs[:enforce_signing_order] ||
submission.template&.preferences&.dig('submitters_order') == 'preserved') &&
!Submitters.current_submitter_order?(@submitter)
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
Submissions.preload_with_pages(submission)
Submitters::MaybeUpdateDefaultValues.call(@submitter, current_user)
render(@submitter.submission.template.archived_at? || @submitter.submission.archived_at? ? :archived : :show)
@attachments_index = build_attachments_index(submission)
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
unless Submitters::AuthorizedForForm.call(@submitter, current_user, request)
return render json: { error: I18n.t('verification_required_refresh_the_page_and_pass_2fa') },
status: :unprocessable_content
end
if submitter.template.archived_at? || submitter.submission.archived_at?
Rollbar.info("Archived template: #{submitter.template.id}") if defined?(Rollbar)
return render json: { error: 'Form has been archived.' }, status: :unprocessable_entity
if @submitter.completed_at?
return render json: { error: I18n.t('form_has_been_completed_already') }, status: :unprocessable_content
end
Submitters::SubmitValues.call(submitter, params, request)
if @submitter.submission.template&.archived_at? || @submitter.submission.archived_at?
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_content
end
if @submitter.submission.expired?
return render json: { error: I18n.t('form_has_been_expired') }, status: :unprocessable_content
end
if @submitter.declined_at?
return render json: { error: I18n.t('form_has_been_declined') },
status: :unprocessable_content
end
Submitters::SubmitValues.call(@submitter, params, request)
head :ok
rescue Submitters::SubmitValues::RequiredFieldError => e
Rollbar.warning("Required field #{@submitter.id}: #{e.message}") if defined?(Rollbar)
render json: { field_uuid: e.message }, status: :unprocessable_content
rescue Submitters::SubmitValues::ValidationError => e
render json: { error: e.message }, status: :unprocessable_entity
render json: { error: e.message }, status: :unprocessable_content
end
def completed
@submitter = Submitter.completed.find_by!(slug: params[:submit_form_slug])
raise ActionController::RoutingError, I18n.t('not_found') if @submitter.account.archived_at?
return if Submitters::AuthorizedForForm.call(@submitter, current_user, request)
redirect_to submit_form_path(params[:submit_form_slug])
end
def success; end
private
def maybe_require_link_2fa
return if Submitters::AuthorizedForForm.pass_link_2fa?(@submitter, current_user, request)
redirect_to start_form_path(@submitter.submission.template.slug)
end
def maybe_render_locked_page
return render :archived if @submitter.submission.template&.archived_at? ||
@submitter.submission.archived_at? ||
@submitter.account.archived_at?
return render :expired if @submitter.submission.expired?
render :declined if @submitter.declined_at?
end
def load_submitter
@submitter = Submitter.find_by!(slug: params[:slug] || params[:submit_form_slug])
end
def build_attachments_index(submission)
ActiveStorage::Attachment.where(record: submission.submitters, name: :attachments)
.preload(:blob).index_by(&:uuid)
end
end
@@ -0,0 +1,34 @@
# 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? ||
!Submitters::AuthorizedForForm.call(submitter, current_user,
request)
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.enqueue_events(submitter, 'form.declined')
redirect_to submit_form_path(submitter.slug)
end
end
@@ -0,0 +1,41 @@
# 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_content if @submitter.declined_at? ||
@submitter.submission.archived_at? ||
@submitter.submission.expired? ||
@submitter.submission.template&.archived_at? ||
AccountConfig.exists?(account_id: @submitter.account_id,
key: AccountConfig::ALLOW_TO_PARTIAL_DOWNLOAD_KEY,
value: false) ||
!Submitters::AuthorizedForForm.call(@submitter, current_user, request)
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.schema_documents.preload(:blob)
end
urls = attachments.map do |attachment|
ActiveStorage::Blob.proxy_path(attachment.blob, expires_at: FILES_TTL.from_now.to_i)
end
render json: urls
end
end
@@ -0,0 +1,22 @@
# 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? ||
!Submitters::AuthorizedForForm.call(@submitter, current_user, request)
return redirect_to submit_form_path(@submitter.slug)
end
render :show
end
end
@@ -0,0 +1,52 @@
# frozen_string_literal: true
class SubmitFormEmail2fasController < ApplicationController
around_action :with_browser_locale
skip_before_action :authenticate_user!
skip_authorization_check
before_action :load_submitter
COOKIES_TTL = 12.hours
COOKIES_DEFAULTS = { httponly: true, secure: Rails.env.production? }.freeze
def create
RateLimit.call("verify-2fa-code-#{@submitter.id}", limit: 2, ttl: 45.seconds, enabled: true)
value = [@submitter.email.downcase.strip, @submitter.slug].join(':')
if EmailVerificationCodes.verify(params[:one_time_code].to_s.gsub(/\D/, ''), value)
SubmissionEvents.create_with_tracking_data(@submitter, 'email_verified', request, { email: @submitter.email })
cookies.encrypted[:email_2fa_slug] =
{ value: @submitter.slug, expires: COOKIES_TTL.from_now, **COOKIES_DEFAULTS }
redirect_to submit_form_path(@submitter.slug)
else
redirect_to submit_form_path(@submitter.slug, status: :error), alert: I18n.t(:invalid_code)
end
rescue RateLimit::LimitApproached
redirect_to submit_form_path(@submitter.slug, status: :error), alert: I18n.t(:too_many_attempts)
end
def update
if @submitter.submission_events.where(event_type: 'send_2fa_email').exists?(created_at: 15.seconds.ago..)
return redirect_to submit_form_path(@submitter.slug, status: :error), alert: I18n.t(:rate_limit_exceeded)
end
RateLimit.call("send-email-code-#{@submitter.id}", limit: 2, ttl: 45.seconds, enabled: true)
SendSubmitterVerificationEmailJob.perform_async('submitter_id' => @submitter.id, 'locale' => I18n.locale.to_s)
redir_params = params[:resend] ? { alert: I18n.t(:code_has_been_resent) } : {}
redirect_to submit_form_path(@submitter.slug, status: :sent), **redir_params
rescue RateLimit::LimitApproached
redirect_to submit_form_path(@submitter.slug, status: :error), alert: I18n.t(:too_many_attempts)
end
def load_submitter
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
end
end
@@ -0,0 +1,63 @@
# 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_content unless can_invite?(submitter)
invite_submitters = filter_invite_submitters(submitter, 'invite_by_uuid')
optional_invite_submitters = filter_invite_submitters(submitter, 'optional_invite_by_uuid')
ApplicationRecord.transaction do
(invite_submitters + optional_invite_submitters).each do |item|
attrs = submitters_attributes.find { |e| e[:uuid] == item['uuid'] }
next unless attrs
next if attrs[:email].blank?
email = Submissions.normalize_email(attrs[:email])
submitter.submission.submitters.create!(uuid: attrs[:uuid], email:, 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_content
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? &&
Submitters::AuthorizedForForm.call(submitter, current_user, request)
end
def filter_invite_submitters(submitter, key = 'invite_by_uuid')
(submitter.submission.template_submitters || submitter.submission.template.submitters).select do |s|
s[key] == 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,25 @@
# 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? ||
submitter.submission.template&.archived_at? ||
submitter.submission.archived_at? ||
submitter.submission.expired? ||
!Submitters::AuthorizedForForm.call(submitter, current_user, request)
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
@@ -7,27 +7,41 @@ class SubmittersAutocompleteController < ApplicationController
LIMIT = 100
def index
submitters = search_submitters(@submitters)
field = SELECT_COLUMNS.find { |c| c == params[:field] }
values = submitters.limit(LIMIT).group(SELECT_COLUMNS.join(', ')).pluck(SELECT_COLUMNS.join(', '))
submitters = search_submitters(@submitters, field)
arel_columns = SELECT_COLUMNS.map { |col| Submitter.arel_table[col] }
values =
if field
max_ids = submitters.group(field).limit(LIMIT).select(Submitter.arel_table[:id].maximum)
submitters.where(id: max_ids).order(id: :desc).pluck(arel_columns)
else
submitters.limit(LIMIT).group(arel_columns).pluck(arel_columns)
end
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]
def search_submitters(submitters, field)
if field
if Docuseal.fulltext_search?
Submitters.fulltext_search_field(current_user, submitters, params[:q], field)
else
column = Submitter.arel_table[field.to_sym]
term = "#{params[:q].downcase}%"
term = "#{params[:q].downcase}%"
submitters.where(column.matches(term, false, true))
submitters.where(column.matches(term))
end
else
Submitters.search(submitters, params[:q])
Submitters.search(current_user, submitters, params[:q])
end
end
end
+82
View File
@@ -0,0 +1,82 @@
# 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)
SearchEntries.enqueue_reindex(@submitter)
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
@@ -0,0 +1,64 @@
# frozen_string_literal: true
class SubmittersResubmitController < ApplicationController
load_and_authorize_resource :submitter, parent: false
def update
return redirect_to submit_form_path(slug: @submitter.slug) if @submitter.email != current_user.email
submission = @submitter.account.submissions.new(created_by_user: current_user,
submitters_order: :preserved,
**@submitter.submission.slice(:template_fields,
:account_id,
:name,
:template_id,
:template_schema,
:template_submitters,
:preferences))
@submitter.submission.submitters.each do |submitter|
new_submitter = submission.submitters.new(submitter.slice(:uuid, :email, :phone, :name,
:preferences, :metadata, :account_id))
next unless submitter.uuid == @submitter.uuid
assign_submitter_values(new_submitter, submitter)
@new_submitter ||= new_submitter
end
submission.save!
@submitter.submission.documents_attachments.each do |attachment|
submission.documents_attachments.create!(uuid: attachment.uuid, blob_id: attachment.blob_id)
end
redirect_to submit_form_path(slug: @new_submitter.slug)
end
private
def assign_submitter_values(new_submitter, submitter)
attachments_index = submitter.attachments.index_by(&:uuid)
submitter.submission.template_fields.each do |field|
next if field['submitter_uuid'] != submitter.uuid
next if field['default_value'] == '{{date}}'
next if field['type'] == 'stamp'
next if field['type'] == 'signature'
next if field.dig('preferences', 'formula').present?
value = submitter.values[field['uuid']]
next if value.blank?
if field['type'].in?(%w[image file initials])
Array.wrap(value).each do |attachment_uuid|
new_submitter.attachments << attachments_index[attachment_uuid].dup
end
end
new_submitter.values[field['uuid']] = value
end
end
end
@@ -6,18 +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
@@ -3,12 +3,20 @@
class TemplateDocumentsController < ApplicationController
load_and_authorize_resource :template
FILES_TTL = 5.minutes
def index
render json: @template.schema_documents.map { |d| ActiveStorage::Blob.proxy_path(d.blob, expires_at: FILES_TTL.from_now.to_i) }
end
def create
return head :unprocessable_entity if params[:blobs].blank? && params[:files].blank?
if params[:blobs].blank? && params[:files].blank?
return render json: { error: I18n.t('file_is_missing') }, status: :unprocessable_content
end
old_fields_hash = @template.fields.hash
documents = Templates::CreateAttachments.call(@template, params)
documents, = Templates::CreateAttachments.call(@template, params, extract_fields: true)
schema = documents.map do |doc|
{ attachment_uuid: doc.uuid, name: doc.filename.base }
@@ -19,13 +27,13 @@ class TemplateDocumentsController < ApplicationController
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],
methods: %i[metadata signed_key],
include: {
preview_images: { methods: %i[url metadata filename] }
}
)
}
rescue Templates::CreateAttachments::PdfEncrypted
render json: { error: 'PDF encrypted' }, status: :unprocessable_entity
render json: { error: 'PDF encrypted', status: 'pdf_encrypted' }, status: :unprocessable_content
end
end
@@ -3,12 +3,31 @@
class TemplateFoldersAutocompleteController < ApplicationController
load_and_authorize_resource :template_folder, parent: false
LIMIT = 100
LIMIT = 30
def index
template_folders = @template_folders.joins(:templates).where(templates: { archived_at: nil }).distinct
template_folders = TemplateFolders.search(template_folders, params[:q]).limit(LIMIT)
parent_name, name =
if params[:parent_name].present?
[params[:parent_name], params[:q]]
else
params[:q].to_s.split(' /', 2).map(&:squish)
end
render json: template_folders.as_json(only: %i[name archived_at])
if name
parent_folder = @template_folders.find_by(name: parent_name, parent_folder_id: nil)
else
name = parent_name
end
template_folders = TemplateFolders.filter_active_folders(@template_folders.where(parent_folder:),
Template.accessible_by(current_ability))
name = name.to_s.downcase
template_folders = TemplateFolders.search(template_folders, name).order(id: :desc).limit(LIMIT)
render json: template_folders.preload(:parent_folder)
.sort_by { |e| e.name.downcase.index(name) || Float::MAX }
.as_json(only: %i[name archived_at], methods: %i[full_name])
end
end
+63 -6
View File
@@ -3,11 +3,41 @@
class TemplateFoldersController < ApplicationController
load_and_authorize_resource :template_folder
def show
@templates = @template_folder.templates.active.preload(:author).order(id: :desc)
@templates = Templates.search(@templates, params[:q])
helper_method :selected_order
@pagy, @templates = pagy(@templates, items: 12)
TEMPLATES_PER_PAGE = 12
FOLDERS_PER_PAGE = 18
def show
@templates = Template.active.accessible_by(current_ability)
.where(folder: [@template_folder, *(params[:q].present? ? @template_folder.subfolders : [])])
.preload(:author, :template_accesses)
@template_folders =
@template_folder.subfolders.where(id: Template.accessible_by(current_ability).active.select(:folder_id))
@template_folders = TemplateFolders.search(@template_folders, params[:q])
@template_folders = TemplateFolders.sort(@template_folders, current_user, selected_order)
if @templates.exists?
@templates = Templates.search(current_user, @templates, params[:q])
@templates = Templates::Order.call(@templates, current_user, selected_order)
limit =
if @template_folders.size < 4
TEMPLATES_PER_PAGE
else
(@template_folders.size < 7 ? 9 : 6)
end
@pagy, @templates = pagy_auto(@templates, limit:)
load_related_submissions if params[:q].present? && @templates.blank?
else
@pagy, @template_folders = pagy(@template_folders, limit: FOLDERS_PER_PAGE)
@templates = @templates.none
end
end
def edit; end
@@ -15,15 +45,42 @@ 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
private
def selected_order
@selected_order ||=
if cookies.permanent[:dashboard_templates_order].blank? ||
(cookies.permanent[:dashboard_templates_order] == 'used_at' && can?(:manage, :countless))
'created_at'
else
cookies.permanent[:dashboard_templates_order]
end
end
def template_folder_params
params.require(:template_folder).permit(:name)
end
def load_related_submissions
@related_submissions =
Submission.accessible_by(current_ability)
.where(archived_at: nil)
.where(template_id: current_account.templates.active
.where(folder: [@template_folder, *@template_folder.subfolders])
.select(:id))
.preload(:template_accesses, :created_by_user,
template: :author,
submitters: :start_form_submission_events)
@related_submissions = Submissions.search(current_user, @related_submissions, params[:q])
.order(id: :desc)
@related_submissions_pagy, @related_submissions = pagy_auto(@related_submissions, limit: 5)
end
end
@@ -4,9 +4,27 @@ 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.search(@templates, params[:q])
@templates = @templates.where.not(archived_at: nil)
.preload(:author, :template_accesses, folder: :parent_folder)
.order(id: :desc)
@pagy, @templates = pagy(@templates, items: 12)
@templates = Templates.search(current_user, @templates, params[:q])
@pagy, @templates = pagy_auto(@templates, limit: 12)
return unless params[:q].present? && @templates.blank?
@related_submissions =
Submission.accessible_by(current_ability)
.joins(:template)
.where.not(templates: { archived_at: nil })
.preload(:template_accesses, :created_by_user,
template: :author,
submitters: :start_form_submission_events)
@related_submissions = Submissions.search(current_user, @related_submissions, params[:q])
.order(id: :desc)
@related_submissions_pagy, @related_submissions = pagy_auto(@related_submissions, limit: 5)
end
end
@@ -6,9 +6,16 @@ class TemplatesArchivedSubmissionsController < ApplicationController
def index
@submissions = @submissions.where.not(archived_at: nil)
@submissions = Submissions.search(@submissions, params[:q], search_values: true)
@submissions = Submissions.search(current_user, @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_auto(@submissions.preload(submitters: :start_form_submission_events))
rescue ActiveRecord::RecordNotFound
redirect_to root_path
end
@@ -0,0 +1,39 @@
# frozen_string_literal: true
class TemplatesCloneAndReplaceController < ApplicationController
load_and_authorize_resource :template
def create
return head :unprocessable_content if params[:files].blank?
ActiveRecord::Associations::Preloader.new(
records: [@template],
associations: [schema_documents: :preview_images_attachments]
).call
cloned_template = Templates::Clone.call(@template, author: current_user)
cloned_template.name = File.basename(params[:files].first.original_filename, '.*')
cloned_template.save!
documents = Templates::ReplaceAttachments.call(cloned_template, params, extract_fields: true)
Templates.maybe_assign_access(cloned_template)
cloned_template.save!
Templates::CloneAttachments.call(template: cloned_template, original_template: @template,
excluded_attachment_uuids: documents.map(&:uuid))
SearchEntries.enqueue_reindex(cloned_template)
respond_to do |f|
f.html { redirect_to edit_template_path(cloned_template) }
f.json { render json: { id: cloned_template.id } }
end
rescue Templates::CreateAttachments::PdfEncrypted
respond_to do |f|
f.html { render turbo_stream: turbo_stream.append(params[:form_id], html: helpers.tag.prompt_password) }
f.json { render json: { error: 'PDF encrypted', status: 'pdf_encrypted' }, status: :unprocessable_content }
end
end
end
@@ -0,0 +1,56 @@
# frozen_string_literal: true
class TemplatesCloneController < ApplicationController
load_and_authorize_resource :template, instance_name: :base_template
def new
authorize!(:create, Template)
@template = Template.new(name: "#{@base_template.name} (#{I18n.t('clone')})")
end
def create
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])
authorize!(:create, @template)
if params[:account_id].present? && true_ability.can?(:manage, Account.find(params[:account_id]))
@template.account_id = params[:account_id]
@template.author = true_user if true_user.account_id == @template.account_id
@template.folder = @template.account.default_template_folder if @template.account_id != current_account.id
else
@template.account = current_account
end
Templates.maybe_assign_access(@template)
if @template.save
Templates::CloneAttachments.call(template: @template, original_template: @base_template)
SearchEntries.enqueue_reindex(@template)
WebhookUrls.enqueue_events(@template, 'template.created')
maybe_redirect_to_template(@template)
else
render turbo_stream: turbo_stream.replace(:modal, partial: 'templates_clone/form'), status: :unprocessable_content
end
end
private
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: I18n.t('template_has_been_cloned'))
end
end
end
+44 -58
View File
@@ -3,37 +3,39 @@
class TemplatesController < ApplicationController
load_and_authorize_resource :template
before_action :load_base_template, only: %i[new create]
def show
submissions = @template.submissions.accessible_by(current_ability)
submissions = submissions.active if @template.archived_at.blank?
submissions = Submissions.search(submissions, params[:q], search_values: true)
submissions = Submissions.search(current_user, submissions, params[:q], search_values: true)
submissions = Submissions::Filter.call(submissions, current_user, params.except(:status))
@base_submissions = submissions
submissions = submissions.pending if params[:status] == 'pending'
submissions = submissions.completed if params[:status] == 'completed'
submissions = Submissions::Filter.filter_by_status(submissions, 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_auto(submissions.preload(:template_accesses, submitters: :start_form_submission_events))
rescue ActiveRecord::RecordNotFound
redirect_to root_path
end
def new
@template.name = "#{@base_template.name} (Clone)" if @base_template
end
def new; 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 =
@template.as_json.merge(
documents: @template.schema_documents.as_json(
methods: %i[metadata signed_uuid],
methods: %i[metadata signed_key],
include: { preview_images: { methods: %i[url metadata filename] } }
)
).to_json
@@ -42,49 +44,47 @@ class TemplatesController < ApplicationController
end
def create
if @base_template
@template = Templates::Clone.call(@base_template, author: current_user,
name: params.dig(:template, :name),
folder_name: params[:folder_name])
else
@template.author = current_user
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:folder_name])
end
@template.author = current_user
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:folder_name])
@template.account = current_account
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
else
@template.account = current_account
end
Templates.maybe_assign_access(@template)
if @template.save
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
SearchEntries.enqueue_reindex(@template)
maybe_redirect_to_template(@template)
WebhookUrls.enqueue_events(@template, 'template.created')
redirect_to(edit_template_path(@template))
else
render turbo_stream: turbo_stream.replace(:modal, template: 'templates/new'), status: :unprocessable_entity
render turbo_stream: turbo_stream.replace(:modal, template: 'templates/new'), status: :unprocessable_content
end
end
def update
@template.update!(template_params)
@template.assign_attributes(template_params)
is_name_changed = @template.name_changed?
@template.save!
SearchEntries.enqueue_reindex(@template) if is_name_changed
WebhookUrls.enqueue_events(@template, 'template.updated')
head :ok
end
def destroy
notice =
if params[:permanently].present?
if params[:permanently].in?(['true', true])
@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:)
@@ -95,34 +95,20 @@ class TemplatesController < ApplicationController
def template_params
params.require(:template).permit(
:name,
{ schema: [%i[attachment_uuid name]],
submitters: [%i[name uuid]],
{ schema: [[:attachment_uuid, :google_drive_file_id, :name, :dynamic,
{ conditions: [%i[field_uuid value action operation]] }]],
submitters: [%i[name uuid is_requester linked_to_uuid invite_via_field_uuid
invite_by_uuid optional_invite_by_uuid email order]],
variables_schema: {},
fields: [[:uuid, :submitter_uuid, :name, :type,
:required, :readonly, :default_value,
:title, :description,
:title, :description, :prefillable,
{ preferences: {},
conditions: [%i[field_uuid value action]],
default_value: [],
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]] }]] }
validation: %i[message pattern min max step],
areas: [%i[uuid 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)
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')
end
end
def load_base_template
return if params[:base_template_id].blank?
@base_template = Template.accessible_by(current_ability).find_by(id: params[:base_template_id])
end
end
@@ -8,12 +8,18 @@ class TemplatesDashboardController < ApplicationController
TEMPLATES_PER_PAGE = 12
FOLDERS_PER_PAGE = 18
helper_method :selected_order
def index
@template_folders = filter_template_folders(@template_folders)
@template_folders =
TemplateFolders.filter_active_folders(@template_folders.where(parent_folder_id: nil), @templates)
@template_folders = TemplateFolders.search(@template_folders, params[:q])
@template_folders = TemplateFolders.sort(@template_folders, current_user, selected_order)
@pagy, @template_folders = pagy(
@template_folders,
items: FOLDERS_PER_PAGE,
limit: FOLDERS_PER_PAGE,
page: @template_folders.count > SHOW_TEMPLATES_FOLDERS_THRESHOLD ? params[:page] : 1
)
@@ -21,43 +27,70 @@ class TemplatesDashboardController < ApplicationController
@templates = @templates.none
else
@template_folders = @template_folders.reject { |e| e.name == TemplateFolder::DEFAULT_NAME }
@templates = filter_templates(@templates)
@templates = filter_templates(@templates).preload(:author, :template_accesses)
@templates = Templates::Order.call(@templates, current_user, selected_order)
items =
limit =
if @template_folders.size < 4
TEMPLATES_PER_PAGE
else
(@template_folders.size < 7 ? 9 : 6)
end
@pagy, @templates = pagy(@templates, items:)
@pagy, @templates = pagy_auto(@templates, limit:)
load_related_submissions if params[:q].present? && @templates.blank?
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)
rel = templates.active
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)
if Docuseal.multitenant? ? current_account.testing? : current_account.linked_account_account
shared_account_ids = [current_user.account_id]
shared_account_ids << TemplateSharing::ALL_ID if !Docuseal.multitenant? && !current_account.testing?
rel = rel.where(folder_id: current_account.default_template_folder.id).or(rel.where(id: shared_template_ids))
shared_template_ids = TemplateSharing.where(account_id: shared_account_ids).select(:template_id)
rel = Template.where(
Template.arel_table[:id].in(
rel.where(folder_id: current_account.default_template_folder.id).select(:id).arel
.union(:all, shared_template_ids.arel)
)
)
else
rel = rel.where(folder_id: current_account.default_template_folder.id)
end
end
Templates.search(rel, params[:q])
Templates.search(current_user, rel, params[:q])
end
def selected_order
@selected_order ||=
if cookies.permanent[:dashboard_templates_order].blank? ||
(cookies.permanent[:dashboard_templates_order] == 'used_at' && can?(:manage, :countless))
'created_at'
else
cookies.permanent[:dashboard_templates_order]
end
end
def load_related_submissions
@related_submissions = Submission.accessible_by(current_ability)
.left_joins(:template)
.where(archived_at: nil)
.where(templates: { archived_at: nil })
.preload(:template_accesses, :created_by_user,
template: :author,
submitters: :start_form_submission_events)
@related_submissions = Submissions.search(current_user, @related_submissions, params[:q])
.order(id: :desc)
@related_submissions_pagy, @related_submissions = pagy_auto(@related_submissions, limit: 5)
end
end
@@ -0,0 +1,76 @@
# frozen_string_literal: true
class TemplatesDebugController < ApplicationController
load_and_authorize_resource :template
DEBUG_FILE = ''
def show
schema_uuids = @template.schema.index_by { |e| e['attachment_uuid'] }
attachment = @template.documents.find { |a| schema_uuids[a.uuid] }
if attachment
data = attachment.download
unless attachment.image?
pdf = HexaPDF::Document.new(io: StringIO.new(data))
fields = Templates::FindAcroFields.call(pdf, attachment, data)
end
# fields, = Templates::DetectFields.call(StringIO.new(data), attachment:) if fields.blank?
attachment.metadata['pdf'] ||= {}
attachment.metadata['pdf']['fields'] = fields
@template.update!(fields: Templates::ProcessDocument.normalize_attachment_fields(@template, [attachment]))
end
debug_file if DEBUG_FILE.present?
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_key],
include: { preview_images: { methods: %i[url metadata filename] } }
)
).to_json
render 'templates/edit', layout: 'plain'
end
def debug_file
tempfile = Tempfile.new
tempfile.binmode
tempfile.write(File.read(DEBUG_FILE))
tempfile.rewind
filename = File.basename(DEBUG_FILE)
file = ActionDispatch::Http::UploadedFile.new(
tempfile:,
filename:,
type: Marcel::MimeType.for(tempfile)
)
params = { files: [file] }
documents, dynamic_documents = Templates::CreateAttachments.call(@template, params,
dynamic: DEBUG_FILE.ends_with?('.docx'))
schema = documents.map do |doc|
{
attachment_uuid: doc.uuid,
name: doc.filename.base,
dynamic: dynamic_documents.find { |e| e.uuid == doc.uuid }.present?
}
end
@template.update!(schema:)
end
end
@@ -0,0 +1,30 @@
# frozen_string_literal: true
class TemplatesDetectFieldsController < ApplicationController
include ActionController::Live
load_and_authorize_resource :template
def create
response.headers['Content-Type'] = 'text/event-stream'
sse = SSE.new(response.stream)
documents = @template.schema_documents.preload(:blob)
documents = documents.where(uuid: params[:attachment_uuid]) if params[:attachment_uuid].present?
page_number = params[:page].presence&.to_i
documents.each do |document|
io = StringIO.new(document.download)
Templates::DetectFields.call(io, attachment: document, page_number:) do |(attachment_uuid, page, fields)|
sse.write({ attachment_uuid:, page:, fields: })
end
end
sse.write({ completed: true })
ensure
response.stream.close
end
end
@@ -6,12 +6,14 @@ class TemplatesFoldersController < ApplicationController
def edit; end
def update
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:name])
name = [params[:parent_name], params[:name]].compact_blank.join(' / ')
@template.folder = TemplateFolders.find_or_create_by_name(current_user, 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']) }
Submissions.preload_with_pages(@submitter.submission)
@attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments)
.preload(:blob).index_by(&:uuid)
@form_configs = Submitters::FormConfigs.call(@submitter)
end
end
@@ -3,20 +3,80 @@
class TemplatesPreferencesController < ApplicationController
load_and_authorize_resource :template
RESETTABLE_PREFERENCE_KEYS = {
AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY => %w[request_email_subject request_email_body submitters],
AccountConfig::SUBMITTER_INVITATION_REMINDER_EMAIL_KEY => %w[invitation_reminder_email_subject
invitation_reminder_email_body],
AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY => %w[documents_copy_email_subject documents_copy_email_body],
AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY => %w[completed_notification_email_subject
completed_notification_email_body]
}.freeze
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
def destroy
authorize!(:update, @template)
config_key = params[:config_key]
preferences_to_delete = RESETTABLE_PREFERENCE_KEYS[config_key]
return head :ok if preferences_to_delete.blank?
preferences_to_delete.each do |key|
@template.preferences.delete(key)
end
@template.save!
render turbo_stream: turbo_stream.replace("#{config_key}_form",
partial: "templates_preferences/#{config_key}_form"),
status: :ok
end
private
def template_params
params.require(:template).permit(preferences: %i[bcc_completed request_email_subject request_email_body])
params.require(:template).permit(
preferences: %i[bcc_completed request_email_subject request_email_body
invitation_reminder_email_subject invitation_reminder_email_body
documents_copy_email_subject documents_copy_email_body
documents_copy_email_enabled documents_copy_email_attach_audit
documents_copy_email_attach_documents documents_copy_email_reply_to
completed_notification_email_attach_documents
completed_redirect_url validate_unique_submitters
require_all_submitters submitters_order require_phone_2fa require_email_2fa
default_expire_at_duration shared_link_2fa default_expire_at request_email_enabled
completed_notification_email_subject completed_notification_email_body
completed_notification_email_enabled completed_notification_email_attach_audit] +
[completed_message: %i[title body],
submitters: [%i[uuid request_email_subject request_email_body]], link_form_fields: []]
).tap do |attrs|
attrs[:preferences].delete(:submitters) if params[:request_email_per_submitter] != '1'
if (default_expire_at = attrs.dig(:preferences, :default_expire_at).presence)
attrs[:preferences][:default_expire_at] =
(ActiveSupport::TimeZone[current_account.timezone] || Time.zone).parse(default_expire_at).utc
end
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,26 @@
# frozen_string_literal: true
class TemplatesPrefillableFieldsController < ApplicationController
PREFILLABLE_FIELD_TYPES = %w[text number cells date checkbox select radio phone].freeze
load_and_authorize_resource :template
def create
authorize!(:update, @template)
field = @template.fields.find { |f| f['uuid'] == params[:field_uuid] }
if params[:prefillable] == 'false'
field.delete('prefillable')
field.delete('readonly')
elsif params[:prefillable] == 'true'
field['prefillable'] = true
field['readonly'] = true
end
@template.save!
render turbo_stream: turbo_stream.replace(:prefillable_fields_list, partial: 'list',
locals: { template: @template })
end
end
@@ -12,7 +12,7 @@ class TemplatesPreviewController < ApplicationController
@template_data =
@template.as_json.merge(
documents: @template.schema_documents.as_json(
methods: %i[metadata signed_uuid],
methods: %i[metadata signed_key],
include: { preview_images: { methods: %i[url metadata filename] } }
)
).to_json
@@ -0,0 +1,70 @@
# 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? } }
if @template.submitters.each_with_index.all? { |s, index| s['order'] == index }
@template.submitters.each { |s| s.delete('order') }
end
@template.save!
render json: { submitters: @template.submitters }
end
private
def submitters_params
permit_params = { submitters: [%i[name uuid is_requester optional_invite_by_uuid
invite_by_uuid invite_via_field_uuid linked_to_uuid email option order]] }
params.require(:template).permit(permit_params).fetch(:submitters, {}).values.filter_map do |s|
next if s[:uuid].blank?
if s[:is_requester] == '1' && s[:invite_by_uuid].blank? && s[:optional_invite_by_uuid].blank?
s[:is_requester] = true
else
s.delete(:is_requester)
end
s[:order] = s[:order].to_i if s[:order].present?
s.delete(:invite_by_uuid) if s[:invite_by_uuid].blank?
s.delete(:optional_invite_by_uuid) if s[:optional_invite_by_uuid].blank?
s.delete(:invite_via_field_uuid) if s[:invite_via_field_uuid].blank?
normalize_option_value(s)
end
end
def normalize_option_value(attrs)
option = attrs.delete(:option)
if option.present?
case option
when 'is_requester'
attrs[:is_requester] = true
when 'not_set'
attrs.delete(:is_requester)
attrs.delete(:email)
attrs.delete(:linked_to_uuid)
attrs.delete(:invite_by_uuid)
attrs.delete(:invite_via_field_uuid)
attrs.delete(:optional_invite_by_uuid)
when /\Alinked_to_(.*)\z/
attrs[:linked_to_uuid] = ::Regexp.last_match(-1)
when /\Aoptional_invite_by_(.*)\z/
attrs[:optional_invite_by_uuid] = ::Regexp.last_match(-1)
when /\Ainvite_by_(.*)\z/
attrs[:invite_by_uuid] = ::Regexp.last_match(-1)
end
end
attrs
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
@@ -0,0 +1,25 @@
# frozen_string_literal: true
class TemplatesShareLinkController < ApplicationController
load_and_authorize_resource :template
def show; end
def create
authorize!(:update, @template)
@template.update!(template_params)
if params[:redir].present?
redirect_to params[:redir]
else
head :ok
end
end
private
def template_params
params.require(:template).permit(:shared_link)
end
end
+21 -13
View File
@@ -12,19 +12,30 @@ 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:)
WebhookUrls.enqueue_events(@template, 'template.created')
SearchEntries.enqueue_reindex(@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
@@ -35,6 +46,8 @@ class TemplatesUploadsController < ApplicationController
template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:folder_name])
template.name = File.basename((url_params || params)[:files].first.original_filename, '.*')
Templates.maybe_assign_access(template)
template.save!
template
@@ -43,23 +56,18 @@ 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], validate: true).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
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.request :authorization, :basic, 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
+4 -2
View File
@@ -5,7 +5,8 @@ class UserConfigsController < ApplicationController
authorize_resource :user_config
ALLOWED_KEYS = [
UserConfig::RECEIVE_COMPLETED_EMAIL
UserConfig::RECEIVE_COMPLETED_EMAIL,
UserConfig::SHOW_APP_TOUR
].freeze
InvalidKey = Class.new(StandardError)
@@ -26,8 +27,9 @@ class UserConfigsController < ApplicationController
end
def user_config_params
params.required(:user_config).permit!.tap do |attrs|
params.required(:user_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs|
attrs[:value] = attrs[:value] == '1' if attrs[:value].in?(%w[1 0])
attrs[:value] = attrs[:value] == 'true' if attrs[:value].in?(%w[true false])
end
end
end

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