Compare commits

..

2183 Commits

Author SHA1 Message Date
Andrey Antukh
1884a8abe6 Merge pull request #2209 from penpot/palba-protect-profile-url
🐛 Fix opening profile URL while signed out takes to "your account" section
2022-08-30 18:48:30 +02:00
Pablo Alba
c54354f143 🐛 Fix opening profile URL while signed out takes to "your account" section 2022-08-30 17:51:41 +02:00
Alejandro Alonso
9f1540cd00 📎 Update version.txt file 2022-08-30 13:50:16 +02:00
Alejandro
ab94968648 Merge pull request #2206 from penpot/palba-fix-color-picker
🐛 Fix on color-picker, click+drag adds lots of recent colors
2022-08-30 13:29:23 +02:00
Alejandro
4ba5be4450 Merge pull request #2193 from penpot/palba-avoid-bring-file-libraries-on-export
🐛 Fix bringing complete file data when launching the export dialog
2022-08-30 13:28:57 +02:00
Pablo Alba
1bb83b3019 🐛 Fix bringing complete file data when launching the export dialog 2022-08-30 13:18:40 +02:00
Pablo Alba
d175c96871 🐛 Fix on color-picker, click+drag adds lots of recent colors 2022-08-30 13:14:57 +02:00
Andrey Antukh
ecfc20f514 Merge pull request #2205 from penpot/superalex-fix-jittering-on-firefox-scroll-fixed-elements
🐛 Fix jittering on firefox scroll fixed elements
2022-08-30 12:32:42 +02:00
Alejandro Alonso
c69bf9fd35 🐛 Fix jittering on firefox scroll fixed elements 2022-08-30 12:26:56 +02:00
Alejandro
77118a3cc7 Merge pull request #2204 from penpot/alotor-bugfixes
🐛 Fix problems with texts
2022-08-30 12:23:16 +02:00
alonso.torres
282941d284 🐛 Fix problems with texts 2022-08-30 12:12:32 +02:00
Alejandro
d034b61318 Merge pull request #2199 from penpot/palba-shadow-nested-artboard
🐛 Fix Shadows doesn't work on nested artboards
2022-08-30 11:58:04 +02:00
Pablo Alba
1c033fd9f6 Merge pull request #2200 from penpot/alotor-fix-selection
🐛 Fix problems with double-click and selection
2022-08-29 13:45:31 +02:00
alonso.torres
40130d1ca7 🐛 Fix problems with double-click and selection 2022-08-29 12:27:43 +02:00
Pablo Alba
5376c4aa23 🐛 Fix Shadows doesn't work on nested artboards 2022-08-29 12:18:56 +02:00
Pablo Alba
01d99222e0 Merge pull request #2192 from penpot/superalex-fix-viewer-scroll-problems
🐛 Fix viewer scroll problems
2022-08-25 12:09:11 +02:00
Andrey Antukh
85ec1668f3 🐛 Add missing rpc-command definition on metrics 2022-08-25 11:47:14 +02:00
Alejandro Alonso
a1654aeb0e 🐛 Fix viewer scroll problems 2022-08-25 09:12:50 +02:00
Andrey Antukh
e705a333a9 Merge pull request #2190 from penpot/superalex-fix-drag-and-drop-boards
🐛 Fix drag and drop boards
2022-08-24 14:14:17 +02:00
Alejandro Alonso
08ccd7be70 🐛 Fix drag and drop boards 2022-08-24 14:03:38 +02:00
Pablo Alba
aa4344a76f 🐛 Fix drag and drop graphic assets in groups 2022-08-24 13:15:59 +02:00
Andrey Antukh
02efffceb4 Merge pull request #2188 from penpot/superalex-fix-permissions-when-moving-comments
🐛 Fix permissions when moving comments
2022-08-24 12:17:57 +02:00
Alejandro Alonso
44330ffb3b 🐛 Fix permissions when moving comments 2022-08-24 12:16:54 +02:00
Alejandro Alonso
8a33a63f91 🐛 Fix permissions when moving comments 2022-08-24 12:08:38 +02:00
Alejandro
35c1008b37 Merge pull request #2187 from penpot/niwinz-viewer-comments-positioning-bug
Viewer comments positioning regression
2022-08-24 11:07:42 +02:00
Andrey Antukh
8ce8b3fdef 📎 Update docker images related files 2022-08-24 10:59:56 +02:00
Andrey Antukh
be1c19e718 🐛 Fix comments positioning on viewer (regression) 2022-08-24 10:59:38 +02:00
Andrey Antukh
1e9fb6e391 Merge pull request #2186 from penpot/superalex-fix-permissions-when-moving-comments
🐛 fix permissions when moving comments
2022-08-24 10:42:00 +02:00
Eva
8dfd74547a 💄 Change some styles in viewer mode 2022-08-24 10:36:38 +02:00
Alejandro Alonso
cb064358f8 🐛 Fix permissions when moving comments 2022-08-24 10:26:08 +02:00
Alejandro Alonso
8d8e4c5e22 Merge remote-tracking branch 'origin/staging' 2022-08-24 08:11:22 +02:00
Alejandro
595700f8b3 Merge pull request #2184 from penpot/palba-fix-multiselection-assets
🐛 Fix multiselection with shift not working inside a library group
2022-08-24 08:00:30 +02:00
Pablo Alba
29223e8db8 🐛 Fix multiselection with shift not working inside a library group 2022-08-23 17:29:36 +02:00
Andrés Moya
4e319fd9ef Merge pull request #2182 from penpot/niwinz-viewer-performance
Viewer performance issues
2022-08-23 14:01:25 +02:00
Andrey Antukh
c1348189d4 🐛 Fix path with images on binfile importation 2022-08-23 13:57:31 +02:00
Andrey Antukh
1b42e324a2 Avoid recursive rerender and react warning 2022-08-23 13:57:31 +02:00
Andrey Antukh
7af914eef0 📎 Properly print on console UI related errors 2022-08-23 13:57:31 +02:00
Andrey Antukh
1649ca4ff7 📎 Fix linter issues 2022-08-23 13:57:31 +02:00
Andrey Antukh
f9b44ccc5c Refactor viewer shape-container component
Still need a rething for the fixed position shapes
feature because watching scroll position on all shapes
is killing viewer performance.
2022-08-23 13:57:31 +02:00
Andrey Antukh
b9f767a614 Rename active-frames-ctx to active-frames 2022-08-23 13:57:31 +02:00
Andrey Antukh
3e3a10b5dd Rename render-ctx to render-id 2022-08-23 13:57:31 +02:00
Andrey Antukh
082bcd2bde 🔥 Remove unused def-ctx react context var 2022-08-23 13:57:31 +02:00
Andrey Antukh
10bb75c1a1 🔥 Remove unused code related to remap colors of fo-text component 2022-08-23 13:57:31 +02:00
Andrey Antukh
a37c1f7fca ♻️ Refactor viewer comments related components 2022-08-23 13:57:31 +02:00
Andrey Antukh
50d371c14b ♻️ Refactor viewer state management (partial) 2022-08-23 13:57:31 +02:00
Andrey Antukh
48de242a2d 🐛 Fix z-index on viewer sidebar 2022-08-23 13:57:31 +02:00
Andrey Antukh
9722e6ea97 📎 Update gitignore file 2022-08-23 13:57:30 +02:00
Andrey Antukh
f9502315ec Remove duplicate helper from page helpers 2022-08-23 13:57:30 +02:00
Alejandro Alonso
eb797f37a7 🐛 Fix hide html options on handoff 2022-08-23 09:34:13 +02:00
Alejandro Alonso
36af303850 🐛 Fix share prototypes overlay and stroke 2022-08-23 09:34:13 +02:00
Alejandro Alonso
d16761772b 🐛 Fix border radious on boolean operations 2022-08-23 09:34:13 +02:00
Alejandro Alonso
7325322ebf 🐛 Fix text alignment undefined after paste text 2022-08-19 15:15:29 +02:00
Alejandro Alonso
a5975864fb 🎉 Update login methods translations 2022-08-19 15:02:54 +02:00
Alejandro Alonso
7f7032aaa5 🐛 Fix inconsistent representation of rectangles 2022-08-17 13:03:03 +02:00
Alejandro
5c5ec8ef56 Merge pull request #2152 from penpot/niwinz-colorpicker-state-management-refactor
♻️ Refactor state management on colorpicker and gradients
2022-08-17 11:12:22 +02:00
Andrey Antukh
d6faf68dce 📎 Add entry to the changelog 2022-08-12 15:11:20 +02:00
Andrey Antukh
9950c5dc0f 🎉 Add shared state hook and broadcast channel api 2022-08-12 15:11:20 +02:00
Andrey Antukh
756b6d4fbd 💄 Minor cosmetic changes on resize and thumbnail render 2022-08-12 15:11:20 +02:00
Andrey Antukh
8d06227d1e ♻️ Refactor state management of colorpicker & gradients 2022-08-12 15:11:20 +02:00
Andrey Antukh
4cc88bf84f Merge pull request #2162 from penpot/release-1.15
🎉 Add new release info
2022-08-12 12:48:25 +02:00
Andrey Antukh
d8332e62d1 Merge pull request #2161 from penpot/superalex-fix-text-edit-when-using-certain-fonts
🐛 Fix text edition when using certain fonts
2022-08-12 12:48:08 +02:00
Alejandro
95335e64b1 Merge pull request #2160 from penpot/niwinz-enhancements
Enhancements on tasks
2022-08-12 09:37:21 +02:00
Alejandro Alonso
c219d1cc89 🐛 Fix text edition when using certain fonts 2022-08-12 09:30:12 +02:00
Andrey Antukh
7fa609d5f4 Allow disable worker 2022-08-12 08:52:36 +02:00
Andrey Antukh
95bb3f31af Fix all tasks related tests 2022-08-12 08:35:04 +02:00
Andrey Antukh
8d7baa75de Improve tasks-gc task 2022-08-12 08:35:04 +02:00
Andrey Antukh
5867e64d36 Improve objects-gc task 2022-08-12 08:34:57 +02:00
Andrey Antukh
df00760ffa Improve file-xlog-gc task 2022-08-11 17:31:32 +02:00
Andrey Antukh
ac8ef1d622 🔥 Remove completly unused file-offload task 2022-08-11 17:31:32 +02:00
Andrey Antukh
ec2a3c0de1 Improve the file-gc task logging and params 2022-08-11 17:31:32 +02:00
Andrey Antukh
d533e37ae0 Improve logging on gc-deleted storage task 2022-08-11 17:31:32 +02:00
Andrey Antukh
6ee6e5e23e Improve logging on gc-touched storage task 2022-08-11 17:31:32 +02:00
Andrey Antukh
7626d912b9 🎉 Add srepl helpers for run and print available tasks 2022-08-11 17:31:32 +02:00
Andrey Antukh
ada0938e27 Remove key warning on import dialog 2022-08-11 17:31:32 +02:00
Andrey Antukh
918d2ab4a9 🎉 Add more helpers on srepl ns 2022-08-11 17:31:32 +02:00
Andrey Antukh
b3623ed14c 🎉 Add migration for remove on cascade action on file-media-object table 2022-08-11 17:31:32 +02:00
Andrey Antukh
a77f9eae7c 🎉 Backport binfile improvements from develop 2022-08-11 07:44:47 +02:00
elhombretecla
b38f99b2f6 🎉 Add new release info 2022-08-10 12:26:14 +02:00
Alejandro
6df2089a60 Merge pull request #2154 from penpot/niwinz-upload-size-config
 Make the upload media size configurable
2022-08-10 12:22:13 +02:00
Andrey Antukh
b9b53258c1 Make the upload media size configurable 2022-08-10 12:10:45 +02:00
Alejandro
0471df36ef Merge pull request #2142 from penpot/niwinz-session-management-refactor
♻️ Refactor session management
2022-08-10 08:06:49 +02:00
Alejandro
37f5b41486 Merge pull request #2147 from penpot/niwinz-enhancements-20220808
Enhancements & Fixes
2022-08-10 07:46:02 +02:00
Andrey Antukh
36def65c87 Merge pull request #2150 from penpot/eva-fix-recent-fonts
🐛 Fix recent fonts info
2022-08-09 12:25:32 +02:00
Eva
763877b713 🐛 Fix recent fonts info 2022-08-09 12:07:16 +02:00
Andrey Antukh
58a06b8cf3 🐛 Ignore invalid file references on importing file-media-object 2022-08-08 12:16:31 +02:00
Andrey Antukh
c30d4d313c 🐛 Force file-id association with file-media-object on exportation
This is needed because we may have situation when a file
is using a file-media-object reference from other file (probably
a library that is not included in the exportation); in this case
we need to forcely embed it.
2022-08-08 12:09:16 +02:00
Andrey Antukh
183e0bf985 Simplify select all implementation 2022-08-08 10:51:08 +02:00
Andrey Antukh
aceefc0485 ♻️ Move comments mutations to commands 2022-08-08 10:36:15 +02:00
Andrey Antukh
0b3d25a890 Make frontend use new cmd based repo methods for comments queries 2022-08-08 09:51:11 +02:00
Andrey Antukh
173f0d68bb 📎 Properly deprecate comments related queries 2022-08-08 09:42:45 +02:00
Andrey Antukh
61f2799e49 🐛 Fix unexpected response truncation on viewer 2022-08-08 09:28:31 +02:00
Andrey Antukh
adbadc8743 ♻️ Refactor session management 2022-08-08 07:54:15 +02:00
Alejandro
6d61f75db6 Merge pull request #2144 from penpot/eva-improve-team-icon
💄 Improve team icon
2022-08-05 12:56:38 +02:00
Eva
efa382c906 💄 Improve team icon 2022-08-05 11:24:34 +02:00
Eva Marco
a54e0900d0 Merge pull request #2137 from penpot/superalex-fix-clipped-elements-affect-artboards-centering
🐛 Fix clipped elements affect artboards centering
2022-08-05 08:25:01 +02:00
Alejandro Alonso
9ffd00d821 🐛 Fix clipped elements affect artboards centering 2022-08-04 15:12:33 +02:00
Andrew Zhurov
5febd35cfe 🐛 Fix layers get out of their group when moved
Signed-off-by: Andrei Zhurau <zhurov.andrew@gmail.com>
2022-08-04 07:11:43 +02:00
Alejandro
b926409fa2 Merge pull request #2135 from penpot/eva-bugfixes-1.15
🐛 Bugfixes Eva
2022-08-04 06:52:33 +02:00
Andrey Antukh
fd08511514 Merge pull request #2129 from penpot/palba/select-all-group
 Select all inside a group select only the objects at this …
2022-08-03 13:37:42 +02:00
Pablo Alba
52cc91f4c4 Select all inside a group select only the objects at this group level 2022-08-03 11:37:33 +02:00
Andrey Antukh
fdc01cfed5 Merge branch 'andrew-fixes-backports' into staging 2022-08-03 09:27:09 +02:00
Andrey Antukh
0cc51db533 📎 Update changelog 2022-08-03 09:26:29 +02:00
Eva
8795e134c1 🐛 Fix intro action in multi input 2022-08-03 09:23:36 +02:00
Andrew Zhurov
732755066e 🐛 Fix text alignment becoming undefined on pasting text from clipboard
Signed-off-by: Andrei Zhurau <zhurov.andrew@gmail.com>
2022-08-03 09:23:07 +02:00
Andrew Zhurov
424e9faa8e 🐛 Fix paste frame removes all guides
Signed-off-by: Andrei Zhurau <zhurov.andrew@gmail.com>
2022-08-03 09:22:57 +02:00
Andrey Antukh
c62427501e Merge pull request #2128 from penpot/palba-copy-paste-layers-order
🐛 Fix copy and paste layers order
2022-08-02 10:27:55 +02:00
Pablo Alba
64217b34ca 🐛 Fix copy and paste layers order 2022-08-02 10:23:25 +02:00
Eva
140731cf34 🐛 Change default team image in config 2022-08-02 08:30:07 +02:00
Andrew Zhurov
39ae2ed98d 🐛 Fix svg upload
Signed-off-by: Andrei Zhurau <zhurov.andrew@gmail.com>
2022-08-01 16:43:59 +02:00
Andrey Antukh
6237829445 📎 Add additional reformating to specs naming 2022-08-01 15:03:45 +02:00
Andrey Antukh
ddc7f412a4 📎 Mainly reformat specs code 2022-08-01 14:27:11 +02:00
Andrey Antukh
5cd12ac710 ⬆️ Update shadow-cljs to 2.19.8 2022-08-01 13:11:44 +02:00
Andrey Antukh
91baae3580 📎 Minor change on session internal timestamp handling 2022-08-01 13:10:01 +02:00
Pablo Alba
01306841a9 Merge pull request #2084 from penpot/eva-alex-move-comments
❇️ Comments positioning
2022-08-01 10:03:03 +02:00
Eva
1c446a011e Move comments 2022-08-01 09:53:55 +02:00
Alejandro
8379cc3625 Merge pull request #2094 from penpot/niwinz-enhancements-20220713
Bugfixes & Enhancements
2022-07-28 11:56:08 +02:00
Andrey Antukh
d084f17430 Add ssh client to devenv dockerfile 2022-07-28 11:14:59 +02:00
Andrey Antukh
e3f878ef2f ♻️ Move doc ns from http to rpc ns 2022-07-28 11:14:59 +02:00
Andrey Antukh
05a86581a5 Reorganize comments related rpc methods
Mutations becomes deprecated and queries moved to commands. The
old queries still maintained with deprecated flag.
2022-07-27 21:41:38 +02:00
Andrey Antukh
8237805cf5 🐛 Fix minor issues on page helpers 2022-07-27 21:41:38 +02:00
Andrey Antukh
8fd908a59f 💄 Add mainly cosmetic improvements to delete-shapes event impl 2022-07-27 21:41:38 +02:00
Andrey Antukh
07eab923f0 Improve doc endpoint
Add changes, added and deprecation notices
2022-07-27 21:41:38 +02:00
Andrey Antukh
2e077e3ea9 🐛 Fix awsns endpoint 2022-07-27 21:41:38 +02:00
Andrey Antukh
99dea51eea ⬆️ Update yetti to v9.3 (bugfixing) 2022-07-27 21:41:38 +02:00
Andrey Antukh
e7ae8f5c58 🐛 Fix unexpected null pointer exception on decoding pgarray 2022-07-27 21:41:38 +02:00
Andrey Antukh
ee51e8c719 Always assoc :iat claim to tokens for better traceability 2022-07-27 21:41:38 +02:00
Andrey Antukh
b4ad907c73 📎 Improve clj-kondo hook impl for defservice 2022-07-27 21:41:38 +02:00
Andrey Antukh
333e1d32a2 Merge pull request #2097 from penpot/palba-fix-drag-drop-fonts
🐛 Fix drag and drop font assets in groups
2022-07-27 14:17:28 +02:00
Pablo Alba
58f93d2177 🐛 Fix drag and drop font assets in groups 2022-07-27 14:17:02 +02:00
Andrey Antukh
08c0070f22 Merge branch 'niwinz-scripts-and-fixes' into staging 2022-07-27 13:16:48 +02:00
Andrey Antukh
14c28ccce7 Merge pull request #2095 from penpot/alotor-bugfixes
Alotor bugfixes
2022-07-27 12:59:31 +02:00
Andrey Antukh
dece149c9e 🎉 Add migration for fix legacy storage object backend names 2022-07-27 12:55:43 +02:00
Andrey Antukh
9275f5e5ce Reorganize migrations directory 2022-07-27 12:55:43 +02:00
Andrey Antukh
483da5248f 🎉 Add internal script for move some legacy files stored on fs backend to s3 2022-07-27 12:55:43 +02:00
Andrey Antukh
4bf05c8a42 Minor reorganization of srepl namespace 2022-07-27 12:55:43 +02:00
Andrey Antukh
cd8578480f 🐛 Fix unexpected exception on i18n autodetect code 2022-07-26 11:52:43 +02:00
alonso.torres
d2a5344407 🐛 Fix problem with snap-pixel on resize 2022-07-15 14:48:05 +02:00
alonso.torres
48615ca5b2 🐛 Round coordinates in viewport and paths 2022-07-15 14:48:05 +02:00
alonso.torres
f89ccac567 🐛 Fix problems with nested boards 2022-07-15 11:11:12 +02:00
alonso.torres
b57ddf9dca 🐛 Fix problem with 180 degree rotations 2022-07-15 11:11:12 +02:00
alonso.torres
8e9ab32a9f 🐛 Fix moving frame-guides outside frames 2022-07-15 11:11:12 +02:00
alonso.torres
fdbcf977f5 🐛 Fix problem with line-height and texts 2022-07-15 11:11:12 +02:00
alonso.torres
cc6b3dcec6 🐛 Fix problem with group coordinates 2022-07-15 11:11:12 +02:00
alonso.torres
7abbcdf226 Move text position calculation outside foreign object 2022-07-15 11:11:12 +02:00
alonso.torres
4088e55c9f 🐛 Fix problem with span overflow 2022-07-15 11:03:13 +02:00
Andrey Antukh
54d9b02b4d Add specific font for persian and arabic locales
And remove deprecated and not used font files, simplifying
the font-face mixin.
2022-07-15 11:03:13 +02:00
Andrey Antukh
3e7b9805c9 Merge pull request #2099 from penpot/superalex-fix-worker-synchronize-cron-entries
🐛 Fix worker synchronize cron entries
2022-07-15 09:42:55 +02:00
Alejandro Alonso
be0c810c5f 🐛 Fix worker synchronize cron entries 2022-07-15 08:03:06 +02:00
Alejandro
a958aed058 Merge pull request #2093 from penpot/niwinz-minor-release-1.14.2
Prepare the 1.14.2 minor release
2022-07-14 07:26:58 +02:00
Andrey Antukh
2e2b05a7a4 📎 Sort translations files 2022-07-14 07:10:05 +02:00
Andrey Antukh
4e5146c210 Merge remote-tracking branch 'weblate/develop' into translations 2022-07-14 07:08:42 +02:00
Andrey Antukh
4bac2f15a2 ⬆️ Use correct version of im4java (fixes tests) 2022-07-13 11:39:36 +02:00
Andrey Antukh
1c09328d0e 📎 Update version.txt file 2022-07-13 11:22:06 +02:00
alonso.torres
06905d5fa6 🐛 Fix SVG texts positioning inconsistencies 2022-07-13 11:22:06 +02:00
Andrey Antukh
46c9fc1c5f 🐛 Normalize return value from parse-client-ip function 2022-07-13 11:18:33 +02:00
Andrey Antukh
b901a10aaa 🐛 Fix typographies grouping 2022-07-13 11:17:55 +02:00
Andrey Antukh
c4bdb84d70 Merge pull request #2089 from penpot/palba-create-shared-link-log
 Add audit log for create shared link
2022-07-13 11:03:18 +02:00
Pablo Alba
8ac32fc3c2 Add audit log for create shared link 2022-07-12 13:07:51 +02:00
Andrey Antukh
8c84cc7fa0 📎 Update changelog 2022-07-12 11:56:35 +02:00
Andrey Antukh
40415bb0d8 Merge branch 'develop' into staging 2022-07-12 11:55:13 +02:00
alonso.torres
f2bd6a552f Feature toggle 2022-07-11 11:45:26 +02:00
Josep Ponsà
62bb3d9087 🌐 Add translations for: Catalan.
Currently translated at 99.5% (1105 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ca/
2022-07-09 13:14:47 +02:00
Alejandro
374f52a819 Merge pull request #2080 from penpot/superalex-set-project-on-binary-file-import
🐛 Fix set project on binary file import
2022-07-08 08:00:54 +02:00
Alejandro Alonso
d140f15f37 🐛 Fix set project on binary file import 2022-07-08 07:43:44 +02:00
Alejandro
f32bb56b95 Merge pull request #2079 from penpot/superalex-set-project-on-binary-file-import
🐛 Fix set project on binary file import
2022-07-08 06:47:22 +02:00
Alejandro Alonso
37e9adc6b6 🐛 Fix set project on binary file import 2022-07-08 06:43:04 +02:00
Alejandro
602cead4ae Merge pull request #2077 from penpot/niwinz-asserts-improvements
Asserts & binfile cosmetic refactor
2022-07-07 13:19:02 +02:00
Andrey Antukh
aadb7cb1bf Don't call rp/command internal method 2022-07-07 13:12:38 +02:00
Andrey Antukh
d60f849089 💄 Cosmetic refactor of binfile internal API impl 2022-07-07 13:08:18 +02:00
Andrey Antukh
98190ed92d ♻️ Improve the asserts framework 2022-07-07 12:29:13 +02:00
Andrey Antukh
c02e8ff883 Print the spec error explain to logging message 2022-07-07 12:29:13 +02:00
Andrey Antukh
4d55ed4860 Ensure vector ids on export debug handler 2022-07-07 12:29:13 +02:00
Andrey Antukh
5e2c1fb4cd 🎉 Add missing predicate on util/bytes ns 2022-07-07 12:29:13 +02:00
Andrey Antukh
f9447029f3 🔥 Remove some deprecated config attrs 2022-07-07 12:28:13 +02:00
Andrey Antukh
2a9c8eb9af 📎 Print parsed flags on start 2022-07-07 12:28:13 +02:00
Andrey Antukh
cdcf3facd2 🐛 Fix flags parsing order 2022-07-07 12:28:13 +02:00
Alejandro Alonso
5c696851bf 📎 Update CHANGES.md file 2022-07-07 11:48:41 +02:00
Andrey Antukh
c8051633d9 Merge pull request #2076 from penpot/superalex-frontend-binary-file-support
  Frontend binary file support
2022-07-07 11:42:46 +02:00
Alejandro Alonso
17645bb2a7 Frontend support for binary files 2022-07-07 11:37:34 +02:00
Alejandro
2fe770e0bb Merge pull request #2075 from penpot/niwinz-export-embed-assets
Embed assets and multiple files support for binfile export
2022-07-07 07:31:05 +02:00
Andrey Antukh
d032953121 Enable exporte multiple files in binfile format 2022-07-06 16:05:10 +02:00
Andrey Antukh
f4f58bc163 Add parameters validation to binfile write-export! fn 2022-07-06 16:05:10 +02:00
Andrey Antukh
d90b4370fb 📎 Update default devenv logging configuration 2022-07-06 16:05:10 +02:00
Andrey Antukh
ade41f77f3 📎 Add some notes to assets ns in sidebar 2022-07-06 16:05:10 +02:00
Andrey Antukh
c405e9a7a3 🔥 Remove unused code 2022-07-06 16:05:10 +02:00
Andrey Antukh
50f30eb12f Add the ability to embed assets on export binfile 2022-07-06 16:01:21 +02:00
Alejandro
6b8ab7aa72 Merge pull request #2072 from penpot/niwinz-update-ubuntu-and-openjdk-on-docker-images
⬆️ Update docker images system dependencies
2022-07-06 11:18:10 +02:00
Alejandro
0dac3f7845 Merge pull request #2071 from penpot/niwinz-improve-api-documentation-output
 Improve _doc endpoint output format
2022-07-06 11:17:22 +02:00
Andrey Antukh
537fff4c80 ⬆️ Update docker images system dependencies 2022-07-05 11:51:36 +02:00
Andrey Antukh
dd130615a1 Improve _doc endpoint output format 2022-07-05 11:04:37 +02:00
Andrey Antukh
356ff4683d Revert "📎 Allow set statement timeout on db module"
This reverts commit 70028e1371.
2022-07-04 14:04:56 +02:00
Andrey Antukh
70028e1371 📎 Allow set statement timeout on db module 2022-07-04 13:34:17 +02:00
Andrey Antukh
a3580a5ab9 📎 Update log4j2 default configuration 2022-07-04 12:41:55 +02:00
Alejandro
6bb5fb0361 Merge pull request #2068 from penpot/niwinz-fix-worker-cron-locking-mechanism
🐛 Fix cron scheduler locking mechanism
2022-07-04 12:30:10 +02:00
Andrey Antukh
f2140a1421 🐛 Fix cron scheduler locking mechanism
And add improved logging to the worker/cron code
2022-07-04 11:32:36 +02:00
Alejandro
f7f9ba99f7 Merge pull request #2067 from penpot/niwinz-auth-improvements
♻️ Refactor auth code
2022-07-04 11:28:26 +02:00
Andrey Antukh
14d1cb90bd ♻️ Refactor auth code 2022-07-04 11:23:33 +02:00
Lucie Lesage
11f7efb850 🌐 Add translations for: French.
Currently translated at 71.3% (792 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-07-02 11:14:04 +02:00
Locness
a16606c8e3 🌐 Add translations for: French.
Currently translated at 71.3% (792 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-07-01 10:17:03 +02:00
Lucie Lesage
7fe7b234bf 🌐 Add translations for: French.
Currently translated at 71.3% (792 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-07-01 10:17:03 +02:00
Alejandro Alonso
ba4f558f62 Merge remote-tracking branch 'origin/staging' into develop 2022-07-01 08:21:02 +02:00
Alejandro
8446df2056 Merge pull request #2065 from penpot/eva-bugfix-selected
🐛 Fix color indicators from unlinked libraries
2022-07-01 08:20:21 +02:00
Alejandro
8f22c421de Merge pull request #2064 from penpot/palba-signin-register-from-shared-link
 Signin/Signup from shared link
2022-07-01 08:17:57 +02:00
Eva
2c0725a9d2 🐛 Fix color indicators from unlinked libraries 2022-07-01 08:05:27 +02:00
Pablo Alba
288dab3fe7 Signin/Signup from shared link 2022-07-01 07:39:57 +02:00
Eranot
672c52b369 🌐 Add translations for: Portuguese (Brazil).
Currently translated at 47.9% (532 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/
2022-07-01 03:20:21 +02:00
Alejandro
e458e3adb7 Merge pull request #2063 from penpot/alotor-refactor-data
♻️ Refactor workspace common
2022-06-30 13:15:10 +02:00
alonso.torres
b38ffdcf30 ♻️ Refactor workspace common 2022-06-30 13:09:35 +02:00
Andrey Antukh
09a3cf4b58 Merge pull request #2062 from penpot/circleci-experiments
📎 Add additional CI step to circleci config
2022-06-30 07:55:24 +02:00
Alejandro
7406aac0c7 Merge pull request #2058 from penpot/niwinz-exporter-tmp-files
 Put all temporal files under the same directory
2022-06-30 07:31:14 +02:00
Andrey Antukh
e44fb2cdbf 📎 Add additional CI step to circleci config 2022-06-29 23:00:45 +02:00
Andrey Antukh
bfb0ba47f5 💄 Fix linter issues on exporter 2022-06-29 14:53:57 +02:00
Andrey Antukh
9c194ee3cb 🐛 Fix websocket unexpected exception on exportation module
A regression caused by the previous commit that refactos
the websockets API and its state management.
2022-06-29 14:39:56 +02:00
Andrey Antukh
ebe8fdcba8 ♻️ Refactor temporal files management on exporter 2022-06-29 14:39:40 +02:00
Andrey Antukh
d021ac0226 🐛 Fix share link migration for backward compatibilty 2022-06-29 12:30:17 +02:00
Alejandro Alonso
7256bdbcd5 Merge remote-tracking branch 'origin/staging' into develop 2022-06-29 12:00:52 +02:00
Alejandro
27d81ee47d Merge pull request #2060 from penpot/community-nav-link
🎉 Add new community link to dashboard and workspace menus
2022-06-29 12:00:13 +02:00
elhombretecla
be304811d5 🎉 Add new community link to dashboard and workspace menus 2022-06-29 11:52:24 +02:00
Alejandro
bd4548cd25 Merge pull request #2046 from penpot/niwinz-20220624-websockets-fixes
Websocket protocol diagnostic info & Some deps updates
2022-06-29 11:05:37 +02:00
Andrey Antukh
cbc5811290 Improve websockets impl
Make it more extensible and move all the websocket unrelated stuff
to the new hooks API. Also adds observability from repl.
2022-06-29 11:01:16 +02:00
Andrey Antukh
935639411c ⬆️ Update devenv to use latest ubuntu lts and jdk18 2022-06-29 10:59:50 +02:00
Andrey Antukh
6de78cabd4 ⬆️ Update shadow-cljs cljs compiler on frontend and common 2022-06-29 10:59:50 +02:00
Andrey Antukh
73f1418c95 🐛 Normalize return value from parse-client-ip function 2022-06-29 10:59:50 +02:00
Alejandro
cf2de3cfac Merge pull request #2030 from penpot/eva-palba-share-link
Eva palba share link
2022-06-29 10:55:16 +02:00
Alejandro Alonso
481c45ee60 Merge remote-tracking branch 'origin/staging' into develop 2022-06-29 10:38:35 +02:00
Alejandro
716b0639f2 Merge pull request #2057 from penpot/3565-community-access
🎉 Add new community links
2022-06-29 10:37:52 +02:00
elhombretecla
ced3830d7a 🎉 Add new coomunity info 2022-06-29 10:34:41 +02:00
Pablo Alba
115314e97c In view mode allow comment/inspect to non-team users (by shared link permissions) 2022-06-29 09:41:30 +02:00
Alejandro Alonso
d2250274f2 Merge remote-tracking branch 'origin/staging' into develop 2022-06-29 09:37:31 +02:00
Eva
0f04398e61 💄 Improve shared link modal 2022-06-29 09:31:41 +02:00
Eva Marco
72979e4535 Merge pull request #2056 from penpot/alotor-fix-resize
🐛 Fix problem with resize groups
2022-06-29 08:51:41 +02:00
alonso.torres
a271a285ad 🐛 Fix problem with resize groups 2022-06-29 08:48:00 +02:00
Andrey Antukh
b68407a6c0 Merge pull request #2054 from penpot/superalex-update-auth-urls-navigation
 Update auth urls navigation
2022-06-29 08:35:41 +02:00
Alejandro Alonso
5136eef4bc Update auth urls navigation 2022-06-29 08:05:22 +02:00
Alejandro
f132651175 Merge pull request #2055 from penpot/hiru-types
♻️ Rename specs -> types
2022-06-29 06:33:21 +02:00
Andrés Moya
6f94745aed ♻️ Rename specs -> types
NO FUNCTIONALITY IS CHANGED in this commit, only moving things around
2022-06-29 06:25:06 +02:00
Ahmad HosseinBor
7052f64547 🌐 Add translations for: Persian.
Currently translated at 53.4% (593 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2022-06-28 15:16:38 +02:00
Alejandro Alonso
29220cd0d3 Merge remote-tracking branch 'origin/staging' into develop 2022-06-28 12:24:42 +02:00
Eva Marco
ec55d64454 Merge pull request #2052 from penpot/superalex-fix-fill-information-not-complete-when-paste-plain-text
🐛 Fix fill information not complete when paste plain text
2022-06-28 12:21:54 +02:00
Alejandro Alonso
e4eb8004e2 🐛 Fix fill information not complete when paste plain text 2022-06-28 12:18:31 +02:00
Andrey Antukh
b1e6a8b1e9 Merge pull request #2051 from penpot/eva-component-name
 Show shape name in right toolbar
2022-06-28 11:37:09 +02:00
Eva
da2214379c Show shape name in right toolbar 2022-06-28 11:33:28 +02:00
Alejandro
4d19ceff8d Merge pull request #2016 from penpot/niwinz-experiments-custom-export-import
Experimental support for binary file format for exportation/importation of penpot files
2022-06-27 13:23:06 +02:00
Andrey Antukh
b944d977bb 🎉 Add binfile import/export internal functionality 2022-06-27 11:12:00 +02:00
Alejandro Alonso
07881eed65 Merge remote-tracking branch 'origin/staging' into develop 2022-06-27 09:28:54 +02:00
Alejandro
f2862b6c16 Merge pull request #2039 from penpot/niwinz-hotfix-exporter-uri-param
🐛 Remove unused setting on exporter
2022-06-27 08:44:39 +02:00
Andrey Antukh
ccae7cc2d4 📎 Clean and improve default docker config.env file 2022-06-27 07:40:06 +02:00
Pablo Alba
c6de41421e Merge pull request #2033 from penpot/circleci-changes
Circleci changes
2022-06-27 07:16:40 +02:00
Andrey Antukh
fa06da36ac 🐛 Remove unused setting on exporter
That causes many troubles on configuring exporter on the onpremise
instances but serves for nothing because it is completly unused.
2022-06-24 16:37:27 +02:00
Alejandro
03c019ded0 Merge pull request #2034 from wodin/wodin/fix-spelling-of-peek
📚 Fix spelling of 'sneak peek'
2022-06-24 13:31:21 +02:00
Alejandro
248ab953b2 Merge pull request #2038 from penpot/eva-bugfix-3
🐛 Fix color change in a row
2022-06-24 13:12:45 +02:00
Eva
14754aae05 🐛 Fix color change in a row 2022-06-24 12:35:23 +02:00
Alejandro
dc7464220d Merge pull request #2028 from penpot/alotor-frames
 Improved nested boards thumbnail handling
2022-06-24 11:49:58 +02:00
Alejandro
7396410267 Merge pull request #2037 from penpot/niwinz-fix-region-spec-on-s3-storage-backend
Fix spec on S3 storage backend region parameter
2022-06-24 11:07:19 +02:00
Alejandro
9bd3cba58c Merge pull request #2035 from penpot/eva-bugfix-shortcuts
🐛 Fix shortcut acction in main menu
2022-06-24 10:59:56 +02:00
Andrey Antukh
b08b1a546a 🐛 Fix region spec on s3 storage backend
This allows users use different region
2022-06-24 10:58:42 +02:00
alonso.torres
639eaa2458 Improved nested boards thumbnail handling 2022-06-24 10:47:33 +02:00
Eva
ab1405b79c 🐛 Fix shortcut acction in main menu 2022-06-24 09:55:29 +02:00
Michael Wood
ce14acac2c 📚 Fix spelling of 'sneak peek'
https://theoatmeal.com/comics/sneak_peek
2022-06-24 08:41:32 +02:00
Andrey Antukh
826bd29327 📎 Disable :non-arg-vec-return-type-hint linter on clj-kondo 2022-06-24 08:33:20 +02:00
Andrey Antukh
5151a7bd49 📎 Ignore linter issues on single function on frontend
Because it happens to the `new` function previuously defined
clash with the instance creation (probably linter bug).
2022-06-24 07:34:34 +02:00
Andrey Antukh
0ad0a65fa9 📎 Minor changes on circleci config 2022-06-24 07:29:14 +02:00
Alejandro
10a33fb102 Merge pull request #2027 from penpot/eva-bugfix-selected-colors
🐛 Fix modify colors in a row in selected colors
2022-06-23 15:57:34 +02:00
Alejandro
b0c0c6ed43 Merge pull request #2026 from penpot/niwinz-hotfix-20220623
Minor fixes
2022-06-23 15:53:54 +02:00
Eva
e31fbb5c5f 🐛 Fix modify colors in a row in selected colors 2022-06-23 15:07:40 +02:00
andy
e2bdf1a155 🌐 Add translations for: Spanish.
Currently translated at 100.0% (1110 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/
2022-06-23 14:19:39 +02:00
Andrey Antukh
5e2ff2cf6f 📎 Minor update on telemetry task 2022-06-23 14:17:26 +02:00
Andrey Antukh
c211e84498 🐛 Fix incorrect register-profile audit log handling 2022-06-23 14:17:26 +02:00
alonso.torres
75dc9e64a7 Merge remote-tracking branch 'origin/staging' into develop 2022-06-23 13:55:24 +02:00
Alejandro Alonso
69810750c5 📎 Tag new minor release 2022-06-23 13:47:27 +02:00
Alejandro
4549281b6c Merge pull request #2025 from penpot/alotor-fix-path-performance
 Improved performance when rendering paths
2022-06-23 13:46:59 +02:00
alonso.torres
90532b760a Improved performance when rendering paths 2022-06-23 13:24:03 +02:00
Alejandro
eb190296d7 Merge pull request #2021 from penpot/alotor-frames
 Fix shadows in frames for dashboard and viewer
2022-06-22 11:45:29 +02:00
Andrey Antukh
46d075611d ♻️ Adapt media & fonts handling to new tmp service
And storage backend changes
2022-06-22 11:39:57 +02:00
Andrey Antukh
ebcb385593 ♻️ Minor refactor on storages
Fix many issues on FS & S3 backend; removes the unused and broken
DB backend. Normalize operations on bytes and byte streams on a
separated namespace: app.util.bytes
2022-06-22 11:37:45 +02:00
alonso.torres
8e60834292 Fix shadows in frames for dashboard and viewer 2022-06-22 11:18:55 +02:00
Eva Marco
6469a543ba Merge pull request #2023 from penpot/niwinz-hotfix-20220622
🚑 Fix unexpected exception on typography asset context menu
2022-06-22 09:39:07 +02:00
Andrey Antukh
666b9fa4d4 🚑 Fix unexpected exception on typography asset context menu 2022-06-22 09:36:34 +02:00
Eva Marco
137c10f631 Merge pull request #2018 from penpot/eva-fix-double-click-viewer
🐛 Fix double click crash on viewer layers
2022-06-22 09:34:22 +02:00
Eva
ac1167d0c9 🐛 Fix double click crash on viewer layers 2022-06-22 09:31:13 +02:00
Eva Marco
e1d6cded62 Merge pull request #2019 from penpot/palba-view-mode-improvements-2
On view mode only show arrows on hover
2022-06-21 11:52:52 +02:00
Pablo Alba
53df0f7585 On view mode only show arrows on hover 2022-06-21 11:10:05 +02:00
Alejandro
95829ff3de Merge pull request #2014 from penpot/3487-release-1.14
🎉 Adds new release info and images
2022-06-21 10:22:09 +02:00
alonso.torres
6d4e898f79 Merge remote-tracking branch 'origin/staging' into develop 2022-06-21 09:30:54 +02:00
Alejandro
2bed06de64 Merge pull request #2017 from penpot/hiru-fix-asset-names
🐛 Fix display of asset names and console warning
2022-06-21 06:46:22 +02:00
Andrés Moya
a08c1b1278 🐛 Fix display of asset names and console warning 2022-06-20 18:15:26 +02:00
Alejandro
3053e867cb Merge pull request #2006 from penpot/alotor-fix-thumbnails-viewer
🐛 Fix thumbnails in viewer thumbnails
2022-06-20 16:24:57 +02:00
Andrey Antukh
3a55f07f45 🐛 Remove duplicate work on storing already existing files in storage 2022-06-20 14:17:31 +02:00
Alejandro
408f73396f Merge pull request #2000 from penpot/alotor-frames
Nested/Rotated Boards
2022-06-20 11:51:03 +02:00
Andrey Antukh
7cdbadc5b7 Merge pull request #2015 from penpot/alotor-fix-text-problem
🐛 Fix problem with empty text boxes events
2022-06-20 11:34:25 +02:00
alonso.torres
fb1dbd6f31 🐛 Fix problem with empty text boxes events 2022-06-20 11:29:52 +02:00
elhombretecla
9dabe2959f 🎉 Adds new release info and images 2022-06-20 06:47:08 +02:00
Ahmad HosseinBor
2d61497159 🌐 Add translations for: Persian.
Currently translated at 51.2% (569 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2022-06-19 06:19:08 +02:00
Wang Jiaxiang
c582ae667b 🌐 Add translations for: Chinese (Simplified).
Currently translated at 85.7% (952 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2022-06-19 06:19:06 +02:00
alonso.torres
529fb350fa 🐛 Fix thumbnails in viewer thumbnails 2022-06-17 14:39:07 +02:00
alonso.torres
e638475a67 Handoff handling nested frames 2022-06-17 14:26:32 +02:00
alonso.torres
1bde183c50 🐛 Fix thumbnails in viewer thumbnails 2022-06-17 14:24:37 +02:00
alonso.torres
45b690ed05 Fix shadows and thumbnails 2022-06-17 13:15:27 +02:00
alonso.torres
2799c09294 Fix interaction targets 2022-06-17 12:54:51 +02:00
alonso.torres
a774f4d4fa Fix guides, grids and constraints for nested frames 2022-06-17 12:54:51 +02:00
alonso.torres
2e3f443758 Fix problems with shadows and strokes for nested frames 2022-06-17 12:54:51 +02:00
alonso.torres
e0a1da6bca 🐛 Fix problems with thumbnails 2022-06-17 12:54:51 +02:00
alonso.torres
108291337d Improved frame indices 2022-06-17 12:54:51 +02:00
alonso.torres
ca326ac231 Fix dashboard thumbnails for nested frames 2022-06-17 12:54:51 +02:00
alonso.torres
566dde21a5 Fix viewer for new frames 2022-06-17 12:54:51 +02:00
alonso.torres
cab2b8469e Fix nested frames with thumbnails 2022-06-17 12:54:51 +02:00
alonso.torres
a37233be1e 🐛 Improved thumbnails rendering 2022-06-17 12:54:51 +02:00
alonso.torres
b4e218c13a Fix copy/paste for multiple frames 2022-06-17 12:54:51 +02:00
alonso.torres
9bd382f833 Fixed export/import for nested frames 2022-06-17 12:54:51 +02:00
alonso.torres
a4cc57886b Thumbnails for clipped and nested artboards 2022-06-17 12:54:51 +02:00
alonso.torres
0bb0063be4 Fix comments for nested frames 2022-06-17 12:54:51 +02:00
alonso.torres
79a46efa35 Create nested frames from selection 2022-06-17 12:54:51 +02:00
alonso.torres
c8ad379bf8 Adapted viewer for new frames 2022-06-17 12:54:50 +02:00
alonso.torres
8c5cc446b0 Improved hover behavior 2022-06-17 12:51:24 +02:00
alonso.torres
688ec2589a Changes in selection feedback 2022-06-17 12:51:24 +02:00
alonso.torres
aa584e6d35 ♻️ Refactor transform matrix 2022-06-17 12:51:24 +02:00
alonso.torres
a9303c37c4 Allow for nested frames 2022-06-17 12:51:24 +02:00
Alejandro
0bbd898173 Merge pull request #2002 from penpot/palba-improvements-view-mode
🎉 Improvements on view mode
2022-06-17 11:33:19 +02:00
Pablo Alba
ae468ecdf2 🎉 Improvements on view mode 2022-06-17 11:05:43 +02:00
Pablo Alba
0654741e28 🎉 Navigate to the original link after log in 2022-06-17 10:22:11 +02:00
Andrey Antukh
c60c04f167 Merge pull request #2004 from penpot/alotor-bugfixing
1.14 Bugfixes
2022-06-17 08:22:02 +02:00
Eva
8f7fd21454 New layout and layout item menur 2022-06-16 18:55:35 +02:00
alonso.torres
24d23d9e5a 🐛 Fix visual glitch with thumbnails 2022-06-16 18:50:01 +02:00
alonso.torres
66cec51c44 🐛 Fix text problem 2022-06-16 10:40:33 +02:00
Alejandro
65b6d1e07b Merge pull request #2001 from penpot/niwinz-telemetry-enhacements-2
Minor improvements
2022-06-15 12:29:04 +02:00
Andrey Antukh
adf2d82a52 🎉 Add proper logging reports on audit-log-archive task 2022-06-15 12:21:23 +02:00
Andrey Antukh
dce479bc4b Make the pool initialization process and defaults reusable
And add the ability to skip pool initialization if no enough data is
provided. Mainly for initialize pools based on configuration for not
essential/dynamic services.
2022-06-15 12:19:16 +02:00
Andrey Antukh
199360efa6 📎 Update default repl script 2022-06-15 12:18:39 +02:00
Alejandro Alonso
943fa880a7 Merge remote-tracking branch 'origin/staging' into develop 2022-06-15 12:00:31 +02:00
Alejandro Alonso
5e2a7e76f3 Merge remote-tracking branch 'origin/main' into staging 2022-06-15 12:00:14 +02:00
Andrey Antukh
e0bd3425bc Merge pull request #1999 from penpot/superalex-add-project-ids-to-create-file-audit-log
 Add project ids to create-file mutation for audit log
2022-06-15 10:53:33 +02:00
Locness
963df4b44f 🌐 Add translations for: French.
Currently translated at 70.2% (780 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-06-15 10:19:07 +02:00
Alexandre Pawlak
32b2b46df7 🌐 Add translations for: French.
Currently translated at 70.2% (780 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-06-15 10:19:07 +02:00
Alejandro Alonso
667598a0eb Add project ids to create-file mutation for audit log 2022-06-15 07:49:05 +02:00
Alejandro
58a1060ed8 Merge pull request #1997 from penpot/niwinz-make-s3-storace-region-optional
 Make the region param optional on s3 storage backend
2022-06-14 13:33:10 +02:00
Alejandro
b3f8d98c34 Merge pull request #1996 from penpot/niwinz-im4java-fix
⬆️ Update im4java version to our internal fork version
2022-06-14 13:11:07 +02:00
Andrey Antukh
20f357d75d Make the region param optional on s3 storage backend
Defaulting to the eu-central-1
2022-06-14 12:13:47 +02:00
Alejandro Alonso
310c322883 🐛 Fix show baground on export arboards 2022-06-14 11:26:19 +02:00
Andrey Antukh
9ae5528355 ⬆️ Update im4java version to our internal fork version
It fixes the v7 compatibility issues. Now, adding the -Dim4java.useV7=true
property to the java command when executing the penpot backend bundle it
switches to use the `magick` (ImageMagick v7 CLI) instead of `convert`
and `identify`.
2022-06-14 11:09:48 +02:00
Alexandre Pawlak
e7e231b719 🌐 Add translations for: French.
Currently translated at 67.5% (750 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-06-14 09:44:28 +02:00
Alejandro Alonso
69cb626cab Merge remote-tracking branch 'origin/staging' into develop 2022-06-14 09:14:07 +02:00
Alejandro Alonso
7f9d070692 Merge remote-tracking branch 'origin/main' into staging 2022-06-14 09:13:33 +02:00
Andrey Antukh
206ffcc6e8 Merge pull request #1995 from penpot/superalex-add-team-and-project-ids-to-update-file-audit-log
 Add team and project ids to update-file mutation for audit log
2022-06-14 09:12:21 +02:00
Alejandro Alonso
6b5ee24010 Add team and project ids to update-file mutation for audit log 2022-06-14 09:08:06 +02:00
Andrey Antukh
2132bad898 🐛 Add missing resolver to frontend docker image 2022-06-13 15:54:28 +02:00
Andrey Antukh
189d33221e 🐛 Add missing resolver to frontend docker image 2022-06-13 15:54:03 +02:00
Alejandro
5870d25bec Merge pull request #1993 from penpot/niwinz-update-deps
⬆️ Update deps & linter fixes
2022-06-13 15:53:40 +02:00
Andrey Antukh
6190ce9b35 🐛 Add missing resolver to frontend docker image 2022-06-13 14:44:40 +02:00
Andrey Antukh
65753cdc17 ⬆️ Update yetti dep (fix multipart field size validation params handling) 2022-06-13 13:42:32 +02:00
Andrey Antukh
1174590af4 📎 Add hack for devtools unhandled rejection 2022-06-13 13:10:36 +02:00
Andrey Antukh
e5cb5860a8 ⬆️ Update cuerdas dep (fixes dm/str nil handling) 2022-06-13 13:01:31 +02:00
Andrey Antukh
65e99cabbf 📎 Fix linter issues
Related to the linter update on devenv
2022-06-13 11:18:35 +02:00
Andrey Antukh
97bf20dd4c ⬆️ Update dependencies 2022-06-13 11:18:02 +02:00
Alejandro Alonso
a3fd5d6516 📚 Update changelog 2022-06-13 10:16:10 +02:00
ascarida
c26273c9b3 🌐 Add translations for: Galician.
Currently translated at 12.7% (142 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/gl/
2022-06-11 11:14:34 +02:00
Alexandre Pawlak
7e1a771e24 🌐 Add translations for: French.
Currently translated at 67.3% (748 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-06-11 11:14:33 +02:00
Locness
fa7b0d3b35 🌐 Add translations for: French.
Currently translated at 67.3% (748 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-06-11 11:14:33 +02:00
Alejandro Alonso
85f2804af8 📎 Tag new minor release 2022-06-10 08:24:13 +02:00
Alejandro Alonso
d9420081c4 Merge remote-tracking branch 'origin/staging' into develop 2022-06-09 13:45:25 +02:00
Alejandro Alonso
424dd5c41a Merge remote-tracking branch 'origin/main' into staging 2022-06-09 13:45:13 +02:00
Alejandro Alonso
97f5f54d1c 📎 Tag new minor release 2022-06-09 13:44:22 +02:00
Alejandro
e6b80bf73e Merge pull request #1988 from penpot/superalex-fix-exporter-to-frontend-communication
🐛 fix exporter to frontend communication
2022-06-09 13:43:49 +02:00
Alejandro Alonso
bc3914e7e0 🐛 Fix exporter to frontend communication 2022-06-09 13:39:59 +02:00
Alejandro Alonso
e6b1c578d4 Merge remote-tracking branch 'origin/staging' into develop 2022-06-09 08:55:23 +02:00
Alejandro Alonso
801cdd940a Merge remote-tracking branch 'origin/main' into staging 2022-06-09 08:55:05 +02:00
Alejandro Alonso
28b6175943 📎 Tag new minor release 2022-06-09 08:47:55 +02:00
Alejandro Alonso
ba85dcf1a3 🐛 Fix orientation artboard preset does not work with differently sized artboards 2022-06-08 13:11:59 +02:00
Alejandro Alonso
c3486c566a 🐛 Fix exporter to frontend communication 2022-06-08 13:09:16 +02:00
andy
437e352bf4 🌐 Added translation for: Galician. 2022-06-08 08:05:32 +02:00
Andrés Moya
71501d966c 🐛 Fix resize parents when there are nested groups 2022-06-07 14:36:26 +02:00
alonso.torres
288e6e1ea1 Merge remote-tracking branch 'origin/staging' into develop 2022-06-07 13:02:32 +02:00
alonso.torres
8bb2f20eae 🐛 Fix problem with shadow spec 2022-06-07 12:53:48 +02:00
alonso.torres
a8c3ac630d ⬆️ Update to version 1.15.0-beta 2022-06-06 15:27:55 +02:00
alonso.torres
0fcd414792 📚 Update changelog 2022-06-06 15:27:24 +02:00
alonso.torres
da6675c91e 📚 Update changelog 2022-06-06 15:26:40 +02:00
alonso.torres
9eba666c31 Merge remote-tracking branch 'origin/main' into develop 2022-06-06 15:23:22 +02:00
alonso.torres
1764d965c1 📚 Upgrade to version 1.13.4-beta 2022-06-06 15:22:23 +02:00
Alejandro Alonso
a120630a7f 🐛 Fix environment import for exporter at docker 2022-06-06 13:23:40 +02:00
Alejandro
f33ad5e8fa Merge pull request #1972 from penpot/hiru-fix-orphaned-shapes
 Add script to fix broken objects
2022-06-06 13:18:07 +02:00
Andrés Moya
f04859f8a6 Add script to fix broken objects 2022-06-06 12:56:37 +02:00
Alejandro Alonso
31aed2aaa4 🐛 Fix base background not visible for imported svg 2022-06-06 12:34:19 +02:00
Alejandro
18109b2387 Merge pull request #1976 from penpot/hiru-fix-scrollintoview
🐛 Fix auto scroll layers panel in firefox
2022-06-06 11:05:35 +02:00
Andrés Moya
a0cc8a06b6 🐛 Fix auto scroll layers panel in firefox 2022-06-06 10:21:32 +02:00
Oğuz Ersen
3b26ec6b8c 🌐 Add translations for: Turkish.
Currently translated at 100.0% (1110 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2022-06-05 15:15:47 +02:00
Ahmad HosseinBor
71ce0b66e0 🌐 Add translations for: Persian.
Currently translated at 28.4% (316 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2022-06-03 21:13:58 +02:00
Pablo Alba
4c1903b4e8 💄 Change text properties position at the sidebar 2022-06-03 12:42:35 +02:00
Pablo Alba
462ec0c12a Merge pull request #1973 from penpot/alotor-more-hotfixes
Hotfixes
2022-06-03 10:59:26 +02:00
Alejandro Alonso
2b61b1768f 🐛 Fix exporter white list domains configuration 2022-06-03 07:43:19 +02:00
alonso.torres
424630a67f 📚 Update changelog 2022-06-02 22:53:50 +02:00
alonso.torres
14b1970a8a 🐛 Fix concurrent thumbnail modification 2022-06-02 22:37:33 +02:00
alonso.torres
541168aee4 🐛 Fix problem with some data and text input 2022-06-02 22:35:59 +02:00
alonso.torres
6e9a77edcd 🐛 Fix undo for drawing curves 2022-06-02 22:31:27 +02:00
Pablo Alba
8d1cd2f56d 🐛 Empty groups were not deleted 2022-06-02 16:53:01 +02:00
Pablo Alba
65cda41245 Merge pull request #1970 from penpot/eva-shortcuts2
 Shortcuts improvements
2022-06-02 16:50:42 +02:00
Eva
c029948cce Shortcuts improvements 2022-06-02 16:40:46 +02:00
Pablo Alba
32540f1ba5 🐛 Components groups were not exported 2022-06-02 16:40:00 +02:00
alonso.torres
5d20815776 Merge remote-tracking branch 'origin/main' into develop 2022-06-01 10:42:59 +02:00
alonso.torres
0b149dd302 ⬆️ Update to 1.13.3-beta 2022-06-01 10:41:08 +02:00
Pablo Alba
662fc073df Merge pull request #1966 from penpot/alotor-fix-font-loading
🐛 Fix problem with font loading
2022-06-01 10:37:30 +02:00
alonso.torres
46be4ca6d1 🐛 Fix problem with font loading 2022-06-01 09:38:27 +02:00
Ahmad HosseinBor
784365f45c 🌐 Add translations for: Persian.
Currently translated at 27.4% (305 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2022-05-31 15:14:29 +02:00
alonso.torres
23d3e88214 Merge remote-tracking branch 'origin/main' into develop 2022-05-31 11:15:10 +02:00
Alejandro Alonso
c356ae6de8 🐛 Fix github auth without name 2022-05-31 10:26:13 +02:00
Pablo Alba
c5e872b81d 🐛 Remove default font on team change 2022-05-30 14:19:34 +02:00
Alejandro Alonso
0307e58fbe 🐛 Fix old texts with opacity and no fill 2022-05-30 12:40:07 +02:00
Alejandro
5c14c3fafc Merge pull request #1960 from penpot/alotor-fixes
🐛 Fix thumbnails. Add safety text position
2022-05-30 12:21:40 +02:00
alonso.torres
321c3fb34b 🐛 Fix problem with missplaced texts 2022-05-30 12:09:04 +02:00
alonso.torres
4764674374 🐛 Fix thumbnails. Add safety text position 2022-05-30 12:09:04 +02:00
Pablo Alba
0416988913 Set invitations expiration to 7 days 2022-05-30 10:41:23 +02:00
Eva Marco
72251f57b1 Merge pull request #1957 from yarons/patch-1
Typo fix
2022-05-30 10:21:45 +02:00
Vincas Dundzys
05aee3507a 🌐 Add translations for: Lithuanian.
Currently translated at 8.9% (99 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lt/
2022-05-28 23:16:02 +02:00
Radek Sawicki
f651b7585d 🌐 Add translations for: Polish.
Currently translated at 100.0% (1110 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pl/
2022-05-28 23:16:02 +02:00
Yaron Shahrabani
68e603a86c 🌐 Add translations for: Hebrew.
Currently translated at 100.0% (1110 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2022-05-28 23:16:01 +02:00
Andrés Moya
52adf7eaf1 🌐 Add translations for: English.
Currently translated at 100.0% (1110 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/en/
2022-05-28 23:16:00 +02:00
Andrés Moya
ec884787f1 🔧 Fix docker dependencies 2022-05-27 13:57:33 +02:00
alonso.torres
c08ad5c8c0 ⬆️ Update version 1.13.2-beta 2022-05-27 10:29:39 +02:00
alonso.torres
3d8c41cd69 Merge remote-tracking branch 'origin/main' into develop 2022-05-27 09:25:22 +02:00
alonso.torres
2ce766c49e 🐛 Fix performance issue with focus mode 2022-05-26 17:55:19 +02:00
Alejandro
bb18a69394 Merge pull request #1958 from penpot/alotor-improved-thumbnail-generation
 Improved frame generation performance
2022-05-26 16:51:13 +02:00
alonso.torres
96ed66d86e Improved frame generation performance 2022-05-26 16:33:16 +02:00
Yaron Shahrabani
be7733a2cf Typo fix
intertactions -> interactions
2022-05-26 15:38:49 +03:00
Eva
57ccb18517 💄 remove console 2022-05-26 13:58:00 +02:00
Andrés Moya
d5df465992 🌐 Add Norwegian, Persian and Chinese (Traditional) 2022-05-26 12:48:36 +02:00
Alejandro Alonso
ea6c34f6b2 🐛 Fix github auth without public email 2022-05-26 11:16:09 +02:00
Andrés Moya
36390be72a 🌐 Add new Polish language 2022-05-26 11:10:16 +02:00
alonso.torres
3c41693787 :docs: Update changelog 2022-05-25 21:45:21 +02:00
alonso.torres
b25806b172 🐛 Fix problem with auto-width initial text 2022-05-25 21:43:50 +02:00
Alejandro
0828d43f8f Merge pull request #1954 from penpot/alotor-fix-cache-embeds
🐛 Fix problems with embed data cache
2022-05-25 18:16:15 +02:00
alonso.torres
402212c808 🐛 Fix problems with embed data cache 2022-05-25 18:00:23 +02:00
Andrés Moya
8d51e32c5a 🌐 Add translations for: Spanish.
Currently translated at 100.0% (1110 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/
2022-05-25 17:29:50 +02:00
Andrés Moya
11b2144274 🌐 Add translations for: Spanish.
Currently translated at 100.0% (1110 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/
2022-05-25 17:20:51 +02:00
Andrés Moya
8c51d1ac95 🌐 Add translations for: Spanish.
Currently translated at 100.0% (1110 of 1110 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/
2022-05-25 17:17:58 +02:00
Eva Marco
64e2fa9e2f Merge pull request #1951 from penpot/palba-change-emails-footer
💄 Update footer in emails
2022-05-25 17:14:16 +02:00
Eva Marco
216dbc8e0d Merge pull request #1950 from penpot/palba-invitations-validation
Palba invitations validation
2022-05-25 17:01:21 +02:00
Pablo Alba
fa5b0ed6ac 💄 Update footer in emails 2022-05-25 16:56:40 +02:00
Eva Marco
67b81fbe67 Merge pull request #1949 from penpot/palba-fix-importing-old-penpot-files-frames
Palba fix importing old penpot files frames
2022-05-25 16:50:29 +02:00
Andrés Moya
89f485a674 🌐 Revise translations file format 2022-05-25 16:32:10 +02:00
Hosted Weblate
fcafe66bd8 🌐 Cherry-pick texts from Weblate for main branch 2022-05-25 16:07:05 +02:00
Pablo Alba
931759f468 🎉 Activate invitations validation 2022-05-25 12:03:05 +02:00
Pablo Alba
f33360a22b 🐛 Importing old penpot files generates frames with 0.01 as width and height
https://tree.taiga.io/project/penpot/issue/3455
2022-05-25 11:58:46 +02:00
Alejandro
910fb55b69 Merge pull request #1948 from penpot/fix-issue-hang-file
🐛 Fix problem with hanging file
2022-05-25 11:33:34 +02:00
alonso.torres
18849307e9 🐛 Fix linting issue 2022-05-25 11:29:49 +02:00
alonso.torres
0f2b2d4590 🐛 Fix problem with hanging file 2022-05-25 11:24:07 +02:00
Hosted Weblate
68e38271fb Merge branch 'origin/develop' into Weblate. 2022-05-25 11:24:03 +02:00
Vincas Dundzys
066d53b81b 🌐 Add translations for: Lithuanian.
Currently translated at 8.7% (84 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/lt/
2022-05-25 11:24:03 +02:00
Andrés Moya
ef37abcbbd 🐛 Allow debug load file with random uuid 2022-05-25 09:52:32 +02:00
alonso.torres
02427285ef 📚 Update changelog 2022-05-25 09:25:51 +02:00
Alejandro
38bc3b061a Merge pull request #1940 from penpot/repair-idless-components
🔧 Add a function to manually repair components without :id
2022-05-25 09:04:19 +02:00
Alejandro
047b3f0987 Merge pull request #1944 from penpot/hiru-dbg-update-file
Hiru dbg update file
2022-05-25 09:04:05 +02:00
Alejandro
6a8f3c7283 Merge pull request #1947 from penpot/alotor-hotfix-1.13
Alotor hotfix 1.13
2022-05-25 08:18:00 +02:00
alonso.torres
525da266b8 🐛 Creates a migration to invalidate texts-position-data 2022-05-24 23:34:23 +02:00
alonso.torres
97c9035cfd 🐛 Fix problems with font initialization 2022-05-24 23:34:23 +02:00
alonso.torres
35681c3af8 🐛 Fix problem with multiple users and texts positions 2022-05-24 23:34:23 +02:00
alonso.torres
8a6f01404c 🐛 Fix hide artboard 2022-05-24 23:34:23 +02:00
alonso.torres
6901431f8a Add debugging tool 2022-05-24 23:34:23 +02:00
Alejandro Alonso
2261bde6f1 🐛 Fix fills priority over imported svg attributes 2022-05-24 14:17:23 +02:00
elhombretecla
40e1d5a2a1 tada: Remove discussions and add twitter to form 2022-05-24 13:58:08 +02:00
Pablo Alba
ffbc098af8 🐛 Prototype connection should be under the rules
https://tree.taiga.io/project/penpot/issue/3384
2022-05-24 13:42:10 +02:00
Andrés Moya
d52c4541ae 🔧 Preserve id when downloading file with dbg 2022-05-24 13:34:42 +02:00
Pablo Alba
e8f61df710 🐛 Remove deprecated menu options 2022-05-24 12:49:41 +02:00
Eva Marco
3604d0cfc9 Merge pull request #1933 from penpot/palba-fix-file-menu-not-accessible
🐛 Fix menu file not accessible in certain conditions
2022-05-24 12:39:42 +02:00
Andrés Moya
b0c3b38cc5 🔧 Add a function to manually repair components without :id 2022-05-24 12:26:21 +02:00
Eva Marco
393d959289 Merge pull request #1929 from penpot/hiru-remove-emitf
♻️ Remove obsolete st/emitf macro
2022-05-24 12:21:29 +02:00
Eva Marco
494e2df49f Merge pull request #1937 from penpot/superalex-fix-add-stroke-for-text-with-shortcut
🐛 Fix adding string for texts with shortcut
2022-05-24 11:01:32 +02:00
andy
a453f1a648 🌐 Added translation for: Lithuanian. 2022-05-24 11:00:01 +02:00
Alejandro Alonso
dcac6d9ea4 🐛 Fix adding string for texts with shortcut 2022-05-24 07:17:15 +02:00
Pablo Alba
cdd6801360 🐛 Fix menu file not accessible in certain conditions
https://tree.taiga.io/project/penpot/issue/3385
2022-05-23 17:08:30 +02:00
Alejandro
f5128d8d43 Merge pull request #1932 from penpot/fix-div-by-zero
🐛 Fix problem with division by zero
2022-05-23 13:52:17 +02:00
alonso.torres
4c2182dd0b 🐛 Fix problem with division by zero 2022-05-23 13:46:36 +02:00
alonso.torres
cca5ddb81a Merge remote-tracking branch 'origin/main' into develop 2022-05-23 12:17:56 +02:00
Alejandro
c83affe351 Merge pull request #1931 from penpot/alotor-bugfix-initialize-texts
🐛 Fix problem when initializing texts
2022-05-23 12:15:21 +02:00
alonso.torres
51a9b10d51 🐛 Fix problem when initializing texts 2022-05-23 12:00:46 +02:00
alonso.torres
28e2d64ac6 Merge remote-tracking branch 'origin/main' into develop 2022-05-23 10:58:29 +02:00
alonso.torres
0fc2c312d5 🐛 Fix problem with fonts and thumbnails 2022-05-23 10:26:07 +02:00
Eva Marco
6eb24bd1b7 Merge pull request #1928 from penpot/palba-fix-wrong-not-found-message
🐛 Fix wrong 'no assets found' message
2022-05-23 10:17:50 +02:00
Pablo Alba
79467b7b72 🐛 Fix wrong 'no assets found' message 2022-05-23 10:10:06 +02:00
Locness
e14c6e5a6f 🌐 Add translations for: French.
Currently translated at 72.8% (703 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-05-23 00:15:40 +02:00
Andrés Moya
2be432e1d4 ♻️ Remove obsolete st/emitf macro 2022-05-20 16:37:33 +02:00
alonso.torres
eb07350cac 🐛 Fix problem with typographies 2022-05-20 13:00:50 +02:00
Pablo Alba
ba139d7d2c 🐛 Fix unathorized redirect 2022-05-20 12:37:57 +02:00
alonso.torres
235d3dbf3d Merge remote-tracking branch 'origin/main' into develop 2022-05-20 11:10:14 +02:00
Alejandro
426758d9b2 Merge pull request #1924 from penpot/fix-sync
🐛 Fix some component shapes not synced
2022-05-20 10:39:22 +02:00
alonso.torres
542fb9c754 🐛 Fix problem with nested constraints and text 2022-05-20 10:26:41 +02:00
Andrés Moya
13492f5ac7 🐛 Fix orphaned component instances 2022-05-20 09:17:55 +02:00
Andrés Moya
43d3b06c30 🐛 Fix some component shapes not synced 2022-05-19 17:52:31 +02:00
alonso.torres
d8a7402046 🐛 Fix problems with text position data 2022-05-19 16:33:43 +02:00
alonso.torres
93b582c385 🐛 Fix problem with small with texts 2022-05-19 15:02:50 +02:00
alonso.torres
d45bb0ace1 🐛 Fix dirty text modifiers when changing pages 2022-05-19 15:02:50 +02:00
alonso.torres
25ff15c62e 🐛 Fix rendering thumbnail with pending images/fonts 2022-05-19 15:02:50 +02:00
Andrés Moya
30bcdda90e 🐛 Add a protection for some corner cases 2022-05-19 09:49:42 +02:00
alonso.torres
e22ef536ed 🐛 Fix problem with Safari and text resize 2022-05-18 22:27:21 +02:00
Eva
b5e696c6b4 🐛 Fix ungroup typographies on edit 2022-05-18 17:23:26 +02:00
alonso.torres
2b1e126ff8 🐛 Fix problem with thumbnails 2022-05-18 17:04:59 +02:00
Alejandro
1690f1ee23 Merge pull request #1919 from penpot/alotor-buf-export
🐛 Fix problem when exporting penpot files
2022-05-18 15:59:21 +02:00
alonso.torres
6a74f29f96 🐛 Fix problem when exporting penpot files 2022-05-18 15:52:45 +02:00
Andrés Moya
d666755159 🐛 Synchronize text positions in components 2022-05-18 13:45:03 +02:00
Alejandro
fa00d674eb Merge pull request #1914 from penpot/alotor-performance-improvements
Performance improvements
2022-05-18 11:15:40 +02:00
Pablo Alba
7c23b7ea79 Merge pull request #1916 from penpot/superalex-fix-undo-drawing-curve
🐛 Fix undo when drawing curve
2022-05-18 10:57:07 +02:00
Alejandro Alonso
919ca68a77 🐛 Fix undo when drawing curve 2022-05-18 10:49:55 +02:00
Alejandro
684805067a Merge pull request #1915 from JamieSlome/develop
Create SECURITY.md
2022-05-18 09:39:18 +02:00
Jamie Slome
db7761b742 Create SECURITY.md 2022-05-17 19:35:14 +01:00
Pablo Alba
29010453e6 Merge pull request #1913 from penpot/eva-fix-scroll-into-view
🐛 Fix scroll into view with big groups
2022-05-17 19:44:40 +02:00
alonso.torres
a8cc9ace72 Improved text move performance 2022-05-17 17:02:45 +02:00
alonso.torres
9ab922a0fa Improved snap-pixel performance 2022-05-17 17:02:28 +02:00
alonso.torres
c9dadce12a Skip calculate children modifiers on move 2022-05-17 17:02:11 +02:00
Eva
eabfa7a541 🐛 Fix scroll into view with big groups 2022-05-17 16:38:24 +02:00
Andrés Moya
95a2da5ebc Rework multi edit of measures values 2022-05-17 14:42:16 +02:00
Pablo Alba
180c355340 Merge pull request #1911 from penpot/alotor-fix-texts
Fix problems with texts and thumbnails
2022-05-17 14:26:24 +02:00
alonso.torres
01664a04fc 🐛 Problem recalculating thumbnails 2022-05-17 14:09:03 +02:00
alonso.torres
edce45095e 🐛 Remove xlinkHref to resolve Safari problem 2022-05-17 14:09:03 +02:00
alonso.torres
5a07599fc7 🐛 Fix problem with thumbnail re-rendering 2022-05-17 14:09:03 +02:00
alonso.torres
d684970bfb 🐛 Fix problem with single line texts 2022-05-17 14:09:03 +02:00
Alejandro Alonso
216b510900 🐛 Fix security concern 2022-05-17 13:03:04 +02:00
Alejandro
5c2b5f7cda Merge pull request #1909 from penpot/eva-fix-typo
🐛 Fix typo
2022-05-17 12:57:25 +02:00
Eva
712c68fc77 🐛 Fix typo 2022-05-17 12:43:44 +02:00
Alejandro
f290465edd Merge pull request #1908 from penpot/eva-no-rotation-in-paths
🐛 Fix rotation value when path is not rotated
2022-05-17 12:09:53 +02:00
Eva
141bcdd25e 🐛 Fix rotation value when path is not rotated 2022-05-17 11:59:48 +02:00
Pablo Alba
f68a4eb84a Merge pull request #1907 from penpot/eva-fix-layers-when-group
🐛 Fix change layer position on group or component creation
2022-05-17 10:48:08 +02:00
Eva
a240fbdf5b 🐛 Fix change layer position on group or component creation 2022-05-17 10:29:19 +02:00
Alejandro Alonso
799bb87398 🐛 Fix security concern 2022-05-17 10:25:13 +02:00
Alejandro
2b5282025c Merge pull request #1904 from penpot/alotor-fix-text-problems
Fix text issues
2022-05-17 06:41:39 +02:00
Joseph V M
08beb57ff1 🌐 Add translations for: Malayalam.
Currently translated at 7.1% (69 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ml/
2022-05-16 23:16:12 +02:00
alonso.torres
a2de5f8fb4 🐛 Fix center alignment with new lines 2022-05-13 16:17:05 +02:00
alonso.torres
080139cd56 🐛 Improved performance for text resize 2022-05-13 16:17:05 +02:00
alonso.torres
570f038062 🐛 Disable stroke style for texts 2022-05-13 16:17:05 +02:00
alonso.torres
ae84f3cbe8 🐛 Fix typo in debug option 2022-05-13 16:17:05 +02:00
alonso.torres
abdc9b2cbd 🐛 Fix problem with center vertical align and auto-height 2022-05-13 16:17:05 +02:00
Pablo Alba
92d7521ec7 Merge pull request #1898 from penpot/superalex-fix-paste-svg-with-empty-space
🐛 Fix paste svg with empty space
2022-05-13 16:16:16 +02:00
alonso.torres
4730273ad3 🐛 Rollback thumbnail problem 2022-05-13 13:32:22 +02:00
Alejandro
a3935953f7 Merge pull request #1902 from penpot/palba-fix-artboards-thumbnail-another-page
🐛 Fix artboards thumbnail in another page
2022-05-13 13:17:46 +02:00
alonso.torres
ea50622bf7 🐛 Fine tune thumbnails 2022-05-13 13:16:58 +02:00
Alejandro
4b0b7463c7 Merge pull request #1903 from penpot/eva-bugfix-handoff
🐛 Show strokes and fills for texts when in handoff
2022-05-13 13:11:23 +02:00
Alejandro Alonso
95d4018074 🐛 Fix paste svg with empty space 2022-05-13 13:05:49 +02:00
Eva
3f413e4920 🐛 Show strokes and fills in text when in handoff 2022-05-13 12:44:11 +02:00
Alejandro Alonso
db8e829339 🐛 Fix remove time debug info 2022-05-13 12:00:18 +02:00
Pablo Alba
448e0dd415 🐛 Fix artboards thumbnail in another page 2022-05-13 11:29:46 +02:00
Alejandro
15418a252e Merge pull request #1893 from penpot/superalex-fix-thumbnail-blur
🐛 Fix Thumbnail blur on mouse movements
2022-05-13 09:18:30 +02:00
Alejandro
21d845d254 Merge pull request #1896 from penpot/superalex-multiple-fills-with-texts-are-not-working-properly
🐛 Fix multiple fills with texts are not working properly
2022-05-13 09:17:47 +02:00
Alejandro Alonso
c84017eb72 🐛 Fix multiple fills with texts are not working properly 2022-05-13 07:58:02 +02:00
Alejandro
431e42c80a Merge pull request #1895 from penpot/release-1.13
💄 Release 1.13 onboarding texts
2022-05-13 06:46:49 +02:00
elhombretecla
ca2eb1ac12 💄 Add new onboarding texts 2022-05-13 06:42:22 +02:00
alonso.torres
d2983c1110 🐛 Improve active frame behaviour for thumbnails 2022-05-13 06:20:31 +02:00
Alejandro Alonso
74612178d7 🐛 Fix Thumbnail blur on mouse movements 2022-05-13 06:20:31 +02:00
Eva Marco
af519b3f89 Merge pull request #1892 from penpot/alotor-bugfixing-2
Change text disposition on resize
2022-05-12 16:52:27 +02:00
alonso.torres
d8d4ce7a46 🐛 Fix linter 2022-05-12 16:32:25 +02:00
alonso.torres
3930be5d9e 🐛 Remove warnings from external library 2022-05-12 16:23:45 +02:00
alonso.torres
d85a4d6539 🐛 Minor improvements on refs 2022-05-12 16:23:45 +02:00
alonso.torres
7446fe77b3 🐛 Change text disposition on resize 2022-05-12 16:23:45 +02:00
alonso.torres
8b1f8d1418 🐛 Fix error in view mode 2022-05-12 15:18:23 +02:00
Pablo Alba
d387ca81d8 Merge pull request #1894 from penpot/superalex-fix-scrollbars-not-shown
Fix Scrollbars not shown
2022-05-12 14:25:51 +02:00
Alejandro Alonso
b7b5f3b4c2 Fix Scrollbars not shown 2022-05-12 14:18:26 +02:00
Eva Marco
698dd872e4 Merge pull request #1886 from penpot/superalex-multiple-fills-with-texts-are-not-working-properly
🐛 Fix multiple fills with texts are not working properly
2022-05-12 09:43:21 +02:00
Alejandro Alonso
767f0fe16b 🐛 Fix multiple fills with texts are not working properly 2022-05-12 09:30:37 +02:00
Alejandro
a19c56c0ce Merge pull request #1885 from penpot/eva-bugfix
🐛 Avoid scroll behind fixed element in layers
2022-05-12 09:05:04 +02:00
Eva
b9e984300c 🐛 Avoid scroll behind fixed element in layers 2022-05-12 08:43:53 +02:00
Alejandro
0727757eb1 Merge pull request #1884 from penpot/superalex-fix-import-svg-shapes-without-fill
🐛 Fix import svg shapes without fill
2022-05-12 06:57:05 +02:00
Eva Marco
50037a6a88 Merge pull request #1890 from penpot/alotor-bugfixing-2
🐛 Fix problem with RTL texts
2022-05-11 17:08:51 +02:00
Eva Marco
5bdea086e9 Merge pull request #1889 from penpot/palba-canceled-invitation-page
🎉 Show an error page when the user uses a cancelled/invalid/expired invitation
2022-05-11 16:39:39 +02:00
alonso.torres
fef69cb707 🐛 Fix problem with RTL texts 2022-05-11 15:53:50 +02:00
Eva Marco
20211101b7 Merge pull request #1888 from penpot/alotor-bugfixing-2
Fix problem with frame resize
2022-05-11 14:23:58 +02:00
Pablo Alba
ce41a38098 🎉 Show an error page when the user uses a cancelled/invalid/expired invitation 2022-05-11 13:46:43 +02:00
alonso.torres
c14ece9f8d 🐛 Fix problems with thumbnails 2022-05-11 13:44:47 +02:00
Alejandro Alonso
f2bb59fd77 🐛 Fix Paths have a black fill while being drawn 2022-05-11 13:11:55 +02:00
alonso.torres
af6a687187 🐛 Fix performance problem with import SVG 2022-05-11 11:29:32 +02:00
alonso.torres
40de8781ef 🐛 Improved zoom responsiveness 2022-05-11 11:29:32 +02:00
alonso.torres
33e776fefe 🐛 Fix path handler radius 2022-05-11 11:29:32 +02:00
Alejandro Alonso
efcabe7ffb 🐛 Fix import svg shapes without fill 2022-05-11 11:04:04 +02:00
Pablo Alba
77e9b8aa70 Merge pull request #1873 from penpot/superalex-import-svg-with-exterior-strokes
🐛  Import svg with exterior strokes
2022-05-11 09:23:40 +02:00
Alejandro
238cd14eb8 Merge pull request #1881 from penpot/hirunatan-fix-pdf-page-size
🐛 Fix page size at pdf export
2022-05-10 17:38:27 +02:00
Eva Marco
22193635d6 Merge pull request #1880 from penpot/palba-no-copy-use-for-thumbnail-on-duplicate
🐛 Do not copy the atribute use-for-thumbnail on frame duplicate
2022-05-10 16:04:40 +02:00
Andrés Moya
8432e970cb 🐛 Fix page size at pdf export
https://tree.taiga.io/project/penpot/issue/3371
2022-05-10 15:54:01 +02:00
Alejandro Alonso
55df28d5dc 🐛 Fix change username if not subscribed to newsletter 2022-05-10 15:12:17 +02:00
Eva Marco
33882f44ef Merge pull request #1875 from penpot/alotor-bugfixing-2
Bugfixes
2022-05-10 14:23:39 +02:00
Radek Sawicki
accba56b89 🌐 Add translations for: Polish.
Currently translated at 100.0% (965 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pl/
2022-05-10 14:15:08 +02:00
Pablo Alba
c06042c91b 🐛 Do not copy the atribute use-for-thumbnail on frame duplicate
https://tree.taiga.io/project/penpot/issue/3362
2022-05-10 13:26:19 +02:00
alonso.torres
2976c5c572 🐛 Fix problem with flipped texts 2022-05-10 11:58:44 +02:00
alonso.torres
8df93c2707 🐛 Fix problem when exporting single text 2022-05-10 11:58:21 +02:00
Eva
0c26dad3b2 🐛 Show selrect in paths 2022-05-10 10:51:47 +02:00
Alejandro Alonso
8d399cb562 🐛 Fix import svg shapes without fill 2022-05-10 10:49:50 +02:00
alonso.torres
82d744b94a 🐛 Fix problem with scrolling on already visible layers 2022-05-09 17:50:34 +02:00
alonso.torres
94d3f66ef1 🐛 Fix problem with rotated shapes and auto-width/auto-height 2022-05-09 17:37:37 +02:00
alonso.torres
40a38cbd38 🐛 Fix problem when pasting frame and selected shape 2022-05-09 17:01:42 +02:00
alonso.torres
644c796772 🐛 Fix problem with path edition 2022-05-09 16:46:52 +02:00
alonso.torres
81dac233a7 🐛 Fix problem with text edition selection area 2022-05-09 16:46:52 +02:00
alonso.torres
6bbd76f350 🐛 Fix problem with text shapes in components 2022-05-09 16:46:52 +02:00
alonso.torres
3a6072bc8f 🐛 Fix problem with RTL 2022-05-09 16:46:52 +02:00
Alejandro
0bcf3d99a0 Merge pull request #1872 from penpot/alotor-fix-thumbnail-problem
Fix thumbnails problem
2022-05-09 15:44:05 +02:00
alonso.torres
8cd7f61150 🐛 Fix problem with duplicated ids for thumbnails 2022-05-09 15:37:47 +02:00
Alejandro Alonso
96aa756eb6 🐛 Fix import svg with exterior strokes 2022-05-09 12:46:52 +02:00
Eva Marco
c5ba399bcd Merge pull request #1856 from penpot/palba-onboarding-multiple-invitations
 Multiple team invitations on onboarding
2022-05-09 09:45:05 +02:00
Pablo Alba
fb879660d0 Multiple team invitations on onboarding 2022-05-09 09:40:44 +02:00
Eva Marco
4cdf8cec4e Merge pull request #1866 from penpot/palba-add-icon-to-artboard-thumbnail
Palba add icon to artboard thumbnail
2022-05-09 09:21:27 +02:00
Pablo Alba
d9a9eb3729 Add icon to artboard thumbnail 2022-05-06 19:12:05 +02:00
Eva Marco
8298d460e6 Merge pull request #1865 from penpot/alotor-bugfixing
Alotor bugfixing
2022-05-06 14:10:12 +02:00
Eva
462eabd8a1 🐛 Show '--' when multiple rotations 2022-05-06 13:31:24 +02:00
Eva
afa1af6dc2 🐛 Fix comments in viewer mode 2022-05-06 13:31:24 +02:00
Eva
37fdf51eaf 🐛 Fix copying layout values with only multiple decimals 2022-05-06 13:31:24 +02:00
Eva
1102bc9cba 🐛 Activate button when input change in account 2022-05-06 13:31:24 +02:00
Eva
18afb701fb 🐛 Fix apply color to groups from assets panel 2022-05-06 13:31:24 +02:00
Eva Marco
15a26d10f0 Merge pull request #1867 from penpot/hirunatan-bugfixing
Hirunatan bugfixing
2022-05-06 13:09:44 +02:00
Andrés Moya
9b8b6134c5 🐛 Allow images to adjust to the shape size
https://tree.taiga.io/project/penpot/issue/3329
2022-05-06 12:07:19 +02:00
Andrés Moya
7e05b7e6d9 🐛 Fix group typographies
https://tree.taiga.io/project/penpot/issue/3338
2022-05-06 10:56:20 +02:00
Andrés Moya
b86ea5b5e2 🐛 Fix notifications of external library changes
https://tree.taiga.io/project/penpot/issue/3348
2022-05-06 10:56:20 +02:00
Andrés Moya
1729fe7312 🌐 Add translations for: Spanish.
Currently translated at 98.9% (955 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/
2022-05-06 10:05:46 +02:00
alonso.torres
66f7d35510 🐛 Fix problem with multi-line text and strokes 2022-05-05 17:21:28 +02:00
Andrés Moya
8fb22b8eee 🐛 Add a protection for some possible race condition 2022-05-05 17:16:27 +02:00
alonso.torres
5b37c11221 🐛 Fix letter spacing for svg texts 2022-05-05 17:16:05 +02:00
alonso.torres
1723ff1da5 🐛 Numeric input for font size 2022-05-05 17:04:03 +02:00
alonso.torres
9099403421 🐛 Improved resilience for thumbnail generation 2022-05-05 16:46:21 +02:00
alonso.torres
baf3f7ea15 🐛 Fix problem with outerstrokes for frames 2022-05-05 14:24:14 +02:00
Pablo Alba
1d39bbaa3c 🐛 Do not show team-up modal for users already on a team 2022-05-05 14:08:51 +02:00
alonso.torres
0db2f87e3e 🐛 Fix problems with thumbnails generation 2022-05-05 13:11:03 +02:00
alonso.torres
430ccda02c 🐛 Fix problem with black frame background 2022-05-05 13:03:36 +02:00
Pablo Alba
fe6e62482a 🐛 Fix bad texts in layers filter pills 2022-05-05 09:25:51 +02:00
Pablo Alba
82185794a8 🐛 Fix shapes filter 2022-05-05 09:25:19 +02:00
Pablo Alba
053975ef82 Fix members menu popup is not correctly aligned 2022-05-05 09:24:34 +02:00
Pablo Alba
7185199d05 🐛 Fix feedback crash 2022-05-05 09:24:21 +02:00
Eva
bd7ea210f5 💄 Add changes line 2022-05-05 09:13:22 +02:00
Eva
9cacca4802 Add shortcut panel 2022-05-04 16:36:47 +02:00
Ahmad HosseinBor
9fab2fc24a 🌐 Add translations for: Persian.
Currently translated at 31.3% (303 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2022-05-04 12:14:36 +02:00
Pablo Alba
9dcad7ebef 🐛 Round the size values on handoff to two decimals 2022-05-03 10:42:37 +02:00
alonso.torres
4363e32aae Merge remote-tracking branch 'origin/staging' into develop 2022-05-03 10:29:19 +02:00
alonso.torres
39e4651374 📚 Update changelog 2022-05-03 09:49:37 +02:00
Alejandro Alonso
fe1ae7dbb4 🐛 Fix import svg shapes without fill 2022-05-03 09:30:36 +02:00
Ahmad HosseinBor
28fc7178f1 🌐 Add translations for: Persian.
Currently translated at 21.4% (207 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2022-05-02 19:14:48 +02:00
alonso.torres
39b0de1ced 🐛 Fix thumbnails problem 2022-04-29 14:56:14 +02:00
Alejandro Alonso
2f0e85f619 🐛 Fix scroll bars 2022-04-29 14:55:05 +02:00
Andrés Moya
151de33586 🔧 Small fix of debug functions 2022-04-29 11:05:04 +02:00
Alejandro
4d106d9e15 Merge pull request #1849 from penpot/alotor-bugfixing
Bugfixes
2022-04-29 10:46:02 +02:00
elhombretecla
e5ccf36c07 add new release info and images 2022-04-29 10:30:47 +02:00
alonso.torres
d92df31b3e 🐛 Fix problem with horizontal scroll 2022-04-28 16:51:27 +02:00
alonso.torres
8b3062be0b 🐛 Fix problem when resizing a group with texts with auto-width/height 2022-04-28 15:32:41 +02:00
alonso.torres
c7e23c1b58 🐛 Fix problem when export/importing guides attached to frame 2022-04-28 14:43:44 +02:00
alonso.torres
9923268589 🐛 Fix issue with paste ordering sometimes not being respected 2022-04-28 14:43:44 +02:00
alonso.torres
a8103cbc3e ⬆️ Update potok 2022-04-28 14:43:44 +02:00
alonso.torres
26a074768f 🐛 Fix path editing 2022-04-28 14:43:44 +02:00
alonso.torres
1c87195fa6 🐛 Fix error when drawing curves with only one point 2022-04-28 14:43:44 +02:00
alonso.torres
2a1ca07554 🐛 Fix problem when changing group size with decimal values 2022-04-28 14:43:44 +02:00
alonso.torres
c3be87ed30 🐛 Fix problem with thumbnail refresh 2022-04-28 14:27:23 +02:00
alonso.torres
0afbf02443 💄 Fix linter 2022-04-28 11:22:36 +02:00
Ahmad HosseinBor
eb143c8399 🌐 Add translations for: Persian.
Currently translated at 21.3% (206 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2022-04-28 11:14:38 +02:00
Locness
85f1cb47a7 🌐 Add translations for: French.
Currently translated at 72.7% (702 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-04-28 11:14:38 +02:00
alonso.torres
609ce1c106 🐛 Fix poblems with SVG transformations 2022-04-27 14:37:53 +02:00
alonso.torres
f7dbb4f944 Merge remote-tracking branch 'origin/staging' into develop 2022-04-27 12:24:16 +02:00
Andrey Antukh
5b2d1b310a Merge pull request #1845 from penpot/alotor-performance
Loading time improvement
2022-04-27 12:15:05 +02:00
Andrey Antukh
a7ded66eab Merge pull request #1846 from penpot/alotor-bugfixes
Fix focus mode problem
2022-04-27 11:59:28 +02:00
alonso.torres
74d195c745 🐛 Fix style issue with focus mode 2022-04-27 11:08:18 +02:00
alonso.torres
1705954b07 🐛 Fix problem with transforms 2022-04-27 09:17:35 +02:00
alonso.torres
71bb34efc5 Improved first load time 2022-04-27 09:17:35 +02:00
Alejandro
32d61eaf70 Merge pull request #1844 from penpot/superalex-fix-duplicate-artobard-without-guides
:bug Fix duplicate artboard without whithout guides
2022-04-27 06:42:02 +02:00
Alejandro Alonso
20badb7676 :bug Fix duplicate artboard without whithout guides 2022-04-26 17:37:10 +02:00
Andrey Antukh
b90a308d66 Merge remote-tracking branch 'origin/staging' into develop 2022-04-26 17:11:00 +02:00
Andrey Antukh
dbfa0e7a4b 🐛 Fix unexpected exception on workspace libraries modal 2022-04-26 17:08:02 +02:00
Andrey Antukh
95c73585d2 Merge pull request #1843 from penpot/remove-backend-only-devenv
🔥 Remove backend-only devenv container
2022-04-26 17:01:06 +02:00
Andrés Moya
c4939c152d 🔥 Remove backend-only devenv container
(disable requirement of using cors and secure cookies in devenv)
2022-04-26 16:47:14 +02:00
Pablo Alba
7560e32911 Merge pull request #1840 from penpot/alotor-improved-filter-layers
 Improved filter layers
2022-04-26 16:16:00 +02:00
alonso.torres
d50299bdbb Improved performance for layers filtering 2022-04-26 16:15:34 +02:00
Andrey Antukh
c34c1c4375 📎 Update docker files 2022-04-26 13:28:05 +02:00
Alejandro Alonso
b62f387ff4 :bug Fix blend modes are ignored in component updates 2022-04-26 09:57:28 +02:00
Alejandro Alonso
b3847cafa8 Merge remote-tracking branch 'origin/staging' into develop 2022-04-26 06:17:27 +02:00
Alejandro Alonso
d28b4092d9 🐛 Fix guides are not duplicated with the artboard 2022-04-25 17:43:39 +02:00
Pablo Alba
658e3b7aee 🐛 Fix mouse leave in handoff close overlay animation breaks 2022-04-25 17:20:24 +02:00
Eva Marco
d18c96360f Merge pull request #1836 from penpot/alotor-more-performance-changes
Alotor more performance changes
2022-04-25 15:32:14 +02:00
Alejandro
c83bb70074 Merge pull request #1834 from penpot/hirunatan-update-color-library
Synchronize library colors in all parts of a shape
2022-04-25 14:00:05 +02:00
Andrés Moya
02157cbeb9 🎉 Synchronize library colors in all parts of a shape 2022-04-25 12:18:51 +02:00
Andrés Moya
7581230b6e 🔧 Small refactor of sync helper 2022-04-25 12:18:51 +02:00
Andrey Antukh
049f4ce784 ♻️ Refactor persistence flow 2022-04-25 12:07:26 +02:00
Andrey Antukh
c01e4e52f8 ♻️ Reorganize workspace persistence related namespace 2022-04-25 12:07:26 +02:00
Andrey Antukh
3ab3ea68b4 📎 Change namespace alias naming on persistence ns 2022-04-25 12:07:26 +02:00
alonso.torres
41948ff86b 🐛 Changes after review 2022-04-25 11:41:05 +02:00
alonso.torres
01ca538c72 Debounce update indices event 2022-04-25 10:47:47 +02:00
alonso.torres
2b9badfd4e Debounce update position-data event 2022-04-25 10:47:47 +02:00
alonso.torres
6ad591eb23 🐛 Fix problem with export texts and fonts 2022-04-25 10:47:47 +02:00
alonso.torres
581c50b5ff Improved copy objects performance 2022-04-25 10:47:47 +02:00
Radek Sawicki
a18e067d7a 🌐 Add translations for: Polish.
Currently translated at 33.7% (326 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pl/
2022-04-23 21:14:43 +02:00
Locness
036fe44471 🌐 Add translations for: French.
Currently translated at 72.6% (701 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-04-23 21:14:41 +02:00
Andrey Antukh
b008835d43 Merge remote-tracking branch 'origin/staging' into develop 2022-04-22 14:50:04 +02:00
alonso.torres
fc95443cc4 Merge remote-tracking branch 'origin/staging' into develop 2022-04-22 14:46:22 +02:00
Andrey Antukh
9492dd7856 Merge branch 'main' into staging 2022-04-22 14:40:41 +02:00
Andrey Antukh
b239a9b09e Merge pull request #1819 from penpot/alotor-performance-improvements
Frames performance improvements
2022-04-22 14:20:27 +02:00
Andrey Antukh
e0aeb3b5ac 📎 Reduce default chunk size of the audit log archive task 2022-04-22 12:08:29 +02:00
Andrey Antukh
58cfd61997 🐛 Don't send url on file-media-upload 2022-04-22 12:08:29 +02:00
alonso.torres
a82bcd0ab2 🐛 Fixes after review 2022-04-22 11:33:40 +02:00
alonso.torres
dfc9d0709d 🐛 Fix problems with masks 2022-04-22 11:09:59 +02:00
alonso.torres
b7d33041e8 Improved performand for text editing 2022-04-22 11:09:59 +02:00
alonso.torres
f945a6e649 Changed thumbnails to webp format 2022-04-22 11:09:59 +02:00
alonso.torres
6a3a460203 Advanced frame thumbnail handling 2022-04-22 11:09:59 +02:00
alonso.torres
b576ef02af Performance improvements 2022-04-22 11:09:58 +02:00
Alejandro Alonso
814042909a 🐛 Import svg with exterior stroke 2022-04-22 11:06:59 +02:00
Alejandro Alonso
9856da4a1f 🐛 Fix black background while drawing a path 2022-04-22 11:05:01 +02:00
andy
2061018742 🌐 Added translation for: Polish. 2022-04-22 08:31:21 +02:00
Andrey Antukh
202e7eb3f2 Merge pull request #1823 from penpot/superalex-drop-shadow-not-working-on-fill-less-strokes
🐛 Fix drop shadow not working on fill-less strokes
2022-04-21 15:52:12 +02:00
Eva Marco
38deacdf31 Merge pull request #1826 from penpot/superalex-internal-error-when-hoverin-over-shape
🐛 Internal error when hoverin over shape
2022-04-21 13:31:37 +02:00
Alejandro Alonso
c809890cfd 🐛 Fix black background while drawing a path 2022-04-21 13:31:19 +02:00
Alejandro Alonso
224d466122 Fix internal error when hoverin over shape 2022-04-21 13:27:40 +02:00
Alejandro Alonso
08c6e9b702 🐛 Fix different behaviour during image drag 2022-04-21 12:13:12 +02:00
Andrey Antukh
9e940dc042 Improve dm/get-in macro to be fully compliant with core/get-in 2022-04-21 09:43:54 +02:00
Alejandro Alonso
6fda156164 🐛 Fix drop shadow not working on fill-less strokes 2022-04-21 07:16:48 +02:00
Andrey Antukh
5eb53da374 Merge pull request #1824 from penpot/alotor-fix-problem-with-texts
Fix problem with texts
2022-04-20 15:46:55 +02:00
alonso.torres
68e0b3e756 🐛 Fix problem with text and blank spaces 2022-04-20 14:16:51 +02:00
Alejandro Alonso
cfe374b08c 📎 Tag new minor release 2022-04-20 11:26:01 +02:00
alonso.torres
cc046555a3 🐛 Fix problem with zoom with wheel in Firefox 2022-04-20 10:40:07 +02:00
Andrey Antukh
31ec4092ed Improve logging performance
Delay the message building until it really needed to be
printed.
2022-04-20 10:03:04 +02:00
Andrey Antukh
d9d47b2c65 🐛 Fix missing key properties and react warnings 2022-04-20 10:03:04 +02:00
Andrey Antukh
506f63317a Merge pull request #1805 from penpot/hirunatan-set-html-theme
Hirunatan set html theme
2022-04-20 09:20:46 +02:00
Andrey Antukh
d658145450 Merge pull request #1813 from penpot/superalex-prototype-connection-handler-is-extremely-hard-to-use
🐛 Prototype connection handler is extremely hard to use
2022-04-20 09:19:35 +02:00
Andrey Antukh
b2d13f277a Merge pull request #1815 from penpot/superalex-bullet-colors-from-pasted-shapes-with-library-colors
🐛 Fix bullet colors from pasted shapes with library colors
2022-04-20 09:18:31 +02:00
Andrey Antukh
59310cdd71 Merge pull request #1822 from penpot/superalex-multiselected-elements-drag-problem-on-empty-areas
🐛 Multiselected elements drag problem on empty areas
2022-04-20 09:16:13 +02:00
Andrey Antukh
121b5af5d0 Merge pull request #1820 from penpot/palba-handoff-size-round-two-decimals
🐛 Round the size values on handoff to two decimals
2022-04-20 09:11:10 +02:00
Alejandro Alonso
1d69cb2580 Merge remote-tracking branch 'origin/staging' into develop 2022-04-20 06:31:19 +02:00
Pablo Alba
e68689aa4f 🐛 Round the size values on handoff to two decimals 2022-04-19 19:29:11 +02:00
Andrey Antukh
989ff8db7a Merge pull request #1796 from penpot/fixed-scroll
 Add fixed position in viewer
2022-04-19 14:54:24 +02:00
Andrés Moya
b68fdee946 Add fixed position in viewer 2022-04-19 14:41:19 +02:00
Alejandro Alonso
c8d3975680 🐛 Fix multiselected elements drag problem on empty areas 2022-04-19 14:20:42 +02:00
alonso.torres
b6f2800aa3 🐛 Fix pinch to zoom on mac 2022-04-19 13:22:50 +02:00
alonso.torres
a579ea3c25 🐛 Fix pinch to zoom on mac 2022-04-19 13:21:45 +02:00
Andrey Antukh
7b3ab2287a 🎉 Backport pprint module to common 2022-04-19 12:08:47 +02:00
Andrey Antukh
81df2ca355 Merge pull request #1794 from penpot/palba-group-assets-by-drag-drop
 Group assets by drag and drop
2022-04-19 10:30:29 +02:00
Andrey Antukh
b78d9dcc52 Merge pull request #1814 from penpot/alotor-backports
Backport 1.13.4
2022-04-19 08:52:29 +02:00
Andrey Antukh
caa81b4fe2 Merge pull request #1812 from penpot/release-1.12.4
Release 1.12.4
2022-04-19 08:52:15 +02:00
Alejandro Alonso
b9ab00c549 🐛 Fix bullet colors from pasted shapes with library colors 2022-04-19 07:33:55 +02:00
alonso.torres
2707903f8a 🐛 Fix start script in local environment 2022-04-18 19:04:24 +02:00
alonso.torres
28031a247a 🐛 Fix problem with ctrl+click context menu in mac 2022-04-18 19:03:25 +02:00
Pablo Alba
56cdd1ffeb Group assets by drag and drop 2022-04-18 17:36:20 +02:00
alonso.torres
175f4b57f5 🐛 Fix problem with ctrl+click context menu in mac 2022-04-18 16:41:35 +02:00
Andrey Antukh
2ae2877f45 Improve email console logging
And invitation console logging
2022-04-18 14:10:52 +02:00
Alejandro Alonso
5e7a609b3d 🐛 Fix prototype connection handler is extremely hard to use 2022-04-18 14:07:08 +02:00
alonso.torres
9ffe406d0d 🐛 Fix shift+2 shortcut in MacOS with non-english keyboards 2022-04-18 11:36:03 +02:00
alonso.torres
adfc0902a2 🐛 Fix problems with CTRL in MacOS 2022-04-18 11:36:03 +02:00
alonso.torres
620efcb5cb 🐛 Fix problem with copy/paste in Safari 2022-04-18 11:36:03 +02:00
alonso.torres
0ed23f94c7 🐛 Fix problems with trackpad zoom and scroll in MacOS 2022-04-18 11:36:03 +02:00
alonso.torres
1cac7d55d0 🐛 Fix crash on iOS when displaying viewer 2022-04-18 11:36:03 +02:00
Andrey Antukh
c9937f6b91 Merge remote-tracking branch 'origin/staging' into develop 2022-04-18 11:16:08 +02:00
alonso.torres
875fd78f73 🐛 Fix rounding problem with texts 2022-04-18 10:49:50 +02:00
Andrey Antukh
7e37aca5ee Merge remote-tracking branch 'origin/staging' into develop 2022-04-18 08:59:19 +02:00
Yaron Shahrabani
070886bbf6 🌐 Add translations for: Hebrew.
Currently translated at 100.0% (965 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2022-04-13 18:08:55 +02:00
nautilusx
c00168b61d 🌐 Add translations for: German.
Currently translated at 98.2% (948 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2022-04-13 18:08:54 +02:00
Oğuz Ersen
0e9119d603 🌐 Add translations for: Turkish.
Currently translated at 100.0% (965 of 965 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2022-04-13 18:08:54 +02:00
Andrés Moya
df39e9baf4 🌐 Disable italian for now 2022-04-11 16:00:15 +02:00
Andrés Moya
668aca725c 🌐 Update translations 2022-04-11 15:52:01 +02:00
Andrés Moya
c865082a6a Merge remote-tracking branch 'weblate/develop' into translations 2022-04-11 15:44:55 +02:00
Alejandro Alonso
82ae4e60f8 🐛 Texts with center align and fixed width are not shown 2022-04-11 15:28:09 +02:00
Alejandro Alonso
5fc27a7594 🐛 Blur not working 2022-04-11 14:03:55 +02:00
Andrés Moya
6ad06d9665 🎉 Show Penpot color in Safari tab bar 2022-04-11 12:51:24 +02:00
Alejandro Alonso
c766e08027 🐛 [LIBRARIES & TEMPLATES] Missing fills and texts 2022-04-11 12:45:37 +02:00
Andrey Antukh
62f55a47c5 ⬆️ Update okulary dependency 2022-04-11 01:05:06 +02:00
Eva Marco
b1edcba0c2 Merge pull request #1798 from penpot/palba-dashboard-import-file-name-hidden
Palba dashboard import file name hidden
2022-04-08 09:20:41 +02:00
Pablo Alba
f7d2f6ec51 🐛 Fix hidden file name on import 2022-04-08 09:13:43 +02:00
Andrey Antukh
3a95a1cea1 Merge pull request #1797 from penpot/palba-unnecessary-scrollbars-color-list
Palba unnecessary scrollbars color list
2022-04-08 00:12:35 +02:00
Andrey Antukh
4143573868 🐛 Fix okulary and tab component 2022-04-07 23:52:27 +02:00
Pablo Alba
26daf507b3 🐛 Fix unneccessary scrollbars at the color list 2022-04-07 22:15:28 +02:00
Eva
f2c0683803 Revert "🐛 Fix gap between contiguous shapes"
This reverts commit 39fa939f58.
2022-04-07 16:21:01 +02:00
andy
395f23dec8 🌐 Added translation for: Italian. 2022-04-07 11:34:14 +02:00
Andrey Antukh
58905f0b99 Merge remote-tracking branch 'origin/staging' into develop 2022-04-07 10:16:29 +02:00
Pablo Alba
aa2bb75f95 Merge pull request #1792 from penpot/niwinz-minor-enhancements
Enhancements
2022-04-07 10:10:40 +02:00
Pablo Alba
004fddfcf4 Merge pull request #1789 from penpot/superalex-show-in-exports-is-showing-in-multiselections
🐛 'Show in exports' is showing in multiselections
2022-04-06 13:58:21 +02:00
Andrés Moya
a61301c698 🐛 Fix call to exporter and exporter setup in devenv 2022-04-06 12:54:05 +02:00
Andrey Antukh
b2607b28ff 🎉 Add build date and changelog to the bundle 2022-04-06 11:20:48 +02:00
Andrey Antukh
c2c01831fb Merge pull request #1791 from penpot/alotor-bug-fixing
Bug fixes
2022-04-06 10:49:21 +02:00
Eva Marco
bf70719899 Merge pull request #1787 from penpot/superalex-fix-selected-colors-doesnt-work-for-shadows
🐛 Selected colors doesn't work for shadows
2022-04-06 10:39:11 +02:00
alonso.torres
ea38d12a73 🐛 Fix problem with exported text 2022-04-06 10:08:35 +02:00
alonso.torres
76abd6796e 🐛 Fix import problems 2022-04-06 10:08:35 +02:00
alonso.torres
0bb20197f1 Improved performance of refs 2022-04-06 10:08:35 +02:00
Andrey Antukh
2af057a79f ⬆️ Update backend and docker dependencies 2022-04-06 09:54:40 +02:00
Andrey Antukh
fd9b442075 Improve email console logging
And invitation console logging
2022-04-06 09:40:20 +02:00
Alejandro Alonso
5edbebcfec 🐛 'Show in exports' is showing in multiselections 2022-04-06 09:37:12 +02:00
Andrey Antukh
e62f0603b5 Merge pull request #1788 from penpot/hirunatan-fix-multi-user
Hirunatan fix multi user
2022-04-06 09:20:27 +02:00
Andrés Moya
654e12a2c3 🐛 Fix multi user not working 2022-04-06 09:16:22 +02:00
Alejandro Alonso
5299465864 🐛 Setting in-progress to false when export fails 2022-04-06 08:28:57 +02:00
Alejandro Alonso
18855ef2ef 🐛 Selected colors doesn't work for shadows 2022-04-06 08:05:58 +02:00
Eva
39fa939f58 🐛 Fix gap between contiguous shapes 2022-04-05 13:53:03 +02:00
Andrey Antukh
4adc5d25a7 📎 Fix review issues 2022-04-05 13:23:39 +02:00
Andrey Antukh
7a38b08506 🐛 Fix default configuration 2022-04-05 13:23:39 +02:00
Andrey Antukh
df4b92fb6b Improve logging ordering of message parts 2022-04-05 13:23:39 +02:00
Andrey Antukh
ca02999ae9 Improve error reporting 2022-04-05 13:23:39 +02:00
Andrey Antukh
701a98fab6 Improve backend and worker error handling 2022-04-05 13:23:39 +02:00
Andrey Antukh
c026d05bc3 Set consistent max body size
And make it configurable
2022-04-05 13:23:39 +02:00
Andrey Antukh
602b736163 📎 Update default scripts 2022-04-05 13:23:39 +02:00
Andrey Antukh
c5b1b67c50 📎 Add TODO comment on changes ns 2022-04-05 13:23:39 +02:00
Andrey Antukh
8eae892983 🔥 Remove old and already deprecated utils.data ns 2022-04-05 13:23:39 +02:00
Andrey Antukh
7d32d03156 💄 Add cosmetic changes on workspace/changes ns 2022-04-05 13:23:39 +02:00
Andrey Antukh
f9e83f2cc7 Improve implementation of without-keys helper 2022-04-05 13:23:39 +02:00
Andrey Antukh
20d3251a93 🎉 Add generic file object thumbnail abstraction
As replacement to the file frame thumbnail mechanism
2022-04-05 13:23:39 +02:00
Andrey Antukh
147f56749e ⬆️ Update some dependencies 2022-04-05 13:23:39 +02:00
Andrey Antukh
9140fc71b9 ♻️ Refactor exportation process, make it considerably faster 2022-04-05 13:23:39 +02:00
alonso.torres
d6abd2202c 🐛 Revert pixel grid color change 2022-04-05 13:04:44 +02:00
Alejandro Alonso
911d4edb9f 🐛 Import a file with image background won't show the background 2022-04-05 12:09:06 +02:00
Andrey Antukh
e9e5b07bdb Merge pull request #1782 from penpot/superalex-fix-edit-file-name-navigates-to-the-file-workspace
🐛 Fix edit file name navigates to the file workspace
2022-04-05 11:16:18 +02:00
Alejandro Alonso
cef1c0d1d1 🐛 Edit file name navigates to the file workspace 2022-04-05 11:15:51 +02:00
Andrey Antukh
0fb54a5edd Merge pull request #1777 from penpot/eva-fix_scroll_into_view
🐛 fix scroll into view behind fixed Element
2022-04-05 11:13:39 +02:00
Eva
abd7a88ba0 🐛 Fix scroll into view behing fixed element 2022-04-05 11:03:04 +02:00
Andrey Antukh
d37457dc10 Merge pull request #1783 from penpot/eva-fix-sidebar-icon-in-viewer
🐛 Fix sidebar icon in viewer mode
2022-04-05 10:56:46 +02:00
Eva
fc7707ad3e 🐛 Fix sidebar icon in viewer mode 2022-04-05 10:35:26 +02:00
Andrés Moya
f43c6ab3c5 🐛 Fix resize for rotated shapes with top&down constraints 2022-04-05 09:58:04 +02:00
Andrey Antukh
8ae05ff7b6 🐛 Fix issue with password persistence 2022-04-04 23:55:05 +02:00
Andrey Antukh
11c3b6cfe2 🐛 Fix issue with password persistence 2022-04-04 23:54:54 +02:00
Andrey Antukh
b4a997cde9 🐛 Fix issue with password persistence 2022-04-04 23:46:42 +02:00
Andrey Antukh
9e4650cbb6 Merge remote-tracking branch 'origin/staging' into develop 2022-04-04 23:18:29 +02:00
Andrey Antukh
7105255212 Merge branch 'us/newsletter_subscription' into staging 2022-04-04 23:12:03 +02:00
Andrey Antukh
1338491616 Make the subscription modal configurable 2022-04-04 23:10:41 +02:00
Andrey Antukh
0afb47ade0 Update telemetry task for handle user subscriptions 2022-04-04 22:57:27 +02:00
Andrey Antukh
88292f2f3b Properly initialize options and profile forms 2022-04-04 22:57:27 +02:00
Andrey Antukh
d389dab8d2 Mark form as touched on changing the checkbox or radio buttons 2022-04-04 22:57:27 +02:00
Andrey Antukh
1205bdcaae Make the update-profile operation atomic with prop update 2022-04-04 22:57:27 +02:00
Eva
5e7e055539 🎉 Add newsletter subscription modal 2022-04-04 22:57:27 +02:00
Eva
3822be76a8 🐛 Fix send to back several shapes at a time 2022-04-04 17:44:50 +02:00
Eva Marco
b904237c5a Merge pull request #1773 from penpot/eva-fix_artboard_fills
🐛 Fix add fill to artboard modify children
2022-04-04 16:58:12 +02:00
Eva
df930cb879 🐛 Fix add fill to artboard modify children 2022-04-04 16:54:35 +02:00
Alejandro Alonso
327331475e 🐛 Hide the drop shadow also hides the shape 2022-04-04 16:39:17 +02:00
Eva
91a8386ba4 🐛 Fix duplicate multiselected elements 2022-04-04 16:24:50 +02:00
Andrés Moya
b7e0619e9a 🐛 Fix order of undo operations 2022-04-04 14:05:01 +02:00
Oğuz Ersen
3b75d9b362 🌐 Add translations for: Turkish.
Currently translated at 100.0% (949 of 949 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2022-04-04 13:11:35 +02:00
Andrey Antukh
0b984a44d7 🐛 Fix default configuration 2022-04-04 10:54:40 +02:00
Andrey Antukh
c9ddc83eef Merge remote-tracking branch 'origin/staging' into develop 2022-04-01 11:58:07 +02:00
Alejandro
b2b221516c Merge pull request #1768 from penpot/alotor/bugfixes
Bugfixing
2022-04-01 11:06:59 +02:00
bingling_sama
5170634b90 🌐 Add translations for: Chinese (Simplified).
Currently translated at 93.8% (891 of 949 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2022-04-01 09:10:00 +02:00
Oğuz Ersen
01c92c04cf 🌐 Add translations for: Turkish.
Currently translated at 99.7% (947 of 949 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2022-04-01 09:10:00 +02:00
Andrés Moya
1bcb0128f0 🐛 Fix paste shapes while editing text 2022-03-31 14:35:33 +02:00
alonso.torres
5633291ab0 🐛 Fix problem when alt+drag duplicate frames 2022-03-31 12:44:56 +02:00
alonso.torres
785ae01a51 🐛 Fix problem rendering some SVG filters 2022-03-31 11:21:15 +02:00
alonso.torres
34fd9d0d88 🐛 Fix problem with fonts in viewer 2022-03-31 11:18:28 +02:00
alonso.torres
9f19676dc2 🐛 Fix problem with wheel-zoom on an editing text 2022-03-31 11:18:28 +02:00
alonso.torres
4a3fb55b30 🐛 Fix issue with drag-select shapes 2022-03-31 11:11:44 +02:00
alonso.torres
eaa6327663 🐛 Fix issue with drag-select shapes 2022-03-31 11:06:19 +02:00
Andrey Antukh
13ca506015 Improve migrate-data function (file data migrations)
This will enable the ability to apply some migration to a specific
file from the Server REPL.
2022-03-31 10:40:15 +02:00
Andrey Antukh
59d0bafdc9 📎 Add analyze-file helper to srepl.main namespace 2022-03-31 10:40:15 +02:00
Andrey Antukh
cee85942e6 📎 Set explicit clojure version on frontend and backend 2022-03-31 10:40:15 +02:00
Andrey Antukh
f303d3c45d 🐛 Fix wrong type hints 2022-03-31 10:40:15 +02:00
Andrey Antukh
6f7f74f7c6 🐛 Add migrations to fix wrongly migrated data
Also port the migration introduced in main branch
for the recent hotfix
2022-03-31 10:40:15 +02:00
Alejandro Alonso
ba398569c1 🐛 Fix shapes with no fill 2022-03-31 08:13:46 +02:00
Eva Marco
a8a47dca8f Merge pull request #1760 from penpot/fix-name-component
Fix name component
2022-03-30 16:51:42 +02:00
Andrés Moya
f782a7027a 🐛 Fix error when deleting all children of a nested group 2022-03-30 16:46:29 +02:00
Andrés Moya
a434318535 🐛 Fix show component name in sidebar 2022-03-30 16:39:47 +02:00
Eva
134265094c 🐛 Avoid numeric inputs to allow big numbers 2022-03-30 16:35:36 +02:00
Eva
4909e7861f 🐛 FIx the context menu of component widget 2022-03-30 16:35:36 +02:00
Andrey Antukh
ad9a7fdce8 📎 Set explicit clojure version on frontend and backend 2022-03-30 15:10:28 +02:00
Andrés Moya
97e97d0984 🐛 Fix undo after rotating a group 2022-03-30 15:07:56 +02:00
Andrey Antukh
4c6433b0f1 Improve migration 14
Remove frame thumbnail if the migration modifies a shape.
2022-03-30 14:38:36 +02:00
Andrey Antukh
f0d956f71c 📎 Update version.txt file 2022-03-30 13:43:46 +02:00
Alejandro Alonso
3a9d348cab 🐛 Add shadow to artboard make it lose the fill 2022-03-30 13:35:52 +02:00
alonso.torres
586bd13cc2 🐛 Fix issue with shift+select to deselect shapes 2022-03-30 13:28:25 +02:00
alonso.torres
e601e2acca 🐛 Fix linter problem 2022-03-30 13:23:59 +02:00
Alejandro Alonso
2a3c0e11da 🐛 Fixing export styles prettier 2022-03-30 13:13:29 +02:00
alonso.torres
bee40ae35c 🐛 Fix issue with shift+select to deselect shapes 2022-03-30 13:06:54 +02:00
Andrey Antukh
0392a1649f 🐛 Remove default fill-color and fill-opacity on image shapes 2022-03-30 12:27:30 +02:00
Andrey Antukh
3cb15df08d Merge remote-tracking branch 'origin/staging' into develop 2022-03-30 11:50:03 +02:00
Alejandro Alonso
d4b52ad4f1 🐛 Fixing export styles 2022-03-29 18:25:11 +02:00
Alejandro Alonso
91249bc892 🐛 Weird stroke behaviour on duplicate 2022-03-29 16:27:33 +02:00
Eva
87f5efeadb Add Selected colors menu 2022-03-29 11:56:18 +02:00
Eva
369eab3b5f 🐛 Avoid rotating shape when scrolling 2022-03-29 10:56:17 +02:00
alonso.torres
6780d17d2e 🐛 Fix drag guides to delete target area 2022-03-29 09:55:38 +02:00
alonso.torres
af22fee0c1 🐛 Fix problem with boolean and children objects 2022-03-29 09:55:38 +02:00
alonso.torres
61c111d5ae 🐛 Some fixes to SVG imports 2022-03-29 09:55:38 +02:00
Rodolfo Carvalho
6897c0c3fe Remove duplicate require of clojure.test 2022-03-29 09:54:58 +02:00
Andrey Antukh
4010fb7d1e Merge remote-tracking branch 'origin/staging' into develop 2022-03-28 20:27:19 +02:00
Eva
3301148da6 🐛 Fix comments modal remains open on page change 2022-03-28 17:31:53 +02:00
Eva
09c57bdb86 🐛 Fix comments modal remains open on page change 2022-03-28 17:26:20 +02:00
Andrey Antukh
9ce0497f00 Add proper error handlings on http middleware 2022-03-28 17:24:52 +02:00
Andrey Antukh
36027583cd 📎 Minor change on create team instrumentation 2022-03-28 17:24:52 +02:00
Andrey Antukh
9abf4b126c Improve error handling 2022-03-28 17:24:52 +02:00
Andrey Antukh
ec5a4d09b8 🐛 Fix possible issue that causes exception on node tests 2022-03-28 17:24:52 +02:00
Andrey Antukh
2832736826 🎉 Add garbage collection task for file thumbnails
And additionally, rename the current task to file-gc
to match the real purpose of the task.
2022-03-28 17:24:52 +02:00
Andrey Antukh
b87e3c22b3 Improve worker error handling
Use the global error handlers for handle
also the worker errors.
2022-03-28 17:24:52 +02:00
Andrey Antukh
9582cc0211 🔥 Remove unused code 2022-03-28 17:24:52 +02:00
Andrey Antukh
1943877b21 Simplify d/group-by impl 2022-03-28 17:24:52 +02:00
Andrey Antukh
c876534c85 Move the dashboard grid thumbnails to backend cache 2022-03-28 17:24:52 +02:00
Andrey Antukh
b91c42e186 Add performance improvements to file thumbnails
Mainly addresing unnecesary object transmission. The new code strips
unnecesary data to be transferred from back to front.

Additionally it removes some legacy code and simplifies other
parts of code.
2022-03-28 17:24:52 +02:00
Alejandro Alonso
27c8f883ff 🐛 Fix ctrl-click on assets 2022-03-28 09:16:38 +02:00
Yaron Shahrabani
d28bbdaaf7 🌐 Add translations for: Hebrew.
Currently translated at 99.8% (948 of 949 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2022-03-28 01:09:43 +02:00
Alejandro Alonso
5817b5fe19 🐛 Fix completed export text not shown 2022-03-25 14:50:13 +01:00
Alejandro Alonso
1db9b04bfd 🐛 Fix error when adding gradient stroke to shape 2022-03-25 14:49:42 +01:00
Andrey Antukh
00d851998b Merge pull request #1744 from penpot/multiexport-checkbox-fixes
🐛 Fix export multiple styles
2022-03-25 14:48:41 +01:00
Alejandro Alonso
927dbbfe82 🐛 Fix precission on export modal 2022-03-25 13:37:38 +01:00
Alejandro Alonso
d73ed95719 🐛 Fix export multiple styles 2022-03-25 13:20:46 +01:00
alonso.torres
01194d5e25 Add dashboard to shortcuts 2022-03-25 12:18:33 +01:00
alonso.torres
32d31da0da Show shortcuts debugging command 2022-03-25 12:00:58 +01:00
Alejandro Alonso
655afa088d 🐛 Fix copy paste inside a text layer leaves pasted text transparent 2022-03-25 10:08:41 +01:00
Andrey Antukh
0355e1bfc7 Merge branch 'alotor/bugfixes' into staging 2022-03-25 09:33:03 +01:00
Andrey Antukh
a44f1df0d4 Merge pull request #1728 from penpot/alotor/bugfixes
Safari/MacOS Fixes
2022-03-25 09:30:33 +01:00
Yaron Shahrabani
3e745ff45d 🌐 Add translations for: Hebrew.
Currently translated at 99.4% (944 of 949 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2022-03-25 08:03:09 +01:00
bingling_sama
bc87e3d6d0 🌐 Add translations for: Chinese (Simplified).
Currently translated at 80.6% (765 of 949 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2022-03-25 08:03:08 +01:00
alonso.torres
5aa68c7052 🐛 Fix problem with text displacement in Safari 2022-03-24 18:03:14 +01:00
alonso.torres
6e36f66dde 🐛 Fix shift+2 shortcut in MacOS with non-english keyboards 2022-03-24 18:03:14 +01:00
alonso.torres
32e4569495 🐛 Fix problems with CTRL in MacOS 2022-03-24 18:03:14 +01:00
alonso.torres
5a591d2acd 🐛 Fix paste ordering for frames not being respected 2022-03-24 17:25:43 +01:00
alonso.torres
e8980fbbfe 🐛 Fix problem with copy/paste in Safari 2022-03-24 17:25:43 +01:00
alonso.torres
8e68781a1b 🐛 Fix problems with trackpad zoom and scroll in MacOS 2022-03-24 17:25:43 +01:00
alonso.torres
ad19d64ce8 🐛 Fix problem with localhost register in Safari 2022-03-24 17:25:43 +01:00
Andrey Antukh
c1a67c0097 Merge remote-tracking branch 'origin/staging' into develop 2022-03-24 17:03:43 +01:00
Andrey Antukh
5ed84e3ae5 🐛 Set proper extension on download exported asset 2022-03-24 17:02:38 +01:00
Pablo Alba
5264863863 🐛 Fix enter on empty search page 2022-03-24 16:36:49 +01:00
Andrey Antukh
9c5c2ac8bf Merge pull request #1725 from penpot/multiexport-fixes
🐛 Multiexport fixes
2022-03-24 16:36:01 +01:00
Alejandro Alonso
1bbcf67396 🐛 Fix paths with no fill 2022-03-24 16:35:18 +01:00
Andrey Antukh
8b44b4d8f1 🐛 Fix unexpected decoding of fresian data 2022-03-24 15:15:42 +01:00
Andrey Antukh
4ef9d4d5f6 🐛 Fix unexpected decoding of fresian data 2022-03-24 15:14:43 +01:00
alonso.torres
ea7266dc3b 🐛 Fix performance problem with new texts 2022-03-24 13:50:08 +01:00
Alejandro Alonso
effb76c8db 🐛 Fix export multiple styles 2022-03-24 12:38:31 +01:00
Alejandro Alonso
2d52c4f4f5 🐛 Fix export translation 2022-03-24 12:19:06 +01:00
Andrey Antukh
4ed093f28f Merge remote-tracking branch 'origin/staging' into develop 2022-03-24 11:41:33 +01:00
Alejandro Alonso
a753037178 🐛 Fix migration of fills and strokes for components 2022-03-24 11:39:01 +01:00
Alejandro Alonso
0d449f1292 🐛 Fix constraints assignation on multi-selection 2022-03-23 16:21:54 +01:00
Andrés Moya
2e3addc6da 🎉 Add more unit tests 2022-03-23 15:44:55 +01:00
Andrey Antukh
a0762aca45 🐛 Fix pdf print on exporter 2022-03-23 14:46:04 +01:00
Andrey Antukh
80549bda9b Merge remote-tracking branch 'origin/staging' into develop 2022-03-23 14:16:15 +01:00
Andrey Antukh
88ad68069c 📚 Update contributing file 2022-03-23 14:16:03 +01:00
Alejandro Alonso
80ef69c710 🐛 Fix sorting on multiple export 2022-03-23 14:08:33 +01:00
Andrey Antukh
1d5d597103 📎 Set correct version on version.txt file 2022-03-23 13:24:08 +01:00
Andrey Antukh
6b164e10f2 📎 Update version.txt file 2022-03-23 13:23:16 +01:00
Andrey Antukh
b3d70f2556 🐛 Fix many issues related to exportation process 2022-03-23 13:21:52 +01:00
Pablo Alba
8fa708d573 Merge pull request #1715 from penpot/add-translations-terms-privacy
🐛 Translations missing on login/register for 'Terms of service an…
2022-03-23 13:20:18 +01:00
Pablo Alba
a68612ca2b 🐛 Translations missing on login/register for 'Terms of service and Privacy policy' 2022-03-23 13:10:53 +01:00
Alejandro
7d483b36d0 Merge pull request #1713 from penpot/keep-pencil-cursor
🐛 Pencil cursor changes when activated
2022-03-23 11:47:18 +01:00
Pablo Alba
61e409a09e 🐛 Pencil cursor changes when activated 2022-03-23 11:40:29 +01:00
Alejandro
5564d93d59 Merge pull request #1712 from penpot/revert-not-allow-edits-on-prototype-mode
🐛 Revert d2590c7: 🐛 [Prototype] Prototype mode should not all…
2022-03-23 11:22:49 +01:00
Pablo Alba
6674135c74 🐛 Revert d2590c7: 🐛 [Prototype] Prototype mode should not allow edits 2022-03-22 19:21:04 +01:00
Andrey Antukh
a4fbc050cc Merge remote-tracking branch 'origin/staging' into develop 2022-03-22 15:01:43 +01:00
Andrey Antukh
205b6d9881 Merge pull request #1708 from penpot/alotor/bugfixes
Alotor/bugfixes
2022-03-22 15:01:30 +01:00
alonso.torres
f2d1a4190a Don't stop SVG import when an image cannot be imported 2022-03-22 15:01:16 +01:00
alonso.torres
6008dc12d3 🐛 Fix clickable area in layers 2022-03-22 15:01:16 +01:00
alonso.torres
118b4367e7 🐛 Parametrized render to embed objects. Fix problem with fonts when exporting to SVG 2022-03-22 15:01:16 +01:00
alonso.torres
e6f8269c0b 🐛 Fix problem with inconsistency with border-radius 2022-03-22 15:01:16 +01:00
alonso.torres
928128ba2d 🐛 Fix problem when changing page while editing text 2022-03-22 15:01:16 +01:00
alonso.torres
444567faac 🐛 Fix problem when importing SVG's with uses with overriding properties 2022-03-22 15:01:16 +01:00
alonso.torres
eaa6ea80e6 🐛 Fix problem when adding shadows to imported text 2022-03-22 15:01:16 +01:00
alonso.torres
a4d362d43d 🐛 Fix problem when importing a SVG with text 2022-03-22 15:01:16 +01:00
alonso.torres
89e2f4a481 🐛 Fix crash on iOS when displaying viewer 2022-03-22 15:01:16 +01:00
Andrey Antukh
8acc9af1f5 📎 Add more events instrumentation 2022-03-22 14:48:10 +01:00
Andrey Antukh
0ebc1a766e Merge remote-tracking branch 'origin/staging' into develop 2022-03-22 14:34:25 +01:00
Andrey Antukh
bf6211903c 🐛 Fix issue on logging (backend) 2022-03-22 14:34:00 +01:00
Andrey Antukh
ad262f6fb3 Merge remote-tracking branch 'origin/library-changes-builder' into staging 2022-03-22 13:14:53 +01:00
Andrey Antukh
0a7d1831d2 Merge pull request #1701 from penpot/library-changes-builder
Library changes builder
2022-03-22 13:13:25 +01:00
Andrés Moya
ca56e08459 🎉 Add more test cases, and some fixes 2022-03-22 13:12:19 +01:00
Andrés Moya
31bfe3930d Prepare debug functions to be used in unit tests 2022-03-22 13:12:19 +01:00
Andrés Moya
48624b1db6 🔧 Refactor frontend unit tests and some fixes 2022-03-22 13:12:19 +01:00
Andrés Moya
5a33a002e4 🔧 Use changes-builder in library synchronization module 2022-03-22 13:12:19 +01:00
Andrey Antukh
43d3cc36e9 📎 Start new development cycle 2022-03-22 12:59:34 +01:00
Andrey Antukh
ee813abdc1 📎 Update changelog file 2022-03-22 12:58:33 +01:00
Andrey Antukh
411acc0a2f 📎 Sort translation files 2022-03-22 12:54:11 +01:00
Andrey Antukh
28cd649db3 Merge remote-tracking branch 'weblate/develop' into translations 2022-03-22 12:53:31 +01:00
bingling_sama
94f2269ff2 🌐 Add translations for: Chinese (Simplified).
Currently translated at 79.0% (714 of 903 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2022-03-22 12:53:14 +01:00
Andrey Antukh
c106b74239 Merge remote-tracking branch 'weblate/develop' into translations 2022-03-22 12:52:43 +01:00
Alejandro Alonso
3ae7c42afa Exporting big files flow 2022-03-22 12:31:34 +01:00
Andrey Antukh
0d4de50f13 📎 Minor fix on docker image files 2022-03-22 11:47:18 +01:00
Andrey Antukh
d4c1e2fc36 📎 Minor cosmetic fixes 2022-03-22 11:34:32 +01:00
Andrey Antukh
903a9356a9 🐛 Fix many issues after PR review 2022-03-22 11:34:32 +01:00
Alejandro Alonso
2f6018c35c 📎 Update changelog 2022-03-22 11:34:32 +01:00
Alejandro Alonso
0e0fb68c38 🎉 Add assets exportation in bulk (multiple)
And adapt to the websocket changes on backend and
exporter.
2022-03-22 11:34:32 +01:00
Andrey Antukh
f60d8c6c96 ♻️ Refactor websockets subsystem (on backend)
- Refactor msgbus subsystem, simplifying many parts.
- Enable persistent websocket connection for the all session duration.
2022-03-22 11:34:32 +01:00
Andrey Antukh
4a9e38a221 ♻️ Refactor exporter
- Migrate from puppeteer to playwright
- Fix many lifecycle and resource usage issues
- Add redis integration
- Enable multiple exportation
- Enable asynchronos exportation (with progress reporting)
2022-03-22 11:34:32 +01:00
Pablo Alba
f0a9889f33 🐛 Remove a decimal sets value to 0 (refactor) 2022-03-22 10:07:32 +01:00
Alejandro
aa386e12bc Merge pull request #1705 from penpot/fix/minus_placement
🐛 fix alignement of icon
2022-03-22 10:02:41 +01:00
Eva
ba46ab7361 🐛 fix alignement of icon 2022-03-22 09:51:52 +01:00
Alejandro
5ce3ce06c6 Merge pull request #1704 from penpot/fix/scroll_comments
🐛 Fix scroll in comment section
2022-03-22 09:49:48 +01:00
Eva
e95d940b5d 🐛 Fix scroll in comment section 2022-03-22 09:36:19 +01:00
Pablo Alba
14ed83fb31 🐛 Remove a decimal sets value to 0 2022-03-21 21:41:32 +01:00
Ahmad HosseinBor
497d42b822 🌐 Add translations for: Persian.
Currently translated at 22.7% (205 of 903 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2022-03-21 14:56:12 +01:00
Pablo Alba
3bae4839bd Search and filter layers 2022-03-21 11:21:12 +01:00
Andrey Antukh
81adcd03fb Minor fixes on devenv dockerfile 2022-03-20 13:37:37 +01:00
Andrey Antukh
7f3c67724e 🐛 Fix svg media asset upload internal server error 2022-03-20 13:04:12 +01:00
Andrey Antukh
741ad29d82 🎉 Add missing rlimit metadata and configuration 2022-03-18 17:12:12 +01:00
Ahmad HosseinBor
374de57e15 🌐 Add translations for: Persian.
Currently translated at 21.4% (194 of 903 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2022-03-18 16:58:06 +01:00
Andrey Antukh
ff30d505af ⬆️ Update CircleCI config 2022-03-18 15:16:08 +01:00
Alejandro Alonso
d4dc32a5e5 🐛 Go to style library file to edit in a new tab 2022-03-18 13:20:30 +01:00
Alejandro Alonso
c073a66e7e 🐛 Inner shadow with border not working properly 2022-03-18 10:55:55 +01:00
Andrés Moya
4d2de63374 Merge pull request #1690 from penpot/feat/pixel-precision
Pixel precision
2022-03-18 10:49:01 +01:00
Andrey Antukh
fa33c5852c Add missing rlimits on team and profile rpc mutations 2022-03-18 09:59:10 +01:00
Eva
510d9ab4d8 🐛 Fix overflow in color picker 2022-03-18 09:09:34 +01:00
alonso.torres
4f07613154 After review changes 2022-03-17 14:53:21 +01:00
alonso.torres
d2b5283489 🐛 Revert debugging text utilities 2022-03-16 17:52:38 +01:00
alonso.torres
aec68c52ab Improved snap to grids 2022-03-16 17:46:38 +01:00
alonso.torres
b5e965cf1a Improved behaviour for horizontal/vertical lines 2022-03-16 17:46:38 +01:00
alonso.torres
640723a4e7 Improved options input 2022-03-16 17:46:38 +01:00
alonso.torres
ccca3a38f0 🐛 Fix problem with multiple values in inputs 2022-03-16 17:46:38 +01:00
alonso.torres
9b862b672f Show pixel grid 2022-03-16 17:46:38 +01:00
alonso.torres
ad4c1aae45 🐛 Fix problem with flip rotations 2022-03-16 17:46:38 +01:00
alonso.torres
099d1259b2 Pixel/half-pixel on path drawing 2022-03-16 17:46:38 +01:00
alonso.torres
e5206e65e7 Pixel precision on modifiers 2022-03-16 17:46:38 +01:00
alonso.torres
9332d6f36c Improved resize/rotation handlers for shapes with tiny height/width 2022-03-16 17:46:38 +01:00
alonso.torres
f4be3aa9de Improvements over selrect generation 2022-03-16 17:46:38 +01:00
alonso.torres
0f54e85b36 ♻️ Refactor selrec generation 2022-03-16 17:46:38 +01:00
alonso.torres
ed9400912c Fix problems with extreme values 2022-03-16 17:46:38 +01:00
Alejandro Alonso
999af63118 🐛 Fixing dbg file upload with new http implementation 2022-03-16 13:07:01 +01:00
Alejandro
b0e2200166 Merge pull request #1686 from penpot/artboard-fixed
 Set the artboard layer fixed at the top side of the layers
2022-03-15 11:37:20 +01:00
alonso.torres
43d4acc94b 🐛 Fix linter issue 2022-03-15 11:27:12 +01:00
alonso.torres
7a253dc9e4 🐛 Fix problem with thumbnails not working 2022-03-15 11:17:06 +01:00
andy
b587f88968 🌐 Added translation for: Persian. 2022-03-15 10:29:41 +01:00
alonso.torres
491748af9f 🐛 Fix problem with import old files 2022-03-15 09:46:17 +01:00
alonso.torres
10e981d034 🐛 Fix problem with strokes and texts 2022-03-14 17:21:26 +01:00
Andrey Antukh
e188ae732a Merge remote-tracking branch 'origin/main' into develop 2022-03-14 14:34:58 +01:00
Andrey Antukh
7e8d8eef5a 🐛 Fix minor issues on event instumentation module 2022-03-14 13:56:32 +01:00
Andrey Antukh
e6d6b60b63 🐛 Properly filter complex data on events payload 2022-03-14 12:39:37 +01:00
Eva
70beb6c60c 🐛 Add ellipsis in long page names 2022-03-14 12:39:27 +01:00
alonso.torres
1990722f18 Merge remote-tracking branch 'origin/main' into develop 2022-03-14 12:17:06 +01:00
alonso.torres
aa416a782d 🐛 Fix problem with handlers over rules 2022-03-14 10:23:13 +01:00
Pablo Alba
7f2d5f4d69 Set the artboard layer fixed at the top side of the layers 2022-03-14 09:54:08 +01:00
Rodion Borisov
4fa6d37d6f 🌐 Add translations for: Russian.
Currently translated at 61.7% (558 of 903 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/
2022-03-13 00:56:28 +01:00
Rubén
b061844530 🌐 Add translations for: Catalan.
Currently translated at 99.4% (898 of 903 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ca/
2022-03-13 00:56:26 +01:00
Andrey Antukh
5add196d88 🐛 Don't instrument events with complex data 2022-03-11 18:11:59 +01:00
Andrey Antukh
1e580638d2 Merge pull request #1656 from penpot/social-logins-redesign
Authentication page and OIDC flows improvements
2022-03-11 17:22:03 +01:00
Andrey Antukh
f33d6610e7 📎 Properly log error on audit archive task fail 2022-03-11 16:21:11 +01:00
alonso.torres
a592f37593 Merge remote-tracking branch 'origin/main' into develop 2022-03-11 16:18:15 +01:00
Andrey Antukh
51dd869874 Merge pull request #1682 from penpot/alotor/hotfixes
Hotfixes
2022-03-11 15:56:45 +01:00
alonso.torres
5347409804 🐛 Fix problem with shift+ctrl+click to select 2022-03-11 15:38:48 +01:00
alonso.torres
aa6f82c31f 🐛 Fix issue with guides over shape handlers 2022-03-11 15:38:48 +01:00
Andrey Antukh
d9bd63d34f 📎 Reduce audit log archive task chunk size 2022-03-11 15:14:40 +01:00
Andrey Antukh
a8f5604718 📎 Improve http server configuration 2022-03-11 15:01:49 +01:00
Andrey Antukh
cf4f999b6a 📎 Improve api ergonomy of http server module 2022-03-11 09:50:49 +01:00
Andrey Antukh
52029f83ef 📎 Disable by default terms and privacy links
And make them configurable
2022-03-10 18:26:00 +01:00
Andrey Antukh
0c9a06789a 📎 Add correct copys and icons to login page 2022-03-10 17:45:20 +01:00
Alejandro
5709d2e757 Merge pull request #1677 from penpot/fix-select-color-for-stroke-from-palette
🐛 Fixing select color for stroke from palette
2022-03-10 17:13:02 +01:00
Andrey Antukh
11a0e01f08 Merge pull request #1670 from penpot/more-changes-builder
More changes builder
2022-03-10 16:50:55 +01:00
Alejandro Alonso
553c0e6d6a 🐛 Fixing select color for stroke from palette 2022-03-10 16:34:55 +01:00
Andrés Moya
7b81bb3fc2 💄 Change some code styles 2022-03-10 16:12:22 +01:00
Andrés Moya
e609670a41 🔧 Use changes-builder in many places 2022-03-10 15:37:10 +01:00
Andrés Moya
a7b455fb9a 🔧 Use changes-builder in workspace common operations 2022-03-10 15:21:58 +01:00
Andrés Moya
8ed857b4b9 🔧 Move :reg-objects operation to frontend 2022-03-10 15:21:58 +01:00
Eva
2bb8c535bd 🐛 Fix palette selection in color picker 2022-03-10 14:40:37 +01:00
Eva
e09884af60 🐛 Add ellipsis in long page names 2022-03-10 14:02:47 +01:00
Andrey Antukh
57399aeab2 🎉 Add the ability to specify email attr on oidc integration 2022-03-10 13:35:23 +01:00
Andrey Antukh
33c3e86e66 Add tests and improve impl of registration with invitation 2022-03-10 13:32:06 +01:00
Andrey Antukh
a7e77c3ea6 Minor fixes on login and register page structure 2022-03-10 13:32:06 +01:00
Andrey Antukh
2d76364b09 Enable login flag and disable demo-users by default 2022-03-10 13:32:06 +01:00
Andrey Antukh
36eaa18749 Enable register by invitation when register is disabled 2022-03-10 13:32:06 +01:00
Andrey Antukh
f7bb08382c Fix issues from previous refactor peer review 2022-03-10 13:32:06 +01:00
Andrey Antukh
9841a39d04 🐛 Fix issues on github oauth integration 2022-03-10 13:32:06 +01:00
Andrey Antukh
edf53840de 🐛 Fix issues with gitlab oidc provider 2022-03-10 13:32:06 +01:00
Andrey Antukh
6bd2dcff2a Minor improvements on error reporting 2022-03-10 13:32:06 +01:00
Andrey Antukh
73117f6f27 🐛 Set correct scopes for gitlab auth integration 2022-03-10 13:32:06 +01:00
Pablo Alba
3d588a88e2 💄 Social login redesign 2022-03-10 13:32:04 +01:00
Andrey Antukh
636dbd4e57 Merge pull request #1672 from penpot/set-artboard-as-thumbnail
 Set an artboard as the file thumbnail
2022-03-10 09:27:20 +01:00
Pablo Alba
0a04a856da Set an artboard as the file thumbnail 2022-03-10 09:05:41 +01:00
Andrey Antukh
e139284a98 Merge remote-tracking branch 'origin/main' into develop 2022-03-09 17:51:48 +01:00
Andrés Moya
a04980b251 Merge pull request #1660 from penpot/niwinz-async-refactor-2
Refactor backend (part3)
2022-03-09 17:20:12 +01:00
Andrey Antukh
8120a0cb9c 📎 Change backend repl script default env options 2022-03-09 17:18:06 +01:00
Andrey Antukh
c84f8808cb ♻️ Refactor loki integration
Make it implemented as worker thread instead of async
process just for simplify it.
2022-03-09 17:18:06 +01:00
Andrey Antukh
1b444a42f2 ♻️ Refactor http server layer
Make it fully asynchronous.
2022-03-09 17:18:06 +01:00
Andrey Antukh
a7e79b13f9 🐛 Fix library selection on color palette 2022-03-09 15:12:07 +01:00
Andrey Antukh
3e6be7e04c Merge pull request #1658 from penpot/fix-get-attrs-multi
🐛 Fix multiple edition
2022-03-08 15:25:15 +01:00
Andrés Moya
aa1e3f59ed 🔧 Small refactors 2022-03-08 15:17:02 +01:00
Andrés Moya
a13fb1f94f 🐛 Fix multiple edition 2022-03-08 15:10:23 +01:00
Andrey Antukh
19f4faa03f ♻️ Refactor workspace layout initialization and persistence 2022-03-08 12:59:56 +01:00
Andrey Antukh
965148f3a6 📎 Port fixes from main branch 2022-03-08 12:59:56 +01:00
alonso.torres
a0c0ab1871 🐛 Fix problem with handoff css 2022-03-08 11:53:56 +01:00
Alejandro
43cbe2dd39 Merge pull request #1665 from penpot/fix/bool-with-multiple-shapes
🐛 Fix problem with booleans and new fills/strokes
2022-03-08 10:02:20 +01:00
alonso.torres
9c00de047a 🐛 Fix problem with booleans and new fills/strokes 2022-03-08 09:52:20 +01:00
Andrey Antukh
49649a8814 Merge pull request #1662 from penpot/niwinz-hotfix-event-tracing-improvements
Minor improvements (hotfix)
2022-03-07 15:52:26 +01:00
Andrey Antukh
18a67a80bc 🔥 Remove unused code 2022-03-07 15:50:31 +01:00
Andrey Antukh
867669cc98 Add missing origin meta on left-toolbar events 2022-03-07 15:19:51 +01:00
Andrey Antukh
0158a93391 📎 Fix linter issues on staging branch 2022-03-07 15:10:03 +01:00
Andrey Antukh
fdb6533149 Minor improvement on workspace flags and modal event tracing 2022-03-07 15:10:03 +01:00
Andrey Antukh
6f32d721c2 📎 Minor changes on default values on devenv docker compose 2022-03-07 15:10:03 +01:00
Andrey Antukh
5f49656e30 Add proper event tracing on nudge modal
And ♻️ refactor data event handling, moving
some logic from component to the event.
2022-03-07 15:10:03 +01:00
Andrey Antukh
8114b165d9 📎 Update version.txt file 2022-03-07 13:13:41 +01:00
Andrey Antukh
dd39cb5a1c Merge pull request #1661 from penpot/fix/viewer-performance
🐛 Fix problems with viewer performance
2022-03-07 13:13:11 +01:00
Andrey Antukh
7f8c217e7c Merge remote-tracking branch 'origin/main' into staging 2022-03-07 13:11:38 +01:00
Andrey Antukh
d731a095c6 Merge branch 'main' into staging 2022-03-07 13:08:20 +01:00
alonso.torres
6630899d6e 🐛 Fix problems with viewer performance 2022-03-07 12:40:27 +01:00
Andrey Antukh
0cfd5095a7 🐛 Fix stack trace reporting on loki 2022-03-07 11:31:36 +01:00
Andrey Antukh
a588267fc2 Merge remote-tracking branch 'origin/main' into develop 2022-03-07 11:22:02 +01:00
Andrey Antukh
4f379821b5 🐛 Fix labels on loki logger 2022-03-07 11:09:06 +01:00
Eva
9eea7dabc2 🐛 Fix length of names in sidebar 2022-03-07 11:07:15 +01:00
Joseph V M
ca85a9a2a5 🌐 Add translations for: Malayalam.
Currently translated at 7.5% (68 of 903 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ml/
2022-03-05 21:57:58 +01:00
Pablo Alba
e34885de9b 🐛 Fix error on frame with border 2022-03-04 15:38:46 +01:00
Andrey Antukh
192b9213ac Merge pull request #1655 from penpot/multiple-members-invitations
 Allow send multiple team invitations at once
2022-03-04 15:20:51 +01:00
Pablo Alba
7e26e2bc21 Small changes on multi-input behaviour and styles 2022-03-04 15:06:58 +01:00
Eva
f9c0482949 Show actual coordinates while modifying and creating a shape 2022-03-04 13:16:57 +01:00
Eva
7e0d7ef727 🐛 avoid show rotation options with frames 2022-03-04 09:43:45 +01:00
Alejandro Alonso
d6820a69d4 🐛 Fixing texts with multiple strokes and fills 2022-03-04 07:56:47 +01:00
Pablo Alba
cf09ff8dc3 📎 Change spanish translation of pin-unpin 2022-03-03 22:02:36 +01:00
Pablo Alba
bda941746b Add '_' as zoom out shortcut 2022-03-03 21:54:30 +01:00
Andrey Antukh
f638a2ff49 Add revision fixes 2022-03-03 16:05:52 +01:00
Andrey Antukh
b348a882f4 🎉 Add minio client to devenv
And minor fix the nginx config.
2022-03-03 16:05:52 +01:00
Andrey Antukh
9e4a50fb15 ♻️ Refactor backend to be more async friendly 2022-03-03 16:05:52 +01:00
Andrey Antukh
cfe657d853 Make the multi-input more generic 2022-03-03 14:49:10 +01:00
Andrey Antukh
a1c3789ec2 🎉 Add parse email helper function 2022-03-03 14:49:10 +01:00
Pablo Alba
1cf9ad55c6 Allow send multiple team invitations at once 2022-03-03 14:49:09 +01:00
Andrés Moya
087d896569 🔧 Fix multiple edition 2022-03-03 11:36:25 +01:00
alonso.torres
17fc15138a Add suport to export/import frames with radius 2022-03-03 11:36:25 +01:00
Eva
d4af28c52b Add border radius to artboards 2022-03-03 11:36:25 +01:00
nautilusx
767a162077 🌐 Add translations for: German.
Currently translated at 97.8% (884 of 903 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2022-03-02 19:54:23 +01:00
alonso.torres
78d7fe3e10 New focus mode in workspace 2022-03-02 10:41:13 +01:00
Andrey Antukh
dc18a6c3bc 📎 Fix linter issues 2022-03-01 15:30:58 +01:00
Andrey Antukh
03cb738e55 Merge remote-tracking branch 'origin/main' into develop 2022-03-01 15:10:33 +01:00
Andrey Antukh
d1c834e647 🐛 Fix minor issue on executors monitor 2022-03-01 14:34:13 +01:00
Andrey Antukh
03a082fe40 🐛 Fix metrics on websocket connections 2022-03-01 14:19:26 +01:00
Pablo Alba
7691377c1b Persist color palette and color picker across refresh 2022-03-01 14:06:13 +01:00
alonso.torres
0534570784 🐛 Fix typo in text palette 2022-03-01 13:00:48 +01:00
Andrey Antukh
f2e389593a 🐛 Fix graphic asset rename 2022-03-01 12:50:10 +01:00
Alejandro
2037c3b202 Merge pull request #1649 from penpot/fixing-default-path-for-strokes
🐛 Fixing default path for strokes
2022-03-01 11:53:22 +01:00
Alejandro Alonso
1dc7db4456 🐛 Fixing default path for strokes 2022-03-01 11:23:20 +01:00
Andrey Antukh
fae79d67e6 Merge branch 'staging' 2022-03-01 11:10:27 +01:00
Andrey Antukh
271f69d59d Merge branch 'release-1.12' into staging 2022-03-01 11:08:21 +01:00
elhombretecla
6563cd9c8b 🎉 Add new release info dialog 2022-03-01 11:07:50 +01:00
alonso.torres
8d700491da 🐛 Fix 404 error on fills 2022-03-01 09:52:17 +01:00
Alejandro Alonso
7962c104b6 Adding specs for fills and strokes 2022-03-01 09:14:23 +01:00
Andrey Antukh
505d0f4768 📎 Update clj-kondo config 2022-02-28 22:11:42 +01:00
Andrey Antukh
e60b8a7aef 🐛 Minor fix on worker executors monitor 2022-02-28 17:21:36 +01:00
Andrey Antukh
cb65eca062 🐛 Fix double deref 2022-02-28 17:17:54 +01:00
alonso.torres
d6a5913086 Merge remote-tracking branch 'origin/staging' into develop 2022-02-28 16:10:30 +01:00
alonso.torres
a644599b16 🐛 Fix problem when disabling grid snap 2022-02-28 16:07:43 +01:00
alonso.torres
52def43f5a 🐛 Fix issue with react hooks 2022-02-28 15:46:11 +01:00
Andrey Antukh
5d2715dd32 Improve monitors monitor 2022-02-28 15:29:30 +01:00
Alejandro Alonso
13af98e5ad 📎 Removing unncesary TODO 2022-02-28 15:13:59 +01:00
Andrey Antukh
d14e907954 Merge remote-tracking branch 'origin/staging' into develop 2022-02-28 12:54:02 +01:00
alonso.torres
3f804339b9 🐛 Fix linter issues 2022-02-28 12:38:57 +01:00
Alejandro Alonso
a73a393e26 Ability to add multiple strokes to a shape 2022-02-28 12:38:57 +01:00
Andrey Antukh
1bad233e2f 📎 Fix linter issues on staging branch 2022-02-28 12:09:59 +01:00
Andrey Antukh
f64b1d3651 🐛 Properly handle invitations on login 2022-02-28 12:08:31 +01:00
Andrey Antukh
eb57c2f980 💄 Cosmetic changes on mutation profile ns 2022-02-28 12:08:05 +01:00
Andrey Antukh
ecd491cd09 🐛 Don't mark as touched temporal file 2022-02-28 12:07:44 +01:00
Andrey Antukh
dead3138b3 Reduce the size of the default thread pool 2022-02-28 12:07:21 +01:00
Andrey Antukh
0416082d4d 🐛 Fix awsns handler, convert it ot async 2022-02-28 12:06:47 +01:00
Joseph V M
98d1fd85fb 🌐 Add translations for: Malayalam.
Currently translated at 6.5% (59 of 903 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ml/
2022-02-25 21:56:12 +01:00
Andrey Antukh
719aacd6f8 🎉 Add new fmt macro 2022-02-25 14:57:37 +01:00
Andrey Antukh
4ee2ca2a33 🐛 Backport some fixes from staging 2022-02-25 13:18:51 +01:00
Andrey Antukh
45f9d5bb81 Merge remote-tracking branch 'origin/staging' into develop 2022-02-25 12:56:30 +01:00
Andrey Antukh
9f2d87d7d7 📎 Fix linter issues related to clj-kondo update 2022-02-25 12:54:29 +01:00
Andrey Antukh
d5b163f04d 🐛 Fix naming consistency and page background forwarding 2022-02-25 12:54:29 +01:00
alonso.torres
05c77d0248 🐛 Fix problem with collapsing pages 2022-02-25 12:53:22 +01:00
Alejandro Alonso
2fc4c30bed 🐛 [Prototype] Prototype mode should not allow edits 2022-02-25 12:41:19 +01:00
Alejandro Alonso
d2590c7651 🐛 [Prototype] Prototype mode should not allow edits 2022-02-25 12:24:09 +01:00
alonso.torres
237af505f9 🐛 Fix problem when editing texts 2022-02-25 11:41:55 +01:00
Andrey Antukh
7b4f522a33 📎 Minor fixes on frontend test code. 2022-02-25 11:07:40 +01:00
Andrey Antukh
0e7ce55f9a 📎 Fix linter issues and linter config 2022-02-25 11:07:40 +01:00
Andrey Antukh
fe43b3494c 🐛 Fix minor issues on es6 imports 2022-02-25 11:07:40 +01:00
Andrey Antukh
4c00c8f3ec Minor performance enhancement on str concat opetations
And proper stringify of :key prop of react components
2022-02-25 11:07:40 +01:00
Andrey Antukh
f05518e357 ♻️ Refactor workspace state organization
Move many local to a specific global prop.
2022-02-25 11:07:40 +01:00
Andrey Antukh
6e667e078c 🎉 Add cljs benchmark code under dev directory 2022-02-25 11:07:40 +01:00
Andrey Antukh
84a36624a6 🎉 Add specific namespace for data macros
And additionally add optimized macros for get-in,
select-keys and str.
2022-02-25 11:07:40 +01:00
Andrey Antukh
165c551e39 ⬆️ Update dependencies 2022-02-25 11:07:40 +01:00
Andrey Antukh
fe6ed2ceae Merge pull request #1631 from penpot/fix/color_palette_animation
🐛 Fix color palette animation
2022-02-25 09:15:39 +01:00
Andrey Antukh
92bcd549ef ⬆️ Update dependencies on devenv docker 2022-02-25 08:46:38 +01:00
Andrés Maldonado
5216471226 🐳 Fix run-devenv on systems with SELinux
This sets the selinux label on bind mounts (https://docs.docker.com/storage/bind-mounts/#configure-the-selinux-label), which is necessary so that containers can read the files.

Signed-off-by: Andrés Maldonado <maldonado@codelutin.com>
2022-02-24 22:31:33 +01:00
Joseph V M
6497ee02fb 🌐 Add translations for: Malayalam.
Currently translated at 5.3% (48 of 903 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ml/
2022-02-24 20:53:58 +01:00
Yaron Shahrabani
859e26cf8f 🌐 Add translations for: Hebrew.
Currently translated at 100.0% (903 of 903 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2022-02-24 20:53:58 +01:00
alonso.torres
9964360656 📚 Updated changelog 2022-02-24 18:11:12 +01:00
Andrey Antukh
73f5e7c2ef Merge pull request #1623 from penpot/feat/svg-texts
Render Text as native SVG elements
2022-02-24 14:34:11 +01:00
alonso.torres
64ffa9bb3f 🐛 Fix problems with old texts 2022-02-24 14:05:01 +01:00
alonso.torres
ec63d23666 Multiple fills in text shapes 2022-02-24 14:05:01 +01:00
alonso.torres
a3063eb46d Add support for multiple shapes 2022-02-24 14:05:00 +01:00
alonso.torres
40b7cafacc Fix problems with strokes 2022-02-24 14:05:00 +01:00
alonso.torres
82c6b8daae Fix problems with export/import 2022-02-24 14:05:00 +01:00
alonso.torres
3228582cbe Fix problems when migrating old texts 2022-02-24 14:05:00 +01:00
alonso.torres
d0e008665f Fix masks for Firefox 2022-02-24 14:05:00 +01:00
alonso.torres
96eacb6efe Changed update text flow 2022-02-24 14:05:00 +01:00
alonso.torres
e183d67e2a Add spec for new text data 2022-02-24 14:05:00 +01:00
alonso.torres
bbf91a8957 Improved text selection 2022-02-24 14:05:00 +01:00
alonso.torres
618d22d214 Changes to text editor 2022-02-24 14:05:00 +01:00
alonso.torres
d83459f674 ❇️ Change mutation listener 2022-02-24 14:05:00 +01:00
alonso.torres
6cb6adc134 Allows svg text on test edit and creation 2022-02-24 14:05:00 +01:00
alonso.torres
18dded1a00 Fix editor and bounds for new texts 2022-02-24 14:05:00 +01:00
alonso.torres
1c2785f34e Adds borders to SVG texts 2022-02-24 14:05:00 +01:00
alonso.torres
a411cbc640 Initial SVG text support 2022-02-24 14:05:00 +01:00
alonso.torres
b4c87ad0b9 🐛 Fix font for guides and rulese 2022-02-24 11:45:56 +01:00
Andrey Antukh
37a35b1827 Minor improvements on telemetry task 2022-02-24 11:02:05 +01:00
Eva
ddae26b48b 🐛 Fix color palette animation 2022-02-24 09:46:19 +01:00
Andrey Antukh
c3f57cf900 Merge pull request #1619 from penpot/use-changes-builder
🔧 Refactor to use changes-builder
2022-02-24 09:19:51 +01:00
Andrés Moya
56b74c6ff2 🔧 Refactor shape ordering to use changes-builder 2022-02-23 14:16:45 +01:00
Andrés Moya
8682c07148 🔧 Small refactor changes-builder 2022-02-23 14:16:45 +01:00
Andrés Moya
96870c3fee 🔧 Refactor page actions to use changes-builder 2022-02-23 14:16:45 +01:00
Eva
24a0b4445e Open feedback page in a new tab 2022-02-23 12:51:02 +01:00
Eva
e139cba621 Scroll to selected font size or closest in font size selector 2022-02-23 12:50:23 +01:00
Andrey Antukh
07e8d110a2 🐛 Fix incorrect error id reporting on mattermost webhook 2022-02-23 12:41:33 +01:00
Andrey Antukh
87c1bc4bdb 🐛 Fix incorrect error id reporting on mattermost webhook 2022-02-23 12:40:28 +01:00
Andrey Antukh
31b13f3551 🐛 Fix issues with not authenticated requests
Related to concurrency model refactor.
2022-02-23 12:34:59 +01:00
Andrey Antukh
e15f5bb432 🐛 Fix issues with not authenticated requests
Related to concurrency model refactor.
2022-02-23 12:34:08 +01:00
Andrey Antukh
340ee859f9 📎 Fix linter issues 2022-02-23 12:17:18 +01:00
Andrey Antukh
496ba433e9 📎 Fix linter issues 2022-02-23 12:16:51 +01:00
Andrey Antukh
b183dc3e62 Merge remote-tracking branch 'origin/staging' into develop 2022-02-23 12:00:50 +01:00
Andrey Antukh
0b0ae756a3 🐛 Minor fix on audit http handler 2022-02-23 11:59:17 +01:00
Andrey Antukh
0ade0405f5 🐛 Fix feedback and audit-log http handlers 2022-02-23 11:49:25 +01:00
Eva
fcf8ad0611 ♻️ Rearrange changelog 2022-02-23 09:34:01 +01:00
Andrey Antukh
e0cb6d32ea Merge remote-tracking branch 'origin/staging' into develop 2022-02-23 09:14:51 +01:00
Andrey Antukh
aeed535f1b Minor improvement on reference handling on touched-gc task 2022-02-23 09:13:48 +01:00
Andrey Antukh
974084a9ca 🐛 Add missing executor dependency to auth handlers 2022-02-23 09:13:48 +01:00
Alejandro Alonso
88706534c2 🐛 Fixing fil typo 2022-02-23 08:33:03 +01:00
Eva
70def21153 ♻️ Improve file menu usage 2022-02-22 13:36:01 +01:00
Eva
941174a9fa 🐛 Show code icon on preview hover 2022-02-22 13:11:59 +01:00
Andrés Moya
46bfb2aacd 🐛 Fixed alignment of layers with children 2022-02-22 13:10:59 +01:00
Andrey Antukh
a4ef3f770c Merge remote-tracking branch 'origin/staging' into develop 2022-02-22 13:06:09 +01:00
Andrey Antukh
7cf27ac86d ♻️ Refactor general resource and concurrency model on backend 2022-02-22 13:05:41 +01:00
Joseph V M
823e5ca058 🌐 Add translations for: Malayalam.
Currently translated at 2.4% (22 of 903 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ml/
2022-02-22 12:57:50 +01:00
John Terroa
b7a182129d 🌐 Add translations for: Portuguese (Brazil).
Currently translated at 56.0% (506 of 903 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/
2022-02-22 12:57:50 +01:00
Alejandro Alonso
10b147a25d 🐛 Importing shapes without fills 2022-02-22 10:53:47 +01:00
alonso.torres
6550631003 📚 Updated changelog 2022-02-22 10:52:58 +01:00
Migara
9d04dc7d9a 🎉 Add invitation section to dashboard 2022-02-22 09:20:31 +01:00
Andrey Antukh
486d89c5d0 Merge pull request #1607 from penpot/duplicate-flow
Duplicate flow
2022-02-22 08:48:20 +01:00
alonso.torres
d24f16563f Use remove to delete guides 2022-02-21 17:30:08 +01:00
Eva Marco
bb68838fa4 Merge pull request #1620 from penpot/fix_double_click
🐛 Fix problem with double click
2022-02-21 17:27:46 +01:00
alonso.torres
aed6a8a5ff 🐛 Fix problem with double click 2022-02-21 16:57:35 +01:00
Andrey Antukh
e13bceeb59 Merge remote-tracking branch 'origin/staging' into develop 2022-02-21 16:29:45 +01:00
Alejandro Alonso
1dab89f7ae 🌐 Added translation for: Malayalam. 2022-02-21 12:18:44 +01:00
Andrey Antukh
96facc5100 ♻️ Refactor invitation flow
Enfoces security and make the flow more deterministic.
2022-02-21 11:38:28 +01:00
Rubén
43d94d208f 🌐 Add translations for: Catalan.
Currently translated at 97.5% (881 of 903 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ca/
2022-02-19 22:58:21 +01:00
Yaron Shahrabani
741ee99e6b 🌐 Add translations for: Hebrew.
Currently translated at 99.6% (900 of 903 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2022-02-18 18:56:07 +01:00
Oğuz Ersen
6f2cff2f33 🌐 Add translations for: Turkish.
Currently translated at 99.7% (901 of 903 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2022-02-18 18:56:06 +01:00
Andrés Moya
0035827209 🎉 Duplicate shapes must create new flows if needed 2022-02-18 17:46:26 +01:00
Andrés Moya
c626b1d106 ♻️ Refactor duplicate objects 2022-02-18 13:14:20 +01:00
Andrés Moya
9c895cb8bb ♻️ Reorder some functions 2022-02-18 13:14:20 +01:00
Alejandro Alonso
23a9c74297 Ability to add multiple fills to a shape 2022-02-17 11:19:21 +01:00
Andrés Moya
aecb8a1464 🐛 Fix some broken tests 2022-02-17 11:19:21 +01:00
Andrés Moya
b9e3426532 🔧 Refactor calculation of multi selection attributes 2022-02-17 11:19:21 +01:00
Andrey Antukh
809d7ab7f4 Merge remote-tracking branch 'origin/staging' into develop 2022-02-17 11:16:00 +01:00
Andrey Antukh
6486b24c8b ⬆️ Update shadow-cljs version 2022-02-17 11:15:19 +01:00
Alejandro
e11d78d37a Merge pull request #1589 from penpot/us/team_members_redesing
Redesign Team members
2022-02-17 09:22:56 +01:00
Eva
3a34b3ae5f Team member redesign 2022-02-17 09:04:29 +01:00
Pablo Alba
75a8f85ebb Do not show the templates modal on onboarding 2022-02-16 21:34:47 +01:00
Andrés Moya
3d8f757712 🐛 Fixed cannot align objects inside a group but not inside a frame 2022-02-16 17:07:28 +01:00
Andrey Antukh
b37d6ec500 Merge remote-tracking branch 'origin/staging' into develop 2022-02-16 16:30:45 +01:00
Alejandro Alonso
4efd8b7d3f 🐛 Select All (CTRL+A) fails 2022-02-16 16:24:00 +01:00
Andrés Moya
5d17933593 🐛 Fix touched component marker appearing when it's not needed 2022-02-16 16:23:31 +01:00
Andrey Antukh
277d8f8b93 📎 Increase version on develop branch. 2022-02-16 14:01:30 +01:00
Andrey Antukh
f2c5add752 📎 Add new ongoing release to CHANGES.md file 2022-02-16 14:01:00 +01:00
Andrey Antukh
60d37b6de0 Merge branch 'staging' into develop 2022-02-16 14:00:46 +01:00
Andrey Antukh
206778021f 📎 Update changes.md file 2022-02-16 14:00:25 +01:00
Andrey Antukh
4a262de550 Merge branch 'niwinz-storage-transactionality-refactor' into staging 2022-02-16 13:58:36 +01:00
Andrey Antukh
350663b7ce 🎉 Add support for alternative S3 compatible services
And also add support for all AWS regions (prevoiosly onlu eu-central-1)
was supported.
2022-02-16 13:58:19 +01:00
Andrey Antukh
f1db0fea03 ♻️ Refactor storage transaction management 2022-02-16 13:58:15 +01:00
Pablo Alba
1990232adc 🎉 Add team invitations API 2022-02-16 13:52:31 +01:00
Andrey Antukh
256ed7410f Add unique id (uuid) on each log entry 2022-02-16 11:58:43 +01:00
Andrey Antukh
09a4cb30ec 🐛 Fix unresolved symbol error introduced in prev merge 2022-02-16 11:29:30 +01:00
Andrey Antukh
aa3826c389 📎 Sort translations 2022-02-16 11:26:13 +01:00
Andrey Antukh
b91042c1e5 Merge remote-tracking branch 'weblate/develop' into translations 2022-02-16 11:24:56 +01:00
Andrey Antukh
7eed8c5ee5 Merge remote-tracking branch 'origin/main' into develop 2022-02-16 11:23:26 +01:00
Andrey Antukh
3207860374 🐛 Fix compatibility issues of some requires and shadow-cljs 2022-02-15 16:01:46 +01:00
Keunes
b3bb8b6692 📎 Update bug_report.md file
Make clearer what information should be provided when filing a bug report.
2022-02-15 15:54:59 +01:00
Andrey Antukh
5b8b13c94c ⬆️ Update shadow-cljs to 2.17.2 2022-02-15 15:07:29 +01:00
Andrey Antukh
e8426006e3 Update version.txt file 2022-02-15 13:27:08 +01:00
Andrey Antukh
116fafd0e1 📎 Minor log param naming change 2022-02-15 13:25:46 +01:00
Andrey Antukh
e9fe1800e0 Fix minor issues on session expiration handling 2022-02-15 13:25:06 +01:00
Andrey Antukh
82796822d1 🐛 Fix possible race condition on component rename and deletion 2022-02-15 12:26:36 +01:00
Andrey Antukh
ce61b783fb Minor improvements on telemetry task 2022-02-15 12:26:36 +01:00
Andrey Antukh
9b78b2a432 Improve error reporting on background tasks 2022-02-15 12:26:36 +01:00
Andrey Antukh
321b2c7c23 🐛 Fix error handling on s3 delete-in-bulk operation 2022-02-15 12:26:36 +01:00
Andrey Antukh
dee397615c 📎 Update changelog file 2022-02-15 12:26:36 +01:00
Andrey Antukh
ef9339f6f1 🐛 Fix unexpected exception on handling empty state on boolean calc 2022-02-15 12:26:36 +01:00
Alejandro
f7f32408fc Merge pull request #1577 from penpot/fix/radial-gradients
 Changed radial gradients to use objectBoundingBox
2022-02-14 12:26:43 +01:00
Andrey Antukh
d4e6992442 Merge remote-tracking branch 'origin/main' into develop 2022-02-12 17:36:19 +01:00
Andrey Antukh
420ece7005 📎 Increase *print-level* on error reporting. 2022-02-12 17:35:29 +01:00
Andrey Antukh
741d2b3f3c Merge remote-tracking branch 'origin/main' into develop 2022-02-12 17:33:28 +01:00
Andrey Antukh
c8bf319b39 Merge pull request #1567 from penpot/frame-snapshot-api
 Frame snapshot api
2022-02-12 16:09:03 +01:00
Pablo Alba
34df52be5f 🎉 Add frame thumbnail API 2022-02-12 16:08:46 +01:00
Pablo Alba
fc2399a885 Rotation to snap to 15º intervals with shift 2022-02-11 12:42:43 +01:00
alonso.torres
699ec93ca4 Changed radial gradients to use objectBoundingBox 2022-02-11 12:33:13 +01:00
Andrés Moya
10598063d1 🔧 Provisional change menu to staging landing page 2022-02-11 12:32:57 +01:00
Eva Marco
db1e9574cd Merge pull request #1568 from penpot/fix/gradient-problem
🐛 Fix problem with gradient handlers
2022-02-11 11:27:01 +01:00
Andrés Moya
af74a1575b 🐛 Clear authentication cookies when logged out 2022-02-11 10:07:03 +01:00
Eva
03242e1a9c 🐛 Fix ungroup typography when editing 2022-02-10 16:20:13 +01:00
Andrey Antukh
dcbd89ff7c Increase default max connection pool size to 60. 2022-02-10 15:12:35 +01:00
Andrey Antukh
2312561041 Temporaly disable parallel uploading of files on import 2022-02-10 15:12:35 +01:00
Andrey Antukh
b591fbecf0 🎉 Add health check api endpoint 2022-02-10 15:12:35 +01:00
Andrey Antukh
3fbb440436 Handle EOF on websocket write/ping operations 2022-02-10 15:12:35 +01:00
Andrey Antukh
d358185a04 💄 Minor cosmetic change on database logger processor 2022-02-10 15:12:35 +01:00
Andrey Antukh
8babb59f75 Process audit log events only if profile-id is known 2022-02-10 15:12:35 +01:00
Andrey Antukh
3461ec2281 Ignore EOF errors on writting streamed response 2022-02-10 15:12:35 +01:00
Andrey Antukh
3dd94bd362 🐛 Log correct deleted number value on recheck task 2022-02-10 15:12:35 +01:00
Andrey Antukh
827c2140b7 ♻️ Refactor error reporting and logging context formatting
The prev approach uses clojure.pprint without any limit extensivelly
for format error context data and all this is done on the calling
thread. The clojure.pprint seems very inneficient in cpu and memory
usage on pprinting large data structures.

This is improved in the following way:

- All formatting and pretty printing is moved to logging thread,
  reducing unnecesary blocking and load on jetty http threads.
- Replace the clojure.pprint with fipp.edn that looks considerably
  faster than the first one.
- Add some safe limits on pretty printer for strip printing some
  data when the data structure is very large, very deep or both.
2022-02-10 15:12:35 +01:00
Andrés Moya
5a5222a97a 🐛 Fix error getting file library 2022-02-10 13:17:57 +01:00
Andrés Moya
bea3699451 🐛 Fix error instantiating a component 2022-02-10 12:27:44 +01:00
alonso.torres
93174f54a3 Change menu to add show/hide ui 2022-02-10 09:41:50 +01:00
Eva
e1348725c1 🐛 fix error when posting an empty comment 2022-02-10 09:28:05 +01:00
Andrey Antukh
528839cde2 Merge pull request #1569 from penpot/dashboard-user-menu
Dashboard user menu and session cookie
2022-02-09 23:51:14 +01:00
Andrés Moya
c5c331ee30 Refactor user menu in dashboard 2022-02-09 15:52:04 +01:00
Eva Marco
69effa37a3 Merge pull request #1570 from penpot/fix/problem-with-typographies
🐛 Fix problem with typographies in assets
2022-02-09 15:48:34 +01:00
alonso.torres
4c7a781228 🐛 Fix problem with typographies in assets 2022-02-09 15:26:45 +01:00
Andrés Moya
62a67bdb94 🎉 Set a domain cookie to check for logged from landing page 2022-02-09 15:25:40 +01:00
alonso.torres
c5c0b36f28 Improved mouse collision detection for groups and text shapes 2022-02-09 15:17:59 +01:00
Andrés Moya
0d48c758df 📚 Add new contributor change 2022-02-09 15:16:19 +01:00
Andrés Moya
4856413b24 Merge branch 'rhcarvalho-zopflipng' into develop 2022-02-09 15:13:53 +01:00
Rodolfo Carvalho
a1586280a9 Compress PNG images using zopflipng
Add a helper script and compress existing PNG images with zopflipng.

Before
552K    total

After
428K    total

Signed-off-by: Rodolfo Carvalho
2022-02-09 15:11:46 +01:00
Andrés Moya
00950b2c97 📚 Add new contributor change 2022-02-09 15:07:05 +01:00
Andrés Moya
79666bd51a Merge branch 'rhcarvalho-remove-dangling-png' into develop 2022-02-09 14:48:07 +01:00
Rodolfo Carvalho
ca284a86a3 Remove dangling images
Clean up images that are no longer in use.

Removed in 50eb744c3b:
- frontend/resources/images/color-bar-library.png
- frontend/resources/images/color-bar-options.png

Removed in 0de4f9074d:
- frontend/resources/images/color-gamma.png

Removed in 196b4dd89b:
- frontend/resources/images/colorspecrum-400x300.png

Added in 35c172a06b but maybe never used:
- frontend/resources/images/favicon-preview.png

Removed in d93fa72e48:
- frontend/resources/images/pot.png
2022-02-09 13:55:19 +01:00
alonso.torres
ee5b341d0e 🐛 Fix problem with gradient handlers 2022-02-09 13:04:16 +01:00
Alejandro
85cab5031d Merge pull request #1564 from penpot/fix/missing_translation
🐛 Fixed missing translation texts
2022-02-09 11:26:35 +01:00
Eva
2f7029516b 🐛 Fixed missing translation texts 2022-02-09 11:14:24 +01:00
Andrey Antukh
a1da4d4233 ♻️ Refactor common.page.helpers namespace. 2022-02-08 15:30:13 +01:00
Andrey Antukh
24724e3340 📎 Add helpful require on user ns 2022-02-08 15:30:13 +01:00
Eva
048ab9a0fc 🐛 fix missing translace string 2022-02-08 15:17:40 +01:00
Eva
40b005f46e 🐛 fix color palette overflow 2022-02-08 15:11:06 +01:00
Alejandro
ae2a99acb0 Merge pull request #1558 from penpot/fix/problem-svg-import
🐛 Fix problem with svg icons
2022-02-08 12:49:52 +01:00
alonso.torres
a81b6db093 🐛 Fix problem with svg icons 2022-02-08 12:30:52 +01:00
alonso.torres
39b05f5f9f 🐛 Fix problem with selection rect 2022-02-08 12:11:56 +01:00
Andrey Antukh
979f61df99 Merge remote-tracking branch 'origin/main' into develop 2022-02-08 09:12:13 +01:00
Andrey Antukh
e665f4e285 🐛 Log correct deleted number value on recheck task 2022-02-08 00:18:48 +01:00
Andrey Antukh
2c25dfcf1b 📎 Add exec perms to build script 2022-02-07 23:42:26 +01:00
Andrey Antukh
0632028579 📎 Set version to 1.11.1-beta 2022-02-07 23:26:21 +01:00
Andrey Antukh
95b9085258 Merge pull request #1555 from penpot/fix/thumbnails_firefox_problem
🐛 Fix Firefox problem when rendering frames
2022-02-07 18:03:33 +01:00
alonso.torres
cdc91feb28 🐛 Fix Firefox problem when rendering frames 2022-02-07 17:48:51 +01:00
alonso.torres
4caf278da5 🐛 Fix problems with handoff layout 2022-02-07 16:34:31 +01:00
Andrey Antukh
809a3420c1 Merge pull request #1554 from penpot/feat/tablet-improvements
Tablet improvements
2022-02-07 15:42:55 +01:00
alonso.torres
af8e9058a3 Move selection with space 2022-02-07 15:32:27 +01:00
alonso.torres
2b1c8cafe9 Improved color picker 2022-02-07 15:18:30 +01:00
alonso.torres
1abcd5819b Enter in dashboard to open files 2022-02-07 15:18:30 +01:00
alonso.torres
76b34bb600 Workspace interactions improvements 2022-02-07 15:18:30 +01:00
alonso.torres
67c6a042a0 Improved incremental selection 2022-02-07 15:18:30 +01:00
alonso.torres
72c2a213b4 Curve tool improvements 2022-02-07 15:18:30 +01:00
alonso.torres
ec1cc8ec64 Adds new shortcut for zoom in 2022-02-07 15:18:30 +01:00
alonso.torres
fbbb079599 ♻️ Remove rx/first calls and replaced by safer rx/take 1 2022-02-07 15:18:30 +01:00
Eva
b8f2f3e34d Show recent fonts only on text edition area not in typographies 2022-02-07 15:06:05 +01:00
Alejandro
39b29ee3f0 Merge pull request #1552 from penpot/fix/shadow_type_text
🐛 Fix shadow type text in handoff section
2022-02-07 13:15:46 +01:00
Eva
5f6cb1e0d7 🐛 Fix shadow type text in handoff section 2022-02-07 13:04:52 +01:00
Andrey Antukh
46250e6fab ⬆️ Update nodejs version on docker images. 2022-02-07 11:40:53 +01:00
Alejandro Alonso
fc2a26f249 🎉 Add border radius support to image shapes 2022-02-07 11:33:23 +01:00
Andrey Antukh
341caa3489 🎉 Add docker images auxiliar build script. 2022-02-07 11:21:54 +01:00
Eva
38b7474f0b Add a little improvent in recent fonts selector 2022-02-07 09:34:22 +01:00
Andrey Antukh
c91e2d13c0 📎 Add temporal workaround on config.env file 2022-02-06 23:40:04 +01:00
Pablo Alba
7134bbf484 Disallow using same password as user email 2022-02-04 17:41:01 +01:00
Andrey Antukh
6b18b258a4 🐛 Set proper default http server host. 2022-02-04 16:02:51 +01:00
Eva
86e4826e48 Add configurable nudge amount 2022-02-04 15:15:48 +01:00
Andrey Antukh
6461ebe2b8 🔥 Remove unreachable code. 2022-02-04 15:04:47 +01:00
Andrey Antukh
bfb23ad60b ⬆️ Update backend and frontend clojure deps 2022-02-04 15:04:47 +01:00
Andrey Antukh
637d6a0076 ⬆️ Update common module deps 2022-02-04 15:04:47 +01:00
Andrey Antukh
cbb8d13570 ⬆️ Update frontend npm dependencies 2022-02-04 15:04:47 +01:00
Andrey Antukh
2a6ba79e9a Ignore EOF errors on writting streamed response 2022-02-04 15:04:47 +01:00
Andrey Antukh
1e0dacfe9b Add reusable helper for expound pretty printing 2022-02-04 15:04:47 +01:00
Andrey Antukh
b194c0c5d8 Merge pull request #1534 from penpot/feat/toolbars-redesign
Toolbars Redesign
2022-02-04 09:26:22 +01:00
alonso.torres
9789b7081a Post-review changes 2022-02-03 18:27:12 +01:00
alonso.torres
03052ddd28 Fixed hover on sidebar 2022-02-03 18:27:12 +01:00
alonso.torres
779f685f72 Update strings for the new tabs 2022-02-03 18:27:12 +01:00
alonso.torres
1dee767762 Selection area on rules 2022-02-03 18:27:12 +01:00
alonso.torres
5cac5eb26b New text typographies palette 2022-02-03 18:27:12 +01:00
alonso.torres
b26cbeccca Resizable color palette 2022-02-03 18:27:12 +01:00
alonso.torres
8d4612c683 🐛 Fix some problems with scroll into view for layers 2022-02-03 18:27:12 +01:00
alonso.torres
e352c70013 Move layers and assets to tabs 2022-02-03 18:27:12 +01:00
alonso.torres
8c3c9a8ca4 Refactor workspace header 2022-02-03 18:27:12 +01:00
alonso.torres
ada837f7e4 New rules styles, resize pages 2022-02-03 18:27:12 +01:00
alonso.torres
1599b2644a Resizeable panels 2022-02-03 18:27:12 +01:00
Alejandro Alonso
acc3d00fd5 🎉 Add stroke properties to image shape 2022-02-03 17:23:26 +01:00
Alejandro Alonso
0f459ede50 🐛 Fix issue in viewport-scrollbars 2022-02-03 13:24:51 +01:00
Pablo Alba
105cb6fa13 Enhance the behaviour of the artboards list on view mode 2022-02-03 11:52:04 +01:00
Pablo Alba
1797c702a7 Automatically open comments from dashboard notifications 2022-02-03 11:38:30 +01:00
Pablo Alba
5f580f10ca On user settings, hide the theme selector as long as we only have one theme 2022-02-03 11:26:45 +01:00
Andrey Antukh
bd359f42f5 📎 Add package-lock.json to .gitignore file 2022-02-02 19:17:51 +01:00
Andrey Antukh
34bf73210e 🔥 Remove package-lock.json file. 2022-02-02 19:14:12 +01:00
Andrey Antukh
f1db4aae35 Merge branch 'main' into develop 2022-02-02 16:23:11 +01:00
Andrey Antukh
7710ffcbf1 🐛 Fix issue on 400 error handler. 2022-02-02 15:31:54 +01:00
Andrey Antukh
e9f45a0d0a 🐛 Fix release modal. 2022-02-02 15:02:47 +01:00
Andrey Antukh
743c2c3385 Merge branch 'staging' 2022-02-02 14:29:30 +01:00
Andrey Antukh
6f714facf9 🐛 Fix many minor issues on telemetry task 2022-02-01 17:49:18 +01:00
Andrey Antukh
5f81c7bc2d Merge remote-tracking branch 'origin/staging' into develop 2022-02-01 16:14:52 +01:00
Pablo Alba
72b00fa9af On team settings set color of members count to black 2022-02-01 15:35:51 +01:00
Andrés Moya
449756a0e4 🐛 Fix ungroup a component leaves asterisk in layers 2022-02-01 15:32:50 +01:00
Andrey Antukh
75930a0ce9 📚 Minor changes on commit guidelines on CONTRIBUTING.md file 2022-02-01 14:25:50 +01:00
Eva
a2c3b0926b Add recent used fonts in font selection widget 2022-02-01 14:11:54 +01:00
Andrey Antukh
57666e9173 Minor improvements on http reporting on 400 responses 2022-02-01 13:53:10 +01:00
alonso.torres
37f4b83d96 🐛 Fix problem with hover shapes 2022-02-01 13:09:51 +01:00
elhombretecla
5576b7568c 🎉 Add new content for release dialog. 2022-02-01 13:03:02 +01:00
Eva Marco
99e067b863 Merge pull request #1523 from penpot/test-e2e-enter-dashboard
👷 e2e tests for dashboard
2022-02-01 12:47:29 +01:00
Pablo Alba
5103624fe0 👷 e2e tests for dashboard
Including test for signing/singup, projects, files, teams, and misc
2022-02-01 11:50:33 +01:00
Andrey Antukh
26e5d57ced 🐛 Fix incorrect alias on shape-attrs spec on workspace. 2022-01-28 16:19:30 +01:00
Andrey Antukh
b586f2552c Merge branch 'staging' into develop 2022-01-28 13:58:22 +01:00
Andrey Antukh
0fbcec667c 📎 Minor format change on changelog file. 2022-01-28 13:57:11 +01:00
Eva
f40c58c64a 💄 Remove dots at the end of each line in changes file in actual sprint 2022-01-28 11:40:22 +01:00
Eva
d66619fe6d 💄 Remove dots at the end of each line in changes file 2022-01-28 11:36:47 +01:00
Eva
5c1b007c1b Align item to it's parent 2022-01-28 10:54:31 +01:00
Pablo Alba
86c394f4ce Merge pull request #1514 from penpot/enhacement/add-profile-e2e-tests
👷 Add e2e test to profile area
2022-01-28 10:32:49 +01:00
Andrés Moya
90d130a3bc 📚 Remove unneeded section in changelog 2022-01-28 10:21:36 +01:00
Eva
f185836fd4 👷 Add e2e test to profile area 2022-01-28 10:20:48 +01:00
Andrey Antukh
4c851856ff Emit warning when an error is raised on formating body. 2022-01-27 17:40:03 +01:00
Andrey Antukh
bc2a0432b9 Allow connect to read-only databases. 2022-01-27 16:11:32 +01:00
Alejandro Alonso
f72e140327 Graphic tablet use improvements: add scroll bars 2022-01-27 16:02:40 +01:00
Andrey Antukh
a633ed3c9a More tweaks on the error reporter. 2022-01-27 15:17:44 +01:00
Andrey Antukh
a8a6882708 💄 Minor changes on repl script. 2022-01-27 15:00:42 +01:00
Andrés Moya
1b76ed97e1 🐛 Fix rotation when set to 0 again 2022-01-27 14:57:26 +01:00
Andrey Antukh
04f7169aef ♻️ Refactor and modularize all specs. 2022-01-27 13:03:44 +01:00
Andrés Moya
d83b362c9f 🐛 Fix "move to" menu when duplicated team or project names 2022-01-27 11:39:18 +01:00
Andrey Antukh
b1d55348dc Merge remote-tracking branch 'origin/staging' into develop 2022-01-26 18:13:48 +01:00
Andrey Antukh
f8a46c56e9 🎉 Add helper for offline validate files with specs. 2022-01-26 18:12:17 +01:00
Andrey Antukh
420525cdf0 🐛 Make the path command params optional. 2022-01-26 18:11:20 +01:00
Andrey Antukh
686cacd5ae Add truncate function to time ns on backend. 2022-01-26 18:10:47 +01:00
Andrey Antukh
0092806dda Prevent high cpu usage on reporting big errors. 2022-01-26 18:09:59 +01:00
Andrey Antukh
2f8c63505f 💄 Fix linter issues. 2022-01-26 14:45:22 +01:00
Andrey Antukh
d892be4971 💄 Fix linter issues. 2022-01-26 14:44:01 +01:00
Andrey Antukh
59ed833abc Merge remote-tracking branch 'origin/staging' into develop 2022-01-26 14:24:34 +01:00
Andrey Antukh
110fb2e8db Minor improvements on error reporting.
Add missing expound.
2022-01-26 14:18:44 +01:00
Andrey Antukh
9f7a04e330 🐛 Fix unexpected exception on handling export data validation. 2022-01-26 14:08:54 +01:00
Andrey Antukh
ccbc519c04 💄 Minor cosmetic changes on internal spec naming. 2022-01-26 12:59:26 +01:00
Eva
036860b91b 🐛 fix typo in zoom options 2022-01-26 12:59:02 +01:00
Eva
7ac2a55315 🐛 Fix header z-index in viewer mode fullscreen 2022-01-26 12:58:27 +01:00
Andrey Antukh
f6cf8d2b1b 🐛 Relax text shape content spec validation.
Allow to be null in some circumstances.
2022-01-26 12:56:42 +01:00
Andrey Antukh
16788d7ab7 🐛 Make a path command params property nilable on spec. 2022-01-26 12:41:54 +01:00
Andrey Antukh
3142d48f3c 💄 Minor cosmetic change on changelog file. 2022-01-26 12:19:10 +01:00
Andrey Antukh
e1a88ae899 Merge branch 'staging' into develop 2022-01-26 12:16:50 +01:00
Andrey Antukh
a2e80cee47 📚 Update changelog file. 2022-01-26 12:09:27 +01:00
alonso.torres
5f14769abc 🐛 Fix problem with hover-ids 2022-01-26 11:49:01 +01:00
Eva
406c4063de Add select layer to contest menu 2022-01-26 11:49:01 +01:00
Andrey Antukh
b4bc30e56f Improve shape specs and add missing specs for content. 2022-01-26 11:30:50 +01:00
Eva
3482d6c303 Add Update component in bulk option 2022-01-26 10:53:31 +01:00
Andrés Moya
9dfd5c0bcc 🐛 Avoid empty names in projects, files and pages 2022-01-26 09:31:55 +01:00
Eva
b2b3de2782 🐛 fix typo in zoom options 2022-01-26 09:30:10 +01:00
Eva
50c20e2290 🐛 Fix header z-index in viewer mode fullscreen 2022-01-26 09:30:10 +01:00
Andrey Antukh
a10dcbd918 Merge pull request #1508 from penpot/feat/guides
Guides
2022-01-25 14:58:36 +01:00
alonso.torres
6e0433a34b Review changes 2022-01-25 14:54:13 +01:00
alonso.torres
8833e19c7f 🐛 Small fixes for guides 2022-01-25 14:17:13 +01:00
alonso.torres
663358bdae 📚 Update changelog 2022-01-25 14:17:13 +01:00
alonso.torres
d9b1c0e2e6 More tests for snap data 2022-01-25 14:17:13 +01:00
alonso.torres
39334b81ac Guides cursors 2022-01-25 14:17:13 +01:00
alonso.torres
62f7323acf Move frames with guides move the guides 2022-01-25 14:17:13 +01:00
alonso.torres
3f89baa1fe Move guides together with frames 2022-01-25 14:17:13 +01:00
alonso.torres
f0fd1bb40c Add menu option for guides 2022-01-25 14:17:13 +01:00
alonso.torres
f303d7b33e Add support to export/import guides 2022-01-25 14:17:13 +01:00
alonso.torres
d356a3fa56 Spec definition for guides 2022-01-25 14:17:13 +01:00
alonso.torres
64e7cad292 ♻️ Redone the snap calculation and added guides 2022-01-25 14:17:13 +01:00
alonso.torres
0766938f98 Add guides UI 2022-01-25 14:17:13 +01:00
alonso.torres
918829ad0a Improve import with parallel media upload 2022-01-25 11:30:32 +01:00
Pablo Alba
540e1fc492 🐛 Fix missing entry of e2e fixtures on gitignore 2022-01-25 11:11:51 +01:00
alonso.torres
ac30754a96 🐛 Fix problem with import 2022-01-25 10:28:04 +01:00
Andrey Antukh
b470a0ebbf 🐛 Add missing dependency update (lost in some merge). 2022-01-24 22:22:43 +01:00
Andrey Antukh
69daee4137 Merge branch 'staging' into develop 2022-01-24 16:21:01 +01:00
Andrey Antukh
3d6c903273 Improve path rendering performance. 2022-01-24 16:14:28 +01:00
Andrey Antukh
bc04a0b9f0 🐛 Fix incorrect behavior of trim-file-data. 2022-01-24 16:08:52 +01:00
Andrey Antukh
bfef94dbfb Merge branch 'main' into staging. 2022-01-24 16:07:47 +01:00
Andrey Antukh
9e06275945 🐛 Prevent exception on broken path. 2022-01-24 15:41:44 +01:00
Andrey Antukh
6410bcf3c8 Minor change on debug endpoint. 2022-01-24 15:13:02 +01:00
Andrés Moya
20baf02726 🐛 Normalize zoom levels in workspace and viewer 2022-01-24 13:30:11 +01:00
Andrey Antukh
8f6fdf361b Improve path rendering performance. 2022-01-24 13:23:09 +01:00
Andrey Antukh
ffa134f824 🐛 Fix incorrect behavior of trim-file-data. 2022-01-24 13:23:09 +01:00
Andrés Moya
b4bf6b9235 🐛 Fix zoom in/out after fit or fill in viewer 2022-01-24 11:04:01 +01:00
Andrés Moya
c3e37b0e04 🐛 Fix auto hide header in viewer full screen 2022-01-24 11:04:01 +01:00
Andrés Moya
374bba763b 🐛 Fix edit blur attributes for multiselection 2022-01-24 11:04:01 +01:00
Pablo Alba
2d00e68b78 👷 Tests e2e for drawing basic forms 2022-01-24 10:56:56 +01:00
Andrey Antukh
9a965dc693 Merge remote-tracking branch 'origin/staging' into develop 2022-01-21 14:54:32 +01:00
Andrey Antukh
b96ad5b37f 💄 Minor cosmetic change on get-parents fn. 2022-01-21 14:47:13 +01:00
Andrey Antukh
07a0f67b32 💄 Minor cosmetic change on reg-object. 2022-01-21 14:47:13 +01:00
Andrey Antukh
c754a757eb Upgrade rumext and add some examples of syntax sugar. 2022-01-21 14:47:13 +01:00
Andrey Antukh
dcd53183a8 📎 Simplify distribute-objects fn impl. 2022-01-21 14:47:13 +01:00
Andrés Moya
5641132eb9 Reload viewer and reset starting frame from workspace 2022-01-21 14:42:11 +01:00
Andrés Moya
b4c23f3554 🐛 Display animations only for allowed actions 2022-01-21 14:41:21 +01:00
Andrés Moya
7385445aa8 💄 Format translation files and remove unneeded script 2022-01-21 14:40:37 +01:00
Eva
5409f83167 Divide file menu options in semantically groups 2022-01-21 12:36:09 +01:00
Voxybuns
43951aad69 🌐 Add translations for: French.
Currently translated at 79.9% (694 of 868 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-01-20 21:55:48 +01:00
Rubén
9681d8c805 🌐 Add translations for: Catalan.
Currently translated at 98.8% (858 of 868 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ca/
2022-01-20 21:55:48 +01:00
Andrey Antukh
ff4d3cfeac 🐛 Fix issue on fressian blob encoding. 2022-01-20 16:57:20 +01:00
Andrey Antukh
8e4338c1c9 🐛 Minor fix on version parsing impl. 2022-01-20 14:47:28 +01:00
Andrey Antukh
c27d709b6b Merge remote-tracking branch 'origin/staging' into develop 2022-01-20 14:30:16 +01:00
Andrey Antukh
8caa289586 Fix version logging on exporter. 2022-01-20 14:27:03 +01:00
Andrey Antukh
f7568f6348 Minor improvements on exporter. 2022-01-20 14:16:58 +01:00
Pablo Alba
6a6f079a84 👷 Create firsts e2e tests 2022-01-20 14:10:48 +01:00
Andrey Antukh
0f04b86316 Improve performance on import .penpot files. 2022-01-20 11:47:17 +01:00
Andrey Antukh
1dae8a0771 📎 Minor improvement on error logging on worker. 2022-01-20 11:47:17 +01:00
Andrey Antukh
9bc816fc1c 🐛 Remove duplicate audit action reporting. 2022-01-20 11:47:17 +01:00
Andrey Antukh
11ea4c7aec 🐛 Fix raw logging on cljs. 2022-01-20 11:47:17 +01:00
Andrey Antukh
0c53aa158b 🐛 Fix profile image generation issue. 2022-01-20 11:47:17 +01:00
Andrey Antukh
072e4a4f98 Revert some memoizes on page/helpers.
And improves base performance of get-children and
remove duplicated code. Also optimize the use
of get-children on react components with corresponding
use-memo hook.
2022-01-20 11:47:17 +01:00
Andrey Antukh
1b3b3b0ee6 Minor naming change on page query parameters. 2022-01-20 11:47:17 +01:00
Andrey Antukh
d1e4f0de3e Improve performance and resolve render issues on exporter. 2022-01-20 11:47:17 +01:00
Andrey Antukh
fd3f304e07 📎 Increase default devenv jvm heapsize. 2022-01-20 11:47:17 +01:00
alonso.torres
9e7551551f 🐛 Fix problem with SVG's import/export 2022-01-19 16:07:44 +01:00
Andrés Moya
36bb5cbe01 🐛 Fix several questions about frame background export 2022-01-19 11:01:23 +01:00
Alejandro Alonso
f754c12e8c Limit pasted object position to container boundaries 2022-01-18 17:07:46 +01:00
Andrey Antukh
6f5916e334 ♻️ Minor reorganization on export & render namespaces. 2022-01-18 16:48:17 +01:00
Andrés Moya
13dd1cb6b6 🐛 Disable embed images in export 2022-01-18 16:48:17 +01:00
Andrey Antukh
eb4e7e0f0c 🐛 Fix dashboard grid thumbnails cache invalidation. 2022-01-18 16:48:17 +01:00
Andrey Antukh
7afb3e2c6d Stream transit encoding to the response output-stream.
Instead of in-memmory encoding. This will prevent many OOM errors.
2022-01-18 16:48:17 +01:00
Andrey Antukh
9cf5258053 Improve logging on worker and import process. 2022-01-18 16:48:17 +01:00
Andrey Antukh
56dfdaecb7 💄 Add cosmetic improvements on project_menu component. 2022-01-18 16:48:17 +01:00
Andrey Antukh
1d174a4379 🐛 Fix unexpected exception on time equiv impl. 2022-01-18 16:48:17 +01:00
Andrés Moya
2aeded1940 🐛 Show view mode buttons correctly centered in viewer 2022-01-18 13:26:09 +01:00
Andrés Moya
c23691284c 🐛 Show correctly group types label in handoff 2022-01-18 13:26:09 +01:00
Andrey Antukh
f7f6515561 ⬆️ Update exporter dependencies. 2022-01-18 11:25:06 +01:00
Andrey Antukh
438c14d29d ⬆️ Update exporter dependencies. 2022-01-18 10:58:45 +01:00
Andrés Moya
87351000ae 🐛 Avoid marking component as touched when moving into a group 2022-01-18 09:39:02 +01:00
Pablo Alba
0895a69bac Add an option to hide artboards names on the viewport 2022-01-18 09:38:03 +01:00
Eva
4285972e41 Add new zoom options in workspace and viewer mode 2022-01-17 17:18:12 +01:00
Pablo Alba
d33542c4dc Allow decimals on stroke width and positions 2022-01-17 15:52:32 +01:00
Andrey Antukh
bda97adf4f Fix minor issues on error reporting. 2022-01-17 13:09:15 +01:00
Andrey Antukh
b6f460940f ⬆️ Update dependencies. 2022-01-17 13:09:15 +01:00
Alejandro Alonso
aa0e8ed8d6 Pressing enter to exit the fields 2022-01-17 10:00:28 +01:00
Yaron Shahrabani
b99fa16b96 🌐 Add translations for: Hebrew.
Currently translated at 100.0% (868 of 868 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2022-01-15 17:53:28 +01:00
Oğuz Ersen
630d7a3220 🌐 Add translations for: Turkish.
Currently translated at 99.7% (866 of 868 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2022-01-15 17:53:27 +01:00
Andrey Antukh
03c91664cb 🐛 Fix session invalidation issue on password change. 2022-01-14 13:44:58 +01:00
Andrey Antukh
13773d829a Merge pull request #1473 from penpot/alotor/bugfixes_fixes
Bugfixes
2022-01-14 13:33:23 +01:00
Alejandro Alonso
d9e6e9b017 Ability to ignore background when exporting an artboard 2022-01-14 13:31:28 +01:00
alonso.torres
5d8982c734 🐛 Fix transform text 2022-01-14 12:10:55 +01:00
alonso.torres
f13c82da2a 🐛 Fix tab to apply changes in inputs 2022-01-14 11:58:56 +01:00
alonso.torres
363b0ba997 🐛 Fix problems with gradient and libraries 2022-01-14 11:57:07 +01:00
Alejandro Alonso
a4c45942c9 Fixing show color hex or name on hover 2022-01-14 11:57:07 +01:00
Andrey Antukh
a86e3a8636 📎 Sort translation files. 2022-01-14 11:32:43 +01:00
Andrey Antukh
db61d579e6 Merge remote-tracking branch 'weblate/develop' into translations 2022-01-14 11:30:25 +01:00
Andrey Antukh
e6e3f2cbd5 Add debugging pages for download/upload file data. 2022-01-14 10:54:57 +01:00
Andrey Antukh
ffdd539233 Minor improvements on error reporting mechanism. 2022-01-14 10:54:57 +01:00
Andrey Antukh
ef17af38a1 🎉 Add jvm-repl script to frontend directory. 2022-01-14 10:54:57 +01:00
Andrey Antukh
6dedfaea2f 📎 Minor changes on :reg-objects impl. 2022-01-14 10:54:57 +01:00
Andrey Antukh
cbb3783d84 📎 Add performance related helpers. 2022-01-14 10:54:57 +01:00
Eva
327c095d79 add V as shortcut to exit path editor 2022-01-13 22:39:05 +01:00
Andrey Antukh
88e222420c Merge pull request #1470 from penpot/alotor/bugfixes
Bugfixes
2022-01-13 18:33:32 +01:00
alonso.torres
045eec072b 🐛 Fix import/export with SVG edge cases 2022-01-13 18:28:47 +01:00
alonso.torres
5f3c381f88 🐛 Fix thumbnail not taking frame blending mode 2022-01-13 15:56:01 +01:00
alonso.torres
6090cf6c68 🐛 Fix problem with imported svgs with filters 2022-01-13 15:15:55 +01:00
alonso.torres
9ac4239c11 🐛 Restored rules color 2022-01-13 10:09:03 +01:00
alonso.torres
da2a3b6883 🐛 Fix handoff text with opacity 2022-01-13 09:56:46 +01:00
alonso.torres
b4accaad07 🐛 Fix scroll jumps in handoff mode 2022-01-13 09:56:46 +01:00
alonso.torres
edaef0096a 🐛 Improved copy for import/export files 2022-01-13 09:56:46 +01:00
alonso.torres
afba5ff083 🐛 Fix issues when updating selrect in paths 2022-01-13 09:56:46 +01:00
alonso.torres
8b8d614150 🐛 Fixed problem with stroke 2022-01-13 09:30:21 +01:00
Alejandro Alonso
090dbfda10 Upload/dismiss all custom fonts at once 2022-01-12 20:00:24 +01:00
Alejandro Alonso
04f5a6a9f9 Show color hex or name on hover 2022-01-12 18:20:31 +01:00
Andrey Antukh
d8311ac3fa Merge pull request #1462 from penpot/adding-prettier-to-ci
 Adding prettier to ci
2022-01-12 14:25:50 +01:00
Andrey Antukh
9c7f4dfd98 Merge pull request #1452 from penpot/bugfixes
Bugfixes
2022-01-12 14:24:24 +01:00
alonso.torres
8da66e1599 🐛 Fix problem when importing a file with grids 2022-01-12 13:47:50 +01:00
alonso.torres
2927b0cfc6 🐛 Fix default page id in workspace 2022-01-12 13:30:52 +01:00
alonso.torres
4663c296cd 🐛 Fix blur input field when click on viewport 2022-01-12 13:30:52 +01:00
alonso.torres
9403f8fd6e 🐛 Fix lossing changes when changing selection and an input was already changed 2022-01-12 13:13:28 +01:00
alonso.torres
badb5c6a9b 🐛 Fix keep name of component equal to the shape name 2022-01-12 13:13:27 +01:00
alonso.torres
e5430259e9 🐛 Changing pages while comments activated will not close the panel 2022-01-12 13:12:54 +01:00
alonso.torres
50fd44d3f2 🐛 Fix division by zero in bool operation 2022-01-12 13:12:16 +01:00
Andrés Moya
a8249b73b6 🐛 Fix nav to comments in a different page 2022-01-12 13:09:11 +01:00
Andrey Antukh
a15f867059 🐛 Fix open in new tab action on dashboard. 2022-01-12 13:00:50 +01:00
Andrey Antukh
4216e2e92b Properly set host on http server. 2022-01-12 12:42:11 +01:00
Alejandro Alonso
8ef20be9bd Adding prettier to ci 2022-01-12 12:32:06 +01:00
Alejandro Alonso
6413c9dddd Apply prettier to resources styles 2022-01-12 11:49:03 +01:00
Andrey Antukh
eb10f075b9 📎 Add some excludes to uber task. 2022-01-12 11:21:25 +01:00
Andrey Antukh
cd55ed7c8d 📎 Minor improvement on css on error report template. 2022-01-12 10:55:47 +01:00
Andrey Antukh
2fb96a1b7d 📎 Add debug to ws messages. 2022-01-12 10:55:47 +01:00
Andrey Antukh
c48da3d316 ♻️ Refactor backend bundle build process.
Now the final artifact is a single, compiled uberjar.
It considerably improves startup speed.
2022-01-12 10:55:47 +01:00
Andrey Antukh
9488a9a1ad Increase default upload size on docker imates. 2022-01-12 10:55:47 +01:00
Andrey Antukh
2feb22d3bd 🐛 Fix unexpected default cookies behavior on redirectiong to penpot.
The SameSite=Strict on chrome behaves differently than in Firefox and
makes the top-level url redirect not sending cookies if the user is
redirected from other page to penpot. The SameSite=Lax fixes the issue.
2022-01-12 10:55:47 +01:00
Andrey Antukh
f74569506e ⬆️ Update devenv dockerfile. 2022-01-12 10:55:47 +01:00
Andrés Moya
6633d0b4fb Right click on frame title to open its menu 2022-01-11 16:02:50 +01:00
Andrey Antukh
6fb35b40d7 Merge pull request #1445 from penpot/add-artboard-selection
 Add artboard selection
2022-01-11 16:02:12 +01:00
Alejandro Alonso
614d699098 Add artboard selection 2022-01-11 14:03:07 +01:00
Andrés Moya
8f4fbff40f 💄 Set consistent Login label 2022-01-10 16:58:15 +01:00
Andrés Moya
aaf8d2a233 🐛 Correct animations overflow 2022-01-10 16:02:36 +01:00
Andrey Antukh
0eb2336bc6 Merge pull request #1449 from penpot/bugfixes
Bugfixes
2022-01-10 15:36:59 +01:00
Andrey Antukh
f9cc9164b3 Merge pull request #1450 from penpot/fix-interactions-header
🐛 Fix viewer header controls when navigating from workspace
2022-01-10 15:31:28 +01:00
Andrés Moya
238ec60f89 🐛 Fix viewer header controls when navigating from workspace 2022-01-10 14:10:29 +01:00
alonso.torres
363a82d068 🐛 Fix decimal numbers in export viewbox 2022-01-10 13:32:29 +01:00
alonso.torres
4360c1fe4b 🐛 Improved behaviour on text options when not text is selected 2022-01-10 13:32:29 +01:00
alonso.torres
1d575ece06 🐛 Allow import to continue from recoverable failures 2022-01-10 13:32:29 +01:00
alonso.torres
d246788a35 🐛 Fix default state in viewer 2022-01-10 13:32:29 +01:00
alonso.torres
e9fa04dd1b 🐛 Fix problem with styles in the viewer 2022-01-10 13:32:29 +01:00
alonso.torres
8e57932966 🐛 Fix problem with multiple exports 2022-01-10 13:32:29 +01:00
alonso.torres
51ea354bcb 🐛 Fix problem when resizing texts inside groups 2022-01-10 13:32:29 +01:00
alonso.torres
6334520c66 🐛 Fix dotted style in strokes 2022-01-10 13:32:29 +01:00
alonso.torres
6354883a6f 🐛 Fix line-height/letter-spacing inputs behaviour 2022-01-10 13:32:29 +01:00
alonso.torres
477f553675 🐛 Fix problem with booleans 2022-01-10 13:32:29 +01:00
Eva
1ded4b2b28 🐛 Remove gradient if any when applyin solid color from library 2022-01-10 12:33:21 +01:00
eva
16c4116c15 🐛 fix enter key as a way to exit path editing mode 2022-01-07 15:51:50 +01:00
eva
f5cfbce1c2 🐛 Fix add fill and stroke color from palette to groups and components 2022-01-07 15:51:50 +01:00
eva
7bbf98dfb1 🐛 Fix default project name in dashboard Move to option 2022-01-07 15:51:50 +01:00
eva
533cac7881 🐛 Fix text inputs to allow negative values 2022-01-07 15:51:50 +01:00
eva
6afc734e91 🐛 Fix typo in handoff tooltip 2022-01-07 15:51:50 +01:00
eva
c4fb826d89 🐛 Fix crash when pressing Shift 1 in an empty file 2022-01-07 15:51:50 +01:00
Alejandro Alonso
1321bdeac5 Add opacity shortcuts 2022-01-07 11:31:21 +01:00
Alejandro Alonso
e0b7001a09 🐛 Fix default color fill for textx 2022-01-05 11:19:25 +01:00
eva
88120b83bd change reference to issue 2022-01-03 17:29:51 +01:00
eva
a952f7369c Add move shortcut 2022-01-03 17:16:03 +01:00
eva
d4fab3b46c Add alt as mod-key to add stroke color to a shape from color in menu library 2022-01-03 16:39:40 +01:00
eva
06b3499e7d Add detach components in bulk option 2022-01-03 13:43:11 +01:00
Eva Marco
fdd66bd513 Merge pull request #1430 from penpot/bugfixes
Bugfixes
2022-01-03 10:46:22 +01:00
alonso.torres
3b5aaf21fa 🐛 Fix problem when resizing texts 2022-01-03 10:00:17 +01:00
eva
59c46833ed Add penpot look and feel to multiuser pointers 2022-01-03 09:35:46 +01:00
alonso.torres
aee35cb456 🐛 Fix lock/hide elements in context menu when multiples shapes selected 2022-01-03 09:14:58 +01:00
alonso.torres
4a55ee2965 🐛 Fix problem exporting shapes from handoff mode 2022-01-03 09:14:58 +01:00
alonso.torres
4b490e3ca4 🐛 After team onboarding importing a file will import into the team drafts 2022-01-03 09:14:58 +01:00
alonso.torres
6727717d1a 🐛 Fix problem with join nodes 2022-01-03 09:14:58 +01:00
alonso.torres
d08891cffa 🐛 Disable running frontend tests in node 2022-01-03 09:10:18 +01:00
Andrey Antukh
799a83ba73 🔥 Remove unused import. 2021-12-31 13:47:49 +01:00
Andrey Antukh
261724e555 📎 Fix common tests. 2021-12-31 13:46:13 +01:00
Andrey Antukh
10e7d660ef Merge branch 'main' into develop 2021-12-31 13:23:42 +01:00
Andrey Antukh
bdfea7cda5 📎 Update version.txt file. 2021-12-31 13:04:15 +01:00
Andrey Antukh
fdb1c5e1f9 📎 Minor changes on error report http handler. 2021-12-31 12:24:46 +01:00
alonso.torres
71734df489 Backport changes from develop. 2021-12-31 12:06:15 +01:00
Andrey Antukh
071b81eadd ⬆️ Update dependencies. 2021-12-31 00:21:38 +01:00
Andrey Antukh
2abe3fde71 Remove unused stacktrace from commit-chages event. 2021-12-31 00:02:19 +01:00
Andrey Antukh
27e64ccaa8 🔥 Remove unused code. 2021-12-30 23:53:33 +01:00
Andrey Antukh
c9185f265c Add error report list template. 2021-12-30 23:51:39 +01:00
Andrey Antukh
79e5716f36 📎 Fix linter issues. 2021-12-30 19:43:05 +01:00
Andrey Antukh
9f0e156916 Improve error reporting. 2021-12-30 19:39:32 +01:00
Andrey Antukh
d24d45f4cb Terminate connection if incoming message cant be parsed. 2021-12-30 16:28:08 +01:00
Andrey Antukh
bf55250ae9 :sparkles Minor changes on websockets error handling. 2021-12-30 16:25:50 +01:00
Andrey Antukh
36016ad9ef Store changes on file_change table as vector. 2021-12-30 16:02:06 +01:00
Andrey Antukh
bf66b81702 Move dbg error http entrypoint handler to debug ns. 2021-12-30 16:01:36 +01:00
Andrey Antukh
758ffbf217 Add authentication to dbg entry points. 2021-12-30 16:00:55 +01:00
Andrey Antukh
f24563503a Parametrice file change snapshoting. 2021-12-30 13:03:49 +01:00
alonso.torres
a2dbc40571 🐛 Fixes problem with mov-objects 2021-12-30 13:03:49 +01:00
Andrey Antukh
a096b0777f 📎 Minor changes on debug ns. 2021-12-30 13:03:28 +01:00
Andrey Antukh
87690a534c 🎉 Add missing files. 2021-12-30 11:45:44 +01:00
Andrey Antukh
a70e416b0b Add more debug handlers. 2021-12-30 11:37:09 +01:00
alonso.torres
cd1170c543 Add a debug option to apply changes 2021-12-30 11:19:46 +01:00
Andrey Antukh
2962dc1faa Merge pull request #1429 from penpot/cache-thumbnails-v2
 Cache thumbnails with browser cache
2021-12-29 13:03:40 +01:00
alonso.torres
535c1fd007 Cache thumbnails with browser cache 2021-12-29 12:59:17 +01:00
Andrey Antukh
2bd94aff0e 🐛 Fix wring metrics usage on websocket module. 2021-12-29 12:55:41 +01:00
Andrey Antukh
9ea90c3400 🐛 Fix unexpected exception on websockets. 2021-12-29 12:16:48 +01:00
alonso.torres
0ac5d85117 🐛 Rollback thumbnail cache 2021-12-29 11:40:37 +01:00
Andrey Antukh
d3a83142ae Merge pull request #1428 from penpot/performance
 Improved thumbnails handling
2021-12-29 11:33:39 +01:00
alonso.torres
d5886123d8 Improved thumbnails handling 2021-12-29 11:21:57 +01:00
Andrey Antukh
dea090e7d3 📚 Update version.txt file. 2021-12-29 11:17:55 +01:00
Andrey Antukh
ba5e345677 Merge branch 'staging' 2021-12-29 11:17:06 +01:00
Andrey Antukh
13ae7b0976 📚 Update changelog. 2021-12-29 11:16:04 +01:00
Andrey Antukh
39c7bfb49f ⬆️ Finally update the prometheus client dependency. 2021-12-29 09:52:32 +01:00
Andrey Antukh
8479a6581d Make matrix use native doubles.
Increases 300x the performance of multiply on the JVM platform.
2021-12-29 09:52:32 +01:00
Andrey Antukh
e5885e83eb Add missing type hints on Matrix multiply function. 2021-12-29 09:52:32 +01:00
Andrey Antukh
914b41fcd4 🐛 Properly handle missing log/error id on database logger. 2021-12-29 09:52:32 +01:00
Andrey Antukh
224aa5b89a 🐛 Properly handle errors on body parsing middleware. 2021-12-29 09:52:32 +01:00
Andrey Antukh
01c89f6554 🐛 Set proper return value on validation error handler. 2021-12-29 09:52:32 +01:00
Andrey Antukh
f0e1bc1d59 🔥 Remove unused code. 2021-12-29 09:52:32 +01:00
Andrey Antukh
7b487e1bc3 📎 Fix unrelated linter issues. 2021-12-29 09:52:32 +01:00
Andrey Antukh
c394495a26 ♻️ Refactor websocket layer.
This commit replaces rj9a with funcool/yetti ring adapter.
Cleans the websocket api and makes it fully asynchronous.
Also a common websocket protocol abstraction that will allow
more easy path for creating new websocket based services.
2021-12-29 09:52:32 +01:00
eva
6dae420254 Add open components, and scroll into view to show main component acction 2021-12-28 16:19:07 +01:00
Andrey Antukh
c69d7f50a3 Merge remote-tracking branch 'origin/staging' into develop 2021-12-28 13:48:16 +01:00
elhombretecla
e9c654f30d Minor enhacements on onboarding modal. 2021-12-28 11:34:21 +01:00
Andrey Antukh
ae9b95f81b Merge pull request #1424 from penpot/performance
Performance improvements
2021-12-28 11:32:55 +01:00
Andrey Antukh
c240b69b5a 📎 Minor changes on error report template. 2021-12-28 11:19:38 +01:00
alonso.torres
493a7680e0 Improve interactions and after-review fixes 2021-12-28 11:12:33 +01:00
alonso.torres
c28a2acfc7 🐛 Fixes tests 2021-12-28 09:18:33 +01:00
alonso.torres
60af960f42 Thumbnail cache on dashboard 2021-12-28 09:18:33 +01:00
alonso.torres
4c86d5cfe3 🐛 Fixes some issues with bool shapes 2021-12-28 09:18:33 +01:00
alonso.torres
99a6142134 Improves boolean performance 2021-12-28 09:18:33 +01:00
alonso.torres
b2211aec59 Change resize to use DOM transformations 2021-12-28 09:18:33 +01:00
alonso.torres
fa09fff2b5 Performance improvements 2021-12-28 09:18:33 +01:00
Andrey Antukh
0204cdab83 🐛 Revert rj9a dep update. 2021-12-28 09:15:31 +01:00
Andrey Antukh
445195e9eb ⬆️ Update dependencies. 2021-12-27 11:55:42 +01:00
Andrey Antukh
7f5b0f359c Merge remote-tracking branch 'origin/staging' into develop 2021-12-27 11:42:44 +01:00
Andrey Antukh
d8f4176487 📎 Minor fixes on versions. 2021-12-27 11:41:13 +01:00
Andrey Antukh
220ab22115 🐛 Fix error reporting hook. 2021-12-27 11:30:22 +01:00
Andrey Antukh
67776c46d6 🐛 Fix NPE on email complains checking. 2021-12-27 11:13:08 +01:00
Andrey Antukh
2d118ecc65 Merge remote-tracking branch 'origin/staging' into develop 2021-12-27 09:50:15 +01:00
Andrey Antukh
4bc2d7444d 📎 Minor changes on dev tools. 2021-12-27 09:32:20 +01:00
Andrey Antukh
5c6d72b353 Improve logging performance and format. 2021-12-24 12:40:44 +01:00
Andrey Antukh
1839397ebc Minor enhacements on log processing. 2021-12-23 18:36:58 +01:00
Andrey Antukh
0ee34637f5 💄 Improve json namespace API (and fix linter). 2021-12-23 00:06:10 +01:00
Andrey Antukh
c6054f7ab2 💄 Improve json namespace API (and fix linter). 2021-12-23 00:04:37 +01:00
Andrey Antukh
9554dfbc5e :lisptick: Cosmetic changes. 2021-12-22 19:04:32 +01:00
Andrey Antukh
98d5789b1b :lisptick: Cosmetic changes. 2021-12-22 19:04:03 +01:00
Andrey Antukh
0cad1a1e7e Merge remote-tracking branch 'origin/staging' into develop 2021-12-22 18:49:46 +01:00
Andrey Antukh
31c07274cd 📎 Increase default session expiration to 15 days. 2021-12-22 18:44:49 +01:00
Andrey Antukh
37a736339e 🔥 Remove ALPHA and BETA batges. 2021-12-22 18:41:06 +01:00
Andrey Antukh
869abcc835 🐛 Fix incorrect grid calculation when size is 1. 2021-12-22 18:38:22 +01:00
Andrey Antukh
a6f05ea8c2 💄 Minor syntax cosmetic changes. 2021-12-22 18:37:29 +01:00
Andrey Antukh
6812099900 Simplify frames selection mechanism. 2021-12-22 18:37:08 +01:00
Andrey Antukh
53e6d7ef2a 🐛 Fix numeric-input component. 2021-12-22 17:06:59 +01:00
Andrey Antukh
c2f604cd01 Properly use take-until on shape movement streams. 2021-12-22 17:06:16 +01:00
Andrey Antukh
888ffa1bcd Merge remote-tracking branch 'origin/staging' into develop 2021-12-22 16:10:24 +01:00
Andrey Antukh
d06cfed50e 🐛 Add missing import. 2021-12-22 15:01:46 +01:00
Andrey Antukh
e06d063946 📎 Remove ALPHA label from feedback button. 2021-12-22 14:59:39 +01:00
Andrey Antukh
634ec1b113 Ensure valid messages on zmq listener. 2021-12-22 14:28:09 +01:00
Andrey Antukh
0bf883d5b2 📎 More updates to logging deps. 2021-12-22 14:09:23 +01:00
Andrey Antukh
c6d0e0124f ⬆️ Update log4j2 dependency to 2.17.0 2021-12-22 11:34:07 +01:00
Andrey Antukh
ce115c53e2 📎 Minor fixes on repl script. 2021-12-22 11:33:53 +01:00
Andrey Antukh
7014bc7a3c 🐛 Fix issue when typography name is empty. 2021-12-22 11:03:11 +01:00
Andrey Antukh
219f9c478d 🎉 Add version 4 of blob encoding.
The version 4 starts using the hight performance fressian
binary encoding with very lightweight compression layer.
2021-12-21 16:19:25 +01:00
Andrey Antukh
a9904c6ada Merge pull request #1386 from penpot/animations
Animations
2021-12-21 15:47:51 +01:00
Andrés Moya
81cbc33dbb 🎉 Add animations to interactions 2021-12-21 15:46:52 +01:00
eva
24062beebe 🐛 fix minor errors 2021-12-20 16:18:33 +01:00
eva
f3548aff8c Add shortcuts to viewer btns 2021-12-20 16:18:33 +01:00
eva
771bb20976 Add shortcuts for dashboard viewer and path 2021-12-20 16:18:33 +01:00
eva
8072caeff1 Add more workspace shortcuts 2021-12-20 16:18:33 +01:00
eva
d5568fcc25 Add alignement shortcuts 2021-12-20 16:18:33 +01:00
Andrey Antukh
eb1bcfba83 🎉 Backport questions form integration.
Among other related that need to be ported.
2021-12-20 16:16:29 +01:00
Andrey Antukh
a2d3616171 📎 Update changelog. 2021-12-20 11:55:32 +01:00
Andrey Antukh
a83e37493a ⬆️ Update log4j2 dependency. 2021-12-20 11:52:32 +01:00
Andrey Antukh
0feccc9d1c ⬆️ Update log4j2 dependency. 2021-12-20 11:49:30 +01:00
Andrey Antukh
e18ecb8c49 ⬆️ Update devenv (deps). 2021-12-20 11:16:26 +01:00
Andrés Moya
f5b87a9865 📚 Add docs contributor 2021-12-19 17:56:37 +01:00
Andy Li
3b93434dd3 🌐 Add translations for: Chinese (Traditional).
Currently translated at 33.5% (283 of 843 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/
2021-12-18 02:51:35 +01:00
Muhammad Insan Al-Amin
d522096caf 🌐 Add translations for: Indonesian.
Currently translated at 7.3% (62 of 843 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/id/
2021-12-18 02:51:35 +01:00
Andrey Antukh
6c67110dde 🐛 Fix middleware order that prevents multipart requests. 2021-12-10 15:06:42 +01:00
Andrey Antukh
963efc369b Merge branch 'main' into develop 2021-12-10 14:45:02 +01:00
Andrey Antukh
384f0a05c6 🐛 Fix race condition issues on workspace. 2021-12-10 12:32:10 +01:00
Andrey Antukh
a3016b8400 Make the media uploading idempotent. 2021-12-10 12:19:12 +01:00
Andrey Antukh
0df219c3ad 📎 Fix frontend tests. 2021-12-10 12:00:29 +01:00
Andrey Antukh
a0d527f795 📎 Add some minor changes to package.json scripts section. 2021-12-10 12:00:29 +01:00
Andrey Antukh
e44ea47497 🐛 Fix issue when typography name is empty. 2021-12-10 12:00:29 +01:00
Andrey Antukh
9ee5a3159c 💄 Add cosmetic changes on ungroup event. 2021-12-10 12:00:29 +01:00
Andrey Antukh
06d41c552b Simplify debuging information used on commit-changes. 2021-12-10 12:00:29 +01:00
Andrey Antukh
7874971550 🐛 Fix race condition issues on workspace. 2021-12-10 12:00:29 +01:00
Andrey Antukh
9925716134 💄 Add syntax improvements on libraries helpers. 2021-12-10 12:00:29 +01:00
Andrey Antukh
64c456678b Merge pull request #1401 from penpot/fix-destination
🐛 Fix error importing file with null destination in one interaction
2021-12-10 11:21:27 +01:00
Andrés Moya
16ed09a303 🐛 Fix error importing file with null destination in one interaction 2021-12-10 10:50:18 +01:00
Andy Li
1359a1aa7a 🌐 Add translations for: Chinese (Traditional).
Currently translated at 31.4% (265 of 843 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/
2021-12-10 09:52:18 +01:00
Andy Li
6ae36982b6 🌐 Add translations for: Chinese (Traditional).
Currently translated at 30.6% (258 of 843 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/
2021-12-09 05:50:27 +01:00
Maemolee
136d269605 🌐 Add translations for: Chinese (Simplified).
Currently translated at 80.6% (680 of 843 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2021-12-09 05:50:27 +01:00
Rubén
932c0ed4ad 🌐 Add translations for: Spanish.
Currently translated at 98.5% (831 of 843 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/
2021-12-09 05:50:26 +01:00
Andy Li
371875440f 🌐 Add translations for: Chinese (Traditional).
Currently translated at 25.7% (217 of 843 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/
2021-12-07 09:12:57 +01:00
Rubén
b01a9f2f95 🌐 Add translations for: Catalan.
Currently translated at 99.6% (840 of 843 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ca/
2021-12-06 12:26:52 +01:00
李安峻
0d2def102f 🌐 Add translations for: Chinese (Traditional).
Currently translated at 23.4% (198 of 843 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/
2021-12-04 03:52:36 +01:00
Andrey Antukh
beff3fe843 📎 Fix common tests. 2021-12-03 16:00:56 +01:00
Andrey Antukh
7a97c94f2b Merge branch 'main' into develop 2021-12-03 15:53:20 +01:00
Andrey Antukh
ce81908f02 📎 Minor changes on devenv. 2021-12-03 15:05:36 +01:00
Andrey Antukh
f8cecfd61f 🐛 Fix unexpected behavior of grid options on right sidebar. 2021-12-03 14:52:40 +01:00
Andrey Antukh
8a2a1d6d70 ♻️ Ensure a correct usage of concat/into operations. 2021-12-03 14:52:40 +01:00
Andrey Antukh
76dafea8a6 Add the abilty to enable verbose output using query param (api). 2021-12-03 11:13:08 +01:00
Andrey Antukh
86bbfde19e 📎 More fixes on ci config. 2021-12-03 11:09:01 +01:00
Andrey Antukh
71d6f7b1a2 📎 Fix linter issues. 2021-12-03 09:43:02 +01:00
Andrey Antukh
0c0ab612c0 📎 Minor fixes to previous commits. 2021-12-03 09:36:39 +01:00
Andrey Antukh
73042115e0 🎉 Add benchmark helper function (cljs only). 2021-12-03 09:30:33 +01:00
Andrey Antukh
0f7166d34a 📎 More fixes on CI. 2021-12-02 18:20:45 +01:00
Andrey Antukh
f35f2c95f0 📎 Fix issues with jvm on CI. 2021-12-02 18:15:44 +01:00
Andrey Antukh
4d280bdb6d Merge branch 'test-e2e' into develop 2021-12-01 16:14:46 +01:00
Andrey Antukh
47acab766d ⬆️ Update to OpenJDK17. 2021-12-01 16:13:46 +01:00
Andrés Moya
1cc3819e65 🧪 Add e2e tests with Cypress 2021-12-01 16:13:46 +01:00
Andrey Antukh
16fa6259ea 🚧 Update docker deps and start working on test coverage. 2021-12-01 16:13:46 +01:00
Andrey Antukh
95717c4c32 Merge pull request #1378 from penpot/performance
Performance Improvements
2021-12-01 14:43:43 +01:00
alonso.torres
7564f27f95 Improvements after review 2021-12-01 14:39:20 +01:00
alonso.torres
565046aaa6 Memoize transform-shape 2021-12-01 14:39:20 +01:00
alonso.torres
fb9b023fae Improve selection performance 2021-12-01 14:39:20 +01:00
alonso.torres
b05908a760 Improved performance for options and area selection 2021-12-01 14:39:20 +01:00
alonso.torres
3bbcd235e1 Improved selection rect calculation 2021-12-01 14:39:20 +01:00
alonso.torres
9d66984c62 Improved set-modifiers-recursive and some utils memoization 2021-12-01 14:39:20 +01:00
alonso.torres
9024408ed2 Improved frame defered handling 2021-12-01 14:39:20 +01:00
alonso.torres
2b32e864fd Performance improvements 2021-12-01 14:39:20 +01:00
alonso.torres
626d0cba46 🐛 Add to spec opacity and blend-mode 2021-12-01 14:39:20 +01:00
alonso.torres
2a11e9962d ♻️ Moved debug utils to debug namespace 2021-12-01 14:39:20 +01:00
Andrés Moya
7dffddd437 📖 Add doc contributors to CHANGES.md 2021-12-01 12:48:48 +01:00
Andrey Antukh
6a7600fd52 ♻️ Ensure a correct usage of concat/into operations. 2021-12-01 11:30:48 +01:00
Andrey Antukh
b897f202dd 📎 Minor changes on exporter shadow-cljs config. 2021-11-30 13:11:52 +01:00
Andrey Antukh
eb396f2367 🐛 Properly show message on password recovery request.
Additionally, add the ability to reset form passing `nil`
on `reset!` function of Atom interface.
2021-11-30 09:44:03 +01:00
Andrey Antukh
95bf3e3af4 🐛 Fix issues related to signup questions form. 2021-11-30 09:44:03 +01:00
Andrey Antukh
19944202fb Handle properly the user redirect after login. 2021-11-30 09:44:03 +01:00
Andrey Antukh
2596ad27c3 ♻️ Minor refactor of auth data-flow.
This fixes many issues related to using penpot on-premise
instances on different domain than localhost. This changes
ensures correct data flow of authenticated and not authenticated
sessions.
2021-11-30 09:44:03 +01:00
Andrey Antukh
ece914303a ⬆️ Update exporter dependencies. 2021-11-29 15:53:24 +01:00
eva
7a0c12e073 ♻️ Create a colors file to save constants of color that can no be refactored into sass variables 2021-11-29 11:21:12 +01:00
eva
14b23b491f use those new css variables in our cljs files 2021-11-29 11:21:12 +01:00
eva
039b03249b Create css variables fron our sass varibles to use them in cljs files 2021-11-29 11:21:12 +01:00
Andrés Moya
3919cf4f86 🐛 Fix resize behavior of masked groups 2021-11-25 15:53:09 +01:00
Yaron Shahrabani
319a9fd2de 🌐 Add translations for: Hebrew.
Currently translated at 100.0% (843 of 843 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2021-11-25 14:50:58 +01:00
Oğuz Ersen
cf1f9f93aa 🌐 Add translations for: Turkish.
Currently translated at 99.7% (841 of 843 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2021-11-25 14:50:57 +01:00
Andrey Antukh
0dd805da7f Improve team deletion process. 2021-11-25 14:19:06 +01:00
Andrey Antukh
e7a1833c44 🐛 Add missing "solid" font-weight parsing style.
Related issue: #1338
2021-11-25 14:19:06 +01:00
Andrey Antukh
54f8487b46 Make team leave & reassing as atomic operation. 2021-11-25 14:19:06 +01:00
alonso.torres
b68d721b39 🐛 Fix problem when importing in shared libraries 2021-11-25 13:28:37 +01:00
Andrey Antukh
b9ccb4e52c 🐛 Fix issues on login being logged. 2021-11-25 13:24:40 +01:00
Andrey Antukh
c4a11f73a0 ⬆️ Update deps. 2021-11-25 13:24:40 +01:00
Andrey Antukh
f96d4198c3 📎 Add scripts/repl to common. 2021-11-25 13:24:40 +01:00
Andrey Antukh
fe6a0ec5b8 📎 Fix linter issues. 2021-11-25 13:24:40 +01:00
Andrey Antukh
e7b4010eba 🐛 Fix undo on page relocate/sorting. 2021-11-25 13:24:40 +01:00
elhombretecla
c4947d3737 Add basic css sf structure 2021-11-25 13:24:40 +01:00
Andrey Antukh
8a8d677f85 🎉 Integrate questions form. 2021-11-25 13:24:40 +01:00
eva
baf4393310 🔨 Fix some spanish terms 2021-11-24 16:42:48 +01:00
eva
723916d930 Allow ungroup groups in bulk and unmask mask in group 2021-11-24 16:42:48 +01:00
Andrey Antukh
591d66564d 📎 Set develop branch to version 1.11.0-beta. 2021-11-24 13:23:36 +01:00
Andrey Antukh
79a2d522bf Merge remote-tracking branch 'origin/main' into develop 2021-11-24 13:23:14 +01:00
Andrey Antukh
4ad34ab5c8 📎 Update version number. 2021-11-24 13:06:36 +01:00
Andrey Antukh
33c7847dfc 🐛 Fix team deletion flow on dashboard. 2021-11-24 13:05:54 +01:00
alonso.torres
7a04f15710 🐛 Fix problems with team management. 2021-11-24 13:05:48 +01:00
Andrey Antukh
7e5b10eb3e 🐛 Fix team deletion flow on dashboard. 2021-11-24 12:45:06 +01:00
alonso.torres
896a07fa9a 🐛 Fix problems with team management 2021-11-24 12:27:14 +01:00
Andrey Antukh
07e8bb00fb Improve debug information of oidc provider. 2021-11-23 16:41:31 +01:00
Andrey Antukh
5d2742dd37 📎 Sort translation files. 2021-11-23 14:29:09 +01:00
Andrey Antukh
9ae3f1eb68 Merge remote-tracking branch 'weblate/develop' into translations 2021-11-23 14:27:29 +01:00
Andrey Antukh
8c6e0cf43a Merge branch 'jsoref-spelling' into develop 2021-11-23 14:23:29 +01:00
Andrey Antukh
1e220fd506 📎 Update changes.md file. 2021-11-23 14:22:46 +01:00
Andrey Antukh
4ff7855fd4 📎 Adapt translation files to spelling fixes. 2021-11-23 14:21:05 +01:00
Andrey Antukh
eb57354109 📎 Adapt migrations file. 2021-11-23 14:19:48 +01:00
Andrey Antukh
a82a33cecf Merge branch 'spelling' of https://github.com/jsoref/penpot into jsoref-spelling 2021-11-23 14:02:16 +01:00
Andrés Moya
c90fc2a9bf :white-check-mark: Add tests for interaction model and some fixes 2021-11-23 13:58:43 +01:00
Paul Schulz
c1a40e4aeb Add preset for ReMarkable 2 screen size
Logged in issue: https://tree.taiga.io/project/penpot/issue/2298

This page size (840x1120)(for the ReMarkable2 was tested with an exported PDF
test page, and does not appear to correspond directly to the resolution of the
device.

Signed-off-by: Paul Schulz <paul@mawsonlakes.org>
2021-11-23 13:57:26 +01:00
Andrey Antukh
9999b8bfab 📎 Update email on contributing file. 2021-11-22 15:42:50 +01:00
Andrey Antukh
cf62008acf Merge remote-tracking branch 'origin/main' into develop 2021-11-22 08:59:48 +01:00
Andrey Antukh
1c959a6653 📎 Add commented keycloak docker compose entry. 2021-11-22 08:57:20 +01:00
Andrey Antukh
b8043a2432 📎 Update ci config. 2021-11-18 17:19:55 +01:00
Andrey Antukh
ed5de525aa 📎 Increase default db pool size to 50. 2021-11-18 17:19:55 +01:00
Andrey Antukh
8105d9388b ♻️ Refactor rlimit usage (backend). 2021-11-18 17:19:55 +01:00
Andrey Antukh
8151dcc05f 📎 Improve services defmethod linter hook. 2021-11-18 17:19:55 +01:00
Andrey Antukh
25b1c5fe90 📎 Minor update on feedback module. 2021-11-17 14:46:18 +01:00
Andrés Moya
f566d2a0da 🐛 Remove unneeded style causing visual glitch 2021-11-17 14:06:15 +01:00
Andrey Antukh
ea218839e4 Minor change on error pruning mechanism. 2021-11-17 11:10:28 +01:00
Andrey Antukh
4c18a1881b 📎 Minor change on feedback subject template. 2021-11-17 11:10:04 +01:00
Andrey Antukh
0be2b2791f ♻️ Refactor error handling on exporter browser module. 2021-11-17 10:09:36 +01:00
Andrey Antukh
bf51e3db60 Invalidate all other sessions on password change. 2021-11-17 10:09:36 +01:00
Andrey Antukh
abca69f408 🐛 Fix tab reuse issue (viewer <-> workspace). 2021-11-17 10:09:36 +01:00
Andrey Antukh
6eac9102c9 🐛 Prevent conflict errors just ignoring repeated requests.
On media image uploading.
2021-11-17 10:09:36 +01:00
Andrey Antukh
0a7da1b7f2 🔥 Remove unused var. 2021-11-17 10:09:36 +01:00
Andrey Antukh
b4361cb202 📎 Increase idle-in-transaction default timeout. 2021-11-17 10:09:36 +01:00
Andrey Antukh
d2d4090e27 🐛 Don't raise exception when profile is not found. 2021-11-17 10:09:36 +01:00
Josh Soref
583eb53c9d 🐛 Fix typos 2021-11-15 09:54:36 -05:00
Josh Soref
39246f2beb 🐛 Fix typos in common 2021-11-15 09:53:42 -05:00
Josh Soref
cd2d3d5fa3 🐛 Fix typos in backend 2021-11-15 09:53:10 -05:00
Josh Soref
589e646023 🐛 Fix typos in frontend 2021-11-15 09:51:34 -05:00
Maciek Baron
b7ba3098ae 🐛 Fix typo in workspace.scss
Correct the spelling of 'width' to ensure it is set to '100%' properly and does not default to 'auto'.
2021-11-15 12:50:52 +01:00
Andrés Moya
631c5ecae3 💄 Change word in german for a shorter one 2021-11-15 12:50:07 +01:00
Andrés Moya
4962e45bd9 Change tool for exporting artboards and fix some issues 2021-11-15 11:55:58 +01:00
Andrey Antukh
c57219a356 📎 Allow specify the build optimization leven using env vars. 2021-11-12 13:31:28 +01:00
Andrey Antukh
03e6a187c5 Merge branch 'main' into develop 2021-11-12 13:31:13 +01:00
Andrey Antukh
0bdbbd35e3 📎 Fix linter issues. 2021-11-12 12:37:38 +01:00
Andrey Antukh
401afe7c1a 📎 Change loggling level on oauth ns. 2021-11-12 12:37:34 +01:00
Andrey Antukh
66b0039566 📎 Fix linter issues. 2021-11-12 12:36:32 +01:00
Andrey Antukh
17da51440c 📎 Change loggling level on oauth ns. 2021-11-12 12:34:50 +01:00
Andrés Moya
c5adeecd90 🐛 Fix problems importing files 2021-11-12 12:34:26 +01:00
Andrey Antukh
da6c62414b Merge remote-tracking branch 'origin/beta-release-info' 2021-11-11 13:54:01 +01:00
Andrey Antukh
6650fe863f 📎 Fix linter issues. 2021-11-11 13:28:02 +01:00
Andrey Antukh
76c00c42b5 📎 Update changelog. 2021-11-11 13:25:51 +01:00
Andrey Antukh
f8609419a1 Merge remote-tracking branch 'origin/develop' 2021-11-11 13:23:49 +01:00
Andrey Antukh
250e79eda1 Disable default project loading on demo users. 2021-11-11 13:23:07 +01:00
Andrey Antukh
f7401daeae 📎 Update label on version.txt 2021-11-11 13:22:43 +01:00
Andrey Antukh
7390e372e0 📎 Add missing translations. 2021-11-11 13:22:29 +01:00
Andrey Antukh
239c521ad9 📎 Minor change on gulpfile. 2021-11-11 12:21:21 +01:00
Andrey Antukh
77b4f09cfb 📎 Update onboarding texts. 2021-11-11 12:13:16 +01:00
Andrey Antukh
bb178af278 🐛 Fix import template on recently created team. 2021-11-11 11:49:23 +01:00
Andrey Antukh
3c39661174 📎 Enable _blank target on all markdown links. 2021-11-11 11:31:27 +01:00
Andrés Moya
1fffc1e828 💄 Change placeholder text 2021-11-11 11:09:17 +01:00
Andrey Antukh
ed50cd1fa8 📎 Remove :insecure-register default flag (backend). 2021-11-11 11:00:23 +01:00
Andrey Antukh
ef6a02e8ef ⬆️ Update clk-kondo dependency on devenv. 2021-11-10 23:21:41 +01:00
Andrey Antukh
e7003dde83 Add :insecure-register flag.
This allows on-premise users skip the email validation.
2021-11-10 23:21:41 +01:00
Andrey Antukh
bf2a393fd3 🎉 Add generic retry middleware for rpc methods. 2021-11-10 23:21:41 +01:00
elhombretecla
bb2cfd52f4 Add new wording 2021-11-10 14:52:48 +01:00
Andrés Moya
6a6f88c6ef 📚 Update changelog 2021-11-10 12:17:23 +01:00
elhombretecla
0a2b1a4fbe 🎉 Add new beta onboarding info 2021-11-10 11:53:14 +01:00
Andrey Antukh
5fd48c9e98 📎 Update changelog. 2021-11-10 11:26:28 +01:00
alonso.torres
022d32cd44 🐛 Fix project files count not refreshing correctly after import 2021-11-10 11:08:32 +01:00
alonso.torres
af10cf71db 🐛 Add placeholder to create shareable link 2021-11-10 11:08:32 +01:00
alonso.torres
1bf1de8ce8 🐛 Fix problem in viewer with dropdowns when comments active 2021-11-10 11:08:32 +01:00
alonso.torres
b80ddfa580 🐛 Remove change style on hover for options 2021-11-10 11:08:32 +01:00
alonso.torres
aa276ab308 🐛 Fix viewer comment position when zoom applied 2021-11-10 11:08:32 +01:00
alonso.torres
f50943d470 🐛 Fix max/min values for opacity fields 2021-11-10 11:08:32 +01:00
alonso.torres
959c998664 🐛 Fix a worker error when transforming a rectangle into path 2021-11-10 11:08:32 +01:00
alonso.torres
b6b6b6043c 🐛 Add shortcuts to boolean icons popups 2021-11-10 11:08:32 +01:00
alonso.torres
8e0807d502 🐛 Fix problem when flattening booleans losing styles 2021-11-10 11:08:32 +01:00
alonso.torres
78d027b25e 🐛 Fix problem with text rendering on export 2021-11-10 11:08:32 +01:00
alonso.torres
503f0bee69 🐛 Add ellipsis in long labels for input fields 2021-11-10 11:08:32 +01:00
Andrés Moya
50d756b189 🐛 Disallow to create a redundant component 2021-11-05 16:55:38 +01:00
Andrey Antukh
7c3d71e572 Merge pull request #1320 from penpot/scroll
Preserve Scroll posiition
2021-11-04 15:20:26 +01:00
Andrey Antukh
bf895d26b0 📎 Port from develop fixes to frontend build script. 2021-11-04 11:00:22 +01:00
Andrey Antukh
5530e8581a Merge remote-tracking branch 'origin/main' into develop 2021-11-04 10:48:47 +01:00
Andrés Moya
f913816d87 🎉 Add preserve scroll option 2021-11-04 10:39:16 +01:00
Andrés Moya
3d59d31b0a 🐛 Fix horizontal scrollbar hidden 2021-11-04 10:37:56 +01:00
alonso.torres
9a66f26bd9 🐛 Fix problem with inner stroke 2021-11-04 10:36:51 +01:00
Andrey Antukh
d5b6605ce8 🐛 Fix issue on translation files. 2021-11-04 10:33:05 +01:00
Andrey Antukh
38e5184be4 📎 Minor fix on frontend build script. 2021-11-04 10:17:19 +01:00
Andrey Antukh
369ec9f814 📎 Fix on previous commit. 2021-11-04 09:43:40 +01:00
Andrey Antukh
620b454c49 📎 Minor changes on build script resource management. 2021-11-04 09:43:03 +01:00
Andrey Antukh
2e5040e65d Don't load initial project on profile creation. 2021-11-04 09:23:14 +01:00
Andrey Antukh
71fe7ef125 📎 Add better auditlog event for profile email change event. 2021-11-04 09:23:14 +01:00
Andrey Antukh
e0e8fd7ddc 📎 Increment version number. 2021-11-04 09:23:14 +01:00
Andrey Antukh
01b4b4933e Update devenv nginx config. 2021-11-04 09:23:14 +01:00
Andrey Antukh
fced22bc60 🎉 Add new onboarding flow. 2021-11-04 09:23:14 +01:00
Andrey Antukh
898ae64a57 ⬆️ Update frontend dependencies. 2021-11-04 09:23:14 +01:00
Andrey Antukh
8d50852cbe Minor imrovements on general purpose specs naming. 2021-11-04 09:23:14 +01:00
Andrey Antukh
a11c7b10ac 🔥 Remove deprecated fixtures related code. 2021-11-04 09:23:14 +01:00
Andrey Antukh
fe9033b8be Merge branch 'main' into develop 2021-11-03 16:41:55 +01:00
alonso.torres
e26f9e4a71 🐛 Fix problem with arrow lines 2021-11-03 16:41:03 +01:00
alonso.torres
c477328da4 🐛 Fix problem with view mode comments 2021-11-03 13:45:43 +01:00
alonso.torres
214c64c49e 🐛 Fix problem when exporting texts with gradients or opacity 2021-11-03 10:56:42 +01:00
Andrés Moya
bce0e9194c Merge branch 'main' into develop 2021-11-02 11:09:11 +01:00
曹恩逢
40326177fd 🌐 Add translations for: Chinese (Traditional).
Currently translated at 23.2% (192 of 826 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hant/
2021-11-01 04:34:53 +01:00
Yaron Shahrabani
4ab0272fa6 🌐 Add translations for: Hebrew.
Currently translated at 99.8% (825 of 826 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2021-11-01 04:34:52 +01:00
Marius
fb33366c91 🌐 Add translations for: German.
Currently translated at 99.3% (821 of 826 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2021-11-01 04:34:51 +01:00
Oğuz Ersen
75352c9afe 🌐 Add translations for: Turkish.
Currently translated at 99.7% (824 of 826 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2021-11-01 04:34:50 +01:00
Andrey Antukh
a0f98e3823 Merge pull request #1306 from penpot/hotfix-validate-url
 Auto add http prefix to interaction url
2021-10-29 14:11:38 +02:00
Andrés Moya
bff6768adf 🐛 Fix linter error 2021-10-29 13:38:47 +02:00
Andrés Moya
8ce2eb448c Auto add http prefix to interaction url 2021-10-29 13:38:47 +02:00
alonso.torres
7c5d00f8a4 🐛 Fix problem with export 2021-10-28 17:56:51 +02:00
Andrés Moya
30cd499014 Enhance border radius options form 2021-10-28 17:32:57 +02:00
Andrey Antukh
99d173789e Merge pull request #1304 from penpot/bugfix
Bugfix
2021-10-28 17:31:40 +02:00
alonso.torres
ae72db8129 🐛 Fix pages dropdown in viewer 2021-10-28 17:18:17 +02:00
alonso.torres
9437cc1806 🐛 Fix undo stacking when changing color from color-picker 2021-10-28 17:18:17 +02:00
alonso.torres
0e76aa0265 🐛 Fix problem with exporting before the document is saved 2021-10-28 17:18:17 +02:00
Andrey Antukh
756e654d32 📎 Fix linter issues. 2021-10-27 16:16:44 +02:00
Andrey Antukh
78d1c57b7c Merge branch 'staging' 2021-10-27 12:45:53 +02:00
Andrey Antukh
bb27405e8f 🐛 Fix some issues with Arabic translations. 2021-10-27 12:26:21 +02:00
Andrey Antukh
0cfc46b417 Merge remote-tracking branch 'origin/1.9-release-notes' into staging 2021-10-27 12:00:57 +02:00
Yaron Shahrabani
bfb30fe68d 🌐 Add translations for: Hebrew.
Currently translated at 95.8% (792 of 826 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2021-10-27 11:49:02 +02:00
Andrey Antukh
63959b4b22 📎 Update gulpfile (related to i18n changes). 2021-10-27 11:22:24 +02:00
Andrey Antukh
66d086892f 📎 Add Arabic lang to i18n module. 2021-10-27 11:18:28 +02:00
Andrey Antukh
b878570b14 📎 Add hebrew lang to i18n module. 2021-10-27 11:14:55 +02:00
Andrey Antukh
b059610d16 📎 Sort all translation strings. 2021-10-27 11:08:30 +02:00
Andrey Antukh
972aa7f4e3 Merge remote-tracking branch 'weblate/develop' into translations 2021-10-27 11:06:45 +02:00
Andrey Antukh
797c1421da 📎 Upadate changelog. 2021-10-27 11:01:05 +02:00
Andrés Moya
e65cbcba65 🌐 Added translation for: Chinese (Traditional). 2021-10-27 10:58:20 +02:00
Andrés Moya
6d96dd3818 🐛 Fix detach stroke color 2021-10-26 14:44:29 +02:00
Andrés Moya
16db31c53c Ignore constraints when flipping 2021-10-26 14:43:55 +02:00
Andrey Antukh
c72138d15a 📎 Update manage.sh to use 'docker compose' command. 2021-10-26 10:58:23 +02:00
Andrés Moya
6d28a9ad58 🐛 Import files with interactions correctly 2021-10-25 17:59:38 +02:00
Andrés Moya
75c8d97a6e 🐛 Fix vertical flip for nested shapes 2021-10-25 17:55:38 +02:00
elhombretecla
c35f53af89 🎉 Add release onboarding texts 2021-10-21 21:54:54 +02:00
Andrey Antukh
55784f64b8 🎉 Add entrypoint for autogenerated api docs. 2021-10-21 11:31:29 +02:00
alonso.torres
a7241d4128 Change cookie config 2021-10-20 17:12:45 +02:00
Andrey Antukh
1573d794b9 Merge pull request #1290 from penpot/devenv-improvements
Dev Environment improvements
2021-10-20 16:03:24 +02:00
alonso.torres
bc725800ed New docker recipes for a backend only environment 2021-10-20 15:44:08 +02:00
alonso.torres
007728819b Allow CORS backend option and fix frontend to allow it 2021-10-20 15:44:08 +02:00
alonso.torres
f32f13069f Improve test workflow 2021-10-20 15:12:25 +02:00
Andrey Antukh
5ec73da17f Merge pull request #1292 from penpot/bugfix
Bug fixing
2021-10-20 15:05:50 +02:00
alonso.torres
5fd3689333 🐛 Fix no color when boolean with an SVG 2021-10-20 14:59:04 +02:00
alonso.torres
cca1431012 🐛 Fix font size input stuck on selection change 2021-10-20 14:59:04 +02:00
alonso.torres
7ba9558a7a 🐛 Fix masks export area 2021-10-20 14:59:04 +02:00
alonso.torres
c65e8b4a5e 🐛 Fix problem with stroke inside/outside 2021-10-20 14:59:04 +02:00
alonso.torres
eed75bcbda 🐛 Fix stroke cut on shapes export 2021-10-20 14:59:04 +02:00
Andrés Moya
1af4325e8f ♻️ Do a small performance refactor 2021-10-20 14:29:26 +02:00
alonso.torres
5e6719e22e 🐛 Fix paste in place in arboards 2021-10-20 14:22:47 +02:00
Andrés Moya
a4bbfe3c79 Consider overlays inside flows 2021-10-20 14:22:39 +02:00
elhombretecla
63f42fc8bb Add new release onboarding info 2021-10-20 13:24:17 +02:00
elhombretecla
5b9bcf8b1d Update CHANGES.md 2021-10-20 07:58:01 +02:00
Andrey Antukh
f02bc82525 Make permissions subsystem more flexible.
And fix some bugs related to permissions.
2021-10-19 11:36:58 +02:00
Andrés Moya
92f89c6cc1 Enhance duplicating prototype connections 2021-10-18 16:01:20 +02:00
alonso.torres
a1908be982 Update gitignore 2021-10-18 09:53:12 +02:00
alonso.torres
f08894629d ♻️ Refactor routes 2021-10-18 09:40:47 +02:00
Yaron Shahrabani
e46f11e6f8 🌐 Add translations for: Hebrew.
Currently translated at 100.0% (767 of 767 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2021-10-18 09:35:05 +02:00
Yaron Shahrabani
df6234ea28 🌐 Add translations for: Hebrew.
Currently translated at 41.0% (315 of 767 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/
2021-10-15 22:36:19 +02:00
Andrey Antukh
21cdf8b0ae 📎 Enable logging on prod builds. 2021-10-15 14:46:16 +02:00
Andrés Moya
192fb07ef1 🐛 Fix broken references on duplicate file 2021-10-14 18:18:48 +02:00
Andrés Moya
226216d111 🌐 Added translation for: Hebrew. 2021-10-14 14:18:36 +02:00
Andrey Antukh
bed2deb683 Merge pull request #1280 from penpot/hotfixes-20211014
Hotfixes 20211014
2021-10-14 13:07:30 +02:00
Andrey Antukh
6e327b69f5 🐛 Fix bugs on audit log module. 2021-10-14 12:57:27 +02:00
Andrey Antukh
1d3c8e867e 🐛 Minor improvements on file-created event (frontend). 2021-10-14 12:20:40 +02:00
Andrey Antukh
d0f761172a Avoid unnecesary error report on audit module. 2021-10-14 12:14:03 +02:00
Andrés Moya
fd6a8aec71 🐛 Fix docker devenv config 2021-10-14 12:07:33 +02:00
alonso.torres
e00e501605 Fix nrepl connection inside docker 2021-10-13 17:42:40 +02:00
Andrés Moya
81a42ef1df 🐛 Fix flow diaplay when index is still not calculated 2021-10-13 17:41:23 +02:00
Andrey Antukh
ee5eb2abc5 Merge pull request #1277 from penpot/auto-flows
🎉 Enable auto-flows
2021-10-13 16:43:00 +02:00
Andrey Antukh
0ed14f0288 📎 Improve db module api. 2021-10-13 15:05:09 +02:00
Andrey Antukh
c55f740978 Update default timeouts on db namespace. 2021-10-13 14:51:38 +02:00
Andrés Moya
38952b6734 🎉 Enable auto-flows 2021-10-13 14:47:42 +02:00
Andrey Antukh
925058467f Merge pull request #1276 from penpot/bugfixes
Bugfixes
2021-10-13 13:40:07 +02:00
Andrey Antukh
e5afeccadf 🐛 Minor improvements on file-created event (frontend). 2021-10-11 13:51:44 +02:00
alonso.torres
ad18604552 🐛 Fix bug in firefox when a text box is inside a mask 2021-10-11 12:41:35 +02:00
alonso.torres
d2d506dbf0 🐛 Fix problem with mix between open/closed and line/curves 2021-10-11 12:41:35 +02:00
alonso.torres
2833d3126f 🐛 Fix bug with transformation operations 2021-10-11 12:41:35 +02:00
alonso.torres
950367b055 🐛 Fix path stroke is not working properly with high thickness 2021-10-11 12:30:57 +02:00
alonso.torres
703859ac75 Change order of the teams menu so it's in the joined time order 2021-10-11 12:30:57 +02:00
alonso.torres
4bf5434e8f 🐛 Fix problem when ctrl+d when moving 2021-10-11 12:30:57 +02:00
alonso.torres
350c44f56f 🐛 Fix problem when calculating group bounds 2021-10-11 12:30:57 +02:00
alonso.torres
679c630a4d 🐛 Fix shift+wheel to horizontal scrolling in MacOS 2021-10-11 12:30:57 +02:00
alonso.torres
dbbb0a4a3d 🐛 Add stroke width in selection calculation 2021-10-11 12:30:57 +02:00
alonso.torres
0ca7d074ac 🐛 Fix problem with lines and inside/outside stroke 2021-10-11 12:30:57 +02:00
alonso.torres
65894bf582 🐛 Fix problem inheriting attributes from SVG root when importing 2021-10-11 12:30:57 +02:00
alonso.torres
8eacf738c2 🐛 Fix problem with page-options spec 2021-10-11 12:30:29 +02:00
Andrey Antukh
1b69eda43e Add some improvements to the auditlog module. 2021-10-11 12:17:51 +02:00
Andrés Moya
09d1c958ce Include advanced interactions and flows in import/export 2021-10-11 12:17:26 +02:00
Marius
589d16bc37 🌐 Add translations for: German.
Currently translated at 98.4% (755 of 767 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2021-10-08 22:58:54 +02:00
Andrés Moya
a6dfa6bbbd Modify behavior of flows menu in viewer 2021-10-08 09:14:20 +02:00
alonso.torres
b2721305c5 Merge remote-tracking branch 'origin/main' into develop 2021-10-07 17:19:22 +02:00
alonso.torres
24b3404876 🐛 Fix problem importing components 2021-10-07 17:04:55 +02:00
Andrés Moya
92ca1e4873 🐛 Fix permissions in viewer 2021-10-07 15:15:38 +02:00
Andrey Antukh
59d44c41e4 Merge pull request #1263 from penpot/bugfixes
Bugfixes
2021-10-07 13:50:52 +02:00
Andrey Antukh
08a503f160 📎 Rename penpot logo svg file. 2021-10-07 12:04:56 +02:00
alonso.torres
e0e68835ef 🐛 Fix problem duplicating paths 2021-10-07 12:04:37 +02:00
alonso.torres
734287b66d 🐛 Fix stroke caps adjustments in relation with stroke size 2021-10-07 12:04:37 +02:00
alonso.torres
ddc9d30a3e 🐛 Fix zoom context menu in viewer 2021-10-07 12:04:37 +02:00
alonso.torres
890bf9eced 🐛 Fix export group with shadows on children 2021-10-07 12:04:37 +02:00
alonso.torres
b8677b2b9a 🐛 Fix group renaming problem 2021-10-07 12:04:37 +02:00
alonso.torres
1f5e974cfc 🐛 Allow lowercase search for fonts 2021-10-07 12:04:37 +02:00
alonso.torres
54e7e44df1 🐛 Allow three character hex and web colors in color picker hex input 2021-10-07 12:04:37 +02:00
alonso.torres
9736810f87 🐛 Add links for terms of service and privacy policy in register checkbox 2021-10-07 12:04:37 +02:00
alonso.torres
5547383434 🐛 Disable boolean operations when selecting invalid shapes 2021-10-07 12:04:32 +02:00
alonso.torres
85f8e77928 🐛 Fix residual stroke on imported svg 2021-10-07 11:42:53 +02:00
alonso.torres
6918216b86 🐛 Fixed open shapes boolean operations 2021-10-07 11:42:53 +02:00
alonso.torres
efd2ad8f8b 🐛 Fix text editor enter behaviour with centered texts 2021-10-07 11:42:53 +02:00
alonso.torres
5a8ce52105 🐛 Fix problem with booleans empty selrect 2021-10-07 11:42:53 +02:00
alonso.torres
cbee65671c 🐛 Fix problem with masked texts on exporting 2021-10-07 11:42:53 +02:00
alonso.torres
75a7ce24bf 🐛 Fix problem with text margin while rendering 2021-10-07 11:42:53 +02:00
alonso.torres
013f56347d 🐛 Fix undo problem when changing typography/color 2021-10-07 11:42:53 +02:00
alonso.torres
a052bfd2fa 🐛 Fix error screen when operations over comments fail 2021-10-07 11:42:53 +02:00
alonso.torres
4b1fa2589e 🐛 Fix menu context for single element nested in components 2021-10-07 11:42:45 +02:00
alonso.torres
1a61c855ca 🐛 Fix problem with overflow dropdown on stroke-cap 2021-10-07 11:40:31 +02:00
Andrés Moya
0159eea526 🎉 Add interaction flows 2021-10-07 11:40:01 +02:00
Andrey Antukh
f3bb5c55f5 🐛 Show proper 404 when look on not-existing page on viewer. 2021-10-06 15:57:49 +02:00
Andrey Antukh
9ecbddc18c ♻️ Refactor internal handling of profile props. 2021-10-06 15:57:49 +02:00
Andrey Antukh
d36bf188ae 📎 Enable audit-log on devenv. 2021-10-06 15:57:49 +02:00
Andrey Antukh
b8cddbca88 📎 Fix linter issues on frontend. 2021-10-06 15:57:49 +02:00
Andrey Antukh
ee9b7166a6 🔥 Remove deprecated event from routr ns. 2021-10-06 15:57:49 +02:00
Andrey Antukh
9c1c755836 🐛 Fix race-condition on 404 states. 2021-10-06 15:57:49 +02:00
Andrey Antukh
6722ca41bf 🐛 Fix bugs on audit log module. 2021-10-06 15:57:49 +02:00
Andrey Antukh
9586d478ad 📎 Add noindex tags on error report handler. 2021-10-06 15:57:49 +02:00
Andrey Antukh
77cf4a5332 ♻️ Refactor frontend error handling. 2021-10-06 15:57:49 +02:00
elhombretecla
7199ab7cbe 🎉 Font size adjustemts 2021-10-05 13:45:37 +02:00
elhombretecla
5de2ff40d8 🎉 fix size variables 2021-10-05 13:45:37 +02:00
Andrés Moya
790d532cee Hide after-delay trigger in non-frames 2021-10-04 17:14:25 +02:00
alonso.torres
9f03e353c7 Boolean shapes enhancements 2021-10-04 11:07:04 +02:00
alonso.torres
68e3d53cb7 🐛 Fix problem with move+alt 2021-10-04 11:04:51 +02:00
alonso.torres
f9082e18e2 Merge remote-tracking branch 'origin/main' into develop 2021-09-30 11:54:22 +02:00
alonso.torres
02d31a7947 Adds progress report to importing process 2021-09-30 11:48:58 +02:00
Andrey Antukh
3e3faf6576 📎 Fix affected backend tests. 2021-09-30 11:48:14 +02:00
Andrey Antukh
bee1db135f 📎 Fix frontend linter issues. 2021-09-30 11:48:14 +02:00
Andrey Antukh
09d39ca425 🔥 Remove SVG parse rpc query. 2021-09-30 11:48:14 +02:00
Andrey Antukh
d58b6e5117 🔥 Remove deprecated queries. 2021-09-30 11:48:14 +02:00
Andrey Antukh
f0cf3e6411 🔥 Remove deprecated and unused tasks. 2021-09-30 11:48:14 +02:00
Andrey Antukh
b64d5ef357 🎉 Add unified logging api. 2021-09-30 11:48:14 +02:00
Andrey Antukh
2eccf77986 📎 Add data.csv dev dependency to backend. 2021-09-29 14:21:07 +02:00
Andrés Moya
0b8b766b62 Some visual fixes 2021-09-29 12:37:54 +02:00
alonso.torres
8d634a79c8 🐛 Fix problem with content 2021-09-29 12:36:45 +02:00
alonso.torres
4b9e7fdb15 🐛 Fix problem with incorrect content 2021-09-29 12:11:06 +02:00
Andrés Moya
165a84534a 📖 Update changelog 2021-09-29 12:01:00 +02:00
Andrés Moya
fe4cab3a9e Protect external links 2021-09-29 11:54:04 +02:00
Andrey Antukh
9e5166d991 Merge branch 'main' into develop 2021-09-29 11:29:01 +02:00
alonso.torres
48e78125e8 🐛 Fix problems with selection index update 2021-09-29 11:27:37 +02:00
Andrey Antukh
3fb3a92a8f 📎 Minor changes to cron schedule. 2021-09-29 11:06:50 +02:00
Andrey Antukh
8dba55d5cb 📎 Port some auditlog changes from develop. 2021-09-29 10:46:31 +02:00
Andrey Antukh
045a5156d1 📎 Minor changes on internal audit archive task params. 2021-09-29 09:02:02 +02:00
Andrey Antukh
8a162e39d5 Merge pull request #1248 from penpot/fix-interactions
 Change overlay position algorithm and some refactor
2021-09-28 17:28:23 +02:00
Andrés Moya
695788df0e ♻️ Move lens to a more specific site 2021-09-28 17:13:53 +02:00
Andrés Moya
4df96b03eb Change overlay position algorithm and some refactor 2021-09-28 17:09:08 +02:00
alonso.torres
49c2cb985c 🐛 Fix problems with imported svgs 2021-09-28 16:19:36 +02:00
alonso.torres
a189dc8243 🐛 Fixed some problems with booleans and paths 2021-09-28 13:09:19 +02:00
alonso.torres
ff8db0cd77 📚 Updated readme 2021-09-28 12:06:28 +02:00
Andrey Antukh
eff3e4015b 📎 Reduce drastically the auditlog archive task cron schedule. 2021-09-28 12:04:42 +02:00
Andrey Antukh
9ad43e13da Merge pull request #1247 from penpot/feat/bool-shapes
Bool shapes
2021-09-28 11:45:49 +02:00
alonso.torres
1bd3a792da Improved intersection edge cases 2021-09-28 11:30:06 +02:00
alonso.torres
75f8e473a5 Export/Import and edgecases fixing 2021-09-27 21:58:29 +02:00
alonso.torres
8c25ee7796 Fix style for bool shapes 2021-09-27 21:58:29 +02:00
alonso.torres
c3520cf606 Improved intersection calculation 2021-09-27 21:58:29 +02:00
alonso.torres
75d2d97d8e Renderers for booleans 2021-09-27 21:58:29 +02:00
alonso.torres
778a542e1c Removes children when flattening a group or bool shape 2021-09-27 21:58:29 +02:00
alonso.torres
74f3d551f2 Context menu for booleans 2021-09-27 21:58:29 +02:00
alonso.torres
fcc7b6791e Correct selrect calculation 2021-09-27 21:58:29 +02:00
alonso.torres
56e2db22eb Clip boolean selrects 2021-09-27 21:58:29 +02:00
alonso.torres
c56f024a86 Handling groups inside bool shapes 2021-09-27 21:58:29 +02:00
alonso.torres
6fd35ae5d9 Updates selrects, groups to path 2021-09-27 21:58:29 +02:00
alonso.torres
1db2895606 Union,intersection,difference 2021-09-27 21:58:29 +02:00
alonso.torres
df60ee06a1 Add exclusion boolean operation 2021-09-27 21:58:29 +02:00
alonso.torres
0b4b2d3814 Add UI for boolean operations 2021-09-27 21:58:29 +02:00
alonso.torres
9f08153a85 Created bool shapes 2021-09-27 21:58:29 +02:00
alonso.torres
5031700af6 Add utilities to calculate boolean shapes 2021-09-27 21:58:29 +02:00
Andrés Moya
57245dd77e 🎉 Add open url action 2021-09-27 12:37:39 +02:00
Andrés Moya
4697a1904a 🎉 Add previous screen action 2021-09-27 12:37:39 +02:00
Andrés Moya
ed380c86eb 🎉 Add delay interactions trigger 2021-09-27 12:37:39 +02:00
andy
02deecf54b 🌐 Add translations for: Spanish.
Currently translated at 99.6% (764 of 767 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/
2021-09-24 15:39:17 +02:00
Andrey Antukh
133c0312be 🐛 Fix unexpected exception on audit log handler. 2021-09-24 13:04:01 +02:00
Andrey Antukh
45e501ce02 🐛 Don't send incorrect flag values on register page. 2021-09-24 13:04:01 +02:00
Andrey Antukh
87dfa8c7fc 📎 Don't redirect user to landing page on clicking on logo. 2021-09-24 13:04:01 +02:00
Andrey Antukh
edefb588b6 Merge branch 'main' into develop 2021-09-23 13:52:08 +02:00
Andrey Antukh
8ce8b85089 📎 Update version.txt file. 2021-09-23 12:00:07 +02:00
Andrey Antukh
54c409a71c Merge pull request #1239 from penpot/fix/mask-viewer-problem
🐛 Fix problem with masking images in viewer
2021-09-23 11:55:33 +02:00
alonso.torres
2f8960d34f 🐛 Fix problem with masking images in viewer 2021-09-23 11:49:18 +02:00
Andrey Antukh
20036bd72b Merge pull request #1237 from penpot/complex-interactions3
Complex interactions3
2021-09-23 08:36:40 +02:00
Andrés Moya
38a84d4598 🎉 Add new triggers for interactions 2021-09-22 15:57:28 +02:00
Andrés Moya
bc1372c2f9 🎉 Add 'toggle overlay' action (and some fixes) 2021-09-22 14:42:48 +02:00
Andrés Moya
c241100886 Hide guide lines when moving overlay marker 2021-09-22 13:38:53 +02:00
Andrés Moya
fea2d91a63 💄 Adjust size of interaction markers 2021-09-22 12:38:58 +02:00
Andrey Antukh
f2c4aa852d Merge pull request #1225 from penpot/complex-interactions2
Complex interactions2
2021-09-22 11:31:46 +02:00
Andrés Moya
f8d09917a5 💄 Make some design adjustments 2021-09-22 10:54:25 +02:00
Andrés Moya
bbdf1152c1 🎉 Add close on click and background overlays 2021-09-22 10:54:25 +02:00
Andrés Moya
f208731746 🎉 Overlay positions buttons 2021-09-22 10:54:25 +02:00
Andrés Moya
0516cfa296 ♻️ Small refactor of basic specs 2021-09-22 10:54:25 +02:00
Andrés Moya
157e8413fb 🎉 Allow to position interaction overlays 2021-09-22 10:54:25 +02:00
Andrey Antukh
4708af3b91 🎉 Add sentry integration (on exporter). 2021-09-22 10:53:53 +02:00
Andrey Antukh
bee47d7fda ♻️ Remove koa dependency from exporter.
Replaced it with a direct usage of node http
server api and some external helpers for body
and cookies parsing.
2021-09-22 10:53:53 +02:00
Andrey Antukh
d246db7be8 📎 Change default params on exporter pool. 2021-09-20 15:21:26 +02:00
Andrey Antukh
02025bc70a 🎉 Add sentry integration (frontend). 2021-09-20 15:21:26 +02:00
Andrey Antukh
4275298f19 Merge branch 'main' into develop 2021-09-20 13:59:40 +02:00
Andrey Antukh
f0a02e4734 📎 Set version to 1.8.1-alpha. 2021-09-20 13:58:49 +02:00
Andrey Antukh
59464469c2 🐛 Fix unexpected exception on audit log persistence function. 2021-09-20 11:26:02 +02:00
Andrey Antukh
4d880a0d77 🐛 Fix unexpected exception related to some potok issues. 2021-09-20 11:22:56 +02:00
Andrey Antukh
26b28e2364 🎉 Add sentry integration (on backend). 2021-09-17 15:26:18 +02:00
Andrey Antukh
835b597af5 ⬆️ Update backend deps. 2021-09-17 14:34:14 +02:00
Andrey Antukh
c44d22ccf5 ⬆️ Update frontend dependencies. 2021-09-17 14:34:14 +02:00
Andrey Antukh
a11cda91de ⬆️ Update devenv system deps. 2021-09-17 14:34:14 +02:00
Andrey Antukh
cfbbb85254 🐛 Properly handle current team id assignation. 2021-09-17 14:34:14 +02:00
Andrey Antukh
8a0bba3c7a ♻️ Unify flags parsing on backend. 2021-09-17 14:34:14 +02:00
Andrey Antukh
da1135c80f Merge branch 'develop' of github.com:penpot/penpot into develop 2021-09-16 15:15:58 +02:00
Andrey Antukh
7fcf481243 Merge branch 'main' into develop 2021-09-16 11:36:15 +02:00
Andrey Antukh
06e54a17c0 📚 Update some comments on docker config.env file. 2021-09-16 11:35:45 +02:00
Andrey Antukh
1fe23ff732 📎 Minor renaming of config variables. 2021-09-16 11:33:40 +02:00
Andrey Antukh
39278b47dd 🐛 Fix typo in prev commit. 2021-09-16 11:31:54 +02:00
Andrey Antukh
bff0030f2b 🐛 Fix frontend docker image entry point. 2021-09-16 11:28:39 +02:00
Andrés Moya
b4b2f91363 🎉 Add interaction overlays 2021-09-16 07:53:54 +02:00
Andrés Moya
c7252a950b 🎉 Allow complex interactions 2021-09-16 07:53:54 +02:00
Andrey Antukh
e48b01fd18 📎 Add default env variable for browser executable path. 2021-09-15 14:28:57 +02:00
Amine Gdoura
ef2337f6d8 🌐 Add translations for: Arabic.
Currently translated at 68.3% (524 of 767 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/
2021-09-14 15:35:48 +02:00
Andrey Antukh
13d83cb0d1 🐛 Fix incorrect handling of metrics on notifications module. 2021-09-14 10:45:06 +02:00
Andrey Antukh
033355395f 🐛 Fix metrics on ws connections. 2021-09-13 15:00:08 +02:00
Andrey Antukh
6c332b949b 📎 Start new development cycle. 2021-09-13 12:55:52 +02:00
Andrey Antukh
0711438433 Merge branch 'main' into develop 2021-09-13 12:55:36 +02:00
Andrey Antukh
ee6350189f Merge remote-tracking branch 'origin/staging' into main 2021-09-13 12:54:44 +02:00
Andrés Moya
46189c0ff1 🐛 Fix wrong timeout in download artboards message 2021-09-13 12:52:14 +02:00
alonso.torres
45d55e87eb 🐛 Fix problem while moving imported SVG's 2021-09-13 11:56:01 +02:00
alonso.torres
8a158146cd 🐛 Fix problem with import broken images 2021-09-13 11:56:01 +02:00
andy
1a859fc639 🌐 Add translations for: Spanish.
Currently translated at 99.6% (764 of 767 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/
2021-09-11 11:32:54 +02:00
Rubén
43518c6cfe 🌐 Add translations for: Catalan.
Currently translated at 99.0% (760 of 767 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ca/
2021-09-11 11:32:53 +02:00
Andrés Moya
7bfb7b6da0 Merge branch 'staging' into develop 2021-09-10 13:24:29 +02:00
Oğuz Ersen
c0474b206e 🌐 Add translations for: Turkish.
Currently translated at 98.5% (756 of 767 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2021-09-10 12:57:50 +02:00
Andrés Moya
fe6623b342 🐛 Fix interactions in viewer 2021-09-10 12:55:21 +02:00
Andrey Antukh
de8220245c Merge branch 'release-1.8-onboarding' into staging 2021-09-10 11:50:16 +02:00
elhombretecla
562f0d9872 🎉 Update 1.8 release onboarding info 2021-09-10 11:50:01 +02:00
elhombretecla
ed89f858e1 🎉 add new onboarding images 2021-09-10 11:50:01 +02:00
Andrey Antukh
9527b2c456 Merge branch 'staging' into develop 2021-09-10 10:57:15 +02:00
Andrey Antukh
5da2e5e7b7 🎉 Add Catalan language to the supporter languages list. 2021-09-10 10:56:23 +02:00
alonso.torres
e55e5aa168 Merge branch 'staging' into develop 2021-09-09 15:18:53 +02:00
alonso.torres
22b45266bf 🐛 Fix problem with path not closing on escape 2021-09-09 15:08:47 +02:00
Andrey Antukh
b280b5a517 Merge pull request #1194 from penpot/fix-pdf-pages
Fix pdf pages
2021-09-09 14:27:24 +02:00
Andrés Moya
60cb358cce 🐛 Fix extra blank pages when exporting to PDF 2021-09-09 14:11:50 +02:00
Andrey Antukh
f03a74abc7 🐛 Fix next frame shortcut on viewer. 2021-09-09 12:05:15 +02:00
Andrey Antukh
34885b64bd 🐛 Fix style on viewer header. 2021-09-09 11:41:18 +02:00
elhombretecla
f3bfa4e587 Update CHANGES.md 2021-09-09 11:09:21 +02:00
Andrey Antukh
3136ce7dc2 Add missing frame index on viewer. 2021-09-09 11:07:47 +02:00
elhombretecla
15a050517b Update CHANGES.md 2021-09-09 09:28:38 +02:00
Andrey Antukh
85a1c61880 Improve 404 and add broken link static page on viewer. 2021-09-08 13:52:11 +02:00
Andrey Antukh
15991d0226 Merge pull request #1189 from penpot/sequential-export
 Change frame exports to be sequential
2021-09-08 13:48:12 +02:00
Andrés Moya
413bc41695 Change frame exports to be sequential 2021-09-08 13:11:32 +02:00
Andrey Antukh
36137808f0 📎 Sort translation strings. 2021-09-08 12:59:24 +02:00
Andrey Antukh
12c1852297 Merge remote-tracking branch 'weblate/develop' into translations 2021-09-08 12:54:20 +02:00
Andrey Antukh
95e3c3eafc 📎 Enable by default demo users. 2021-09-08 11:14:19 +02:00
Andrey Antukh
c458fa6441 📎 Update changelog. 2021-09-08 11:14:19 +02:00
Andrey Antukh
66c1e386ce 🐛 Fix style issues on share link dialog. 2021-09-08 11:14:19 +02:00
Andrey Antukh
59e203fd52 🐛 Fix messages z-index issue.
Happens when modals and messages are visible
in the same time.
2021-09-08 11:14:19 +02:00
Andrey Antukh
7e0c097f23 🎉 Add linter for check duplicte potok types. 2021-09-07 11:48:14 +02:00
Andrey Antukh
926fa483b9 Improve event registry. 2021-09-07 11:48:14 +02:00
Rubén
2ebc92a167 🌐 Add translations for: Spanish.
Currently translated at 98.8% (679 of 687 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/
2021-09-04 17:33:02 +02:00
Rubén
eb511757db 🌐 Add translations for: Catalan.
Currently translated at 99.7% (685 of 687 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ca/
2021-09-04 17:33:02 +02:00
alonso.torres
b5b97f7626 🐛 Fix problem with multiple selection conflicting with hover 2021-09-03 15:31:45 +02:00
Andrés Moya
ba0f7416bb Add some user feedback while exporting artboards 2021-09-03 15:13:32 +02:00
Andrey Antukh
f6e18de6af Add more metrics to update-file. 2021-09-03 10:36:34 +02:00
Andrey Antukh
320a4552bc Merge pull request #1172 from penpot/export-artboards
🎉 Export to PDF all artboards of one page
2021-09-02 16:33:47 +02:00
Andrés Moya
203473c965 🎉 Export to PDF all artboards of one page 2021-09-02 15:21:33 +02:00
Andrés Moya
255177d12b 🐛 Fix linter error 2021-09-02 15:21:01 +02:00
Andrey Antukh
290bf00b2d Use compact transit encoding instead of verbose. 2021-09-02 14:29:53 +02:00
Andrey Antukh
8464e6a822 Disable response streaming.
Because it is really slow.
2021-09-02 14:17:12 +02:00
Andrey Antukh
8af46ac7fc Minor improvements on section initialization. 2021-09-02 09:45:43 +02:00
Andrey Antukh
daeaf14032 Merge pull request #1169 from penpot/bugfixing
Bugfixing
2021-08-31 17:09:15 +02:00
alonso.torres
bd52a7c926 🐛 Fix minor visual issue 2021-08-31 16:10:42 +02:00
alonso.torres
c8c43de510 🐛 Fix group renaming problem 2021-08-31 15:52:39 +02:00
alonso.torres
bb49071088 🐛 Fix SVG components preview 2021-08-31 15:22:38 +02:00
alonso.torres
7a523a9d89 🐛 Fix problems with order in groups 2021-08-31 14:52:59 +02:00
alonso.torres
885d7de11b 🐛 Fix rename typography on text options 2021-08-31 14:52:06 +02:00
alonso.torres
f44675a1e4 🐛 Fix repeated fetch on file selection 2021-08-31 14:52:06 +02:00
alonso.torres
ce912c7430 🐛 Fix problems with export components 2021-08-31 14:12:12 +02:00
Andrey Antukh
e9fdd74a99 🐛 Fix unexpected text wrapping on exporting. 2021-08-31 12:17:52 +02:00
Andrés Moya
df8269bc7f 🐛 Fix color of texts in pdf exported files 2021-08-31 12:05:43 +02:00
alonso.torres
23e4fa82c8 Add translations to onboarding 2021-08-31 12:01:05 +02:00
alonso.torres
9bea604a46 🐛 Fix thumbnail cropping issue 2021-08-31 11:46:43 +02:00
alonso.torres
119fbd114d 🐛 Fix typos in mailing list 2021-08-31 11:46:43 +02:00
alonso.torres
1b6e6ec2e4 🐛 Fix problem with borders on shape export 2021-08-31 11:46:43 +02:00
alonso.torres
2dfa4f9ec9 Add export/import to custom caps 2021-08-30 12:56:22 +02:00
alonso.torres
3cd3e89679 🐛 Fix problem with caps in thumbnails 2021-08-30 12:56:22 +02:00
alonso.torres
c3be1c870d 🐛 Fix problem with zoom and selection 2021-08-30 12:54:07 +02:00
Andrey Antukh
6b571fd2bb 🐛 Fix wrong pages filtering on view-only-bundle rpc output. 2021-08-30 12:39:06 +02:00
alonso.torres
92df7abcf0 🐛 Fix lint error 2021-08-30 12:22:53 +02:00
Andrey Antukh
498d1570ce 📎 Fix linter issues. 2021-08-27 13:37:55 +02:00
Andrey Antukh
e587179359 ♻️ Refactor flags handling on frontend. 2021-08-27 13:19:36 +02:00
Andrey Antukh
c9985121c4 📎 Allow overwrite archive task props. 2021-08-27 09:42:58 +02:00
Andrey Antukh
e768600df3 ♻️ Enable receiving frontend audit log on backend. 2021-08-25 14:01:43 +02:00
Andrés Moya
3dffb9c8a0 Enable line caps in component sync and svg upload 2021-08-24 16:27:58 +02:00
Andrés Moya
eb40297a35 🎉 Enhance line caps selectors 2021-08-24 16:27:58 +02:00
elhombretecla
837985ccc5 💄 Fix ui constraints color 2021-08-20 10:48:02 +02:00
Andrés Moya
1def4b0f0c Merge pull request #1151 from penpot/niwinz-exporter-and-docker
Exporter resource management improvements.
2021-08-19 15:10:15 +02:00
Andrey Antukh
4c430cedf5 ♻️ Refactor exporter browser management.
Replace the cluster dependency with generic-pool.
2021-08-19 14:17:51 +02:00
Andrey Antukh
18d9212253 Enable aarch64 build for exporter docker image. 2021-08-19 14:16:53 +02:00
Andrey Antukh
36314691f1 ⬆️ Update devenv dockerfile. 2021-08-19 14:16:34 +02:00
Andrey Antukh
24da25f0f7 📎 Update changelog and increase version (minor). 2021-08-19 11:15:30 +02:00
Andrey Antukh
84ba8e6dde Add better error reporting when ldap is not configured correctly. 2021-08-19 11:04:08 +02:00
Andrey Antukh
c6fe035939 🐛 Fix demo user login issue. 2021-08-19 11:04:08 +02:00
Andrés Moya
be9073f0b7 🎉 Add stroke caps to path ends 2021-08-19 09:13:22 +02:00
Andrey Antukh
ac6c07b771 🐛 Fix demo user login issue. 2021-08-18 16:54:56 +02:00
Andrey Antukh
c8102f4bff 🎉 Share link & pages on viewer. 2021-08-18 16:54:56 +02:00
Andrey Antukh
df1fcd5e22 📎 Update changelog. 2021-08-18 15:08:25 +02:00
Andrey Antukh
de87da9c91 🐛 Fix font uploading issue on windows. 2021-08-18 15:06:19 +02:00
Andrey Antukh
3532263af4 🐛 Fix font uploading issue on windows. 2021-08-18 13:14:02 +02:00
Andrés Moya
a9cf4dad82 🎉 Allow increment font size by 0.1 with alt 2021-08-18 10:56:33 +02:00
Andrés Moya
1de1eb6b9b 🐛 Fix initial shape names 2021-08-13 13:42:33 +02:00
Andrés Moya
f6742d1bbf 📚 Update changes. 2021-08-13 10:05:13 +02:00
Andrés Moya
a377c602cc 🐛 Fix naming of duplicated objects in copy&paste and others 2021-08-13 09:49:42 +02:00
Andrey Antukh
58f0ad999c Merge pull request #1144 from penpot/colorpicker-tooltips
🎉 Add tooltips to color picker tabs
2021-08-12 11:23:34 +02:00
Andrés Moya
f612d35daf ♻️ Remove locale translation 2021-08-12 11:17:31 +02:00
Andrés Moya
7d202cb492 🎉 Add tooltips to color picker tabs 2021-08-12 10:56:08 +02:00
Andrés Moya
39bb7f209d Use penpot metadata only for whole file export 2021-08-11 12:26:50 +02:00
Mahmoud A. Rabo
bbd38a7e47 🌐 Add translations for: Arabic.
Currently translated at 75.8% (521 of 687 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/
2021-08-11 01:33:17 +02:00
Voxybuns
d8b2cc7e1b 🌐 Add translations for: French.
Currently translated at 100.0% (687 of 687 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2021-08-11 01:33:15 +02:00
Andrés Moya
09b328167c Truncate 2 decimals in font size 2021-08-10 11:24:54 +02:00
Andrés Moya
4439ef07b6 🎉 Allow orthogonal movement 2021-08-10 11:20:43 +02:00
Andrés Moya
f8491e9631 🎉 Increment font size by 10 with shift+arrows 2021-08-10 08:51:23 +02:00
Andrés Moya
63259b3f92 🎉 Add shortut Ctrl+Shift+K to detach instances 2021-08-09 12:02:20 +02:00
Andrés Moya
10db35eab4 Hide options for drafts project in dashboard 2021-08-09 09:37:48 +02:00
Eranot
0fa79c7a46 🌐 Add translations for: Portuguese (Brazil).
Currently translated at 72.9% (501 of 687 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/
2021-08-08 03:35:05 +02:00
Andrés Moya
e20f557bd6 Enhance resize from center, work when locked proportions 2021-08-06 13:42:50 +02:00
Andrés Moya
25d8d76524 🐛 Fix "Allow resizing from center"
This reverts commit cc0f99333f.
2021-08-06 09:20:32 +02:00
Andrés Moya
cc0f99333f Revert "🎉 Allow resizing from center"
This reverts commit 2a70964dce.
2021-08-05 15:39:09 +02:00
Andrés Moya
982aa874f2 🐛 Disable path conversion for raw-svg, to avoid errors 2021-08-05 14:54:54 +02:00
Andrés Moya
2a70964dce 🎉 Allow resizing from center 2021-08-05 14:54:54 +02:00
Mahmoud A. Rabo
3051a185e5 🌐 Add translations for: Arabic.
Currently translated at 47.5% (327 of 687 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/
2021-08-04 12:32:22 +02:00
Andrés Moya
5e788fff99 Merge remote-tracking branch 'origin/main' into develop 2021-08-04 12:00:13 +02:00
Andrey Antukh
326c52604b 🐛 Don't dissoc :current-team-id on finalizing workspace. 2021-08-04 11:54:54 +02:00
Andrey Antukh
e7d1647769 🐛 Don't allow remove default teams. 2021-08-04 10:54:31 +02:00
Andrey Antukh
1e35116d8f 🐛 Don't allow remove default projects. 2021-08-04 10:50:21 +02:00
Andrey Antukh
35ca3ec895 🐛 Fix loggin issue when user uses the same email as previously deleted profile. 2021-08-04 10:42:22 +02:00
Andrés Moya
7c30cccc97 📚 Add contribution 2021-08-03 09:50:09 +02:00
Andrés Moya
4194abe4f2 🧹 Remove unneeded function 2021-08-03 09:50:09 +02:00
Eduard Aymerich
0b698576da fix: remove top right button in settings. #1123 2021-08-03 09:50:09 +02:00
Andrés Moya
3fbd73129e Set email fields to email type to help editing 2021-08-03 09:50:09 +02:00
Andrés Moya
bbd6d171be 🎉 Allow to navigate undo history 2021-08-03 09:50:09 +02:00
Andrés Moya
f7929bbf93 📚 Some cleanup in CHANGES.md 2021-08-03 09:50:09 +02:00
Andrés Moya
29cd8530a3 🎉 Remember displacements when duplicating several shapes in a row 2021-08-03 09:50:09 +02:00
Andrés Moya
574387acac Move artboards when duplicating 2021-08-03 09:50:09 +02:00
Andrés Moya
6a1ab4d73c 🎉 Allow to zoom with ctrl + middle button 2021-08-03 09:50:09 +02:00
Andrés Moya
29e0c32679 Start panning with space+click instread of just space 2021-08-03 09:50:09 +02:00
Andrey Antukh
db7fe023c6 📎 Set next version to 1.8.0-alpha. 2021-08-03 09:50:09 +02:00
Maemolee
ccf3d7a285 🌐 Add translations for: Chinese (Simplified).
Currently translated at 97.3% (669 of 687 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2021-07-30 18:34:06 +02:00
Wang Jiaxiang
fb59d5d268 🌐 Add translations for: Chinese (Simplified).
Currently translated at 82.9% (570 of 687 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2021-07-16 17:35:11 +02:00
Guilherme Dimas
05cf14846c 🌐 Add translations for: Portuguese (Brazil).
Currently translated at 64.3% (442 of 687 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/
2021-07-15 16:33:38 +02:00
Çağlar Yeşilyurt
1d6a421388 🌐 Add translations for: Turkish.
Currently translated at 93.4% (642 of 687 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/
2021-07-05 19:02:21 +02:00
Eranot
f73880e565 🌐 Add translations for: Portuguese (Brazil).
Currently translated at 61.4% (422 of 687 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/
2021-06-29 13:34:09 +02:00
nautilusx
ce13902680 🌐 Add translations for: German.
Currently translated at 91.8% (631 of 687 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2021-06-27 10:33:22 +02:00
Eranot
fdbf94f415 🌐 Add translations for: Portuguese (Brazil).
Currently translated at 54.4% (374 of 687 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/
2021-06-21 05:32:46 +02:00
1012 changed files with 106856 additions and 468619 deletions

View File

@@ -2,22 +2,16 @@ version: 2
jobs:
build:
docker:
# specify the version you desire here
- image: penpotapp/devenv:latest
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
# - image: circleci/postgres:9.4
- image: circleci/postgres:13.3-ram
- image: cimg/postgres:13.5
environment:
POSTGRES_USER: penpot_test
POSTGRES_PASSWORD: penpot_test
POSTGRES_DB: penpot_test
- image: circleci/redis:6.0.8
- image: cimg/redis:6.2.6
working_directory: ~/repo
resource_class: large
environment:
# Customize the JVM maximum heap limit
@@ -33,20 +27,35 @@ jobs:
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run: cd .clj-kondo && cat config.edn
- run:
name: common lint
working_directory: "./common"
command: "clj-kondo --parallel --lint src/"
command: |
clj-kondo --version
clj-kondo --parallel --lint src/
- run:
name: frontend lint
working_directory: "./frontend"
command: "clj-kondo --parallel --lint src/"
command: |
clj-kondo --version
clj-kondo --parallel --lint src/
- run:
name: frontend styles prettier
working_directory: "./frontend"
command: |
yarn install
yarn run lint-scss
- run:
name: backend lint
working_directory: "./backend"
command: "clj-kondo --parallel --lint src/"
command: |
clj-kondo --version
clj-kondo --parallel --lint src/
# run backend test
- run:
@@ -68,21 +77,27 @@ jobs:
node target/tests.js
environment:
JAVA_HOME: /usr/lib/jvm/openjdk16
PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin:/usr/lib/jvm/openjdk16/bin
PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin
# - run:
# working_directory: "./common"
# name: common tests (cljs)
# command: |
# yarn install
# yarn run compile-test
# node target/test.js
#
# environment:
# PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin
- run:
working_directory: "./common"
name: common tests
name: common tests (clj)
command: |
yarn install
clojure -M:dev:shadow-cljs compile test
node target/tests.js
clojure -X:dev:test
environment:
JAVA_HOME: /usr/lib/jvm/openjdk16
PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin:/usr/lib/jvm/openjdk16/bin
PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin
- save_cache:
paths:

View File

@@ -1,15 +1,19 @@
{:lint-as
{promesa.core/let clojure.core/let
promesa.core/->> clojure.core/->>
promesa.core/-> clojure.core/->
rumext.alpha/defc clojure.core/defn
rumext.alpha/fnc clojure.core/fn
app.common.data/export clojure.core/def
app.db/with-atomic clojure.core/with-open}
app.db/with-atomic clojure.core/with-open
app.common.data.macros/get-in clojure.core/get-in
app.common.data.macros/select-keys clojure.core/select-keys
app.common.logging/with-context clojure.core/do}
:hooks
{:analyze-call
{app.common.data/export hooks.export/export
{app.common.data.macros/export hooks.export/export
potok.core/reify hooks.export/potok-reify
cljs.core/specify! hooks.export/clojure-specify
app.util.services/defmethod hooks.export/service-defmethod
}}
@@ -17,12 +21,16 @@
{:exclude-files
["data_readers.clj"
"app/util/perf.cljs"
"app/common/logging.cljc"
"app/common/exceptions.cljc"]}
:linters
{:unsorted-required-namespaces
{:level :warning}
:potok/reify-type
{:level :error}
:unresolved-namespace
{:level :warning
:exclude [data_readers]}
@@ -30,6 +38,9 @@
:single-key-in
{:level :warning}
:non-arg-vec-return-type-hint
{:level :off}
:redundant-do
{:level :off}

View File

@@ -10,15 +10,34 @@
sname])]
{:node result}))
(def registry (atom {}))
(defn potok-reify
[{:keys [:node]}]
[{:keys [:node :filename] :as params}]
(let [[rnode rtype & other] (:children node)
result (api/list-node
(into [(api/token-node (symbol "deftype"))
(api/token-node (gensym (name (:k rtype))))
(api/vector-node [])]
other))]
{:node result}))
rsym (symbol (str "event-type-" (name (:k rtype))))
reg (get @registry filename #{})]
(when-not (:namespaced? rtype)
(let [{:keys [:row :col]} (meta rtype)]
(api/reg-finding! {:message "ptk/reify type should be namespaced"
:type :potok/reify-type
:row row
:col col})))
(if (contains? reg rsym)
(let [{:keys [:row :col]} (meta rtype)]
(api/reg-finding! {:message (str "duplicate type: " (name (:k rtype)))
:type :potok/reify-type
:row row
:col col}))
(swap! registry update filename (fnil conj #{}) rsym))
(let [result (api/list-node
(into [(api/token-node (symbol "deftype"))
(api/token-node rsym)
(api/vector-node [])]
other))]
{:node result})))
(defn clojure-specify
[{:keys [:node]}]
@@ -32,18 +51,39 @@
(defn service-defmethod
[{:keys [:node]}]
(let [[rnode rtype & other] (:children node)
(let [[rnode rtype ?meta & other] (:children node)
rsym (gensym (name (:k rtype)))
result (api/list-node
[(api/token-node (symbol "do"))
(api/list-node
[(api/token-node (symbol "declare"))
(api/token-node rsym)])
(api/list-node
(into [(api/token-node (symbol "defmethod"))
(api/token-node rsym)
rtype]
other))])]
[?docs other] (if (api/string-node? ?meta)
[?meta other]
[nil (cons ?meta other)])
[?meta other] (let [?meta (first other)]
(if (api/map-node? ?meta)
[?meta (rest other)]
[nil other]))
nodes [(api/token-node (symbol "do"))
(api/list-node
[(api/token-node (symbol "declare"))
(api/token-node rsym)])
(when ?docs
(api/list-node
[(api/token-node (symbol "comment")) ?docs]))
(when ?meta
(api/list-node
[(api/token-node (symbol "reset-meta!"))
(api/token-node rsym)
?meta]))
(api/list-node
(into [(api/token-node (symbol "defmethod"))
(api/token-node rsym)
rtype]
other))]
result (api/list-node (filterv some? nodes))]
;; (prn "=====>" rtype)
;; (prn (api/sexpr result))
{:node result}))

View File

@@ -8,49 +8,48 @@ assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Actual behavior**
A clear and concise description of what happens instead; what the bug is.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: (e.g. iOS)
- Browser (e.g. chrome, safari)
- Version (e.g. 22)
- OS (e.g. iOS):
- Browser & version (e.g. Chrome 89.0):
**Smartphone (please complete the following information):**
- Device: (e.g. iPhone6)
- OS: (e.g. iOS8.1)
- Browser (e.g. stock browser, safari)
- Version (e.g. 22)
- Device & model (e.g. iPhone 6):
- OS & version (e.g. iOS 8.1):
- Browser & version (e.g. stock browser 22):
**Environment (please complete the following information):**
Specify if using SAAS (https://design.penpot.app) or self-hosted instance.
- Host (e.g. https://design.penpot.app, local instance):
If self-hosted instance, add OS and runtime information to help explain your problem.
*If self-hosted:*
- OS Version (e.g. Ubuntu 16.04):
- Docker / Docker-compose version (e.g. Docker version 18.03.0-ce, build 0520e24):
- Image version (e.g. Alpine):
- OS Version: (e.g. Ubuntu 16.04)
Docker commands or docker-compose file (if possible and if proceed.x):
```
Also provide Docker commands or docker-compose file if possible and if proceed.x
- Docker / Docker-compose Version: (e.g. Docker version 18.03.0-ce, build 0520e24)
- Image (e.g. alpine)
**Frontend Stack Trace (if self-hosted)**
```
Frontend Stack Trace:
<details>
```
@@ -59,8 +58,7 @@ Also provide Docker commands or docker-compose file if possible and if proceed.x
</details>
**Backend Stack Trace (if self-hosted)**
Backend Stack Trace:
<details>
```
@@ -69,5 +67,6 @@ Also provide Docker commands or docker-compose file if possible and if proceed.x
</details>
**Additional context**
Add any other context about the problem here.
**Additional context:**
Any other context about the problem.

72
.gitignore vendored
View File

@@ -1,41 +1,55 @@
figwheel_server.log
*jar
*-init.clj
*.jar
*.penpot
*.orig
.calva
.clj-kondo
.cpcache
.lein-deps-sum
.lein-failures
.lein-repl-history
.lein-plugins/
.repl
.lein-repl-history
.lsp
.nrepl-port
.cpcache
.nyc_output
.rebel_readline_history
/vendor/**/target
/cd.md
node_modules
/backend/target/
/backend/resources/public/media
/backend/resources/public/assets
.repl
/.clj-kondo/.cache
/_dump
/backend/-
/backend/assets/
/backend/dist/
/backend/logs/
/backend/-
/telemetry/
/frontend/npm-debug.log
/frontend/target/
/frontend/dist/
/frontend/out/
/frontend/.shadow-cljs
/frontend/resources/public/*
/frontend/resources/fonts/experiments
/exporter/target
/exporter/.shadow-cljs
/docker/images/bundle*
/common/.shadow-cljs
/common/target
/.clj-kondo/.cache
/backend/resources/public/assets
/backend/resources/public/media
/backend/target/
/backend/builtin-templates
/bundle*
/media
/cd.md
/clj-profiler/
/common/.shadow-cljs
/common/coverage
/common/target
/deploy
/docker/images/bundle*
/exporter/.shadow-cljs
/exporter/target
/frontend/.shadow-cljs
/frontend/package-lock.json
/frontend/cypress/videos/*/
/frontend/cypress/fixtures/validuser.json
/frontend/dist/
/frontend/npm-debug.log
/frontend/out/
/frontend/resources/fonts/experiments
/frontend/resources/public/*
/frontend/target/
/frontend/cypress/videos/*/
/media
/telemetry/
/vendor/**/target
/vendor/svgclean/bundle*.js
/web
/_dump
/vendor/svgclean/bundle*.js
clj-profiler/
figwheel_server.log
node_modules

View File

File diff suppressed because it is too large Load Diff

View File

@@ -19,9 +19,9 @@ If you found a bug, please report it, as far as possible with:
- a browser and the browser version used
- a dev tools console exception stack trace (if it is available)
If you found a bug that you consider better discuse in private (for
If you found a bug that you consider better discuss in private (for
example: security bugs), consider first send an email to
`info@penpot.app`.
`support@penpot.app`.
**We don't have formal bug bounty program for security reports; this
is an open source application and your contribution will be recognized
@@ -54,7 +54,7 @@ We will use the `easy fix` mark for tag for indicate issues that are
easy for beginners.
## Commit Message Guidelines ##
## Commit Guidelines ##
We have very precise rules over how our git commit messages can be formatted.
@@ -78,7 +78,6 @@ Where type is:
- :ambulance: `:ambulance:` a commit that fixes critical bug
- :books: `:books:` a commit that improves or adds documentation
- :construction: `:construction:`: a wip commit
- :construction_worker: `:construction_worker:` a commit with CI related stuff
- :boom: `:boom:` a commit with breaking changes
- :wrench: `:wrench:` a commit for config updates
- :zap: `:zap:` a commit with performance improvements
@@ -91,12 +90,25 @@ More info:
- https://gist.github.com/parmentf/035de27d6ed1dce0b36a
- https://gist.github.com/rxaviers/7360908
The subject should be:
Each commit should have:
- Use the imperative mood.
- Capitalize the first letter.
- Don't put a period at the end of the subject line.
- Put a blank line between the subject line and the body.
- A concise subject using imperative mood.
- The subject should have capitalized the first letter, without period
at the end and no larger than 65 characters.
- A blank line between the subject line and the body.
- An entry on the CHANGES.md file if applicable, referencing the
github or taiga issue/user-story using the these same rules.
Examples of good commit messags:
- :bug: Fix unexpected error on launching modal
- :bug: Set proper error message on generic error
- :sparkles: Enable new modal for profile
- :zap: Improve performance of dashboard navigation
- :wrench: Update default backend configuration
- :books: Add more documentation for authentication process
- :ambulance: Fix critical bug on user registration process
- :tada: Add new approach for user registration
## Code of conduct ##

View File

@@ -70,9 +70,9 @@ You can ask and answer questions, have open-ended conversations, and follow alon
✉️ [Mail us](mailto:info@penpot.app)
💬 [Github discussions](https://github.com/penpot/penpot/discussions)
💬 [GitHub discussions](https://github.com/penpot/penpot/discussions)
🐞 [Github issues](mailto:info@penpot.apphttps://github.com/penpot/penpot/issues)
🐞 [GitHub issues](https://github.com/penpot/penpot/issues)
✍️️ [Gitter](https://gitter.im/penpot/community)
@@ -81,7 +81,7 @@ You can ask and answer questions, have open-ended conversations, and follow alon
You can ask and answer questions, have open-ended conversations, and follow along on decisions affecting the project.
Would you like to know more about Penpot? We recommend you to visit our youtube channel and learn more about the functionalities and possibilities of Penpot with our video tutorials.
🎞️ [Youtube channel](https://www.youtube.com/channel/UCAqS8G72uv9P5HG1IfgnQ9g)
🎞️ [YouTube channel](https://www.youtube.com/channel/UCAqS8G72uv9P5HG1IfgnQ9g)
## License ##

5
SECURITY.md Normal file
View File

@@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
Please report security issues to `support@penpot.app`

36
backend/build.clj Normal file
View File

@@ -0,0 +1,36 @@
(ns build
(:refer-clojure :exclude [compile])
(:require
[clojure.tools.build.api :as b]
[clojure.java.io]))
(def class-dir "target/classes")
(def basis (b/create-basis {:project "deps.edn"}))
(def jar-file "target/penpot.jar")
(defn clean [_]
(b/delete {:path "target"}))
(defn jar [_]
(b/copy-dir
{:src-dirs ["src" "resources"]
:target-dir class-dir})
(b/compile-clj
{:basis basis
:src-dirs ["src"]
:class-dir class-dir})
(b/uber
{:class-dir class-dir
:uber-file jar-file
:main 'clojure.main
:exclude [#"goog.*" #"^javasist.*"]
:basis basis}))
(defn compile [_]
(b/javac
{:src-dirs ["dev/java"]
:class-dir class-dir
:basis basis
:javac-opts ["-source" "11" "-target" "11"]}))

View File

@@ -1,78 +1,80 @@
{
;; :mvn/repos
;; {"central" {:url "https://repo1.maven.org/maven2/"}
;; "clojars" {:url "https://clojars.org/repo"}
;; "jcenter" {:url "https://jcenter.bintray.com/"}
;; }
:deps
{penpot/common
{:local/root "../common"}
{:deps
{penpot/common {:local/root "../common"}
org.clojure/clojure {:mvn/version "1.11.1"}
org.clojure/core.async {:mvn/version "1.5.648"}
;; Logging
org.zeromq/jeromq {:mvn/version "0.5.2"}
com.taoensso/nippy {:mvn/version "3.1.1"}
com.github.luben/zstd-jni {:mvn/version "1.4.9-5"}
com.github.luben/zstd-jni {:mvn/version "1.5.2-3"}
org.clojure/data.fressian {:mvn/version "1.0.0"}
;; NOTE: don't upgrade to latest version, breaking change is
;; introduced on 0.10.0 that suffixes counters with _total if they
;; are not already has this suffix.
io.prometheus/simpleclient {:mvn/version "0.9.0"}
io.prometheus/simpleclient_hotspot {:mvn/version "0.9.0"}
io.prometheus/simpleclient_jetty {:mvn/version "0.9.0"
io.prometheus/simpleclient {:mvn/version "0.15.0"}
io.prometheus/simpleclient_hotspot {:mvn/version "0.15.0"}
io.prometheus/simpleclient_jetty {:mvn/version "0.15.0"
:exclusions [org.eclipse.jetty/jetty-server
org.eclipse.jetty/jetty-servlet]}
io.prometheus/simpleclient_httpserver {:mvn/version "0.9.0"}
io.prometheus/simpleclient_httpserver {:mvn/version "0.15.0"}
io.lettuce/lettuce-core {:mvn/version "6.1.2.RELEASE"}
java-http-clj/java-http-clj {:mvn/version "0.4.2"}
io.lettuce/lettuce-core {:mvn/version "6.1.8.RELEASE"}
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
info.sunng/ring-jetty9-adapter {:mvn/version "0.15.1"}
com.github.seancorfield/next.jdbc {:mvn/version "1.2.659"}
metosin/reitit-ring {:mvn/version "0.5.13"}
org.postgresql/postgresql {:mvn/version "42.2.20"}
com.zaxxer/HikariCP {:mvn/version "4.0.3"}
funcool/yetti {:git/tag "v9.8" :git/sha "fbe1d7d"
:git/url "https://github.com/funcool/yetti.git"
:exclusions [org.slf4j/slf4j-api]}
funcool/datoteka {:mvn/version "2.0.0"}
com.github.seancorfield/next.jdbc {:mvn/version "1.2.780"}
metosin/reitit-core {:mvn/version "0.5.18"}
org.postgresql/postgresql {:mvn/version "42.4.0"}
com.zaxxer/HikariCP {:mvn/version "5.0.1"}
buddy/buddy-core {:mvn/version "1.10.1"}
buddy/buddy-hashers {:mvn/version "1.8.1"}
buddy/buddy-sign {:mvn/version "3.4.1"}
funcool/datoteka {:mvn/version "3.0.64"}
org.jsoup/jsoup {:mvn/version "1.13.1"}
org.im4java/im4java {:mvn/version "1.4.0"}
org.lz4/lz4-java {:mvn/version "1.7.1"}
buddy/buddy-hashers {:mvn/version "1.8.158"}
buddy/buddy-sign {:mvn/version "3.4.333"}
org.jsoup/jsoup {:mvn/version "1.15.1"}
org.im4java/im4java {:git/tag "1.4.0-penpot-2" :git/sha "e2b3e16"
:git/url "https://github.com/penpot/im4java"}
org.lz4/lz4-java {:mvn/version "1.8.0"}
org.clojars.pntblnk/clj-ldap {:mvn/version "0.0.17"}
integrant/integrant {:mvn/version "0.8.0"}
software.amazon.awssdk/s3 {:mvn/version "2.16.62"}}
io.sentry/sentry {:mvn/version "5.6.1"}
:paths ["src" "resources"]
dawran6/emoji {:mvn/version "0.1.5"}
markdown-clj/markdown-clj {:mvn/version "1.11.1"}
;; Pretty Print specs
pretty-spec/pretty-spec {:mvn/version "0.1.4"}
software.amazon.awssdk/s3 {:mvn/version "2.17.209"}}
:paths ["src" "resources" "target/classes"]
:aliases
{:dev
{:extra-deps
{com.bhauman/rebel-readline {:mvn/version "RELEASE"}
org.clojure/tools.namespace {:mvn/version "RELEASE"}
org.clojure/test.check {:mvn/version "RELEASE"}
com.clojure-goes-fast/clj-async-profiler {:mvn/version "0.5.0"}
criterium/criterium {:mvn/version "RELEASE"}
clojure-humanize/clojure-humanize {:mvn/version "0.2.2"}
org.clojure/data.csv {:mvn/version "RELEASE"}
com.clojure-goes-fast/clj-async-profiler {:mvn/version "RELEASE"}
mockery/mockery {:mvn/version "RELEASE"}}
:extra-paths ["test" "dev"]}
:fn-fixtures
{:exec-fn app.cli.fixtures/run
:args {}}
:kaocha
{:extra-deps {lambdaisland/kaocha {:mvn/version "1.0.829"}}
:main-opts ["-m" "kaocha.runner"]}
:build
{:extra-deps
{io.github.clojure/tools.build {:git/tag "v0.8.2" :git/sha "ba1a2bf"}}
:ns-default build}
:test
{:extra-deps {io.github.cognitect-labs/test-runner
{:git/url "https://github.com/cognitect-labs/test-runner.git"
:sha "705ad25bbf0228b1c38d0244a36001c2987d7337"}}
{:extra-paths ["test"]
:extra-deps
{io.github.cognitect-labs/test-runner
{:git/tag "v0.5.0" :git/sha "b3fd0d2"}}
:exec-fn cognitect.test-runner.api/test}
:outdated

View File

@@ -0,0 +1,114 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
;; This is an example on how it can be executed:
;; clojure -Scp $(cat classpath) -M dev/script-fix-sobjects.clj
(require
'[app.common.logging :as l]
'[app.common.data :as d]
'[app.common.pprint]
'[app.db :as db]
'[app.storage :as sto]
'[app.storage.impl :as impl]
'[app.util.time :as dt]
'[integrant.core :as ig])
;; --- HELPERS
(l/info :hint "initializing script" :args *command-line-args*)
(def noop? (some #(= % "noop") *command-line-args*))
(def chunk-size 10)
(def sql:retrieve-sobjects-chunk
"SELECT * FROM storage_object
WHERE created_at < ? AND deleted_at is NULL
ORDER BY created_at desc LIMIT ?")
(defn get-chunk
[conn cursor]
(let [rows (db/exec! conn [sql:retrieve-sobjects-chunk cursor chunk-size])]
[(some->> rows peek :created-at) (seq rows)]))
(defn get-candidates
[conn]
(->> (d/iteration (partial get-chunk conn)
:vf second
:kf first
:initk (dt/now))
(sequence cat)))
(def modules
[:app.db/pool
:app.storage/storage
[:app.main/default :app.worker/executor]
[:app.main/assets :app.storage.s3/backend]
[:app.main/assets :app.storage.fs/backend]])
(def system
(let [config (select-keys app.main/system-config modules)
config (-> config
(assoc :app.migrations/all {})
(assoc :app.metrics/metrics nil))]
(ig/load-namespaces config)
(-> config ig/prep ig/init)))
(defn update-fn
[{:keys [conn] :as storage} {:keys [id backend] :as row}]
(cond
(= backend "s3")
(do
(l/info :hint "rename storage object backend"
:id id
:from-backend backend
:to-backend :assets-s3)
(assoc row :backend "assets-s3"))
(= backend "assets-s3")
(do
(l/info :hint "ignoring storage object" :id id :backend backend)
nil)
(or (= backend "fs")
(= backend "assets-fs"))
(let [sobj (sto/row->storage-object row)
path (-> (sto/get-object-path storage sobj) deref)]
(l/info :hint "change storage object backend"
:id id
:from-backend backend
:to-backend :assets-s3)
(when-not noop?
(-> (impl/resolve-backend storage :assets-s3)
(impl/put-object sobj (sto/content path))
(deref)))
(assoc row :backend "assets-s3"))
:else
(throw (IllegalArgumentException. "unexpected backend found"))))
(try
(db/with-atomic [conn (:app.db/pool system)]
(let [storage (:app.storage/storage system)
storage (assoc storage :conn conn)]
(loop [items (get-candidates conn)]
(when-let [item (first items)]
(when-let [{:keys [id] :as row} (update-fn storage item)]
(db/update! conn :storage-object (dissoc row :id) {:id (:id item)}))
(recur (rest items))))
(when noop?
(throw (ex-info "explicit rollback" {})))))
(catch Throwable cause
(cond
(= "explicit rollback" (ex-message cause))
(l/warn :hint "transaction aborted")
:else
(l/error :hint "unexpected exception" :cause cause))))
(ig/halt! system)
(System/exit 0)

View File

@@ -6,47 +6,35 @@
(ns user
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.geom.matrix :as gmt]
[app.common.perf :as perf]
[app.common.transit :as t]
[app.config :as cfg]
[app.main :as main]
[app.util.blob :as blob]
[app.util.fressian :as fres]
[app.util.json :as json]
[app.util.time :as dt]
[app.util.transit :as t]
[clj-async-profiler.core :as prof]
[clojure.contrib.humanize :as hum]
[clojure.java.io :as io]
[clojure.pprint :refer [pprint print-table]]
[clojure.repl :refer :all]
[clojure.spec.alpha :as s]
[clojure.spec.gen.alpha :as sgen]
[clojure.test :as test]
[clojure.test :as test]
[clojure.tools.namespace.repl :as repl]
[clojure.walk :refer [macroexpand-all]]
[criterium.core :refer [quick-bench bench with-progress-reporting]]
[datoteka.core]
[integrant.core :as ig]))
(repl/disable-reload! (find-ns 'integrant.core))
(set! *warn-on-reflection* true)
(defonce system nil)
;; --- Benchmarking Tools
(defmacro run-quick-bench
[& exprs]
`(with-progress-reporting (quick-bench (do ~@exprs) :verbose)))
(defmacro run-quick-bench'
[& exprs]
`(quick-bench (do ~@exprs)))
(defmacro run-bench
[& exprs]
`(with-progress-reporting (bench (do ~@exprs) :verbose)))
(defmacro run-bench'
[& exprs]
`(bench (do ~@exprs)))
;; --- Development Stuff
(defn- run-tests
@@ -91,7 +79,16 @@
(defn compression-bench
[data]
(print-table
[{:v1 (alength (blob/encode data {:version 1}))
:v2 (alength (blob/encode data {:version 2}))
:v3 (alength (blob/encode data {:version 3}))}]))
(let [humanize (fn [v] (hum/filesize v :binary true :format " %.4f "))]
(print-table
[{:v1 (humanize (alength (blob/encode data {:version 1})))
:v2 (humanize (alength (blob/encode data {:version 2})))
:v3 (humanize (alength (blob/encode data {:version 3})))
:v4 (humanize (alength (blob/encode data {:version 4})))
}])))
(defonce debug-tap
(do
(add-tap #(locking debug-tap
(prn "tap debug:" %)))
1))

View File

@@ -0,0 +1,54 @@
<li class="rpc-item">
<div class="rpc-row-info">
{# <div class="type">{{item.type}}</div> #}
<div class="module">{{item.module}}:</div>
<div class="name">{{item.name}}</div>
<div class="tags">
{% if item.deprecated %}
<span class="tag">
<span>Deprecated:</span>
<span>since v{{item.deprecated}}</span>,
</span>
{% endif %}
<span class="tag">
<span>Auth:</span>
<span>{% if item.auth %}YES{% else %}NO{% endif %}</span>
</span>
</div>
</div>
<div class="rpc-row-detail hidden">
<h3>DOCSTRING:</h3>
<section class="padded-section">
{% if item.added %}
<p class="small"><strong>Added:</strong> on v{{item.added}}</p>
{% endif %}
{% if item.deprecated %}
<p class="small"><strong>Deprecated:</strong> since v{{item.deprecated}}</p>
{% endif %}
{% if item.docs %}
<p class="docstring"> {{item.docs}}</p>
{% endif %}
</section>
{% if item.changes %}
<h3>CHANGES:</h3>
<section class="padded-section">
<ul class="changes">
{% for change in item.changes %}
<li><strong>{{change.0}}</strong> - {{change.1}}</li>
{% endfor %}
</ul>
</section>
{% endif %}
<h3>SPEC EXPLAIN:</h3>
<section class="padded-section">
<pre class="spec-explain">{{item.spec}}</pre>
</section>
</div>
</li>

View File

@@ -0,0 +1,145 @@
* {
font-family: "JetBrains Mono", monospace;
font-size: 12px;
}
pre {
margin: 0px;
}
body {
margin: 0px;
padding: 0px;
padding-top: 20px;
padding-bottom: 20px;
display: flex;
justify-content: center;
}
main {
display: flex;
flex-direction: column;
align-items: center;
min-width: 900px;
width: 900px;
}
header {
border-bottom: 1px solid #c0c0c0;
display: flex;
justify-content: center;
width: 100%;
}
.rpc-doc-content {
margin-top: 20px;
width: 100%;
display: flex;
flex-direction: column;
/* border: 1px solid red; */
padding: 5px;
}
.rpc-doc-content > h2:not(:first-child) {
margin-top: 30px;
}
.rpc-items {
list-style: none;
padding: 0px;
margin: 0px;
}
.rpc-item {
/* border: 1px solid red; */
/* cursor: pointer; */
display: flex;
flex-direction: column;
}
.rpc-item:not(:last-child) {
margin-bottom: 3px;
}
.rpc-row-info {
cursor: pointer;
display: flex;
background-color: #eeeeee;
padding: 5px 10px;
}
.rpc-row-info > *:not(:last-child) {
margin-right: 10px;
}
.rpc-row-info > * {
/* border: 1px solid green; */
}
.rpc-row-info > .type {
font-weight: bold;
width: 70px;
}
.rpc-row-info > .name {
width: 280px;
/* font-weight: bold; */
border-right: 1px dotted #777;
padding-right: 10px;
}
.rpc-row-info > .module {
width: 120px;
font-weight: bold;
border-right: 1px dotted #777;
text-align: right;
padding-right: 10px;
}
.rpc-row-info > .tags > .tag > span:first-child {
font-weight: bold;
}
.hidden {
display: none;
}
.rpc-row-detail {
padding: 5px 10px;
padding-bottom: 20px;
}
.rpc-row-detail p {
font-weight: 200;
}
.rpc-row-detail p.small {
margin-top: 2px;
margin-bottom: 2px;
font-size: 10px;
}
.rpc-row-detail p.small {
margin-top: 2px;
margin-bottom: 2px;
font-size: 10px;
}
.rpc-row-detail strong {
font-weight: 500;
}
.rpc-row-detail .changes {
font-weight: 200;
list-style: none;
padding: 0px;
}
.rpc-row-detail .padded-section {
padding: 0px 10px;
}
p.small strong {
font-size: 10px;
}

View File

@@ -0,0 +1,27 @@
(function() {
document.addEventListener("DOMContentLoaded", function(event) {
const rows = document.querySelectorAll(".rpc-row-info");
const onRowClick = (event) => {
const target = event.currentTarget;
for (let node of rows) {
if (node !== target) {
node.nextElementSibling.classList.add("hidden");
} else {
const sibling = target.nextElementSibling;
if (sibling.classList.contains("hidden")) {
sibling.classList.remove("hidden");
} else {
sibling.classList.add("hidden");
}
}
}
};
for (let node of rows) {
node.addEventListener("click", onRowClick);
}
});
})();

View File

@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="robots" content="noindex,nofollow">
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>Builtin API Documentation - Penpot</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@200;300;400;500;700&display=swap" rel="stylesheet">
<style>
{% include "api-doc.css" %}
</style>
<script>
{% include "api-doc.js" %}
</script>
</head>
<body>
<main>
<header>
<h1>Penpot API Documentation (v{{version}})</h1>
</header>
<section class="rpc-doc-content">
<h2>RPC COMMAND METHODS:</h2>
<ul class="rpc-items">
{% for item in command-methods %}
{% include "api-doc-entry.tmpl" with item=item %}
{% endfor %}
</ul>
<h2>RPC QUERY METHODS:</h2>
<ul class="rpc-items">
{% for item in query-methods %}
{% include "api-doc-entry.tmpl" with item=item %}
{% endfor %}
</ul>
<h2>RPC MUTATION METHODS:</h2>
<ul class="rpc-items">
{% for item in mutation-methods %}
{% include "api-doc-entry.tmpl" with item=item %}
{% endfor %}
</ul>
</section>
</main>
</body>
</html>

View File

@@ -37,7 +37,7 @@
<mj-section padding="24px 0 0 0">
<mj-column width="425px">
<mj-text align="center" font-size="14px" color="#64666A">
Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.
Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.
</mj-text>
</mj-column>
</mj-section>

View File

@@ -30,7 +30,7 @@
<mj-section padding="24px 0 0 0">
<mj-column width="425px">
<mj-text align="center" font-size="14px" color="#64666A">
Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.
Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.
</mj-text>
</mj-column>
</mj-section>

View File

@@ -39,7 +39,7 @@
<mj-section padding="24px 0 0 0">
<mj-column width="425px">
<mj-text align="center" font-size="14px" color="#64666A">
Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.
Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.
</mj-text>
</mj-column>
</mj-section>

View File

@@ -22,7 +22,7 @@
<mj-text font-size="24px" font-weight="600">Hello {{name}}!</mj-text>
<mj-text>
Thanks for signing up for your Penpot account! Please verify your
email using the link below adn get started building mockups and
email using the link below and get started building mockups and
prototypes today!
</mj-text>
<mj-button href="{{ public-uri }}/#/auth/verify-token?token={{token}}">
@@ -36,7 +36,7 @@
<mj-section padding="24px 0 0 0">
<mj-column width="425px">
<mj-text align="center" font-size="14px" color="#64666A">
Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.
Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.
</mj-text>
</mj-column>
</mj-section>

View File

@@ -250,7 +250,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.</div>
</td>
</tr>
</table>
@@ -450,7 +450,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot @ 2021 | Made with &lt;3 and Open Source</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot | Made with &lt;3 and Open Source</div>
</td>
</tr>
</table>

View File

@@ -1 +1 @@
[PENPOT FEEDBACK]: {{subject|abbreviate:19}} (from {{email}})
[PENPOT FEEDBACK]: {{subject}}

View File

@@ -240,7 +240,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.</div>
</td>
</tr>
</table>
@@ -440,7 +440,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot @ 2021 | Made with &lt;3 and Open Source</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot | Made with &lt;3 and Open Source</div>
</td>
</tr>
</table>

View File

@@ -245,7 +245,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.</div>
</td>
</tr>
</table>
@@ -445,7 +445,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot @ 2021 | Made with &lt;3 and Open Source</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot | Made with &lt;3 and Open Source</div>
</td>
</tr>
</table>

View File

@@ -173,7 +173,7 @@
</tr>
<tr>
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">Thanks for signing up for your Penpot account! Please verify your email using the link below adn get started building mockups and prototypes today!</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">Thanks for signing up for your Penpot account! Please verify your email using the link below and get started building mockups and prototypes today!</div>
</td>
</tr>
<tr>
@@ -240,7 +240,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source prototyping platform that will be embraced by multidisciplinary teams.</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.</div>
</td>
</tr>
</table>
@@ -440,7 +440,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
<tr>
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot @ 2021 | Made with &lt;3 and Open Source</div>
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot | Made with &lt;3 and Open Source</div>
</td>
</tr>
</table>
@@ -465,4 +465,4 @@
</div>
</body>
</html>
</html>

View File

@@ -1,7 +1,7 @@
Hello {{name}}!
Thanks for signing up for your Penpot account! Please verify your email using the
link below adn get started building mockups and prototypes today!
link below and get started building mockups and prototypes today!
{{ public-uri }}/#/auth/verify-token?token={{token}}

View File

@@ -1,194 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>penpot - error report {{id}}</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono">
<style>
body {
margin: 0px;
padding: 0px;
}
pre {
margin: 0px;
}
* {
font-family: "JetBrains Mono", monospace;
font-size: 12px;
}
.table {
display: flex;
flex-direction: column;
margin: 10px;
}
.table-row {
display: flex;
/* width: 100%; */
/* border: 1px solid red; */
}
.table-key {
font-weight: 600;
width: 60px;
padding: 4px;
}
.table-val {
font-weight: 200;
color: #333;
padding: 4px;
}
.multiline {
margin-top: 15px;
flex-direction: column;
}
.multiline .table-key {
margin-bottom: 10px;
border-bottom: 1px dashed #dddddd;
/* padding: 4px; */
width: unset;
}
</style>
</head>
<body>
<div class="table">
<div class="table-row">
<div class="table-key" title="Error ID">ERID: </div>
<div class="table-val">{{id}}</div>
</div>
{% if profile-id %}
<div class="table-row">
<div class="table-key" title="Profile ID">PFID: </div>
<div class="table-val">{{profile-id}}</div>
</div>
{% endif %}
{% if user-agent %}
<div class="table-row">
<div class="table-key">UAGT: </div>
<div class="table-val">{{user-agent}}</div>
</div>
{% endif %}
{% if frontend-version %}
<div class="table-row">
<div class="table-key">FVER: </div>
<div class="table-val">{{frontend-version}}</div>
</div>
{% endif %}
<div class="table-row">
<div class="table-key">BVER: </div>
<div class="table-val">{{version}}</div>
</div>
{% if host %}
<div class="table-row">
<div class="table-key">HOST: </div>
<div class="table-val">{{host}}</div>
</div>
{% endif %}
{% if tenant %}
<div class="table-row">
<div class="table-key">ENV: </div>
<div class="table-val">{{tenant}}</div>
</div>
{% endif %}
{% if public-uri %}
<div class="table-row">
<div class="table-key">PURI: </div>
<div class="table-val">{{public-uri}}</div>
</div>
{% endif %}
{% if type %}
<div class="table-row">
<div class="table-key">TYPE: </div>
<div class="table-val">{{type}}</div>
</div>
{% endif %}
{% if code %}
<div class="table-row">
<div class="table-key">CODE: </div>
<div class="table-val">{{code}}</div>
</div>
{% endif %}
{% if error %}
<div class="table-row">
<div class="table-key">CLSS: </div>
<div class="table-val">{{error.class}}</div>
</div>
{% endif %}
{% if error %}
<div class="table-row">
<div class="table-key">HINT: </div>
<div class="table-val">{{error.message}}</div>
</div>
{% endif %}
{% if method %}
<div class="table-row">
<div class="table-key">PATH: </div>
<div class="table-val">{{method|upper}} {{path}}</div>
</div>
{% endif %}
{% if explain %}
<div>(<a href="#explain">go to explain</a>)</div>
{% endif %}
{% if data %}
<div>(<a href="#edata">go to edata</a>)</div>
{% endif %}
{% if error %}
<div>(<a href="#trace">go to trace</a>)</div>
{% endif %}
{% if params %}
<div id="params" class="table-row multiline">
<div class="table-key">PARAMS: </div>
<div class="table-val">
<pre>{{params}}</pre>
</div>
</div>
{% endif %}
{% if explain %}
<div id="explain" class="table-row multiline">
<div class="table-key">EXPLAIN: </div>
<div class="table-val">
<pre>{{explain}}</pre>
</div>
</div>
{% endif %}
{% if data %}
<div id="edata" class="table-row multiline">
<div class="table-key">EDATA: </div>
<div class="table-val">
<pre>{{data}}</pre>
</div>
</div>
{% endif %}
{% if error %}
<div id="trace" class="table-row multiline">
<div class="table-key">TRACE:</div>
<div class="table-val">
<pre>{{error.trace}}</pre>
</div>
</div>
{% endif %}
</div>
</body>
</html>

View File

@@ -2,7 +2,7 @@
<Configuration status="info" monitorInterval="30">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{YYYY-MM-dd HH:mm:ss.SSS}] [%t] %level{length=1} %logger{36} - %msg%n"/>
<PatternLayout pattern="[%d{YYYY-MM-dd HH:mm:ss.SSS}] %level{length=1} %logger{36} - %msg%n"/>
</Console>
<RollingFile name="main" fileName="logs/main.log" filePattern="logs/main-%i.log">
@@ -20,11 +20,17 @@
</Appenders>
<Loggers>
<Logger name="com.zaxxer.hikari" level="error"/>
<Logger name="io.lettuce" level="error" />
<Logger name="org.eclipse.jetty" level="error" />
<Logger name="com.zaxxer.hikari" level="error"/>
<Logger name="org.postgresql" level="error" />
<Logger name="app.rpc.commands.binfile" level="debug" />
<Logger name="app.storage.tmp" level="info" />
<Logger name="app.worker" level="info" />
<Logger name="app.msgbus" level="info" />
<Logger name="app.http.websocket" level="info" />
<Logger name="app.util.websocket" level="info" />
<Logger name="app.cli" level="debug" additivity="false">
<AppenderRef ref="console"/>
</Logger>
@@ -38,11 +44,6 @@
<AppenderRef ref="zmq" level="debug" />
</Logger>
<Logger name="penpot" level="debug" additivity="false">
<AppenderRef ref="main" level="debug" />
<AppenderRef ref="zmq" level="debug" />
</Logger>
<Logger name="user" level="trace" additivity="false">
<AppenderRef ref="main" level="trace" />
</Logger>

View File

@@ -2,19 +2,16 @@
<Configuration status="info" monitorInterval="60">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{YYYY-MM-dd HH:mm:ss.SSS}] [%t] %level{length=1} %logger{36} - %msg%n"/>
<PatternLayout pattern="[%d{YYYY-MM-dd HH:mm:ss.SSS}] %level{length=1} %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="io.lettuce" level="error" />
<Logger name="com.zaxxer.hikari" level="error" />
<Logger name="org.eclipse.jetty" level="error" />
<Logger name="org.postgresql" level="error" />
<Logger name="app" level="debug" additivity="false">
<AppenderRef ref="console" />
</Logger>
<Logger name="penpot" level="fatal" additivity="false">
<Logger name="app" level="info" additivity="false">
<AppenderRef ref="console" />
</Logger>

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="robots" content="noindex,nofollow">
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono">
<style>
{% include "templates/styles.css" %}
</style>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>

View File

@@ -0,0 +1,127 @@
{% extends "templates/base.tmpl" %}
{% block title %}
Debug Main Page
{% endblock %}
{% block content %}
<nav>
<h1>Debug INDEX:</h1>
<div>[<a href="/dbg/error">ERRORS</a>]</div>
</nav>
<main class="index">
<section class="widget">
<fieldset>
<legend>Download file data:</legend>
<desc>Given an FILE-ID, downloads the file data as file. The file data is encoded using transit.</desc>
<form method="get" action="/dbg/file/data">
<div class="row">
<input type="text" style="width:300px" name="file-id" placeholder="file-id" />
</div>
<div class="row">
<input type="submit" name="download" value="Download" />
<input type="submit" name="clone" value="Clone" />
</div>
</form>
</fieldset>
<fieldset>
<legend>Upload File Data:</legend>
<desc>Create a new file on your draft projects using the file downloaded from the previous section.</desc>
<form method="post" enctype="multipart/form-data" action="/dbg/file/data">
<div class="row">
<input type="file" name="file" value="" />
</div>
<div class="row">
<label>Import with same id?</label>
<input type="checkbox" name="reuseid" />
</div>
<input type="submit" value="Upload" />
</form>
</fieldset>
</section>
<section class="widget">
<fieldset>
<legend>Export binfile:</legend>
<desc>Given an FILE-ID, downloads the file and optionally all
the related libraries in a single custom formatted binary
file.</desc>
<form method="get" action="/dbg/file/export">
<div class="row set-of-inputs">
<input type="text" style="width:300px" name="file-ids" placeholder="file-id" />
<input type="text" style="width:300px" name="file-ids" placeholder="file-id" />
<input type="text" style="width:300px" name="file-ids" placeholder="file-id" />
<input type="text" style="width:300px" name="file-ids" placeholder="file-id" />
</div>
<div class="row">
<label>Include libraries?</label>
<input type="checkbox" name="includelibs" />
</div>
<div class="row">
<label>Embed assets?</label>
<input type="checkbox" name="embedassets" checked/>
</div>
<div class="row">
<input type="submit" name="download" value="Download" />
<input type="submit" name="clone" value="Clone" />
</div>
</form>
</fieldset>
<fieldset>
<legend>Import binfile:</legend>
<desc>Import penpot file in binary
format. If <strong>overwrite</strong> is checked, all files will
be overwriten using the same ids found in the file instead of
generating a new ones.</desc>
<form method="post" enctype="multipart/form-data" action="/dbg/file/import">
<div class="row">
<input type="file" name="file" value="" />
</div>
<div class="row">
<label>Overwrite?</label>
<input type="checkbox" name="overwrite" />
<br />
<small>
Instead of creating a new file with all relations remaped,
reuses all ids and updates/overwrites the objects that are
already exists on the database.
<strong>Warning, this operation should be used with caution.</strong>
</small>
</div>
<div class="row">
<label>Migrate?</label>
<input type="checkbox" name="migrate" />
<br />
<small>
Applies the file migrations on the importation process.
</small>
</div>
<div class="row">
<label>Ignore index errors?</label>
<input type="checkbox" name="ignore-index-errors" checked/>
<br />
<small>
Do not break on index lookup erros (remap operation).
Useful when importing a broken file that has broken
relations or missing pieces.
</small>
</div>
<div class="row">
<input type="submit" name="upload" value="Upload" />
</div>
</form>
</fieldset>
</section>
</main>
{% endblock %}

View File

@@ -0,0 +1,18 @@
{% extends "templates/base.tmpl" %}
{% block title %}
penpot - error list
{% endblock %}
{% block content %}
<nav>
<h1>Latest error reports:</h1>
</nav>
<main class="horizontal-list">
<ul>
{% for item in items %}
<li><a href="/dbg/error/{{item.id}}">{{item.created-at}}</a></li>
{% endfor %}
</ul>
</main>
{% endblock %}

View File

@@ -0,0 +1,98 @@
{% extends "templates/base.tmpl" %}
{% block title %}
penpot - error report {{id}}
{% endblock %}
{% block content %}
<nav>
<div>[<a href="/dbg/error">⮜</a>]</div>
<div>[<a href="#context">context</a>]</div>
<div>[<a href="#params">request params</a>]</div>
{% if data %}
<div>[<a href="#edata">error data</a>]</div>
{% endif %}
{% if spec-explain %}
<div>[<a href="#spec-explain">spec explain</a>]</div>
{% endif %}
{% if spec-problems %}
<div>[<a href="#spec-problems">spec problems</a>]</div>
{% endif %}
{% if spec-value %}
<div>[<a href="#spec-value">spec value</a>]</div>
{% endif %}
{% if trace %}
<div>[<a href="#trace">error trace</a>]</div>
{% endif %}
</nav>
<main>
<div class="table">
<div class="table-row multiline">
<div id="context" class="table-key">CONTEXT: </div>
<div class="table-val">
<h1>{{hint}}</h1>
</div>
<div class="table-val">
<pre>{{context}}</pre>
</div>
</div>
{% if params %}
<div class="table-row multiline">
<div id="params" class="table-key">REQUEST PARAMS: </div>
<div class="table-val">
<pre>{{params}}</pre>
</div>
</div>
{% endif %}
{% if data %}
<div class="table-row multiline">
<div id="edata" class="table-key">ERROR DATA: </div>
<div class="table-val">
<pre>{{data}}</pre>
</div>
</div>
{% endif %}
{% if spec-explain %}
<div class="table-row multiline">
<div id="spec-explain" class="table-key">SPEC EXPLAIN: </div>
<div class="table-val">
<pre>{{spec-explain}}</pre>
</div>
</div>
{% endif %}
{% if spec-problems %}
<div class="table-row multiline">
<div id="spec-problems" class="table-key">SPEC PROBLEMS: </div>
<div class="table-val">
<pre>{{spec-problems}}</pre>
</div>
</div>
{% endif %}
{% if spec-value %}
<div class="table-row multiline">
<div id="spec-value" class="table-key">SPEC VALUE: </div>
<div class="table-val">
<pre>{{spec-value}}</pre>
</div>
</div>
{% endif %}
{% if trace %}
<div class="table-row multiline">
<div id="trace" class="table-key">TRACE:</div>
<div class="table-val">
<pre>{{trace}}</pre>
</div>
</div>
{% endif %}
</div>
</main>
{% endblock %}

View File

@@ -0,0 +1,179 @@
* {
font-family: "JetBrains Mono", monospace;
font-size: 12px;
}
body {
margin: 0px;
padding: 0px;
}
pre {
margin: 0px;
line-height: 16px;
}
desc {
margin-bottom: 10px;
font-size: 10px;
color: #666;
}
input[type=text], input[type=submit] {
padding: 3px;
}
main {
margin: 20px;
}
small {
font-size: 9px;
color: #888;
}
small > strong {
font-size: 9px;
}
nav {
position: fixed;
width: 100vw;
top: 0;
left: 0;
padding: 5px 20px;
display: flex;
background: #e3e3e3;
}
nav > h1 {
padding: 0px;
margin: 0px;
font-size: 11px;
}
nav > div {
text-transform: uppercase;
font-weight: bold;
}
nav > div:not(:last-child) {
margin-right: 10px;
}
.table {
margin-top: 25px;
display: flex;
flex-direction: column;
}
.table-row {
display: flex;
padding-bottom: 15px;
/* width: 100%; */
/* border: 1px solid red; */
}
.table-key {
font-weight: 600;
width: 60px;
padding: 4px;
padding-top: 40px;
margin-top: -40px;
}
.table-val {
font-weight: 200;
color: #333;
padding: 4px;
}
.multiline {
margin-top: 15px;
flex-direction: column;
}
.multiline .table-key {
margin-bottom: 10px;
border-bottom: 1px dashed #dddddd;
/* padding: 4px; */
width: unset;
}
.index {
margin-top: 40px;
display: flex;
}
.index > section {
padding: 10px;
background-color: #e3e3e3;
max-width: 400px;
margin: 5px;
height: fit-content;
}
.index fieldset:not(:first-child) {
margin-top: 15px;
}
/* .index > section:not(:last-child) { */
/* margin-bottom: 10px; */
/* } */
.index > section > h2 {
margin-top: 0px;
}
.horizontal-list {
margin: 20px;
margin-top: 40px;
}
.horizontal-list ul {
display: flex;
margin: 0px;
padding: 0px;
flex-direction: column;
flex-wrap: wrap;
height: calc(100vh - 75px);
justify-content: flex-start;
}
.horizontal-list li {
list-style: none;
padding: 0px;
margin: 0px;
line-height: 18px;
min-width: 210px;
margin: 0px 20px;
cursor: pointer;
display: flex;
justify-content: center;
border-radius: 3px;
}
.horizontal-list li:hover {
background-color: #e9e9e9;
}
.horizontal-list li > a {
text-decoration: none;
color: inherit;
}
form .row {
padding: 5px 0;
}
.set-of-inputs {
flex-direction: column;
display: flex;
}
.set-of-inputs input:not(:last-child) {
margin-bottom: 3px;
}

View File

@@ -1,79 +1,21 @@
#!/usr/bin/env bb
#!/usr/bin/env bash
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
CURRENT_VERSION=$1;
(ns build
(:require
[clojure.string :as str]
[clojure.java.io :as io]
[clojure.pprint :refer [pprint]]
[babashka.fs :as fs]
[babashka.process :refer [$ check]]))
set -ex
(defn split-cp
[data]
(str/split data #":"))
rm -rf target;
mkdir -p target/classes;
mkdir -p target/dist;
echo "$CURRENT_VERSION" > target/classes/version.txt;
cp ../CHANGES.md target/classes/changelog.md;
(def classpath
(->> ($ clojure -Spath)
(check)
(:out)
(slurp)
(split-cp)
(map str/trim)))
clojure -T:build jar;
mv target/penpot.jar target/dist/penpot.jar
cp scripts/run.template.sh target/dist/run.sh;
cp scripts/manage.template.sh target/dist/manage.sh;
chmod +x target/dist/run.sh;
chmod +x target/dist/manage.sh;
(def classpath-jars
(let [xfm (filter #(str/ends-with? % ".jar"))]
(into #{} xfm classpath)))
(def classpath-paths
(let [xfm (comp (remove #(str/ends-with? % ".jar"))
(filter #(.isDirectory (io/file %))))]
(into #{} xfm classpath)))
(def version
(or (first *command-line-args*) "%version%"))
;; Clean previous dist
(-> ($ rm -rf "./target/dist") check)
;; Create a new dist
(-> ($ mkdir -p "./target/dist/deps") check)
;; Copy all jar deps into dist
(run! (fn [item] (-> ($ cp ~item "./target/dist/deps/") check)) classpath-jars)
;; Create the application jar
(spit "./target/dist/version.txt" version)
(-> ($ jar cvf "./target/dist/deps/app.jar" -C ~(first classpath-paths) ".") check)
(-> ($ jar uvf "./target/dist/deps/app.jar" -C "./target/dist" "version.txt") check)
(run! (fn [item]
(-> ($ jar uvf "./target/dist/deps/app.jar" -C ~item ".") check))
(rest classpath-paths))
;; Copy logging configuration
(-> ($ cp "./resources/log4j2.xml" "./target/dist/") check)
;; Create classpath file
(let [jars (->> (into ["app.jar"] classpath-jars)
(map fs/file-name)
(map #(fs/path "deps" %))
(map str))]
(spit "./target/dist/classpath" (str/join ":" jars)))
;; Copy run script template
(-> ($ cp "./scripts/run.template.sh" "./target/dist/run.sh") check)
;; Copy run script template
(-> ($ cp "./scripts/manage.template.sh" "./target/dist/manage.sh") check)
;; Add exec permisions to scripts.
(-> ($ chmod +x "./target/dist/run.sh") check)
(-> ($ chmod +x "./target/dist/manage.sh") check)
nil

View File

@@ -16,4 +16,4 @@ if [ -f ./environ ]; then
source ./environ
fi
exec $JAVA_CMD $JVM_OPTS -classpath $(cat classpath) -Dlog4j2.configurationFile=./log4j2.xml clojure.main -m app.cli.manage "$@"
exec $JAVA_CMD $JVM_OPTS -jar penpot.jar -m app.cli.manage "$@"

View File

@@ -1,10 +1,46 @@
#!/usr/bin/env bash
export PENPOT_ASSERTS_ENABLED=true
export PENPOT_HOST=devenv
export PENPOT_TENANT=dev
export PENPOT_FLAGS="$PENPOT_FLAGS enable-backend-asserts enable-audit-log enable-transit-readable-response enable-demo-users disable-secure-session-cookies"
export OPTIONS="-A:jmx-remote:dev -J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -J-Dlog4j2.configurationFile=log4j2-devenv.xml -J-Djdk.attach.allowAttachSelf -J-XX:+UseZGC -J-XX:ConcGCThreads=1 -J-XX:-OmitStackTraceInFastThrow -J-Xms50m -J-Xmx512m";
# export OPTIONS="$OPTIONS -J-XX:+UnlockDiagnosticVMOptions";
# export OPTIONS="$OPTIONS -J-XX:-TieredCompilation -J-XX:CompileThreshold=10000";
# export PENPOT_DATABASE_URI="postgresql://172.17.0.1:5432/penpot"
# export PENPOT_DATABASE_USERNAME="penpot"
# export PENPOT_DATABASE_PASSWORD="penpot"
# export PENPOT_DATABASE_READONLY=true
# export PENPOT_DATABASE_URI="postgresql://172.17.0.1:5432/penpot_pre"
# export PENPOT_DATABASE_USERNAME="penpot_pre"
# export PENPOT_DATABASE_PASSWORD="penpot_pre"
# export PENPOT_LOGGERS_LOKI_URI="http://172.17.0.1:3100/loki/api/v1/push"
# export PENPOT_AUDIT_LOG_ARCHIVE_URI="http://localhost:6070/api/audit"
# Initialize MINIO config
mc alias set penpot-s3/ http://minio:9000 minioadmin minioadmin
mc admin user add penpot-s3 penpot-devenv penpot-devenv
mc admin policy set penpot-s3 readwrite user=penpot-devenv
mc mb penpot-s3/penpot -p
export AWS_ACCESS_KEY_ID=penpot-devenv
export AWS_SECRET_ACCESS_KEY=penpot-devenv
export PENPOT_ASSETS_STORAGE_BACKEND=assets-s3
export PENPOT_STORAGE_ASSETS_S3_ENDPOINT=http://minio:9000
export PENPOT_STORAGE_ASSETS_S3_BUCKET=penpot
export OPTIONS="
-A:dev:jmx-remote \
-J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
-J-Dlog4j2.configurationFile=log4j2-devenv.xml \
-J-XX:+UseG1GC \
-J-XX:-OmitStackTraceInFastThrow \
-J-Xms50m -J-Xmx1024m \
-J-Djdk.attach.allowAttachSelf \
-J-XX:+UnlockDiagnosticVMOptions \
-J-XX:+DebugNonSafepoints";
# Uncomment for use the ImageMagick v7.x
# export OPTIONS="-J-Dim4java.useV7=true $OPTIONS";
export OPTIONS_EVAL="nil"
# export OPTIONS_EVAL="(set! *warn-on-reflection* true)"

View File

@@ -17,4 +17,4 @@ if [ -f ./environ ]; then
fi
set -x
exec $JAVA_CMD $JVM_OPTS -classpath "$(cat classpath)" -Dlog4j2.configurationFile=./log4j2.xml "$@" clojure.main -m app.main
exec $JAVA_CMD $JVM_OPTS "$@" -jar penpot.jar -m app.main

View File

@@ -1,15 +1,25 @@
#!/bin/sh
#!/usr/bin/env bash
export PENPOT_ASSERTS_ENABLED=true
export PENPOT_HOST=devenv
export PENPOT_TENANT=dev
export PENPOT_FLAGS="$PENPOT_FLAGS enable-backend-asserts enable-audit-log enable-transit-readable-response enable-demo-users disable-secure-session-cookies"
set -ex
if [ ! -e ~/.fixtures-loaded ]; then
echo "Loading fixtures..."
clojure -Adev -X:fn-fixtures
touch ~/.fixtures-loaded
if [ "$1" = "--watch" ]; then
echo "Start Watch..."
clojure -A:dev -M -m app.main &
PID=$!
npx nodemon \
--watch src \
--watch ../common \
--ext "clj" \
--signal SIGKILL \
--exec 'echo "(user/restart)" | nc -N localhost 6062'
kill -9 $PID
else
clojure -A:dev -M -m app.main
fi
clojure -A:dev -M -m app.main

View File

@@ -0,0 +1,137 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.auth.ldap
(:require
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.spec :as us]
[app.config :as cf]
[clj-ldap.client :as ldap]
[clojure.spec.alpha :as s]
[clojure.string]
[integrant.core :as ig]))
(defn- prepare-params
[cfg]
{:ssl? (:ssl cfg)
:startTLS? (:tls cfg)
:bind-dn (:bind-dn cfg)
:password (:bind-password cfg)
:host {:address (:host cfg)
:port (:port cfg)}})
(defn- connect
"Connects to the LDAP provider and returns a connection. An
exception is raised if no connection is possible."
^java.lang.AutoCloseable
[cfg]
(try
(-> cfg prepare-params ldap/connect)
(catch Throwable cause
(ex/raise :type :restriction
:code :unable-to-connect-to-ldap
:hint "unable to connect to ldap server"
:cause cause))))
(defn- replace-several [s & {:as replacements}]
(reduce-kv clojure.string/replace s replacements))
(defn- search-user
[{:keys [conn attrs base-dn] :as cfg} email]
(let [query (replace-several (:query cfg) ":username" email)
params {:filter query
:sizelimit 1
:attributes attrs}]
(first (ldap/search conn base-dn params))))
(defn- retrieve-user
[{:keys [conn] :as cfg} {:keys [email password]}]
(when-let [{:keys [dn] :as user} (search-user cfg email)]
(when (ldap/bind? conn dn password)
{:fullname (get user (-> cfg :attrs-fullname keyword))
:email email
:backend "ldap"})))
(s/def ::fullname ::us/not-empty-string)
(s/def ::email ::us/email)
(s/def ::backend ::us/not-empty-string)
(s/def ::info-data
(s/keys :req-un [::fullname ::email ::backend]))
(defn authenticate
[cfg params]
(with-open [conn (connect cfg)]
(when-let [user (-> (assoc cfg :conn conn)
(retrieve-user params))]
(when-not (s/valid? ::info-data user)
(let [explain (s/explain-str ::info-data user)]
(l/warn ::l/raw (str "invalid response from ldap, looks like ldap is not configured correctly\n" explain))
(ex/raise :type :restriction
:code :wrong-ldap-response
:explain explain)))
user)))
(defn- try-connectivity
[cfg]
;; If we have ldap parameters, try to establish connection
(when (and (:bind-dn cfg)
(:bind-password cfg)
(:host cfg)
(:port cfg))
(try
(with-open [_ (connect cfg)]
(l/info :hint "provider initialized"
:provider "ldap"
:host (:host cfg)
:port (:port cfg)
:tls? (:tls cfg)
:ssl? (:ssl cfg)
:bind-dn (:bind-dn cfg)
:base-dn (:base-dn cfg)
:query (:query cfg))
cfg)
(catch Throwable cause
(l/error :hint "unable to connect to LDAP server (LDAP auth provider disabled)"
:host (:host cfg) :port (:port cfg) :cause cause)
nil))))
(defn- prepare-attributes
[cfg]
(assoc cfg :attrs [(:attrs-username cfg)
(:attrs-email cfg)
(:attrs-fullname cfg)]))
(defmethod ig/init-key ::provider
[_ cfg]
(when (:enabled? cfg)
(some-> cfg try-connectivity prepare-attributes)))
(s/def ::enabled? ::us/boolean)
(s/def ::host ::cf/ldap-host)
(s/def ::port ::cf/ldap-port)
(s/def ::ssl ::cf/ldap-ssl)
(s/def ::tls ::cf/ldap-starttls)
(s/def ::query ::cf/ldap-user-query)
(s/def ::base-dn ::cf/ldap-base-dn)
(s/def ::bind-dn ::cf/ldap-bind-dn)
(s/def ::bind-password ::cf/ldap-bind-password)
(s/def ::attrs-email ::cf/ldap-attrs-email)
(s/def ::attrs-fullname ::cf/ldap-attrs-fullname)
(s/def ::attrs-username ::cf/ldap-attrs-username)
(defmethod ig/pre-init-spec ::provider
[_]
(s/keys :opt-un [::host ::port
::ssl ::tls
::enabled?
::bind-dn
::bind-password
::query
::attrs-email
::attrs-username
::attrs-fullname]))

View File

@@ -0,0 +1,538 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.auth.oidc
"OIDC client implementation."
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.spec :as us]
[app.common.uri :as u]
[app.config :as cf]
[app.db :as db]
[app.http.middleware :as hmw]
[app.loggers.audit :as audit]
[app.rpc.queries.profile :as profile]
[app.util.json :as json]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.set :as set]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[integrant.core :as ig]
[promesa.core :as p]
[promesa.exec :as px]
[yetti.response :as yrs]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- obfuscate-string
[s]
(if (< (count s) 10)
(apply str (take (count s) (repeat "*")))
(str (subs s 0 5)
(apply str (take (- (count s) 5) (repeat "*"))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; OIDC PROVIDER (GENERIC)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- discover-oidc-config
[{:keys [http-client]} {:keys [base-uri] :as opts}]
(let [discovery-uri (u/join base-uri ".well-known/openid-configuration")
response (ex/try (http-client {:method :get :uri (str discovery-uri)} {:sync? true}))]
(cond
(ex/exception? response)
(do
(l/warn :hint "unable to discover oidc configuration"
:discover-uri (str discovery-uri)
:cause response)
nil)
(= 200 (:status response))
(let [data (json/read (:body response))]
{:token-uri (get data :token_endpoint)
:auth-uri (get data :authorization_endpoint)
:user-uri (get data :userinfo_endpoint)})
:else
(do
(l/warn :hint "unable to discover OIDC configuration"
:uri (str discovery-uri)
:response-status-code (:status response))
nil))))
(defn- prepare-oidc-opts
[cfg]
(let [opts {:base-uri (:base-uri cfg)
:client-id (:client-id cfg)
:client-secret (:client-secret cfg)
:token-uri (:token-uri cfg)
:auth-uri (:auth-uri cfg)
:user-uri (:user-uri cfg)
:scopes (:scopes cfg #{"openid" "profile" "email"})
:roles-attr (:roles-attr cfg)
:roles (:roles cfg)
:name "oidc"}
opts (d/without-nils opts)]
(when (and (string? (:base-uri opts))
(string? (:client-id opts))
(string? (:client-secret opts)))
(if (and (string? (:token-uri opts))
(string? (:user-uri opts))
(string? (:auth-uri opts)))
opts
(some-> (discover-oidc-config cfg opts)
(merge opts {:discover? true}))))))
(defmethod ig/prep-key ::generic-provider
[_ cfg]
(d/without-nils cfg))
(defmethod ig/init-key ::generic-provider
[_ cfg]
(when (:enabled? cfg)
(if-let [opts (prepare-oidc-opts cfg)]
(do
(l/info :hint "provider initialized"
:provider :oidc
:method (if (:discover? opts) "discover" "manual")
:client-id (:client-id opts)
:client-secret (obfuscate-string (:client-secret opts))
:scopes (str/join "," (:scopes opts))
:auth-uri (:auth-uri opts)
:user-uri (:user-uri opts)
:token-uri (:token-uri opts)
:roles-attr (:roles-attr opts)
:roles (:roles opts))
opts)
(do
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :oidc)
nil))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GOOGLE AUTH PROVIDER
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod ig/prep-key ::google-provider
[_ cfg]
(d/without-nils cfg))
(defmethod ig/init-key ::google-provider
[_ cfg]
(let [opts {:client-id (:client-id cfg)
:client-secret (:client-secret cfg)
:scopes #{"openid" "email" "profile"}
:auth-uri "https://accounts.google.com/o/oauth2/v2/auth"
:token-uri "https://oauth2.googleapis.com/token"
:user-uri "https://openidconnect.googleapis.com/v1/userinfo"
:name "google"}]
(when (:enabled? cfg)
(if (and (string? (:client-id opts))
(string? (:client-secret opts)))
(do
(l/info :hint "provider initialized"
:provider :google
:client-id (:client-id opts)
:client-secret (obfuscate-string (:client-secret opts)))
opts)
(do
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :google)
nil)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GITHUB AUTH PROVIDER
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- retrieve-github-email
[{:keys [http-client]} tdata info]
(or (some-> info :email p/resolved)
(-> (http-client {:uri "https://api.github.com/user/emails"
:headers {"Authorization" (dm/str (:type tdata) " " (:token tdata))}
:timeout 6000
:method :get})
(p/then (fn [{:keys [status body] :as response}]
(when-not (s/int-in-range? 200 300 status)
(ex/raise :type :internal
:code :unable-to-retrieve-github-emails
:hint "unable to retrieve github emails"
:http-status status
:http-body body))
(->> response :body json/read (filter :primary) first :email))))))
(defmethod ig/prep-key ::github-provider
[_ cfg]
(d/without-nils cfg))
(defmethod ig/init-key ::github-provider
[_ cfg]
(let [opts {:client-id (:client-id cfg)
:client-secret (:client-secret cfg)
:scopes #{"read:user" "user:email"}
:auth-uri "https://github.com/login/oauth/authorize"
:token-uri "https://github.com/login/oauth/access_token"
:user-uri "https://api.github.com/user"
:name "github"
;; Additional hooks for provider specific way of
;; retrieve emails.
:get-email-fn (partial retrieve-github-email cfg)}]
(when (:enabled? cfg)
(if (and (string? (:client-id opts))
(string? (:client-secret opts)))
(do
(l/info :hint "provider initialized"
:provider :github
:client-id (:client-id opts)
:client-secret (obfuscate-string (:client-secret opts)))
opts)
(do
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :github)
nil)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GITLAB AUTH PROVIDER
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod ig/prep-key ::gitlab-provider
[_ cfg]
(d/without-nils cfg))
(defmethod ig/init-key ::gitlab-provider
[_ cfg]
(let [base (:base-uri cfg "https://gitlab.com")
opts {:base-uri base
:client-id (:client-id cfg)
:client-secret (:client-secret cfg)
:scopes #{"openid" "profile" "email"}
:auth-uri (str base "/oauth/authorize")
:token-uri (str base "/oauth/token")
:user-uri (str base "/oauth/userinfo")
:name "gitlab"}]
(when (:enabled? cfg)
(if (and (string? (:client-id opts))
(string? (:client-secret opts)))
(do
(l/info :hint "provider initialized"
:provider :gitlab
:base-uri base
:client-id (:client-id opts)
:client-secret (obfuscate-string (:client-secret opts)))
opts)
(do
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :gitlab)
nil)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HANDLERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- build-redirect-uri
[{:keys [provider] :as cfg}]
(let [public (u/uri (:public-uri cfg))]
(str (assoc public :path (str "/api/auth/oauth/" (:name provider) "/callback")))))
(defn- build-auth-uri
[{:keys [provider] :as cfg} state]
(let [params {:client_id (:client-id provider)
:redirect_uri (build-redirect-uri cfg)
:response_type "code"
:state state
:scope (str/join " " (:scopes provider []))}
query (u/map->query-string params)]
(-> (u/uri (:auth-uri provider))
(assoc :query query)
(str))))
(defn- qualify-props
[provider props]
(reduce-kv (fn [result k v]
(assoc result (keyword (:name provider) (name k)) v))
{}
props))
(defn retrieve-access-token
[{:keys [provider http-client] :as cfg} code]
(let [params {:client_id (:client-id provider)
:client_secret (:client-secret provider)
:code code
:grant_type "authorization_code"
:redirect_uri (build-redirect-uri cfg)}
req {:method :post
:headers {"content-type" "application/x-www-form-urlencoded"
"accept" "application/json"}
:uri (:token-uri provider)
:body (u/map->query-string params)}]
(p/then
(http-client req)
(fn [{:keys [status body] :as res}]
(if (= status 200)
(let [data (json/read body)]
{:token (get data :access_token)
:type (get data :token_type)})
(ex/raise :type :internal
:code :unable-to-retrieve-token
:http-status status
:http-body body))))))
(defn- retrieve-user-info
[{:keys [provider http-client] :as cfg} tdata]
(letfn [(retrieve []
(http-client {:uri (:user-uri provider)
:headers {"Authorization" (str (:type tdata) " " (:token tdata))}
:timeout 6000
:method :get}))
(validate-response [response]
(when-not (s/int-in-range? 200 300 (:status response))
(ex/raise :type :internal
:code :unable-to-retrieve-user-info
:hint "unable to retrieve user info"
:http-status (:status response)
:http-body (:body response)))
response)
(get-email [info]
;; Allow providers hook into this for custom email
;; retrieval method.
(if-let [get-email-fn (:get-email-fn provider)]
(get-email-fn tdata info)
(let [attr-kw (cf/get :oidc-email-attr :email)]
(get info attr-kw))))
(get-name [info]
(let [attr-kw (cf/get :oidc-name-attr :name)]
(get info attr-kw)))
(process-response [response]
(p/let [info (-> response :body json/read)
email (get-email info)]
{:backend (:name provider)
:email email
:fullname (or (get-name info) email)
:props (->> (dissoc info :name :email)
(qualify-props provider))}))
(validate-info [info]
(when-not (s/valid? ::info info)
(l/warn :hint "received incomplete profile info object (please set correct scopes)"
:info (pr-str info))
(ex/raise :type :internal
:code :incomplete-user-info
:hint "inconmplete user info"
:info info))
info)]
(-> (retrieve)
(p/then validate-response)
(p/then process-response)
(p/then validate-info))))
(s/def ::backend ::us/not-empty-string)
(s/def ::email ::us/not-empty-string)
(s/def ::fullname ::us/not-empty-string)
(s/def ::props (s/map-of ::us/keyword any?))
(s/def ::info
(s/keys :req-un [::backend
::email
::fullname
::props]))
(defn retrieve-info
[{:keys [tokens provider] :as cfg} {:keys [params] :as request}]
(letfn [(validate-oidc [info]
;; If the provider is OIDC, we can proceed to check
;; roles if they are defined.
(when (and (= "oidc" (:name provider))
(seq (:roles provider)))
(let [provider-roles (into #{} (:roles provider))
profile-roles (let [attr (cf/get :oidc-roles-attr :roles)
roles (get info attr)]
(cond
(string? roles) (into #{} (str/words roles))
(vector? roles) (into #{} roles)
:else #{}))]
;; check if profile has a configured set of roles
(when-not (set/subset? provider-roles profile-roles)
(ex/raise :type :internal
:code :unable-to-auth
:hint "not enough permissions"))))
info)
(post-process [state info]
(cond-> info
(some? (:invitation-token state))
(assoc :invitation-token (:invitation-token state))
;; If state token comes with props, merge them. The state token
;; props can contain pm_ and utm_ prefixed query params.
(map? (:props state))
(update :props merge (:props state))))]
(when-let [error (get params :error)]
(ex/raise :type :internal
:code :error-on-retrieving-code
:error-id error
:error-desc (get params :error_description)))
(let [state (get params :state)
code (get params :code)
state (tokens :verify {:token state :iss :oauth})]
(-> (p/resolved code)
(p/then #(retrieve-access-token cfg %))
(p/then #(retrieve-user-info cfg %))
(p/then' validate-oidc)
(p/then' (partial post-process state))))))
(defn- retrieve-profile
[{:keys [pool executor] :as cfg} info]
(px/with-dispatch executor
(with-open [conn (db/open pool)]
(some->> (:email info)
(profile/retrieve-profile-data-by-email conn)
(profile/populate-additional-data conn)
(profile/decode-profile-row)))))
(defn- redirect-response
[uri]
(yrs/response :status 302 :headers {"location" (str uri)}))
(defn- generate-error-redirect
[cfg error]
(let [uri (-> (u/uri (:public-uri cfg))
(assoc :path "/#/auth/login")
(assoc :query (u/map->query-string {:error "unable-to-auth" :hint (ex-message error)})))]
(redirect-response uri)))
(defn- generate-redirect
[{:keys [tokens session audit] :as cfg} request info profile]
(if profile
(let [sxf ((:create session) (:id profile))
token (or (:invitation-token info)
(tokens :generate {:iss :auth
:exp (dt/in-future "15m")
:profile-id (:id profile)}))
params {:token token}
uri (-> (u/uri (:public-uri cfg))
(assoc :path "/#/auth/verify-token")
(assoc :query (u/map->query-string params)))]
(when (fn? audit)
(audit :cmd :submit
:type "mutation"
:name "login"
:profile-id (:id profile)
:ip-addr (audit/parse-client-ip request)
:props (audit/profile->props profile)))
(->> (redirect-response uri)
(sxf request)))
(let [info (assoc info
:iss :prepared-register
:is-active true
:exp (dt/in-future {:hours 48}))
token (tokens :generate info)
params (d/without-nils
{:token token
:fullname (:fullname info)})
uri (-> (u/uri (:public-uri cfg))
(assoc :path "/#/auth/register/validate")
(assoc :query (u/map->query-string params)))]
(redirect-response uri))))
(defn- auth-handler
[{:keys [tokens] :as cfg} {:keys [params] :as request}]
(let [props (audit/extract-utm-params params)
state (tokens :generate
{:iss :oauth
:invitation-token (:invitation-token params)
:props props
:exp (dt/in-future "15m")})
uri (build-auth-uri cfg state)]
(yrs/response 200 {:redirect-uri uri})))
(defn- callback-handler
[cfg request]
(letfn [(process-request []
(p/let [info (retrieve-info cfg request)
profile (retrieve-profile cfg info)]
(generate-redirect cfg request info profile)))
(handle-error [cause]
(l/error :hint "error on oauth process" :cause cause)
(generate-error-redirect cfg cause))]
(-> (process-request)
(p/catch handle-error))))
(def provider-lookup
{:compile
(fn [& _]
(fn [handler]
(fn [{:keys [providers] :as cfg} request]
(let [provider (some-> request :path-params :provider keyword)]
(if-let [provider (get providers provider)]
(handler (assoc cfg :provider provider) request)
(ex/raise :type :restriction
:code :provider-not-configured
:provider provider
:hint "provider not configured"))))))})
(s/def ::public-uri ::us/not-empty-string)
(s/def ::http-client fn?)
(s/def ::session map?)
(s/def ::tokens fn?)
(s/def ::providers map?)
(defmethod ig/pre-init-spec ::routes
[_]
(s/keys :req-un [::public-uri
::session
::tokens
::http-client
::providers
::db/pool
::wrk/executor]))
(defmethod ig/init-key ::routes
[_ {:keys [executor session] :as cfg}]
(let [cfg (update cfg :provider d/without-nils)]
["" {:middleware [[(:middleware session)]
[hmw/with-promise-async executor]
[hmw/with-config cfg]
[provider-lookup]
]}
;; We maintain the both URI prefixes for backward compatibility.
["/auth/oauth"
["/:provider"
{:handler auth-handler
:allowed-methods #{:post}}]
["/:provider/callback"
{:handler callback-handler
:allowed-methods #{:get}}]]
["/auth/oidc"
["/:provider"
{:handler auth-handler
:allowed-methods #{:post}}]
["/:provider/callback"
{:handler callback-handler
:allowed-methods #{:get}}]]]))

View File

@@ -1,258 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.cli.fixtures
"A initial fixtures."
(:require
[app.common.pages :as cp]
[app.common.uuid :as uuid]
[app.db :as db]
[app.main :as main]
[app.rpc.mutations.profile :as profile]
[app.util.blob :as blob]
[app.util.logging :as l]
[buddy.hashers :as hashers]
[integrant.core :as ig]))
(defn- mk-uuid
[prefix & args]
(uuid/namespaced uuid/zero (apply str prefix (interpose "-" args))))
;; --- Profiles creation
(def password (hashers/derive "123123"))
(def preset-small
{:num-teams 5
:num-profiles 5
:num-profiles-per-team 5
:num-projects-per-team 5
:num-files-per-project 5
:num-draft-files-per-profile 10})
(defn- rng-ids
[rng n max]
(let [stream (->> (.longs rng 0 max)
(.iterator)
(iterator-seq))]
(reduce (fn [acc item]
(if (= (count acc) n)
(reduced acc)
(conj acc item)))
#{}
stream)))
(defn- rng-vec
[rng vdata n]
(let [ids (rng-ids rng n (count vdata))]
(mapv #(nth vdata %) ids)))
(defn- rng-nth
[rng vdata]
(let [stream (->> (.longs rng 0 (count vdata))
(.iterator)
(iterator-seq))]
(nth vdata (first stream))))
(defn- collect
[f items]
(reduce #(conj %1 (f %2)) [] items))
(defn- register-profile
[conn params]
(->> (#'profile/create-profile conn params)
(#'profile/create-profile-relations conn)))
(defn impl-run
[pool opts]
(let [rng (java.util.Random. 1)]
(letfn [(create-profile [conn index]
(let [id (mk-uuid "profile" index)
_ (l/info :action "create profile"
:index index
:id id)
prof (register-profile conn
{:id id
:fullname (str "Profile " index)
:password "123123"
:is-demo true
:email (str "profile" index "@example.com")})
team-id (:default-team-id prof)
owner-id id]
(let [project-ids (collect (partial create-project conn team-id owner-id)
(range (:num-projects-per-team opts)))]
(run! (partial create-files conn owner-id) project-ids))
prof))
(create-profiles [conn]
(l/info :action "create profiles")
(collect (partial create-profile conn)
(range (:num-profiles opts))))
(create-team [conn index]
(let [id (mk-uuid "team" index)
name (str "Team" index)]
(l/info :action "create team"
:index index
:id id)
(db/insert! conn :team {:id id
:name name})
id))
(create-teams [conn]
(l/info :action "create teams")
(collect (partial create-team conn)
(range (:num-teams opts))))
(create-file [conn owner-id project-id index]
(let [id (mk-uuid "file" project-id index)
name (str "file" index)
data (cp/make-file-data id)]
(l/info :action "create file"
:index index
:id id)
(db/insert! conn :file
{:id id
:data (blob/encode data)
:project-id project-id
:name name})
(db/insert! conn :file-profile-rel
{:file-id id
:profile-id owner-id
:is-owner true
:is-admin true
:can-edit true})
id))
(create-files [conn owner-id project-id]
(l/info :action "create files")
(run! (partial create-file conn owner-id project-id)
(range (:num-files-per-project opts))))
(create-project [conn team-id owner-id index]
(let [id (if index
(mk-uuid "project" team-id index)
(mk-uuid "project" team-id))
name (if index
(str "project " index)
"Drafts")
is-default (nil? index)]
(l/info :action "create project"
:index index
:id id)
(db/insert! conn :project
{:id id
:team-id team-id
:is-default is-default
:name name})
(db/insert! conn :project-profile-rel
{:project-id id
:profile-id owner-id
:is-owner true
:is-admin true
:can-edit true})
id))
(create-projects [conn team-id profile-ids]
(l/info :action "create projects")
(let [owner-id (rng-nth rng profile-ids)
project-ids (conj
(collect (partial create-project conn team-id owner-id)
(range (:num-projects-per-team opts)))
(create-project conn team-id owner-id nil))]
(run! (partial create-files conn owner-id) project-ids)))
(assign-profile-to-team [conn team-id owner? profile-id]
(db/insert! conn :team-profile-rel
{:team-id team-id
:profile-id profile-id
:is-owner owner?
:is-admin true
:can-edit true}))
(setup-team [conn team-id profile-ids]
(l/info :action "setup team"
:team-id team-id
:profile-ids (pr-str profile-ids))
(assign-profile-to-team conn team-id true (first profile-ids))
(run! (partial assign-profile-to-team conn team-id false)
(rest profile-ids))
(create-projects conn team-id profile-ids))
(assign-teams-and-profiles [conn teams profiles]
(l/info :action "assign teams and profiles")
(loop [team-id (first teams)
teams (rest teams)]
(when-not (nil? team-id)
(let [n-profiles-team (:num-profiles-per-team opts)
selected-profiles (rng-vec rng profiles n-profiles-team)]
(setup-team conn team-id selected-profiles)
(recur (first teams)
(rest teams))))))
(create-draft-file [conn owner index]
(let [owner-id (:id owner)
id (mk-uuid "file" "draft" owner-id index)
name (str "file" index)
project-id (:default-project-id owner)
data (cp/make-file-data id)]
(l/info :action "create draft file"
:index index
:id id)
(db/insert! conn :file
{:id id
:data (blob/encode data)
:project-id project-id
:name name})
(db/insert! conn :file-profile-rel
{:file-id id
:profile-id owner-id
:is-owner true
:is-admin true
:can-edit true})
id))
(create-draft-files [conn profile]
(run! (partial create-draft-file conn profile)
(range (:num-draft-files-per-profile opts))))
]
(db/with-atomic [conn pool]
(let [profiles (create-profiles conn)
teams (create-teams conn)]
(assign-teams-and-profiles conn teams (map :id profiles))
(run! (partial create-draft-files conn) profiles))))))
(defn run-in-system
[system preset]
(let [pool (:app.db/pool system)
preset (if (map? preset)
preset
(case preset
(nil "small" :small) preset-small
;; "medium" preset-medium
;; "big" preset-big
preset-small))]
(impl-run pool preset)))
(defn run
[{:keys [preset] :or {preset :small}}]
(let [config (select-keys main/system-config
[:app.db/pool
:app.telemetry/migrations
:app.migrations/migrations
:app.migrations/all
:app.metrics/metrics])
_ (ig/load-namespaces config)
system (-> (ig/prep config)
(ig/init))]
(try
(run-in-system system preset)
(catch Exception e
(l/error :hint "unhandled exception" :cause e))
(finally
(ig/halt! system)))))

View File

@@ -7,11 +7,12 @@
(ns app.cli.manage
"A manage cli api."
(:require
[app.common.logging :as l]
[app.db :as db]
[app.main :as main]
[app.rpc.commands.auth :as cmd.auth]
[app.rpc.mutations.profile :as profile]
[app.rpc.queries.profile :refer [retrieve-profile-data-by-email]]
[app.util.logging :as l]
[clojure.string :as str]
[clojure.tools.cli :refer [parse-opts]]
[integrant.core :as ig])
@@ -54,13 +55,13 @@
:type :password}))]
(try
(db/with-atomic [conn (:app.db/pool system)]
(->> (profile/create-profile conn
(->> (cmd.auth/create-profile conn
{:fullname fullname
:email email
:password password
:is-active true
:is-demo false})
(profile/create-profile-relations conn)))
(cmd.auth/create-profile-relations conn)))
(when (pos? (:verbosity options))
(println "User created successfully."))
@@ -140,7 +141,6 @@
indicating the action the program should take and the options provided."
[args]
(let [{:keys [options arguments errors summary] :as opts} (parse-opts args cli-options)]
;; (pp/pprint opts)
(cond
(:help options) ; help => exit OK with usage summary
{:exit-message (usage summary) :ok? true}

View File

@@ -1,129 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.cli.migrate-media
(:require
[app.common.media :as cm]
[app.config :as cf]
[app.db :as db]
[app.main :as main]
[app.storage :as sto]
[app.util.logging :as l]
[cuerdas.core :as str]
[datoteka.core :as fs]
[integrant.core :as ig]))
(declare migrate-profiles)
(declare migrate-teams)
(declare migrate-file-media)
(defn run-in-system
[system]
(db/with-atomic [conn (:app.db/pool system)]
(let [system (assoc system ::conn conn)]
(migrate-profiles system)
(migrate-teams system)
(migrate-file-media system))
system))
(defn run
[]
(let [config (select-keys main/system-config
[:app.db/pool
:app.migrations/migrations
:app.metrics/metrics
:app.storage.s3/backend
:app.storage.db/backend
:app.storage.fs/backend
:app.storage/storage])]
(ig/load-namespaces config)
(try
(-> (ig/prep config)
(ig/init)
(run-in-system)
(ig/halt!))
(catch Exception e
(l/error :hint "unhandled exception" :cause e)))))
;; --- IMPL
(defn migrate-profiles
[{:keys [::conn] :as system}]
(letfn [(retrieve-profiles [conn]
(->> (db/exec! conn ["select * from profile"])
(filter #(not (str/empty? (:photo %))))
(seq)))]
(let [base (fs/path (cf/get :storage-fs-old-directory))
storage (-> (:app.storage/storage system)
(assoc :conn conn))]
(doseq [profile (retrieve-profiles conn)]
(let [path (fs/path (:photo profile))
full (-> (fs/join base path)
(fs/normalize))
ext (fs/ext path)
mtype (cm/format->mtype (keyword ext))
obj (sto/put-object storage {:content (sto/content full)
:content-type mtype})]
(db/update! conn :profile
{:photo-id (:id obj)}
{:id (:id profile)}))))))
(defn migrate-teams
[{:keys [::conn] :as system}]
(letfn [(retrieve-teams [conn]
(->> (db/exec! conn ["select * from team"])
(filter #(not (str/empty? (:photo %))))
(seq)))]
(let [base (fs/path (cf/get :storage-fs-old-directory))
storage (-> (:app.storage/storage system)
(assoc :conn conn))]
(doseq [team (retrieve-teams conn)]
(let [path (fs/path (:photo team))
full (-> (fs/join base path)
(fs/normalize))
ext (fs/ext path)
mtype (cm/format->mtype (keyword ext))
obj (sto/put-object storage {:content (sto/content full)
:content-type mtype})]
(db/update! conn :team
{:photo-id (:id obj)}
{:id (:id team)}))))))
(defn migrate-file-media
[{:keys [::conn] :as system}]
(letfn [(retrieve-media-objects [conn]
(->> (db/exec! conn ["select fmo.id, fmo.path, fth.path as thumbnail_path
from file_media_object as fmo
join file_media_thumbnail as fth on (fth.media_object_id = fmo.id)"])
(seq)))]
(let [base (fs/path (cf/get :storage-fs-old-directory))
storage (-> (:app.storage/storage system)
(assoc :conn conn))]
(doseq [mobj (retrieve-media-objects conn)]
(let [img-path (fs/path (:path mobj))
thm-path (fs/path (:thumbnail-path mobj))
img-path (-> (fs/join base img-path)
(fs/normalize))
thm-path (-> (fs/join base thm-path)
(fs/normalize))
img-ext (fs/ext img-path)
thm-ext (fs/ext thm-path)
img-mtype (cm/format->mtype (keyword img-ext))
thm-mtype (cm/format->mtype (keyword thm-ext))
img-obj (sto/put-object storage {:content (sto/content img-path)
:content-type img-mtype})
thm-obj (sto/put-object storage {:content (sto/content thm-path)
:content-type thm-mtype})]
(db/update! conn :file-media-object
{:media-id (:id img-obj)
:thumbnail-id (:id thm-obj)}
{:id (:id mobj)}))))))

View File

@@ -10,6 +10,8 @@
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.flags :as flags]
[app.common.logging :as l]
[app.common.spec :as us]
[app.common.version :as v]
[app.util.time :as dt]
@@ -40,36 +42,28 @@
data))
(def defaults
{:http-server-port 6060
:host "devenv"
:tenant "dev"
:database-uri "postgresql://postgres/penpot"
{:database-uri "postgresql://postgres/penpot"
:database-username "penpot"
:database-password "penpot"
:default-blob-version 3
:default-blob-version 4
:loggers-zmq-uri "tcp://localhost:45556"
:asserts-enabled false
:file-change-snapshot-every 5
:file-change-snapshot-timeout "3h"
:public-uri "http://localhost:3449"
:redis-uri "redis://redis/0"
:host "localhost"
:tenant "main"
:redis-uri "redis://redis/0"
:srepl-host "127.0.0.1"
:srepl-port 6062
:assets-storage-backend :assets-fs
:storage-assets-fs-directory "assets"
:feedback-destination "info@example.com"
:feedback-enabled false
:assets-path "/internal/assets/"
:rlimits-password 10
:rlimits-image 2
:smtp-enabled false
:smtp-default-reply-to "Penpot <no-reply@example.com>"
:smtp-default-from "Penpot <no-reply@example.com>"
@@ -79,40 +73,51 @@
:profile-bounce-max-age (dt/duration {:days 7})
:profile-bounce-threshold 10
:allow-demo-users true
:registration-enabled true
:telemetry-enabled false
:telemetry-uri "https://telemetry.penpot.app/"
:ldap-user-query "(|(uid=:username)(mail=:username))"
:ldap-attrs-username "uid"
:ldap-attrs-email "mail"
:ldap-attrs-fullname "cn"
:ldap-attrs-photo "jpegPhoto"
;; a server prop key where initial project is stored.
:initial-project-skey "initial-project"
})
:initial-project-skey "initial-project"})
(s/def ::audit-enabled ::us/boolean)
(s/def ::audit-archive-enabled ::us/boolean)
(s/def ::audit-archive-uri ::us/string)
(s/def ::audit-archive-gc-enabled ::us/boolean)
(s/def ::audit-archive-gc-max-age ::dt/duration)
(s/def ::media-max-file-size ::us/integer)
(s/def ::flags ::us/vec-of-valid-keywords)
(s/def ::telemetry-enabled ::us/boolean)
(s/def ::audit-log-archive-uri ::us/string)
(s/def ::audit-log-gc-max-age ::dt/duration)
(s/def ::admins ::us/set-of-non-empty-strings)
(s/def ::file-change-snapshot-every ::us/integer)
(s/def ::file-change-snapshot-timeout ::dt/duration)
(s/def ::default-executor-parallelism ::us/integer)
(s/def ::blocking-executor-parallelism ::us/integer)
(s/def ::worker-executor-parallelism ::us/integer)
(s/def ::authenticated-cookie-domain ::us/string)
(s/def ::authenticated-cookie-name ::us/string)
(s/def ::auth-token-cookie-name ::us/string)
(s/def ::auth-token-cookie-max-age ::dt/duration)
(s/def ::secret-key ::us/string)
(s/def ::allow-demo-users ::us/boolean)
(s/def ::asserts-enabled ::us/boolean)
(s/def ::assets-path ::us/string)
(s/def ::database-password (s/nilable ::us/string))
(s/def ::database-uri ::us/string)
(s/def ::database-username (s/nilable ::us/string))
(s/def ::database-readonly ::us/boolean)
(s/def ::database-min-pool-size ::us/integer)
(s/def ::database-max-pool-size ::us/integer)
(s/def ::default-blob-version ::us/integer)
(s/def ::error-report-webhook ::us/string)
(s/def ::feedback-destination ::us/string)
(s/def ::feedback-enabled ::us/boolean)
(s/def ::feedback-token ::us/string)
(s/def ::user-feedback-destination ::us/string)
(s/def ::github-client-id ::us/string)
(s/def ::github-client-secret ::us/string)
(s/def ::gitlab-base-uri ::us/string)
@@ -126,18 +131,21 @@
(s/def ::oidc-token-uri ::us/string)
(s/def ::oidc-auth-uri ::us/string)
(s/def ::oidc-user-uri ::us/string)
(s/def ::oidc-scopes ::us/set-of-str)
(s/def ::oidc-roles ::us/set-of-str)
(s/def ::oidc-scopes ::us/set-of-non-empty-strings)
(s/def ::oidc-roles ::us/set-of-non-empty-strings)
(s/def ::oidc-roles-attr ::us/keyword)
(s/def ::oidc-email-attr ::us/keyword)
(s/def ::oidc-name-attr ::us/keyword)
(s/def ::host ::us/string)
(s/def ::http-server-port ::us/integer)
(s/def ::http-session-idle-max-age ::dt/duration)
(s/def ::http-session-updater-batch-max-age ::dt/duration)
(s/def ::http-session-updater-batch-max-size ::us/integer)
(s/def ::http-server-host ::us/string)
(s/def ::http-server-max-body-size ::us/integer)
(s/def ::http-server-max-multipart-body-size ::us/integer)
(s/def ::http-server-io-threads ::us/integer)
(s/def ::http-server-worker-threads ::us/integer)
(s/def ::initial-project-skey ::us/string)
(s/def ::ldap-attrs-email ::us/string)
(s/def ::ldap-attrs-fullname ::us/string)
(s/def ::ldap-attrs-photo ::us/string)
(s/def ::ldap-attrs-username ::us/string)
(s/def ::ldap-base-dn ::us/string)
(s/def ::ldap-bind-dn ::us/string)
@@ -157,13 +165,13 @@
(s/def ::profile-complaint-threshold ::us/integer)
(s/def ::public-uri ::us/string)
(s/def ::redis-uri ::us/string)
(s/def ::registration-domain-whitelist ::us/set-of-str)
(s/def ::registration-enabled ::us/boolean)
(s/def ::rlimits-image ::us/integer)
(s/def ::rlimits-password ::us/integer)
(s/def ::registration-domain-whitelist ::us/set-of-non-empty-strings)
(s/def ::rlimit-font ::us/integer)
(s/def ::rlimit-file-update ::us/integer)
(s/def ::rlimit-image ::us/integer)
(s/def ::rlimit-password ::us/integer)
(s/def ::smtp-default-from ::us/string)
(s/def ::smtp-default-reply-to ::us/string)
(s/def ::smtp-enabled ::us/boolean)
(s/def ::smtp-host ::us/string)
(s/def ::smtp-password (s/nilable ::us/string))
(s/def ::smtp-port ::us/integer)
@@ -177,31 +185,45 @@
(s/def ::storage-assets-fs-directory ::us/string)
(s/def ::storage-assets-s3-bucket ::us/string)
(s/def ::storage-assets-s3-region ::us/keyword)
(s/def ::storage-assets-s3-endpoint ::us/string)
(s/def ::storage-fdata-s3-bucket ::us/string)
(s/def ::storage-fdata-s3-region ::us/keyword)
(s/def ::storage-fdata-s3-prefix ::us/string)
(s/def ::telemetry-enabled ::us/boolean)
(s/def ::storage-fdata-s3-endpoint ::us/string)
(s/def ::telemetry-uri ::us/string)
(s/def ::telemetry-with-taiga ::us/boolean)
(s/def ::tenant ::us/string)
(s/def ::sentry-trace-sample-rate ::us/number)
(s/def ::sentry-attach-stack-trace ::us/boolean)
(s/def ::sentry-debug ::us/boolean)
(s/def ::sentry-dsn ::us/string)
(s/def ::config
(s/keys :opt-un [::secret-key
::flags
::admins
::allow-demo-users
::audit-enabled
::audit-archive-enabled
::audit-archive-uri
::audit-archive-gc-enabled
::audit-archive-gc-max-age
::asserts-enabled
::audit-log-archive-uri
::audit-log-gc-max-age
::auth-token-cookie-name
::auth-token-cookie-max-age
::authenticated-cookie-name
::authenticated-cookie-domain
::database-password
::database-uri
::database-username
::database-readonly
::database-min-pool-size
::database-max-pool-size
::default-blob-version
::error-report-webhook
::feedback-destination
::feedback-enabled
::feedback-token
::default-executor-parallelism
::blocking-executor-parallelism
::worker-executor-parallelism
::file-change-snapshot-every
::file-change-snapshot-timeout
::user-feedback-destination
::github-client-id
::github-client-secret
::gitlab-base-uri
@@ -217,16 +239,19 @@
::oidc-user-uri
::oidc-scopes
::oidc-roles-attr
::oidc-email-attr
::oidc-name-attr
::oidc-roles
::host
::http-server-host
::http-server-port
::http-session-idle-max-age
::http-session-updater-batch-max-age
::http-session-updater-batch-max-size
::http-server-max-body-size
::http-server-max-multipart-body-size
::http-server-io-threads
::http-server-worker-threads
::initial-project-skey
::ldap-attrs-email
::ldap-attrs-fullname
::ldap-attrs-photo
::ldap-attrs-username
::ldap-base-dn
::ldap-bind-dn
@@ -239,6 +264,7 @@
::local-assets-uri
::loggers-loki-uri
::loggers-zmq-uri
::media-max-file-size
::profile-bounce-max-age
::profile-bounce-threshold
::profile-complaint-max-age
@@ -246,38 +272,51 @@
::public-uri
::redis-uri
::registration-domain-whitelist
::registration-enabled
::rlimits-image
::rlimits-password
::rlimit-font
::rlimit-file-update
::rlimit-image
::rlimit-password
::sentry-dsn
::sentry-debug
::sentry-attach-stack-trace
::sentry-trace-sample-rate
::smtp-default-from
::smtp-default-reply-to
::smtp-enabled
::smtp-host
::smtp-password
::smtp-port
::smtp-ssl
::smtp-tls
::smtp-username
::srepl-host
::srepl-port
::assets-storage-backend
::storage-assets-fs-directory
::storage-assets-s3-bucket
::storage-assets-s3-region
::storage-assets-s3-endpoint
::fdata-storage-backend
::storage-fdata-s3-bucket
::storage-fdata-s3-region
::storage-fdata-s3-prefix
::storage-fdata-s3-endpoint
::telemetry-enabled
::telemetry-uri
::telemetry-referer
::telemetry-with-taiga
::tenant]))
(def default-flags
[:enable-backend-api-doc
:enable-backend-worker
:enable-secure-session-cookies])
(defn- parse-flags
[config]
(flags/parse flags/default
default-flags
(:flags config)))
(defn read-env
[prefix]
(let [prefix (str prefix "-")
@@ -300,15 +339,22 @@
(when (ex/ex-info? e)
(println ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;")
(println "Error on validating configuration:")
(println (:explain (ex-data e))
(println ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;")))
(println (us/pretty-explain (ex-data e)))
(println ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"))
(throw e))))
(def version (v/parse (or (some-> (io/resource "version.txt")
(slurp)
(str/trim))
"%version%")))
(def config (atom (read-config)))
(def version
(v/parse (or (some-> (io/resource "version.txt")
(slurp)
(str/trim))
"%version%")))
(defonce ^:dynamic config (read-config))
(defonce ^:dynamic flags
(let [flags (parse-flags config)]
(l/info :hint "flags initialized" :flags (str/join "," (map name flags)))
flags))
(def deletion-delay
(dt/duration {:days 7}))
@@ -316,9 +362,9 @@
(defn get
"A configuration getter. Helps code be more testable."
([key]
(c/get @config key))
(c/get config key))
([key default]
(c/get @config key default)))
(c/get config key default)))
;; Set value for all new threads bindings.
(alter-var-root #'*assert* (constantly (get :asserts-enabled)))
(alter-var-root #'*assert* (constantly (contains? flags :backend-asserts)))

View File

@@ -9,13 +9,13 @@
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.geom.point :as gpt]
[app.common.logging :as l]
[app.common.spec :as us]
[app.common.transit :as t]
[app.common.uuid :as uuid]
[app.db.sql :as sql]
[app.metrics :as mtx]
[app.util.json :as json]
[app.util.logging :as l]
[app.util.migrations :as mg]
[app.util.time :as dt]
[clojure.java.io :as io]
@@ -27,14 +27,16 @@
com.zaxxer.hikari.HikariConfig
com.zaxxer.hikari.HikariDataSource
com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory
java.io.InputStream
java.io.OutputStream
java.lang.AutoCloseable
java.sql.Connection
java.sql.Savepoint
org.postgresql.PGConnection
org.postgresql.geometric.PGpoint
org.postgresql.jdbc.PgArray
org.postgresql.largeobject.LargeObject
org.postgresql.largeobject.LargeObjectManager
org.postgresql.jdbc.PgArray
org.postgresql.util.PGInterval
org.postgresql.util.PGobject))
@@ -45,76 +47,116 @@
;; Initialization
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare instrument-jdbc!)
(declare apply-migrations!)
(s/def ::name keyword?)
(s/def ::uri ::us/not-empty-string)
(s/def ::min-pool-size ::us/integer)
(s/def ::max-pool-size ::us/integer)
(s/def ::connection-timeout ::us/integer)
(s/def ::max-size ::us/integer)
(s/def ::min-size ::us/integer)
(s/def ::migrations map?)
(s/def ::name keyword?)
(s/def ::password ::us/string)
(s/def ::uri ::us/not-empty-string)
(s/def ::username ::us/string)
(s/def ::validation-timeout ::us/integer)
(s/def ::read-only? ::us/boolean)
(defmethod ig/pre-init-spec ::pool [_]
(s/keys :req-un [::uri ::name ::min-pool-size ::max-pool-size ::migrations ::mtx/metrics]))
(s/def ::pool-options
(s/keys :opt-un [::uri ::name
::min-size
::max-size
::connection-timeout
::validation-timeout
::migrations
::username
::password
::mtx/metrics
::read-only?]))
(def defaults
{:name :main
:min-size 0
:max-size 30
:connection-timeout 10000
:validation-timeout 10000
:idle-timeout 120000 ; 2min
:max-lifetime 1800000 ; 30m
:read-only? false})
(defmethod ig/prep-key ::pool
[_ cfg]
(merge defaults (d/without-nils cfg)))
;; Don't validate here, just validate that a map is received.
(defmethod ig/pre-init-spec ::pool [_] ::pool-options)
(defmethod ig/init-key ::pool
[_ {:keys [migrations metrics] :as cfg}]
(l/info :action "initialize connection pool"
:name (d/name (:name cfg))
:uri (:uri cfg))
(instrument-jdbc! (:registry metrics))
(let [pool (create-pool cfg)]
(when (seq migrations)
(with-open [conn ^AutoCloseable (open pool)]
(mg/setup! conn)
(doseq [[name steps] migrations]
(mg/migrate! conn {:name (d/name name) :steps steps}))))
pool))
[_ {:keys [migrations read-only? uri] :as cfg}]
(if uri
(let [pool (create-pool cfg)]
(l/info :hint "initialize connection pool"
:name (d/name (:name cfg))
:uri uri
:read-only read-only?
:with-credentials (and (contains? cfg :username)
(contains? cfg :password))
:min-size (:min-size cfg)
:max-size (:max-size cfg))
(when-not read-only?
(some->> (seq migrations) (apply-migrations! pool)))
pool)
(do
(l/warn :hint "unable to initialize pool, missing url"
:name (d/name (:name cfg))
:read-only read-only?)
nil)))
(defmethod ig/halt-key! ::pool
[_ pool]
(.close ^HikariDataSource pool))
(when pool
(.close ^HikariDataSource pool)))
(defn- instrument-jdbc!
[registry]
(mtx/instrument-vars!
[#'next.jdbc/execute-one!
#'next.jdbc/execute!]
{:registry registry
:type :counter
:name "database_query_total"
:help "An absolute counter of database queries."}))
(defn- apply-migrations!
[pool migrations]
(with-open [conn ^AutoCloseable (open pool)]
(mg/setup! conn)
(doseq [[name steps] migrations]
(mg/migrate! conn {:name (d/name name) :steps steps}))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; API & Impl
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def initsql
(str "SET statement_timeout = 120000;\n"
"SET idle_in_transaction_session_timeout = 120000;"))
(str "SET statement_timeout = 300000;\n"
"SET idle_in_transaction_session_timeout = 300000;"))
(defn- create-datasource-config
[{:keys [metrics] :as cfg}]
(let [dburi (:uri cfg)
username (:username cfg)
password (:password cfg)
config (HikariConfig.)
mtf (PrometheusMetricsTrackerFactory. (:registry metrics))]
[{:keys [metrics uri] :as cfg}]
(let [config (HikariConfig.)]
(doto config
(.setJdbcUrl (str "jdbc:" dburi))
(.setPoolName (d/name (:name cfg)))
(.setJdbcUrl (str "jdbc:" uri))
(.setPoolName (d/name (:name cfg)))
(.setAutoCommit true)
(.setReadOnly false)
(.setConnectionTimeout 8000) ;; 8seg
(.setValidationTimeout 8000) ;; 8seg
(.setIdleTimeout 120000) ;; 2min
(.setMaxLifetime 1800000) ;; 30min
(.setMinimumIdle (:min-pool-size cfg 0))
(.setMaximumPoolSize (:max-pool-size cfg 30))
(.setMetricsTrackerFactory mtf)
(.setReadOnly (:read-only? cfg))
(.setConnectionTimeout (:connection-timeout cfg))
(.setValidationTimeout (:validation-timeout cfg))
(.setIdleTimeout (:idle-timeout cfg))
(.setMaxLifetime (:max-lifetime cfg))
(.setMinimumIdle (:min-size cfg))
(.setMaximumPoolSize (:max-size cfg))
(.setConnectionInitSql initsql)
(.setInitializationFailTimeout -1))
(when username (.setUsername config username))
(when password (.setPassword config password))
;; When metrics namespace is provided
(when metrics
(->> (:registry metrics)
(PrometheusMetricsTrackerFactory.)
(.setMetricsTrackerFactory config)))
(some->> ^String (:username cfg) (.setUsername config))
(some->> ^String (:password cfg) (.setPassword config))
config))
(defn pool?
@@ -123,11 +165,15 @@
(s/def ::pool pool?)
(defn pool-closed?
(defn closed?
[pool]
(.isClosed ^HikariDataSource pool))
(defn- create-pool
(defn read-only?
[pool]
(.isReadOnly ^HikariDataSource pool))
(defn create-pool
[cfg]
(let [dsc (create-datasource-config cfg)]
(jdbc-dt/read-as-instant)
@@ -179,7 +225,7 @@
[& args]
`(jdbc/with-transaction ~@args))
(defn ^Connection open
(defn open
[pool]
(jdbc/get-connection pool))
@@ -199,21 +245,21 @@
([ds table params opts]
(exec-one! ds
(sql/insert table params opts)
(assoc opts :return-keys true))))
(merge {:return-keys true} opts))))
(defn insert-multi!
([ds table cols rows] (insert-multi! ds table cols rows nil))
([ds table cols rows opts]
(exec! ds
(sql/insert-multi table cols rows opts)
(assoc opts :return-keys true))))
(merge {:return-keys true} opts))))
(defn update!
([ds table params where] (update! ds table params where nil))
([ds table params where opts]
(exec-one! ds
(sql/update table params where opts)
(assoc opts :return-keys true))))
(merge {:return-keys true} opts))))
(defn delete!
([ds table params] (delete! ds table params nil))
@@ -231,9 +277,9 @@
(defn get-by-params
([ds table params]
(get-by-params ds table params nil))
([ds table params {:keys [uncheked] :or {uncheked false} :as opts}]
([ds table params {:keys [check-not-found] :or {check-not-found true} :as opts}]
(let [res (exec-one! ds (sql/select table params opts))]
(when (and (not uncheked) (or (not res) (is-deleted? res)))
(when (and check-not-found (or (not res) (is-deleted? res)))
(ex/raise :type :not-found
:table table
:hint "database object not found"))
@@ -267,13 +313,28 @@
(instance? PGpoint v))
(defn pgarray?
[v]
(instance? PgArray v))
([v] (instance? PgArray v))
([v type]
(and (instance? PgArray v)
(= type (.getBaseTypeName ^PgArray v)))))
(defn pgarray-of-uuid?
[v]
(and (pgarray? v) (= "uuid" (.getBaseTypeName ^PgArray v))))
(defn decode-pgarray
([v] (some->> ^PgArray v .getArray vec))
([v in] (some->> ^PgArray v .getArray (into in)))
([v in xf] (some->> ^PgArray v .getArray (into in xf))))
(defn pgarray->set
[v]
(set (.getArray ^PgArray v)))
(defn pgarray->vector
[v]
(vec (.getArray ^PgArray v)))
(defn pgpoint
[p]
(PGpoint. (:x p) (:y p)))
@@ -285,7 +346,6 @@
(.createArrayOf conn ^String type (into-array Object objects))
(.createArrayOf conn ^String type objects))))
(defn decode-pgpoint
[^PGpoint v]
(gpt/point (.-x v) (.-y v)))
@@ -331,7 +391,7 @@
val (.getValue o)]
(if (or (= typ "json")
(= typ "jsonb"))
(json/decode-str val)
(json/read val)
val)))
(defn decode-transit-pgobject
@@ -367,16 +427,7 @@
[data]
(doto (org.postgresql.util.PGobject.)
(.setType "jsonb")
(.setValue (json/encode-str data))))
(defn pgarray->set
[v]
(set (.getArray ^PgArray v)))
(defn pgarray->vector
[v]
(vec (.getArray ^PgArray v)))
(.setValue (json/write-str data))))
;; --- Locks

View File

@@ -7,12 +7,13 @@
(ns app.emails
"Main api for send emails."
(:require
[app.common.logging :as l]
[app.common.pprint :as pp]
[app.common.spec :as us]
[app.config :as cfg]
[app.config :as cf]
[app.db :as db]
[app.db.sql :as sql]
[app.util.emails :as emails]
[app.util.logging :as l]
[app.worker :as wrk]
[clojure.spec.alpha :as s]
[integrant.core :as ig]))
@@ -54,10 +55,10 @@
(defn allow-send-emails?
[conn profile]
(when-not (:is-muted profile false)
(let [complaint-threshold (cfg/get :profile-complaint-threshold)
complaint-max-age (cfg/get :profile-complaint-max-age)
bounce-threshold (cfg/get :profile-bounce-threshold)
bounce-max-age (cfg/get :profile-bounce-max-age)
(let [complaint-threshold (cf/get :profile-complaint-threshold)
complaint-max-age (cf/get :profile-complaint-max-age)
bounce-threshold (cf/get :profile-bounce-threshold)
bounce-max-age (cf/get :profile-bounce-max-age)
{:keys [complaints bounces] :as result}
(db/exec-one! conn [sql:profile-complaint-report
@@ -66,8 +67,8 @@
(:id profile)
(db/interval bounce-max-age)])]
(and (< complaints complaint-threshold)
(< bounces bounce-threshold)))))
(and (< (or complaints 0) complaint-threshold)
(< (or bounces 0) bounce-threshold)))))
(defn has-complaint-reports?
([conn email] (has-complaint-reports? conn email nil))
@@ -140,19 +141,17 @@
(declare send-console!)
(s/def ::username ::cfg/smtp-username)
(s/def ::password ::cfg/smtp-password)
(s/def ::tls ::cfg/smtp-tls)
(s/def ::ssl ::cfg/smtp-ssl)
(s/def ::host ::cfg/smtp-host)
(s/def ::port ::cfg/smtp-port)
(s/def ::default-reply-to ::cfg/smtp-default-reply-to)
(s/def ::default-from ::cfg/smtp-default-from)
(s/def ::enabled ::cfg/smtp-enabled)
(s/def ::username ::cf/smtp-username)
(s/def ::password ::cf/smtp-password)
(s/def ::tls ::cf/smtp-tls)
(s/def ::ssl ::cf/smtp-ssl)
(s/def ::host ::cf/smtp-host)
(s/def ::port ::cf/smtp-port)
(s/def ::default-reply-to ::cf/smtp-default-reply-to)
(s/def ::default-from ::cf/smtp-default-from)
(defmethod ig/pre-init-spec ::sendmail-handler [_]
(s/keys :req-un [::enabled]
:opt-un [::username
(s/keys :opt-un [::username
::password
::tls
::ssl
@@ -164,19 +163,28 @@
(defmethod ig/init-key ::sendmail-handler
[_ cfg]
(fn [{:keys [props] :as task}]
(if (:enabled cfg)
(emails/send! cfg props)
(send-console! cfg props))))
(let [enabled? (or (contains? cf/flags :smtp)
(cf/get :smtp-enabled)
(:enabled task))]
(when enabled?
(emails/send! cfg props))
(when (contains? cf/flags :log-emails)
(send-console! cfg props)))))
(defn- send-console!
[cfg email]
(let [baos (java.io.ByteArrayOutputStream.)
mesg (emails/smtp-message cfg email)]
(.writeTo mesg baos)
(let [out (with-out-str
(println "email console dump:")
(println "******** start email" (:id email) "**********")
(println (.toString baos))
(println "******** end email "(:id email) "**********"))]
(l/info :email out))))
[_ email]
(let [body (:body email)
out (with-out-str
(println "email console dump:")
(println "******** start email" (:id email) "**********")
(pp/pprint (dissoc email :body))
(if (string? body)
(println body)
(println (->> body
(filter #(= "text/plain" (:type %)))
(map :content)
first)))
(println "******** end email" (:id email) "**********"))]
(l/info ::l/raw out)))

View File

@@ -7,153 +7,173 @@
(ns app.http
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.common.logging :as l]
[app.common.transit :as t]
[app.http.errors :as errors]
[app.http.middleware :as middleware]
[app.metrics :as mtx]
[app.util.logging :as l]
[app.worker :as wrk]
[clojure.spec.alpha :as s]
[integrant.core :as ig]
[reitit.ring :as rr]
[ring.adapter.jetty9 :as jetty])
(:import
org.eclipse.jetty.server.Server
org.eclipse.jetty.server.handler.ErrorHandler
org.eclipse.jetty.server.handler.StatisticsHandler))
[reitit.core :as r]
[reitit.middleware :as rr]
[yetti.adapter :as yt]
[yetti.request :as yrq]
[yetti.response :as yrs]))
(declare router-handler)
(declare wrap-router)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HTTP SERVER
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(s/def ::handler fn?)
(s/def ::router some?)
(s/def ::ws (s/map-of ::us/string fn?))
(s/def ::port ::us/integer)
(s/def ::name ::us/string)
(s/def ::port integer?)
(s/def ::host string?)
(s/def ::name string?)
(defmethod ig/pre-init-spec ::server [_]
(s/keys :req-un [::port]
:opt-un [::ws ::name ::mtx/metrics ::router ::handler]))
(s/def ::max-body-size integer?)
(s/def ::max-multipart-body-size integer?)
(s/def ::io-threads integer?)
(s/def ::worker-threads integer?)
(defmethod ig/prep-key ::server
[_ cfg]
(merge {:name "http"} (d/without-nils cfg)))
(merge {:name "http"
:port 6060
:host "0.0.0.0"
:max-body-size (* 1024 1024 30) ; 30 MiB
:max-multipart-body-size (* 1024 1024 120)} ; 120 MiB
(d/without-nils cfg)))
(defmethod ig/pre-init-spec ::server [_]
(s/and
(s/keys :req-un [::port ::host ::name ::max-body-size ::max-multipart-body-size]
:opt-un [::router ::handler ::io-threads ::worker-threads ::wrk/executor])
(fn [cfg]
(or (contains? cfg :router)
(contains? cfg :handler)))))
(defmethod ig/init-key ::server
[_ {:keys [handler router ws port name metrics] :as opts}]
(l/info :msg "starting http server" :port port :name name)
(let [pre-start (fn [^Server server]
(let [handler (doto (ErrorHandler.)
(.setShowStacks true)
(.setServer server))]
(.setErrorHandler server ^ErrorHandler handler)
(when metrics
(let [stats (StatisticsHandler.)]
(.setHandler ^StatisticsHandler stats (.getHandler server))
(.setHandler server stats)
(mtx/instrument-jetty! (:registry metrics) stats)))))
[_ {:keys [handler router port name host] :as cfg}]
(l/info :hint "starting http server" :port port :host host :name name)
(let [options {:http/port port
:http/host host
:http/max-body-size (:max-body-size cfg)
:http/max-multipart-body-size (:max-multipart-body-size cfg)
:xnio/io-threads (:io-threads cfg)
:xnio/worker-threads (:worker-threads cfg)
:xnio/dispatch (:executor cfg)
:ring/async true}
options (merge
{:port port
:h2c? true
:join? false
:allow-null-path-info true
:configurator pre-start}
(when (seq ws)
{:websockets ws}))
handler (if (some? router)
(wrap-router router)
handler (cond
(fn? handler) handler
(some? router) (router-handler router)
:else (ex/raise :type :internal
:code :invalid-argument
:hint "Missing `handler` or `router` option."))
server (jetty/run-jetty handler options)]
(assoc opts :server server)))
handler)
server (yt/server handler (d/without-nils options))]
(assoc cfg :server (yt/start! server))))
(defmethod ig/halt-key! ::server
[_ {:keys [server name port] :as opts}]
(l/info :msg "stoping http server"
:name name
:port port)
(jetty/stop-server server))
[_ {:keys [server name port] :as cfg}]
(l/info :msg "stoping http server" :name name :port port)
(yt/stop! server))
(defn- router-handler
(defn- not-found-handler
[_ respond _]
(respond (yrs/response 404)))
(defn- wrap-router
[router]
(let [handler (rr/ring-handler router
(rr/routes
(rr/create-resource-handler {:path "/"})
(rr/create-default-handler))
{:middleware [middleware/server-timing]})]
(fn [request]
(letfn [(handler [request respond raise]
(if-let [match (r/match-by-path router (yrq/path request))]
(let [params (:path-params match)
result (:result match)
handler (or (:handler result) not-found-handler)
request (-> request
(assoc :path-params params)
(update :params merge params))]
(handler request respond raise))
(not-found-handler request respond raise)))
(on-error [cause request respond]
(let [{:keys [body] :as response} (errors/handle cause request)]
(respond
(cond-> response
(map? body)
(-> (update :headers assoc "content-type" "application/transit+json")
(assoc :body (t/encode-str body {:type :json-verbose})))))))]
(fn [request respond _]
(try
(handler request)
(catch Throwable e
(try
(let [cdata (errors/get-error-context request e)]
(l/update-thread-context! cdata)
(l/error :hint "unhandled exception"
:message (ex-message e)
:error-id (str (:id cdata))
:cause e))
{:status 500 :body "internal server error"}
(catch Throwable e
(l/error :hint "unhandled exception"
:message (ex-message e)
:cause e)
{:status 500 :body "internal server error"})))))))
(handler request respond #(on-error % request respond))
(catch Throwable cause
(on-error cause request respond))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Http Main Handler (Router)
;; HTTP ROUTER
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(s/def ::rpc map?)
(s/def ::session map?)
(s/def ::oauth map?)
(s/def ::storage map?)
(s/def ::assets map?)
(s/def ::feedback fn?)
(s/def ::ws fn?)
(s/def ::audit-handler fn?)
(s/def ::awsns-handler fn?)
(s/def ::session map?)
(s/def ::rpc-routes (s/nilable vector?))
(s/def ::debug-routes (s/nilable vector?))
(s/def ::oidc-routes (s/nilable vector?))
(s/def ::doc-routes (s/nilable vector?))
(defmethod ig/pre-init-spec ::router [_]
(s/keys :req-un [::rpc ::session ::mtx/metrics ::oauth ::storage ::assets ::feedback]))
(s/keys :req-un [::mtx/metrics
::ws
::storage
::assets
::session
::feedback
::awsns-handler
::debug-routes
::oidc-routes
::audit-handler
::rpc-routes
::doc-routes]))
(defmethod ig/init-key ::router
[_ {:keys [session rpc oauth metrics assets feedback] :as cfg}]
[_ {:keys [ws session metrics assets feedback] :as cfg}]
(rr/router
[["/metrics" {:get (:handler metrics)}]
["/assets" {:middleware [[middleware/format-response-body]
[middleware/errors errors/handle]
[middleware/cookies]
(:middleware session)]}
["/by-id/:id" {:get (:objects-handler assets)}]
["/by-file-media-id/:id" {:get (:file-objects-handler assets)}]
["/by-file-media-id/:id/thumbnail" {:get (:file-thumbnails-handler assets)}]]
[["" {:middleware [[middleware/server-timing]
[middleware/format-response]
[middleware/params]
[middleware/parse-request]
[middleware/errors errors/handle]
[middleware/restrict-methods]]}
["/dbg"
["/error-by-id/:id" {:get (:error-report-handler cfg)}]]
["/metrics" {:handler (:handler metrics)}]
["/assets" {:middleware [(:middleware session)]}
["/by-id/:id" {:handler (:objects-handler assets)}]
["/by-file-media-id/:id" {:handler (:file-objects-handler assets)}]
["/by-file-media-id/:id/thumbnail" {:handler (:file-thumbnails-handler assets)}]]
["/webhooks"
["/sns" {:post (:sns-webhook cfg)}]]
(:debug-routes cfg)
["/api" {:middleware [
;; Temporary disabled
#_[middleware/etag]
[middleware/format-response-body]
[middleware/params]
[middleware/multipart-params]
[middleware/keyword-params]
[middleware/parse-request-body]
[middleware/errors errors/handle]
[middleware/cookies]]}
["/webhooks"
["/sns" {:handler (:awsns-handler cfg)
:allowed-methods #{:post}}]]
["/feedback" {:middleware [(:middleware session)]
:post feedback}]
["/ws/notifications" {:middleware [(:middleware session)]
:handler ws
:allowed-methods #{:get}}]
["/auth/oauth/:provider" {:post (:handler oauth)}]
["/auth/oauth/:provider/callback" {:get (:callback-handler oauth)}]
["/api" {:middleware [[middleware/cors]
[(:middleware session)]]}
["/audit/events" {:handler (:audit-handler cfg)
:allowed-methods #{:post}}]
["/feedback" {:handler feedback
:allowed-methods #{:post}}]
(:doc-routes cfg)
(:oidc-routes cfg)
(:rpc-routes cfg)]]]))
["/rpc" {:middleware [(:middleware session)]}
["/query/:type" {:get (:query-handler rpc)
:post (:query-handler rpc)}]
["/mutation/:type" {:post (:mutation-handler rpc)}]]]]))

View File

@@ -14,8 +14,12 @@
[app.metrics :as mtx]
[app.storage :as sto]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.spec.alpha :as s]
[integrant.core :as ig]))
[integrant.core :as ig]
[promesa.core :as p]
[promesa.exec :as px]
[yetti.response :as yrs]))
(def ^:private cache-max-age
(dt/duration {:hours 24}))
@@ -25,73 +29,90 @@
(defn coerce-id
[id]
(let [res (us/uuid-conformer id)]
(let [res (parse-uuid id)]
(when-not (uuid? res)
(ex/raise :type :not-found
:hint "object not found"))
res))
(defn- get-file-media-object
[{:keys [pool] :as storage} id]
(let [id (coerce-id id)
mobj (db/exec-one! pool ["select * from file_media_object where id=?" id])]
(when-not mobj
(ex/raise :type :not-found
:hint "object does not found"))
mobj))
[{:keys [pool executor] :as storage} id]
(px/with-dispatch executor
(let [id (coerce-id id)
mobj (db/exec-one! pool ["select * from file_media_object where id=?" id])]
(when-not mobj
(ex/raise :type :not-found
:hint "object does not found"))
mobj)))
(defn- serve-object
"Helper function that returns the appropriate response depending on
the storage object backend type."
[{:keys [storage] :as cfg} obj]
(let [mdata (meta obj)
backend (sto/resolve-backend storage (:backend obj))]
(case (:type backend)
:db
{:status 200
:headers {"content-type" (:content-type mdata)
"cache-control" (str "max-age=" (inst-ms cache-max-age))}
:body (sto/get-object-bytes storage obj)}
(p/let [body (sto/get-object-bytes storage obj)]
(yrs/response :status 200
:body body
:headers {"content-type" (:content-type mdata)
"cache-control" (str "max-age=" (inst-ms cache-max-age))}))
:s3
(let [url (sto/get-object-url storage obj {:max-age signature-max-age})]
{:status 307
:headers {"location" (str url)
"x-host" (:host url)
"cache-control" (str "max-age=" (inst-ms cache-max-age))}
:body ""})
(p/let [{:keys [host port] :as url} (sto/get-object-url storage obj {:max-age signature-max-age})]
(yrs/response :status 307
:headers {"location" (str url)
"x-host" (cond-> host port (str ":" port))
"cache-control" (str "max-age=" (inst-ms cache-max-age))}))
:fs
(let [purl (u/uri (:assets-path cfg))
purl (u/join purl (sto/object->relative-path obj))]
{:status 204
:headers {"x-accel-redirect" (:path purl)
"content-type" (:content-type mdata)
"cache-control" (str "max-age=" (inst-ms cache-max-age))}
:body ""}))))
(defn- generic-handler
[{:keys [storage] :as cfg} _request id]
(let [obj (sto/get-object storage id)]
(if obj
(serve-object cfg obj)
{:status 404 :body ""})))
(p/let [purl (u/uri (:assets-path cfg))
purl (u/join purl (sto/object->relative-path obj))]
(yrs/response :status 204
:headers {"x-accel-redirect" (:path purl)
"content-type" (:content-type mdata)
"cache-control" (str "max-age=" (inst-ms cache-max-age))})))))
(defn objects-handler
[cfg request]
(let [id (get-in request [:path-params :id])]
(generic-handler cfg request (coerce-id id))))
"Handler that servers storage objects by id."
[{:keys [storage executor] :as cfg} request respond raise]
(-> (px/with-dispatch executor
(p/let [id (get-in request [:path-params :id])
id (coerce-id id)
obj (sto/get-object storage id)]
(if obj
(serve-object cfg obj)
(yrs/response 404))))
(p/bind p/wrap)
(p/then' respond)
(p/catch raise)))
(defn- generic-handler
"A generic handler helper/common code for file-media based handlers."
[{:keys [storage] :as cfg} request kf]
(p/let [id (get-in request [:path-params :id])
mobj (get-file-media-object storage id)
obj (sto/get-object storage (kf mobj))]
(if obj
(serve-object cfg obj)
(yrs/response 404))))
(defn file-objects-handler
[{:keys [storage] :as cfg} request]
(let [id (get-in request [:path-params :id])
mobj (get-file-media-object storage id)]
(generic-handler cfg request (:media-id mobj))))
"Handler that serves storage objects by file media id."
[cfg request respond raise]
(-> (generic-handler cfg request :media-id)
(p/then respond)
(p/catch raise)))
(defn file-thumbnails-handler
[{:keys [storage] :as cfg} request]
(let [id (get-in request [:path-params :id])
mobj (get-file-media-object storage id)]
(generic-handler cfg request (or (:thumbnail-id mobj) (:media-id mobj)))))
"Handler that serves storage objects by thumbnail-id and quick
fallback to file-media-id if no thumbnail is available."
[cfg request respond raise]
(-> (generic-handler cfg request #(or (:thumbnail-id %) (:media-id %)))
(p/then respond)
(p/catch raise)))
;; --- Initialization
@@ -101,10 +122,16 @@
(s/def ::signature-max-age ::dt/duration)
(defmethod ig/pre-init-spec ::handlers [_]
(s/keys :req-un [::storage ::mtx/metrics ::assets-path ::cache-max-age ::signature-max-age]))
(s/keys :req-un [::storage
::wrk/executor
::mtx/metrics
::assets-path
::cache-max-age
::signature-max-age]))
(defmethod ig/init-key ::handlers
[_ cfg]
{:objects-handler #(objects-handler cfg %)
:file-objects-handler #(file-objects-handler cfg %)
:file-thumbnails-handler #(file-thumbnails-handler cfg %)})
{:objects-handler (partial objects-handler cfg)
:file-objects-handler (partial file-objects-handler cfg)
:file-thumbnails-handler (partial file-thumbnails-handler cfg)})

View File

@@ -8,33 +8,45 @@
"AWS SNS webhook handler for bounces."
(:require
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.db :as db]
[app.db.sql :as sql]
[app.util.http :as http]
[app.util.logging :as l]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[integrant.core :as ig]
[jsonista.core :as j]))
[jsonista.core :as j]
[promesa.exec :as px]
[yetti.request :as yrq]
[yetti.response :as yrs]))
(declare parse-json)
(declare handle-request)
(declare parse-notification)
(declare process-report)
(s/def ::http-client fn?)
(defmethod ig/pre-init-spec ::handler [_]
(s/keys :req-un [::db/pool]))
(s/keys :req-un [::db/pool ::http-client]))
(defmethod ig/init-key ::handler
[_ cfg]
(fn [request]
(let [body (parse-json (slurp (:body request)))
[_ {:keys [executor] :as cfg}]
(fn [request respond _]
(let [data (-> request yrq/body slurp)]
(px/run! executor #(handle-request cfg data)))
(respond (yrs/response 200))))
(defn handle-request
[{:keys [http-client] :as cfg} data]
(try
(let [body (parse-json data)
mtype (get body "Type")]
(cond
(= mtype "SubscriptionConfirmation")
(let [surl (get body "SubscribeURL")
stopic (get body "TopicArn")]
(l/info :action "subscription received" :topic stopic :url surl)
(http/send! {:uri surl :method :post :timeout 10000}))
(http-client {:uri surl :method :post :timeout 10000} {:sync? true}))
(= mtype "Notification")
(when-let [message (parse-json (get body "Message"))]
@@ -43,8 +55,11 @@
:else
(l/warn :hint "unexpected data received"
:report (pr-str body)))
{:status 200 :body ""})))
:report (pr-str body))))
(catch Throwable cause
(l/error :hint "unexpected exception on awsns"
:cause cause))))
(defn- parse-bounce
[data]
@@ -173,14 +188,14 @@
(defn- process-report
[cfg {:keys [type profile-id] :as report}]
(l/trace :action "procesing report" :report (pr-str report))
(l/trace :action "processing report" :report (pr-str report))
(cond
;; In this case we receive a bounce/complaint notification without
;; confirmed identity, we just emit a warning but do nothing about
;; it because this is not a normal case. All notifications should
;; come with profile identity.
(nil? profile-id)
(l/warn :msg "a notification without identity recevied from AWS"
(l/warn :msg "a notification without identity received from AWS"
:report (pr-str report))
(= "bounce" type)

View File

@@ -0,0 +1,30 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.http.client
"Http client abstraction layer."
(:require
[app.worker :as wrk]
[clojure.spec.alpha :as s]
[integrant.core :as ig]
[java-http-clj.core :as http]))
(defmethod ig/pre-init-spec :app.http/client [_]
(s/keys :req-un [::wrk/executor]))
(defmethod ig/init-key :app.http/client
[_ {:keys [executor] :as cfg}]
(let [client (http/build-client {:executor executor
:connect-timeout 30000 ;; 10s
:follow-redirects :always})]
(with-meta
(fn send
([req] (send req {}))
([req {:keys [response-type sync?] :or {response-type :string sync? false}}]
(if sync?
(http/send req {:client client :as response-type})
(http/send-async req {:client client :as response-type}))))
{::client client})))

View File

@@ -0,0 +1,395 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.http.debug
(:refer-clojure :exclude [error-handler])
(:require
[app.common.exceptions :as ex]
[app.common.pprint :as pp]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.http.middleware :as mw]
[app.rpc.commands.binfile :as binf]
[app.rpc.mutations.files :refer [create-file]]
[app.rpc.queries.profile :as profile]
[app.util.blob :as blob]
[app.util.bytes :as bs]
[app.util.template :as tmpl]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.java.io :as io]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[emoji.core :as emj]
[integrant.core :as ig]
[markdown.core :as md]
[markdown.transformers :as mdt]
[yetti.request :as yrq]
[yetti.response :as yrs]))
;; (selmer.parser/cache-off!)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HELPERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn authorized?
[pool {:keys [profile-id]}]
(or (= "devenv" (cf/get :host))
(let [profile (ex/ignoring (profile/retrieve-profile-data pool profile-id))
admins (or (cf/get :admins) #{})]
(contains? admins (:email profile)))))
(defn prepare-response
[body]
(let [headers {"content-type" "application/transit+json"}]
(yrs/response :status 200 :body body :headers headers)))
(defn prepare-download-response
[body filename]
(let [headers {"content-disposition" (str "attachment; filename=" filename)
"content-type" "application/octet-stream"}]
(yrs/response :status 200 :body body :headers headers)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INDEX
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn index-handler
[{:keys [pool]} request]
(when-not (authorized? pool request)
(ex/raise :type :authentication
:code :only-admins-allowed))
(yrs/response :status 200
:headers {"content-type" "text/html"}
:body (-> (io/resource "templates/debug.tmpl")
(tmpl/render {}))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; FILE CHANGES
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def sql:retrieve-range-of-changes
"select revn, changes from file_change where file_id=? and revn >= ? and revn <= ? order by revn")
(def sql:retrieve-single-change
"select revn, changes, data from file_change where file_id=? and revn = ?")
(defn- retrieve-file-data
[{:keys [pool]} {:keys [params profile-id] :as request}]
(when-not (authorized? pool request)
(ex/raise :type :authentication
:code :only-admins-allowed))
(let [file-id (some-> params :file-id parse-uuid)
revn (some-> params :revn parse-long)
filename (str file-id)]
(when-not file-id
(ex/raise :type :validation
:code :missing-arguments))
(let [data (if (integer? revn)
(some-> (db/exec-one! pool [sql:retrieve-single-change file-id revn]) :data)
(some-> (db/get-by-id pool :file file-id) :data))]
(when-not data
(ex/raise :type :not-found
:code :enpty-data
:hint "empty response"))
(cond
(contains? params :download)
(prepare-download-response data filename)
(contains? params :clone)
(let [project-id (some-> (profile/retrieve-additional-data pool profile-id) :default-project-id)
data (some-> data blob/decode)]
(create-file pool {:id (uuid/next)
:name (str "Cloned file: " filename)
:project-id project-id
:profile-id profile-id
:data data})
(yrs/response 201 "OK CREATED"))
:else
(prepare-response (some-> data blob/decode))))))
(defn- is-file-exists?
[pool id]
(let [sql "select exists (select 1 from file where id=?) as exists;"]
(-> (db/exec-one! pool [sql id]) :exists)))
(defn- upload-file-data
[{:keys [pool]} {:keys [profile-id params] :as request}]
(let [project-id (some-> (profile/retrieve-additional-data pool profile-id) :default-project-id)
data (some-> params :file :path bs/read-as-bytes blob/decode)]
(if (and data project-id)
(let [fname (str "Imported file *: " (dt/now))
overwrite? (contains? params :overwrite?)
file-id (or (and overwrite? (ex/ignoring (-> params :file :filename parse-uuid)))
(uuid/next))]
(if (and overwrite? file-id
(is-file-exists? pool file-id))
(do
(db/update! pool :file
{:data (blob/encode data)}
{:id file-id})
(yrs/response 200 "OK UPDATED"))
(do
(create-file pool {:id file-id
:name fname
:project-id project-id
:profile-id profile-id
:data data})
(yrs/response 201 "OK CREATED"))))
(yrs/response 500 "ERROR"))))
(defn file-data-handler
[cfg request]
(case (yrq/method request)
:get (retrieve-file-data cfg request)
:post (upload-file-data cfg request)
(ex/raise :type :http
:code :method-not-found)))
(defn file-changes-handler
[{:keys [pool]} {:keys [params] :as request}]
(when-not (authorized? pool request)
(ex/raise :type :authentication
:code :only-admins-allowed))
(letfn [(retrieve-changes [file-id revn]
(if (str/includes? revn ":")
(let [[start end] (->> (str/split revn #":")
(map str/trim)
(map parse-long))]
(some->> (db/exec! pool [sql:retrieve-range-of-changes file-id start end])
(map :changes)
(map blob/decode)
(mapcat identity)
(vec)))
(if-let [revn (parse-long revn)]
(let [item (db/exec-one! pool [sql:retrieve-single-change file-id revn])]
(some-> item :changes blob/decode vec))
(ex/raise :type :validation :code :invalid-arguments))))]
(let [file-id (some-> params :id parse-uuid)
revn (or (some-> params :revn parse-long) "latest")
filename (str file-id)]
(when (or (not file-id) (not revn))
(ex/raise :type :validation
:code :invalid-arguments
:hint "missing arguments"))
(let [data (retrieve-changes file-id revn)]
(if (contains? params :download)
(prepare-download-response data filename)
(prepare-response data))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ERROR BROWSER
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn error-handler
[{:keys [pool]} request]
(letfn [(parse-id [request]
(let [id (get-in request [:path-params :id])
id (parse-uuid id)]
(when (uuid? id)
id)))
(retrieve-report [id]
(ex/ignoring
(some-> (db/get-by-id pool :server-error-report id) :content db/decode-transit-pgobject)))
(render-template [report]
(let [context (dissoc report
:trace :cause :params :data :spec-problems
:spec-explain :spec-value :error :explain :hint)
params {:context (pp/pprint-str context :width 200)
:hint (:hint report)
:spec-explain (:spec-explain report)
:spec-problems (:spec-problems report)
:spec-value (:spec-value report)
:data (:data report)
:trace (or (:trace report)
(some-> report :error :trace))
:params (:params report)}]
(-> (io/resource "templates/error-report.tmpl")
(tmpl/render params))))]
(when-not (authorized? pool request)
(ex/raise :type :authentication
:code :only-admins-allowed))
(let [result (some-> (parse-id request)
(retrieve-report)
(render-template))]
(if result
(yrs/response :status 200
:body result
:headers {"content-type" "text/html; charset=utf-8"
"x-robots-tag" "noindex"})
(yrs/response 404 "not found")))))
(def sql:error-reports
"select id, created_at from server_error_report order by created_at desc limit 100")
(defn error-list-handler
[{:keys [pool]} request]
(when-not (authorized? pool request)
(ex/raise :type :authentication
:code :only-admins-allowed))
(let [items (db/exec! pool [sql:error-reports])
items (map #(update % :created-at dt/format-instant :rfc1123) items)]
(yrs/response :status 200
:body (-> (io/resource "templates/error-list.tmpl")
(tmpl/render {:items items}))
:headers {"content-type" "text/html; charset=utf-8"
"x-robots-tag" "noindex"})))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; EXPORT/IMPORT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn export-handler
[{:keys [pool] :as cfg} {:keys [params profile-id] :as request}]
(let [file-ids (->> (:file-ids params)
(remove empty?)
(mapv parse-uuid))
libs? (contains? params :includelibs)
clone? (contains? params :clone)
embed? (contains? params :embedassets)]
(when-not (seq file-ids)
(ex/raise :type :validation
:code :missing-arguments))
(let [path (-> cfg
(assoc ::binf/file-ids file-ids)
(assoc ::binf/embed-assets? embed?)
(assoc ::binf/include-libraries? libs?)
(binf/export!))]
(if clone?
(let [project-id (some-> (profile/retrieve-additional-data pool profile-id) :default-project-id)]
(binf/import!
(assoc cfg
::binf/input path
::binf/overwrite? false
::binf/ignore-index-errors? true
::binf/profile-id profile-id
::binf/project-id project-id))
(yrs/response
:status 200
:headers {"content-type" "text/plain"}
:body "OK CLONED"))
(yrs/response
:status 200
:headers {"content-type" "application/octet-stream"
"content-disposition" (str "attachmen; filename=" (first file-ids) ".penpot")}
:body (io/input-stream path))))))
(defn import-handler
[{:keys [pool] :as cfg} {:keys [params profile-id] :as request}]
(when-not (contains? params :file)
(ex/raise :type :validation
:code :missing-upload-file
:hint "missing upload file"))
(let [project-id (some-> (profile/retrieve-additional-data pool profile-id) :default-project-id)
overwrite? (contains? params :overwrite)
migrate? (contains? params :migrate)
ignore-index-errors? (contains? params :ignore-index-errors)]
(when-not project-id
(ex/raise :type :validation
:code :missing-project
:hint "project not found"))
(binf/import!
(assoc cfg
::binf/input (-> params :file :path)
::binf/overwrite? overwrite?
::binf/migrate? migrate?
::binf/ignore-index-errors? ignore-index-errors?
::binf/profile-id profile-id
::binf/project-id project-id))
(yrs/response
:status 200
:headers {"content-type" "text/plain"}
:body "OK")))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; OTHER SMALL VIEWS/HANDLERS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn health-handler
"Mainly a task that performs a health check."
[{:keys [pool]} _]
(db/with-atomic [conn pool]
(db/exec-one! conn ["select count(*) as count from server_prop;"])
(yrs/response 200 "OK")))
(defn changelog-handler
[_ _]
(letfn [(transform-emoji [text state]
[(emj/emojify text) state])
(md->html [text]
(md/md-to-html-string text :replacement-transformers (into [transform-emoji] mdt/transformer-vector)))]
(if-let [clog (io/resource "changelog.md")]
(yrs/response :status 200
:headers {"content-type" "text/html; charset=utf-8"}
:body (-> clog slurp md->html))
(yrs/response :status 404 :body "NOT FOUND"))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INIT
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def with-authorization
{:compile
(fn [& _]
(fn [handler pool]
(fn [request respond raise]
(if (authorized? pool request)
(handler request respond raise)
(raise (ex/error :type :authentication
:code :only-admins-allowed))))))})
(s/def ::session map?)
(defmethod ig/pre-init-spec ::routes [_]
(s/keys :req-un [::db/pool ::wrk/executor ::session]))
(defmethod ig/init-key ::routes
[_ {:keys [session pool executor] :as cfg}]
["/dbg" {:middleware [[(:middleware session)]
[with-authorization pool]
[mw/with-promise-async executor]
[mw/with-config cfg]]}
["" {:handler index-handler}]
["/health" {:handler health-handler}]
["/changelog" {:handler changelog-handler}]
;; ["/error-by-id/:id" {:handler error-handler}]
["/error/:id" {:handler error-handler}]
["/error" {:handler error-list-handler}]
["/file/export" {:handler export-handler}]
["/file/import" {:handler import-handler}]
["/file/data" {:handler file-data-handler}]
["/file/changes" {:handler file-changes-handler}]])

View File

@@ -8,30 +8,33 @@
"A errors handling for the http server."
(:require
[app.common.exceptions :as ex]
[app.common.uuid :as uuid]
[app.util.logging :as l]
[app.common.logging :as l]
[app.common.spec :as us]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[expound.alpha :as expound]))
[yetti.request :as yrq]
[yetti.response :as yrs]))
(defn- explain-error
[error]
(with-out-str
(expound/printer (:data error))))
(def ^:dynamic *context* {})
(defn get-error-context
[request error]
(let [edata (ex-data error)]
(merge
{:id (uuid/next)
:path (:uri request)
:method (:request-method request)
:params (:params request)
:data edata}
(let [headers (:headers request)]
{:user-agent (get headers "user-agent")
:frontend-version (get headers "x-frontend-version" "unknown")})
(when (and (map? edata) (:data edata))
{:explain (explain-error edata)}))))
(defn- parse-client-ip
[request]
(or (some-> (yrq/get-header request "x-forwarded-for") (str/split ",") first)
(yrq/get-header request "x-real-ip")
(yrq/remote-addr request)))
(defn get-context
[request]
(merge
*context*
{:path (:path request)
:method (:method request)
:params (:params request)
:ip-addr (parse-client-ip request)
:profile-id (:profile-id request)}
(let [headers (:headers request)]
{:user-agent (get headers "user-agent")
:frontend-version (get headers "x-frontend-version" "unknown")})))
(defmulti handle-exception
(fn [err & _rest]
@@ -41,108 +44,115 @@
(defmethod handle-exception :authentication
[err _]
{:status 401 :body (ex-data err)})
(yrs/response 401 (ex-data err)))
(defmethod handle-exception :restriction
[err _]
{:status 400 :body (ex-data err)})
(yrs/response 400 (ex-data err)))
(defmethod handle-exception :validation
[err req]
(let [header (get-in req [:headers "accept"])
edata (ex-data err)]
(if (and (= :spec-validation (:code edata))
(str/starts-with? header "text/html"))
{:status 400
:headers {"content-type" "text/html"}
:body (str "<pre style='font-size:16px'>"
(explain-error edata)
"</pre>\n")}
{:status 400
:body (cond-> edata
(map? (:data edata))
(-> (assoc :explain (explain-error edata))
(dissoc :data)))})))
[err _]
(let [{:keys [code] :as data} (ex-data err)]
(cond
(= code :spec-validation)
(let [explain (us/pretty-explain data)]
(yrs/response :status 400
:body (-> data
(dissoc ::s/problems ::s/value)
(cond-> explain (assoc :explain explain)))))
(= code :request-body-too-large)
(yrs/response :status 413 :body data)
:else
(yrs/response :status 400 :body data))))
(defmethod handle-exception :assertion
[error request]
(let [edata (ex-data error)
cdata (get-error-context request error)]
(l/update-thread-context! cdata)
(l/error :hint "internal error: assertion"
:error-id (str (:id cdata))
explain (us/pretty-explain edata)]
(l/error ::l/raw (str (ex-message error) "\n" explain)
::l/context (get-context request)
:cause error)
{:status 500
:body {:type :server-error
:code :assertion
:data (-> edata
(assoc :explain (explain-error edata))
(dissoc :data))}}))
(yrs/response :status 500
:body {:type :server-error
:code :assertion
:data (-> edata
(dissoc ::s/problems ::s/value ::s/spec)
(cond-> explain (assoc :explain explain)))})))
(defmethod handle-exception :not-found
[err _]
{:status 404 :body (ex-data err)})
(yrs/response 404 (ex-data err)))
(defmethod handle-exception org.postgresql.util.PSQLException
[error request]
(let [state (.getSQLState ^java.sql.SQLException error)]
(l/error ::l/raw (ex-message error)
::l/context (get-context request)
:cause error)
(cond
(= state "57014")
(yrs/response 504 {:type :server-error
:code :statement-timeout
:hint (ex-message error)})
(= state "25P03")
(yrs/response 504 {:type :server-error
:code :idle-in-transaction-timeout
:hint (ex-message error)})
:else
(yrs/response 500 {:type :server-error
:code :unexpected
:hint (ex-message error)
:state state}))))
(defmethod handle-exception :default
[error request]
(let [edata (ex-data error)]
;; NOTE: this is a special case for the idle-in-transaction error;
;; when it happens, the connection is automatically closed and
;; next-jdbc combines the two errors in a single ex-info. We only
;; need the :handling error, because the :rollback error will be
;; always "connection closed".
(if (and (ex/exception? (:rollback edata))
(ex/exception? (:handling edata)))
(handle-exception (:handling edata) request)
(let [cdata (get-error-context request error)]
(l/update-thread-context! cdata)
(l/error :hint "internal error"
:error-message (ex-message error)
:error-id (str (:id cdata))
:cause error)
{:status 500
:body {:type :server-error
:code :unexpected
:hint (ex-message error)
:data edata}}))))
(defmethod handle-exception org.postgresql.util.PSQLException
[error request]
(let [cdata (get-error-context request error)
state (.getSQLState ^java.sql.SQLException error)]
(l/update-thread-context! cdata)
(l/error :hint "psql exception"
:error-message (ex-message error)
:error-id (str (:id cdata))
:sql-state state
:cause error)
(cond
(= state "57014")
{:status 504
:body {:type :server-timeout
:code :statement-timeout
:hint (ex-message error)}}
;; This means that exception is not a controlled exception.
(nil? edata)
(do
(l/error ::l/raw (ex-message error)
::l/context (get-context request)
:cause error)
(yrs/response 500 {:type :server-error
:code :unexpected
:hint (ex-message error)}))
(= state "25P03")
{:status 504
:body {:type :server-timeout
:code :idle-in-transaction-timeout
:hint (ex-message error)}}
;; This is a special case for the idle-in-transaction error;
;; when it happens, the connection is automatically closed and
;; next-jdbc combines the two errors in a single ex-info. We
;; only need the :handling error, because the :rollback error
;; will be always "connection closed".
(and (ex/exception? (:rollback edata))
(ex/exception? (:handling edata)))
(handle-exception (:handling edata) request)
:else
{:status 500
:body {:type :server-error
:code :psql-exception
:hint (ex-message error)
:state state}})))
(do
(l/error ::l/raw (ex-message error)
::l/context (get-context request)
:cause error)
(yrs/response 500 {:type :server-error
:code :unhandled
:hint (ex-message error)
:data edata})))))
(defn handle
[error req]
(if (or (instance? java.util.concurrent.CompletionException error)
(instance? java.util.concurrent.ExecutionException error))
(handle-exception (.getCause ^Throwable error) req)
(handle-exception error req)))
[cause request]
(cond
(or (instance? java.util.concurrent.CompletionException cause)
(instance? java.util.concurrent.ExecutionException cause))
(handle-exception (.getCause ^Throwable cause) request)
(ex/wrapped? cause)
(let [context (meta cause)
cause (deref cause)]
(binding [*context* context]
(handle-exception cause request)))
:else
(handle-exception cause request)))

View File

@@ -10,57 +10,67 @@
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.config :as cfg]
[app.config :as cf]
[app.db :as db]
[app.emails :as eml]
[app.rpc.queries.profile :as profile]
[app.worker :as wrk]
[clojure.spec.alpha :as s]
[integrant.core :as ig]))
[integrant.core :as ig]
[promesa.core :as p]
[promesa.exec :as px]
[yetti.request :as yrq]
[yetti.response :as yrs]))
(declare send-feedback)
(declare ^:private send-feedback)
(declare ^:private handler)
(defmethod ig/pre-init-spec ::handler [_]
(s/keys :req-un [::db/pool]))
(s/keys :req-un [::db/pool ::wrk/executor]))
(defmethod ig/init-key ::handler
[_ {:keys [pool] :as scfg}]
(let [ftoken (cfg/get :feedback-token ::no-token)
enabled (cfg/get :feedback-enabled)]
(fn [{:keys [profile-id] :as request}]
(let [token (get-in request [:headers "x-feedback-token"])
params (d/merge (:params request)
(:body-params request))]
[_ {:keys [executor] :as cfg}]
(let [enabled? (contains? cf/flags :user-feedback)]
(if enabled?
(fn [request respond raise]
(-> (px/submit! executor #(handler cfg request))
(p/then' respond)
(p/catch raise)))
(fn [_ _ raise]
(raise (ex/error :type :validation
:code :feedback-disabled
:hint "feedback module is disabled"))))))
(when-not enabled
(ex/raise :type :validation
:code :feedback-disabled
:hint "feedback module is disabled"))
(defn- handler
[{:keys [pool] :as cfg} {:keys [profile-id] :as request}]
(let [ftoken (cf/get :feedback-token ::no-token)
token (yrq/get-header request "x-feedback-token")
params (d/merge (:params request)
(:body-params request))]
(cond
(uuid? profile-id)
(let [profile (profile/retrieve-profile-data pool profile-id)
params (assoc params :from (:email profile))]
(send-feedback pool profile params))
(cond
(uuid? profile-id)
(let [profile (profile/retrieve-profile-data pool profile-id)
params (assoc params :from (:email profile))]
(when-not (:is-muted profile)
(send-feedback pool profile params)))
(= token ftoken)
(send-feedback cfg nil params))
(= token ftoken)
(send-feedback scfg nil params))
{:status 204 :body ""}))))
(yrs/response 204)))
(s/def ::content ::us/string)
(s/def ::from ::us/email)
(s/def ::subject ::us/string)
(s/def ::feedback
(s/keys :req-un [::from ::subject ::content]))
(defn send-feedback
(defn- send-feedback
[pool profile params]
(let [params (us/conform ::feedback params)
destination (cfg/get :feedback-destination)]
destination (cf/get :feedback-destination)]
(eml/send! {::eml/conn pool
::eml/factory eml/feedback
:from destination
:to destination
:profile profile
:reply-to (:from params)

View File

@@ -6,192 +6,209 @@
(ns app.http.middleware
(:require
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.transit :as t]
[app.metrics :as mtx]
[app.config :as cf]
[app.util.json :as json]
[app.util.logging :as l]
[buddy.core.codecs :as bc]
[buddy.core.hash :as bh]
[clojure.java.io :as io]
[ring.core.protocols :as rp]
[ring.middleware.cookies :refer [wrap-cookies]]
[ring.middleware.keyword-params :refer [wrap-keyword-params]]
[ring.middleware.multipart-params :refer [wrap-multipart-params]]
[ring.middleware.params :refer [wrap-params]]))
[cuerdas.core :as str]
[promesa.core :as p]
[promesa.exec :as px]
[yetti.adapter :as yt]
[yetti.middleware :as ymw]
[yetti.request :as yrq]
[yetti.response :as yrs])
(:import
com.fasterxml.jackson.core.io.JsonEOFException
io.undertow.server.RequestTooBigException
java.io.OutputStream))
(defn wrap-server-timing
(def server-timing
{:name ::server-timing
:compile (constantly ymw/wrap-server-timing)})
(def params
{:name ::params
:compile (constantly ymw/wrap-params)})
(defn wrap-parse-request
[handler]
(let [seconds-from #(float (/ (- (System/nanoTime) %) 1000000000))]
(fn [request]
(let [start (System/nanoTime)
response (handler request)]
(update response :headers
(fn [headers]
(assoc headers "Server-Timing" (str "total;dur=" (seconds-from start)))))))))
(letfn [(process-request [request]
(let [header (yrq/get-header request "content-type")]
(cond
(str/starts-with? header "application/transit+json")
(with-open [is (yrq/body request)]
(let [params (t/read! (t/reader is))]
(-> request
(assoc :body-params params)
(update :params merge params))))
(defn wrap-parse-request-body
(str/starts-with? header "application/json")
(with-open [is (yrq/body request)]
(let [params (json/read is)]
(-> request
(assoc :body-params params)
(update :params merge params))))
:else
request)))
(handle-error [raise cause]
(cond
(instance? RequestTooBigException cause)
(raise (ex/error :type :validation
:code :request-body-too-large
:hint (ex-message cause)))
(instance? JsonEOFException cause)
(raise (ex/error :type :validation
:code :malformed-json
:hint (ex-message cause)))
:else
(raise cause)))]
(fn [request respond raise]
(when-let [request (try
(process-request request)
(catch RuntimeException cause
(handle-error raise (or (.getCause cause) cause)))
(catch Throwable cause
(handle-error raise cause)))]
(handler request respond raise)))))
(def parse-request
{:name ::parse-request
:compile (constantly wrap-parse-request)})
(defn buffered-output-stream
"Returns a buffered output stream that ignores flush calls. This is
needed because transit-java calls flush very aggresivelly on each
object write."
[^java.io.OutputStream os ^long chunk-size]
(proxy [java.io.BufferedOutputStream] [os (int chunk-size)]
;; Explicitly do not forward flush
(flush [])
(close []
(proxy-super flush)
(proxy-super close))))
(def ^:const buffer-size (:xnio/buffer-size yt/defaults))
(defn wrap-format-response
[handler]
(letfn [(parse-transit [body]
(let [reader (t/reader body)]
(t/read! reader)))
(letfn [(transit-streamable-body [data opts]
(reify yrs/StreamableResponseBody
(-write-body-to-stream [_ _ output-stream]
(try
(with-open [bos (buffered-output-stream output-stream buffer-size)]
(let [tw (t/writer bos opts)]
(t/write! tw data)))
(parse-json [body]
(let [reader (io/reader body)]
(json/read reader)))
(catch java.io.IOException _cause
;; Do nothing, EOF means client closes connection abruptly
nil)
(catch Throwable cause
(l/warn :hint "unexpected error on encoding response"
:cause cause))
(finally
(.close ^OutputStream output-stream))))))
(parse [type body]
(try
(case type
:json (parse-json body)
:transit (parse-transit body))
(catch Exception e
(let [data {:type :parse
:hint "unable to parse request body"
:message (ex-message e)}]
{:status 400
:headers {"content-type" "application/transit+json"}
:body (t/encode-str data {:type :json-verbose})}))))]
(format-response [response request]
(let [body (yrs/body response)]
(if (or (boolean? body) (coll? body))
(let [qs (yrq/query request)
opts (if (or (contains? cf/flags :transit-readable-response)
(str/includes? qs "transit_verbose"))
{:type :json-verbose}
{:type :json})]
(-> response
(update :headers assoc "content-type" "application/transit+json")
(assoc :body (transit-streamable-body body opts))))
response)))
(fn [{:keys [headers body] :as request}]
(let [ctype (get headers "content-type")]
(handler
(case ctype
"application/transit+json"
(let [params (parse :transit body)]
(-> request
(assoc :body-params params)
(update :params merge params)))
(process-response [response request]
(cond-> response
(map? response) (format-response request)))]
"application/json"
(let [params (parse :json body)]
(-> request
(assoc :body-params params)
(update :params merge params)))
(fn [request respond raise]
(handler request
(fn [response]
(let [response (process-response response request)]
(respond response)))
raise))))
request))))))
(def parse-request-body
{:name ::parse-request-body
:compile (constantly wrap-parse-request-body)})
(defn- transit-streamable-body
[data opts]
(reify rp/StreamableResponseBody
(write-body-to-stream [_ response output-stream]
(try
(let [tw (t/writer output-stream opts)]
(t/write! tw data))
(finally
(.close ^java.io.OutputStream output-stream))))))
(defn- impl-format-response-body
[response _request]
(let [body (:body response)
opts {:type :json-verbose}]
(cond
(coll? body)
(-> response
(update :headers assoc "content-type" "application/transit+json")
(assoc :body (transit-streamable-body body opts)))
;; ;; Temporary disabled
;; (-> response
;; (update :headers assoc "content-type" "application/transit+json")
;; (assoc :body
;; (if (= :post (:request-method request))
;; (transit-streamable-body body opts)
;; (t/encode body opts))))
(nil? body)
(assoc response :status 204 :body "")
:else
response)))
(defn- wrap-format-response-body
[handler]
(fn [request]
(let [response (handler request)]
(cond-> response
(map? response) (impl-format-response-body request)))))
(def format-response-body
{:name ::format-response-body
:compile (constantly wrap-format-response-body)})
(def format-response
{:name ::format-response
:compile (constantly wrap-format-response)})
(defn wrap-errors
[handler on-error]
(fn [request]
(try
(handler request)
(catch Throwable e
(on-error e request)))))
(fn [request respond _]
(handler request respond (fn [cause]
(-> cause (on-error request) respond)))))
(def errors
{:name ::errors
:compile (constantly wrap-errors)})
(def metrics
{:name ::metrics
:wrap (fn [handler]
(mtx/wrap-counter handler {:id "http__requests_counter"
:help "Absolute http requests counter."}))})
(def cookies
{:name ::cookies
:compile (constantly wrap-cookies)})
(def params
{:name ::params
:compile (constantly wrap-params)})
(def multipart-params
{:name ::multipart-params
:compile (constantly wrap-multipart-params)})
(def keyword-params
{:name ::keyword-params
:compile (constantly wrap-keyword-params)})
(def server-timing
{:name ::server-timing
:compile (constantly wrap-server-timing)})
(defn wrap-etag
(defn wrap-cors
[handler]
(letfn [(generate-etag [{:keys [body] :as response}]
(str "W/\"" (-> body bh/blake2b-128 bc/bytes->hex) "\""))
(get-match [{:keys [headers] :as request}]
(get headers "if-none-match"))]
(fn [request]
(let [response (handler request)]
(if (= :get (:request-method request))
(let [etag (generate-etag response)
match (get-match request)
response (update response :headers #(assoc % "ETag" etag))]
(cond-> response
(and (string? match)
(= :get (:request-method request))
(= etag match))
(-> response
(assoc :body "")
(assoc :status 304))))
response)))))
(if-not (contains? cf/flags :cors)
handler
(letfn [(add-headers [headers request]
(let [origin (yrq/get-header request "origin")]
(-> headers
(assoc "access-control-allow-origin" origin)
(assoc "access-control-allow-methods" "GET,POST,DELETE,OPTIONS,PUT,HEAD,PATCH")
(assoc "access-control-allow-credentials" "true")
(assoc "access-control-expose-headers" "x-requested-with, content-type, cookie")
(assoc "access-control-allow-headers" "x-frontend-version, content-type, accept, x-requested-width"))))
(def etag
{:name ::etag
:compile (constantly wrap-etag)})
(update-response [response request]
(update response :headers add-headers request))]
(defn activity-logger
[handler]
(let [logger "penpot.profile-activity"]
(fn [{:keys [headers] :as request}]
(let [ip-addr (get headers "x-forwarded-for")
profile-id (:profile-id request)
qstring (:query-string request)]
(l/info ::l/async true
::l/logger logger
:ip-addr ip-addr
:profile-id profile-id
:uri (str (:uri request) (when qstring (str "?" qstring)))
:method (name (:request-method request)))
(handler request)))))
(fn [request respond raise]
(if (= (yrq/method request) :options)
(-> (yrs/response 200)
(update-response request)
(respond))
(handler request
(fn [response]
(respond (update-response response request)))
raise))))))
(def cors
{:name ::cors
:compile (constantly wrap-cors)})
(defn compile-restrict-methods
[data _]
(when-let [allowed (:allowed-methods data)]
(fn [handler]
(fn [request respond raise]
(let [method (yrq/method request)]
(if (contains? allowed method)
(handler request respond raise)
(respond (yrs/response 405))))))))
(def restrict-methods
{:name ::restrict-methods
:compile compile-restrict-methods})
(def with-promise-async
{:compile
(fn [& _]
(fn [handler executor]
(fn [request respond raise]
(-> (px/submit! executor #(handler request))
(p/bind p/wrap)
(p/then respond)
(p/catch raise)))))})
(def with-config
{:compile
(fn [& _]
(fn [handler config]
(fn
([request] (handler config request))
([request respond raise] (handler config request respond raise)))))})

View File

@@ -1,371 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.http.oauth
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.common.uri :as u]
[app.config :as cf]
[app.db :as db]
[app.loggers.audit :as audit]
[app.rpc.queries.profile :as profile]
[app.util.http :as http]
[app.util.logging :as l]
[app.util.time :as dt]
[clojure.data.json :as json]
[clojure.set :as set]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[integrant.core :as ig]))
(defn- build-redirect-uri
[{:keys [provider] :as cfg}]
(let [public (u/uri (:public-uri cfg))]
(str (assoc public :path (str "/api/auth/oauth/" (:name provider) "/callback")))))
(defn- build-auth-uri
[{:keys [provider] :as cfg} state]
(let [params {:client_id (:client-id provider)
:redirect_uri (build-redirect-uri cfg)
:response_type "code"
:state state
:scope (str/join " " (:scopes provider []))}
query (u/map->query-string params)]
(-> (u/uri (:auth-uri provider))
(assoc :query query)
(str))))
(defn retrieve-access-token
[{:keys [provider] :as cfg} code]
(try
(let [params {:client_id (:client-id provider)
:client_secret (:client-secret provider)
:code code
:grant_type "authorization_code"
:redirect_uri (build-redirect-uri cfg)}
req {:method :post
:headers {"content-type" "application/x-www-form-urlencoded"}
:uri (:token-uri provider)
:body (u/map->query-string params)}
res (http/send! req)]
(when (= 200 (:status res))
(let [data (json/read-str (:body res))]
{:token (get data "access_token")
:type (get data "token_type")})))
(catch Exception e
(l/error :hint "unexpected error on retrieve-access-token"
:cause e)
nil)))
(defn- retrieve-user-info
[{:keys [provider] :as cfg} tdata]
(try
(let [req {:uri (:user-uri provider)
:headers {"Authorization" (str (:type tdata) " " (:token tdata))}
:timeout 6000
:method :get}
res (http/send! req)]
(when (= 200 (:status res))
(let [info (json/read-str (:body res) :key-fn keyword)]
{:backend (:name provider)
:email (:email info)
:fullname (:name info)
:props (dissoc info :name :email)})))
(catch Exception e
(l/error :hint "unexpected exception on retrieve-user-info"
:cause e)
nil)))
(s/def ::backend ::us/not-empty-string)
(s/def ::email ::us/not-empty-string)
(s/def ::fullname ::us/not-empty-string)
(s/def ::props (s/map-of ::us/keyword any?))
(s/def ::info
(s/keys :req-un [::backend
::email
::fullname
::props]))
(defn retrieve-info
[{:keys [tokens provider] :as cfg} request]
(let [state (get-in request [:params :state])
state (tokens :verify {:token state :iss :oauth})
info (some->> (get-in request [:params :code])
(retrieve-access-token cfg)
(retrieve-user-info cfg))]
(when-not (s/valid? ::info info)
(l/warn :hint "received incomplete profile info object (please set correct scopes)"
:info (pr-str info))
(ex/raise :type :internal
:code :unable-to-auth
:hint "no user info"))
;; If the provider is OIDC, we can proceed to check
;; roles if they are defined.
(when (and (= "oidc" (:name provider))
(seq (:roles provider)))
(let [provider-roles (into #{} (:roles provider))
profile-roles (let [attr (cf/get :oidc-roles-attr :roles)
roles (get info attr)]
(cond
(string? roles) (into #{} (str/words roles))
(vector? roles) (into #{} roles)
:else #{}))]
;; check if profile has a configured set of roles
(when-not (set/subset? provider-roles profile-roles)
(ex/raise :type :internal
:code :unable-to-auth
:hint "not enought permissions"))))
(cond-> info
(some? (:invitation-token state))
(assoc :invitation-token (:invitation-token state))
;; If state token comes with props, merge them. The state token
;; props can contain pm_ and utm_ prefixed query params.
(map? (:props state))
(update :props merge (:props state)))))
;; --- HTTP HANDLERS
(defn extract-props
[params]
(reduce-kv (fn [params k v]
(let [sk (name k)]
(cond-> params
(or (str/starts-with? sk "pm_")
(str/starts-with? sk "pm-")
(str/starts-with? sk "utm_"))
(assoc (-> sk str/kebab keyword) v))))
{}
params))
(defn- retrieve-profile
[{:keys [pool] :as cfg} info]
(with-open [conn (db/open pool)]
(some->> (:email info)
(profile/retrieve-profile-data-by-email conn)
(profile/populate-additional-data conn)
(profile/decode-profile-row))))
(defn- redirect-response
[uri]
{:status 302
:headers {"location" (str uri)}
:body ""})
(defn- generate-error-redirect
[cfg error]
(let [uri (-> (u/uri (:public-uri cfg))
(assoc :path "/#/auth/login")
(assoc :query (u/map->query-string {:error "unable-to-auth" :hint (ex-message error)})))]
(redirect-response uri)))
(defn- generate-redirect
[{:keys [tokens session audit] :as cfg} request info profile]
(if profile
(let [sxf ((:create session) (:id profile))
token (or (:invitation-token info)
(tokens :generate {:iss :auth
:exp (dt/in-future "15m")
:profile-id (:id profile)}))
params {:token token}
uri (-> (u/uri (:public-uri cfg))
(assoc :path "/#/auth/verify-token")
(assoc :query (u/map->query-string params)))]
(when (fn? audit)
(audit :cmd :submit
:type "mutation"
:name "login"
:profile-id (:id profile)
:ip-addr (audit/parse-client-ip request)
:props (audit/profile->props profile)))
(->> (redirect-response uri)
(sxf request)))
(let [info (assoc info
:iss :prepared-register
:exp (dt/in-future {:hours 48}))
token (tokens :generate info)
params (d/without-nils
{:token token
:fullname (:fullname info)})
uri (-> (u/uri (:public-uri cfg))
(assoc :path "/#/auth/register/validate")
(assoc :query (u/map->query-string params)))]
(redirect-response uri))))
(defn- auth-handler
[{:keys [tokens] :as cfg} {:keys [params] :as request}]
(let [invitation (:invitation-token params)
props (extract-props params)
state (tokens :generate
{:iss :oauth
:invitation-token invitation
:props props
:exp (dt/in-future "15m")})
uri (build-auth-uri cfg state)]
{:status 200
:body {:redirect-uri uri}}))
(defn- callback-handler
[cfg request]
(try
(let [info (retrieve-info cfg request)
profile (retrieve-profile cfg info)]
(generate-redirect cfg request info profile))
(catch Exception e
(l/warn :hint "error on oauth process"
:cause e)
(generate-error-redirect cfg e))))
;; --- INIT
(declare initialize)
(s/def ::public-uri ::us/not-empty-string)
(s/def ::session map?)
(s/def ::tokens fn?)
(s/def ::rpc map?)
(defmethod ig/pre-init-spec ::handler [_]
(s/keys :req-un [::public-uri ::session ::tokens ::rpc ::db/pool]))
(defn wrap-handler
[cfg handler]
(fn [request]
(let [provider (get-in request [:path-params :provider])
provider (get-in @cfg [:providers provider])]
(when-not provider
(ex/raise :type :not-found
:context {:provider provider}
:hint "provider not configured"))
(-> (assoc @cfg :provider provider)
(handler request)))))
(defmethod ig/init-key ::handler
[_ cfg]
(let [cfg (initialize cfg)]
{:handler (wrap-handler cfg auth-handler)
:callback-handler (wrap-handler cfg callback-handler)}))
(defn- discover-oidc-config
[{:keys [base-uri] :as opts}]
(let [discovery-uri (u/join base-uri ".well-known/openid-configuration")
response (http/send! {:method :get :uri (str discovery-uri)})]
(when (= 200 (:status response))
(let [data (json/read-str (:body response))]
(assoc opts
:token-uri (get data "token_endpoint")
:auth-uri (get data "authorization_endpoint")
:user-uri (get data "userinfo_endpoint"))))))
(defn- obfuscate-string
[s]
(if (< (count s) 10)
(apply str (take (count s) (repeat "*")))
(str (subs s 0 5)
(apply str (take (- (count s) 5) (repeat "*"))))))
(defn- initialize-oidc-provider
[cfg]
(let [opts {:base-uri (cf/get :oidc-base-uri)
:client-id (cf/get :oidc-client-id)
:client-secret (cf/get :oidc-client-secret)
:token-uri (cf/get :oidc-token-uri)
:auth-uri (cf/get :oidc-auth-uri)
:user-uri (cf/get :oidc-user-uri)
:scopes (cf/get :oidc-scopes #{"openid" "profile" "email"})
:roles-attr (cf/get :oidc-roles-attr)
:roles (cf/get :oidc-roles)
:name "oidc"}]
(if (and (string? (:base-uri opts))
(string? (:client-id opts))
(string? (:client-secret opts)))
(if (and (string? (:token-uri opts))
(string? (:user-uri opts))
(string? (:auth-uri opts)))
(do
(l/info :action "initialize" :provider "oidc" :method "static"
:opts (pr-str (update opts :client-secret obfuscate-string)))
(assoc-in cfg [:providers "oidc"] opts))
(let [opts (discover-oidc-config opts)]
(l/info :action "initialize" :provider "oidc" :method "discover"
:opts (pr-str (update opts :client-secret obfuscate-string)))
(assoc-in cfg [:providers "oidc"] opts)))
cfg)))
(defn- initialize-google-provider
[cfg]
(let [opts {:client-id (cf/get :google-client-id)
:client-secret (cf/get :google-client-secret)
:scopes #{"openid" "email" "profile"}
:auth-uri "https://accounts.google.com/o/oauth2/v2/auth"
:token-uri "https://oauth2.googleapis.com/token"
:user-uri "https://openidconnect.googleapis.com/v1/userinfo"
:name "google"}]
(if (and (string? (:client-id opts))
(string? (:client-secret opts)))
(do
(l/info :action "initialize" :provider "google"
:opts (pr-str (update opts :client-secret obfuscate-string)))
(assoc-in cfg [:providers "google"] opts))
cfg)))
(defn- initialize-github-provider
[cfg]
(let [opts {:client-id (cf/get :github-client-id)
:client-secret (cf/get :github-client-secret)
:scopes #{"read:user" "user:email"}
:auth-uri "https://github.com/login/oauth/authorize"
:token-uri "https://github.com/login/oauth/access_token"
:user-uri "https://api.github.com/user"
:name "github"}]
(if (and (string? (:client-id opts))
(string? (:client-secret opts)))
(do
(l/info :action "initialize" :provider "github"
:opts (pr-str (update opts :client-secret obfuscate-string)))
(assoc-in cfg [:providers "github"] opts))
cfg)))
(defn- initialize-gitlab-provider
[cfg]
(let [base (cf/get :gitlab-base-uri "https://gitlab.com")
opts {:base-uri base
:client-id (cf/get :gitlab-client-id)
:client-secret (cf/get :gitlab-client-secret)
:scopes #{"read_user"}
:auth-uri (str base "/oauth/authorize")
:token-uri (str base "/oauth/token")
:user-uri (str base "/api/v4/user")
:name "gitlab"}]
(if (and (string? (:client-id opts))
(string? (:client-secret opts)))
(do
(l/info :action "initialize" :provider "gitlab"
:opts (pr-str (update opts :client-secret obfuscate-string)))
(assoc-in cfg [:providers "gitlab"] opts))
cfg)))
(defn- initialize
[cfg]
(let [cfg (agent cfg :error-mode :continue)]
(send-off cfg initialize-google-provider)
(send-off cfg initialize-gitlab-provider)
(send-off cfg initialize-github-provider)
(send-off cfg initialize-oidc-provider)
cfg))

View File

@@ -7,158 +7,273 @@
(ns app.http.session
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.config :as cfg]
[app.common.logging :as l]
[app.config :as cf]
[app.db :as db]
[app.metrics :as mtx]
[app.util.async :as aa]
[app.util.logging :as l]
[app.db.sql :as sql]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.core.async :as a]
[clojure.spec.alpha :as s]
[integrant.core :as ig]))
[integrant.core :as ig]
[promesa.core :as p]
[promesa.exec :as px]
[yetti.request :as yrq]))
;; A default cookie name for storing the session. We don't allow
;; configure it.
(def cookie-name "auth-token")
;; A default cookie name for storing the session.
(def default-auth-token-cookie-name "auth-token")
;; A cookie that we can use to check from other sites of the same
;; domain if a user is authenticated.
(def default-authenticated-cookie-name "authenticated")
;; Default value for cookie max-age
(def default-cookie-max-age (dt/duration {:days 7}))
;; Default age for automatic session renewal
(def default-renewal-max-age (dt/duration {:hours 6}))
(defprotocol ISessionStore
(read-session [store key])
(write-session [store key data])
(update-session [store data])
(delete-session [store key]))
(defn- make-database-store
[{:keys [pool tokens executor]}]
(reify ISessionStore
(read-session [_ token]
(px/with-dispatch executor
(db/exec-one! pool (sql/select :http-session {:id token}))))
(write-session [_ _ data]
(px/with-dispatch executor
(let [profile-id (:profile-id data)
user-agent (:user-agent data)
created-at (or (:created-at data) (dt/now))
token (tokens :generate {:iss "authentication"
:iat created-at
:uid profile-id})
params {:user-agent user-agent
:profile-id profile-id
:created-at created-at
:updated-at created-at
:id token}]
(db/insert! pool :http-session params))))
(update-session [_ data]
(let [updated-at (dt/now)]
(px/with-dispatch executor
(db/update! pool :http-session
{:updated-at updated-at}
{:id (:id data)})
(assoc data :updated-at updated-at))))
(delete-session [_ token]
(px/with-dispatch executor
(db/delete! pool :http-session {:id token})
nil))))
(defn make-inmemory-store
[{:keys [tokens]}]
(let [cache (atom {})]
(reify ISessionStore
(read-session [_ token]
(p/do (get @cache token)))
(write-session [_ _ data]
(p/do
(let [profile-id (:profile-id data)
user-agent (:user-agent data)
created-at (or (:created-at data) (dt/now))
token (tokens :generate {:iss "authentication"
:iat created-at
:uid profile-id})
params {:user-agent user-agent
:created-at created-at
:updated-at created-at
:profile-id profile-id
:id token}]
(swap! cache assoc token params)
params)))
(update-session [_ data]
(let [updated-at (dt/now)]
(swap! cache update (:id data) assoc :updated-at updated-at)
(assoc data :updated-at updated-at)))
(delete-session [_ token]
(p/do
(swap! cache dissoc token)
nil)))))
(s/def ::tokens fn?)
(defmethod ig/pre-init-spec ::store [_]
(s/keys :req-un [::db/pool ::wrk/executor ::tokens]))
(defmethod ig/init-key ::store
[_ {:keys [pool] :as cfg}]
(if (db/read-only? pool)
(make-inmemory-store cfg)
(make-database-store cfg)))
(defmethod ig/halt-key! ::store
[_ _])
;; --- IMPL
(defn- create-session
[{:keys [conn tokens] :as cfg} {:keys [profile-id headers] :as request}]
(let [token (tokens :generate {:iss "authentication"
:iat (dt/now)
:uid profile-id})
params {:user-agent (get headers "user-agent")
:profile-id profile-id
:id token}]
(db/insert! conn :http-session params)))
(defn- create-session!
[store profile-id user-agent]
(let [params {:user-agent user-agent
:profile-id profile-id}]
(write-session store nil params)))
(defn- delete-session
[{:keys [conn] :as cfg} {:keys [cookies] :as request}]
(when-let [token (get-in cookies [cookie-name :value])]
(db/delete! conn :http-session {:id token}))
nil)
(defn- update-session!
[store session]
(update-session store session))
(defn- delete-session!
[store {:keys [cookies] :as request}]
(let [name (cf/get :auth-token-cookie-name default-auth-token-cookie-name)]
(when-let [token (get-in cookies [name :value])]
(delete-session store token))))
(defn- retrieve-session
[{:keys [conn] :as cfg} id]
(when id
(db/exec-one! conn ["select id, profile_id from http_session where id = ?" id])))
[store request]
(let [cookie-name (cf/get :auth-token-cookie-name default-auth-token-cookie-name)]
(when-let [cookie (yrq/get-cookie request cookie-name)]
(read-session store (:value cookie)))))
(defn- retrieve-from-request
[cfg {:keys [cookies] :as request}]
(->> (get-in cookies [cookie-name :value])
(retrieve-session cfg)))
(defn assign-auth-token-cookie
[response {token :id updated-at :updated-at}]
(let [max-age (cf/get :auth-token-cookie-max-age default-cookie-max-age)
created-at (or updated-at (dt/now))
renewal (dt/plus created-at default-renewal-max-age)
expires (dt/plus created-at max-age)
secure? (contains? cf/flags :secure-session-cookies)
cors? (contains? cf/flags :cors)
name (cf/get :auth-token-cookie-name default-auth-token-cookie-name)
comment (str "Renewal at: " (dt/format-instant renewal :rfc1123))
cookie {:path "/"
:http-only true
:expires expires
:value token
:comment comment
:same-site (if cors? :none :lax)
:secure secure?}]
(update response :cookies assoc name cookie)))
(defn- add-cookies
[response {:keys [id] :as session}]
(assoc response :cookies {cookie-name {:path "/" :http-only true :value id}}))
(defn assign-authenticated-cookie
[response {updated-at :updated-at}]
(let [max-age (cf/get :auth-token-cookie-max-age default-cookie-max-age)
created-at (or updated-at (dt/now))
renewal (dt/plus created-at default-renewal-max-age)
expires (dt/plus created-at max-age)
comment (str "Renewal at: " (dt/format-instant renewal :rfc1123))
secure? (contains? cf/flags :secure-session-cookies)
domain (cf/get :authenticated-cookie-domain)
name (cf/get :authenticated-cookie-name "authenticated")
cookie {:domain domain
:expires expires
:path "/"
:comment comment
:value true
:same-site :strict
:secure secure?}]
(cond-> response
(string? domain)
(update :cookies assoc name cookie))))
(defn- clear-cookies
(defn clear-auth-token-cookie
[response]
(assoc response :cookies {cookie-name {:value "" :max-age -1}}))
(let [name (cf/get :auth-token-cookie-name default-auth-token-cookie-name)]
(update response :cookies assoc name {:path "/" :value "" :max-age -1})))
(defn- clear-authenticated-cookie
[response]
(let [name (cf/get :authenticated-cookie-name default-authenticated-cookie-name)
domain (cf/get :authenticated-cookie-domain)]
(cond-> response
(string? domain)
(update :cookies assoc name {:domain domain :path "/" :value "" :max-age -1}))))
(defn- make-middleware
[{:keys [store] :as cfg}]
(letfn [;; Check if time reached for automatic session renewal
(renew-session? [{:keys [updated-at] :as session}]
(and (dt/instant? updated-at)
(let [elapsed (dt/diff updated-at (dt/now))]
(neg? (compare default-renewal-max-age elapsed)))))
;; Wrap respond with session renewal code
(wrap-respond [respond session]
(fn [response]
(p/let [session (update-session! store session)]
(-> response
(assign-auth-token-cookie session)
(assign-authenticated-cookie session)
(respond)))))]
{:name :session
:compile (fn [& _]
(fn [handler]
(fn [request respond raise]
(try
(-> (retrieve-session store request)
(p/finally (fn [session cause]
(cond
(some? cause)
(raise cause)
(nil? session)
(handler request respond raise)
:else
(let [request (-> request
(assoc :profile-id (:profile-id session))
(assoc :session-id (:id session)))
respond (cond-> respond
(renew-session? session)
(wrap-respond session))]
(handler request respond raise))))))
(catch Throwable cause
(raise cause))))))}))
(defn- middleware
[cfg handler]
(fn [request]
(if-let [{:keys [id profile-id] :as session} (retrieve-from-request cfg request)]
(do
(a/>!! (::events-ch cfg) id)
(l/update-thread-context! {:profile-id profile-id})
(handler (assoc request :profile-id profile-id)))
(handler request))))
;; --- STATE INIT: SESSION
(defmethod ig/pre-init-spec ::session [_]
(s/keys :req-un [::db/pool]))
(s/def ::store #(satisfies? ISessionStore %))
(defmethod ig/prep-key ::session
(defmethod ig/pre-init-spec :app.http/session [_]
(s/keys :req-un [::store]))
(defmethod ig/prep-key :app.http/session
[_ cfg]
(d/merge {:buffer-size 64}
(d/merge {:buffer-size 128}
(d/without-nils cfg)))
(defmethod ig/init-key ::session
[_ {:keys [pool] :as cfg}]
(let [events (a/chan (a/dropping-buffer (:buffer-size cfg)))
cfg (-> cfg
(assoc :conn pool)
(assoc ::events-ch events))]
(-> cfg
(assoc :middleware #(middleware cfg %))
(assoc :create (fn [profile-id]
(fn [request response]
(let [request (assoc request :profile-id profile-id)
session (create-session cfg request)]
(add-cookies response session)))))
(assoc :delete (fn [request response]
(delete-session cfg request)
(defmethod ig/init-key :app.http/session
[_ {:keys [store] :as cfg}]
(-> cfg
(assoc :middleware (make-middleware cfg))
(assoc :create (fn [profile-id]
(fn [request response]
(p/let [uagent (yrq/get-header request "user-agent")
session (create-session! store profile-id uagent)]
(-> response
(assign-auth-token-cookie session)
(assign-authenticated-cookie session))))))
(assoc :delete (fn [request response]
(p/do
(delete-session! store request)
(-> response
(assoc :status 204)
(assoc :body "")
(clear-cookies)))))))
(defmethod ig/halt-key! ::session
[_ data]
(a/close! (::events-ch data)))
;; --- STATE INIT: SESSION UPDATER
(declare update-sessions)
(s/def ::session map?)
(s/def ::max-batch-age ::cfg/http-session-updater-batch-max-age)
(s/def ::max-batch-size ::cfg/http-session-updater-batch-max-size)
(defmethod ig/pre-init-spec ::updater [_]
(s/keys :req-un [::db/pool ::wrk/executor ::mtx/metrics ::session]
:opt-un [::max-batch-age
::max-batch-size]))
(defmethod ig/prep-key ::updater
[_ cfg]
(merge {:max-batch-age (dt/duration {:minutes 5})
:max-batch-size 200}
(d/without-nils cfg)))
(defmethod ig/init-key ::updater
[_ {:keys [session metrics] :as cfg}]
(l/info :action "initialize session updater"
:max-batch-age (str (:max-batch-age cfg))
:max-batch-size (str (:max-batch-size cfg)))
(let [input (aa/batch (::events-ch session)
{:max-batch-size (:max-batch-size cfg)
:max-batch-age (inst-ms (:max-batch-age cfg))})
mcnt (mtx/create
{:name "http_session_update_total"
:help "A counter of session update batch events."
:registry (:registry metrics)
:type :counter})]
(a/go-loop []
(when-let [[reason batch] (a/<! input)]
(let [result (a/<! (update-sessions cfg batch))]
(mcnt :inc)
(cond
(ex/exception? result)
(l/error :task "updater"
:hint "unexpected error on update sessions"
:cause result)
(= :size reason)
(l/debug :task "updater"
:action "update sessions"
:reason (name reason)
:count result))
(recur))))))
(defn- update-sessions
[{:keys [pool executor]} ids]
(aa/with-thread executor
(db/exec-one! pool ["update http_session set updated_at=now() where id = ANY(?)"
(into-array String ids)])
(count ids)))
(assoc :body nil)
(clear-auth-token-cookie)
(clear-authenticated-cookie)))))))
;; --- STATE INIT: SESSION GC
@@ -172,22 +287,25 @@
(defmethod ig/prep-key ::gc-task
[_ cfg]
(merge {:max-age (dt/duration {:days 2})}
(merge {:max-age default-cookie-max-age}
(d/without-nils cfg)))
(defmethod ig/init-key ::gc-task
[_ {:keys [pool max-age] :as cfg}]
(l/debug :hint "initializing session gc task" :max-age max-age)
(fn [_]
(db/with-atomic [conn pool]
(let [interval (db/interval max-age)
result (db/exec-one! conn [sql:delete-expired interval])
result (db/exec-one! conn [sql:delete-expired interval interval])
result (:next.jdbc/update-count result)]
(l/debug :task "gc"
:action "clean http sessions"
:count result)
:hint "clean http sessions"
:deleted result)
result))))
(def ^:private
sql:delete-expired
"delete from http_session
where updated_at < now() - ?::interval")
where updated_at < now() - ?::interval
or (updated_at is null and
created_at < now() - ?::interval)")

View File

@@ -0,0 +1,341 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.http.websocket
"A penpot notification service for file cooperative edition."
(:require
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.pprint :as pp]
[app.common.spec :as us]
[app.db :as db]
[app.metrics :as mtx]
[app.util.time :as dt]
[app.util.websocket :as ws]
[clojure.core.async :as a]
[clojure.spec.alpha :as s]
[integrant.core :as ig]
[yetti.websocket :as yws]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; WEBSOCKET HOOKS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def state (atom {}))
(defn- on-connect
[{:keys [metrics]} wsp]
(let [created-at (dt/now)]
(swap! state assoc (::ws/id @wsp) wsp)
(mtx/run! metrics {:id :websocket-active-connections :inc 1})
(fn []
(swap! state dissoc (::ws/id @wsp))
(mtx/run! metrics {:id :websocket-active-connections :dec 1})
(mtx/run! metrics {:id :websocket-session-timing
:val (/ (inst-ms (dt/diff created-at (dt/now))) 1000.0)}))))
(defn- on-rcv-message
[{:keys [metrics]} _ message]
(mtx/run! metrics {:id :websocket-messages-total :labels ["recv"] :inc 1})
message)
(defn- on-snd-message
[{:keys [metrics]} _ message]
(mtx/run! metrics {:id :websocket-messages-total :labels ["send"] :inc 1})
message)
;; REPL HELPERS
(defn repl-get-connections-for-file
[file-id]
(->> (vals @state)
(filter #(= file-id (-> % deref ::file-subscription :file-id)))
(map deref)
(map ::ws/id)))
(defn repl-get-connections-for-team
[team-id]
(->> (vals @state)
(filter #(= team-id (-> % deref ::team-subscription :team-id)))
(map deref)
(map ::ws/id)))
(defn repl-close-connection
[id]
(when-let [wsp (get @state id)]
(a/>!! (::ws/close-ch @wsp) [8899 "closed from server"])
(a/close! (::ws/close-ch @wsp))))
(defn repl-get-connection-info
[id]
(when-let [wsp (get @state id)]
{:id id
:created-at (dt/instant id)
:profile-id (::profile-id @wsp)
:session-id (::session-id @wsp)
:user-agent (::ws/user-agent @wsp)
:ip-addr (::ws/remote-addr @wsp)
:last-activity-at (::ws/last-activity-at @wsp)
:http-session-id (::ws/http-session-id @wsp)
:subscribed-file (-> wsp deref ::file-subscription :file-id)
:subscribed-team (-> wsp deref ::team-subscription :team-id)}))
(defn repl-print-connection-info
[id]
(some-> id repl-get-connection-info pp/pprint))
(defn repl-print-connection-info-for-file
[file-id]
(some->> (repl-get-connections-for-file file-id)
(map repl-get-connection-info)
(pp/pprint)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; WEBSOCKET HANDLER
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmulti handle-message
(fn [_ _ message]
(:type message)))
(defmethod handle-message :connect
[cfg wsp _]
(let [msgbus-fn (:msgbus cfg)
conn-id (::ws/id @wsp)
profile-id (::profile-id @wsp)
session-id (::session-id @wsp)
output-ch (::ws/output-ch @wsp)
xform (remove #(= (:session-id %) session-id))
channel (a/chan (a/dropping-buffer 16) xform)]
(l/trace :fn "handle-message" :event :connect :conn-id conn-id)
;; Subscribe to the profile channel and forward all messages to
;; websocket output channel (send them to the client).
(swap! wsp assoc ::profile-subscription channel)
(a/pipe channel output-ch false)
(msgbus-fn :cmd :sub :topic profile-id :chan channel)))
(defmethod handle-message :disconnect
[cfg wsp _]
(let [msgbus-fn (:msgbus cfg)
conn-id (::ws/id @wsp)
profile-id (::profile-id @wsp)
session-id (::session-id @wsp)
profile-ch (::profile-subscription @wsp)
fsub (::file-subscription @wsp)
tsub (::team-subscription @wsp)
message {:type :disconnect
:subs-id profile-id
:profile-id profile-id
:session-id session-id}]
(l/trace :fn "handle-message"
:event :disconnect
:conn-id conn-id)
(a/go
;; Close the main profile subscription
(a/close! profile-ch)
(a/<! (msgbus-fn :cmd :purge :chans [profile-ch]))
;; Close tram subscription if exists
(when-let [channel (:channel tsub)]
(a/close! channel)
(a/<! (msgbus-fn :cmd :purge :chans [channel])))
(when-let [{:keys [topic channel]} fsub]
(a/close! channel)
(a/<! (msgbus-fn :cmd :purge :chans [channel]))
(a/<! (msgbus-fn :cmd :pub :topic topic :message message))))))
(defmethod handle-message :subscribe-team
[cfg wsp {:keys [team-id] :as params}]
(let [msgbus-fn (:msgbus cfg)
conn-id (::ws/id @wsp)
session-id (::session-id @wsp)
output-ch (::ws/output-ch @wsp)
prev-subs (get @wsp ::team-subscription)
xform (comp
(remove #(= (:session-id %) session-id))
(map #(assoc % :subs-id team-id)))
channel (a/chan (a/dropping-buffer 64) xform)]
(l/trace :fn "handle-message"
:event :subscribe-team
:team-id team-id
:conn-id conn-id)
(a/pipe channel output-ch false)
(let [state {:team-id team-id :channel channel :topic team-id}]
(swap! wsp assoc ::team-subscription state))
(a/go
;; Close previous subscription if exists
(when-let [channel (:channel prev-subs)]
(a/close! channel)
(a/<! (msgbus-fn :cmd :purge :chans [channel]))))
(a/go
(a/<! (msgbus-fn :cmd :sub :topic team-id :chan channel)))))
(defmethod handle-message :subscribe-file
[cfg wsp {:keys [file-id] :as params}]
(let [msgbus-fn (:msgbus cfg)
conn-id (::ws/id @wsp)
profile-id (::profile-id @wsp)
session-id (::session-id @wsp)
output-ch (::ws/output-ch @wsp)
prev-subs (::file-subscription @wsp)
xform (comp (remove #(= (:session-id %) session-id))
(map #(assoc % :subs-id file-id)))
channel (a/chan (a/dropping-buffer 64) xform)]
(l/trace :fn "handle-message"
:event :subscribe-file
:file-id file-id
:conn-id conn-id)
(let [state {:file-id file-id :channel channel :topic file-id}]
(swap! wsp assoc ::file-subscription state))
(a/go
;; Close previous subscription if exists
(when-let [channel (:channel prev-subs)]
(a/close! channel)
(a/<! (msgbus-fn :cmd :purge :chans [channel]))))
;; Message forwarding
(a/go
(loop []
(when-let [{:keys [type] :as message} (a/<! channel)]
(when (or (= :join-file type)
(= :leave-file type)
(= :disconnect type))
(let [message {:type :presence
:file-id file-id
:session-id session-id
:profile-id profile-id}]
(a/<! (msgbus-fn :cmd :pub
:topic file-id
:message message))))
(a/>! output-ch message)
(recur))))
(a/go
;; Subscribe to file topic
(a/<! (msgbus-fn :cmd :sub :topic file-id :chan channel))
;; Notifify the rest of participants of the new connection.
(let [message {:type :join-file
:file-id file-id
:subs-id file-id
:session-id session-id
:profile-id profile-id}]
(a/<! (msgbus-fn :cmd :pub
:topic file-id
:message message))))))
(defmethod handle-message :unsubscribe-file
[cfg wsp {:keys [file-id] :as params}]
(let [msgbus-fn (:msgbus cfg)
conn-id (::ws/id @wsp)
session-id (::session-id @wsp)
profile-id (::profile-id @wsp)
subs (::file-subscription @wsp)
message {:type :leave-file
:file-id file-id
:session-id session-id
:profile-id profile-id}]
(l/trace :fn "handle-message"
:event :unsubscribe-file
:file-id file-id
:conn-id conn-id)
(a/go
(when (= (:file-id subs) file-id)
(let [channel (:channel subs)]
(a/close! channel)
(a/<! (msgbus-fn :cmd :purge :chans [channel]))
(a/<! (msgbus-fn :cmd :pub :topic file-id :message message)))))))
(defmethod handle-message :keepalive
[_ _ _]
(l/trace :fn "handle-message" :event :keepalive)
(a/go :nothing))
(defmethod handle-message :pointer-update
[cfg wsp {:keys [file-id] :as message}]
(let [msgbus-fn (:msgbus cfg)
profile-id (::profile-id @wsp)
session-id (::session-id @wsp)
subs (::file-subscription @wsp)
message (-> message
(assoc :subs-id file-id)
(assoc :profile-id profile-id)
(assoc :session-id session-id))]
(a/go
;; Only allow receive pointer updates when active subscription
(when subs
(a/<! (msgbus-fn :cmd :pub
:topic file-id
:message message))))))
(defmethod handle-message :default
[_ wsp message]
(let [conn-id (::ws/id @wsp)]
(l/warn :hint "received unexpected message"
:message message
:conn-id conn-id)
(a/go :none)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HTTP HANDLER
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(s/def ::msgbus fn?)
(s/def ::session-id ::us/uuid)
(s/def ::handler-params
(s/keys :req-un [::session-id]))
(defmethod ig/pre-init-spec ::handler [_]
(s/keys :req-un [::msgbus ::db/pool ::mtx/metrics]))
(defmethod ig/init-key ::handler
[_ cfg]
(fn [{:keys [profile-id params] :as req} respond raise]
(let [{:keys [session-id]} (us/conform ::handler-params params)]
(cond
(not profile-id)
(raise (ex/error :type :authentication
:hint "Authentication required."))
(not (yws/upgrade-request? req))
(raise (ex/error :type :validation
:code :websocket-request-expected
:hint "this endpoint only accepts websocket connections"))
:else
(do
(l/trace :hint "websocket request" :profile-id profile-id :session-id session-id)
(->> (ws/handler
::ws/on-rcv-message (partial on-rcv-message cfg)
::ws/on-snd-message (partial on-snd-message cfg)
::ws/on-connect (partial on-connect cfg)
::ws/handler (partial handle-message cfg)
::profile-id profile-id
::session-id session-id)
(yws/upgrade req)
(respond)))))))

View File

@@ -9,63 +9,143 @@
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.spec :as us]
[app.common.transit :as t]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.util.async :as aa]
[app.util.http :as http]
[app.util.logging :as l]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.core.async :as a]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[integrant.core :as ig]
[lambdaisland.uri :as u]))
[lambdaisland.uri :as u]
[promesa.core :as p]
[promesa.exec :as px]
[yetti.request :as yrq]
[yetti.response :as yrs]))
(defn parse-client-ip
[{:keys [headers] :as request}]
(or (some-> (get headers "x-forwarded-for") (str/split ",") first)
(get headers "x-real-ip")
(get request :remote-addr)))
[request]
(or (some-> (yrq/get-header request "x-forwarded-for") (str/split ",") first)
(yrq/get-header request "x-real-ip")
(some-> (yrq/remote-addr request) str)))
(defn extract-utm-params
"Extracts additional data from params and namespace them under
`penpot` ns."
[params]
(letfn [(process-param [params k v]
(let [sk (d/name k)]
(cond-> params
(str/starts-with? sk "utm_")
(assoc (->> sk str/kebab (keyword "penpot")) v)
(str/starts-with? sk "mtm_")
(assoc (->> sk str/kebab (keyword "penpot")) v))))]
(reduce-kv process-param {} params)))
(defn profile->props
[profile]
(-> profile
(select-keys [:is-active :is-muted :auth-backend :email :default-team-id :default-project-id :fullname :lang])
(select-keys [:id :is-active :is-muted :auth-backend :email :default-team-id :default-project-id :fullname :lang])
(merge (:props profile))
(d/without-nils)))
(defn clean-props
[{:keys [profile-id] :as event}]
(letfn [(clean-common [props]
(-> props
(dissoc :session-id)
(dissoc :password)
(dissoc :old-password)
(dissoc :token)))
(let [invalid-keys #{:session-id
:password
:old-password
:token}
xform (comp
(remove (fn [kv]
(qualified-keyword? (first kv))))
(remove (fn [kv]
(contains? invalid-keys (first kv))))
(remove (fn [[k v]]
(and (= k :profile-id)
(= v profile-id))))
(filter (fn [[_ v]]
(or (string? v)
(keyword? v)
(uuid? v)
(boolean? v)
(number? v)))))]
(clean-profile-id [props]
(cond-> props
(= profile-id (:profile-id props))
(dissoc :profile-id)))
(update event :props #(into {} xform %))))
(clean-complex-data [props]
(reduce-kv (fn [props k v]
(cond-> props
(or (string? v)
(uuid? v)
(boolean? v)
(number? v))
(assoc k v)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HTTP Handler
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(keyword? v)
(assoc k (name v))))
{}
props))]
(declare persist-http-events)
(update event :props #(-> % clean-common clean-profile-id clean-complex-data))))
(s/def ::profile-id ::us/uuid)
(s/def ::name ::us/string)
(s/def ::type ::us/string)
(s/def ::props (s/map-of ::us/keyword any?))
(s/def ::timestamp dt/instant?)
(s/def ::context (s/map-of ::us/keyword any?))
(s/def ::frontend-event
(s/keys :req-un [::type ::name ::props ::timestamp ::profile-id]
:opt-un [::context]))
(s/def ::frontend-events (s/every ::frontend-event))
(defmethod ig/init-key ::http-handler
[_ {:keys [executor pool] :as cfg}]
(if (or (db/read-only? pool) (not (contains? cf/flags :audit-log)))
(do
(l/warn :hint "audit log http handler disabled or db is read-only")
(fn [_ respond _]
(respond (yrs/response 204))))
(letfn [(handler [{:keys [profile-id] :as request}]
(let [events (->> (:events (:params request))
(remove #(not= profile-id (:profile-id %)))
(us/conform ::frontend-events))
ip-addr (parse-client-ip request)
cfg (-> cfg
(assoc :source "frontend")
(assoc :events events)
(assoc :ip-addr ip-addr))]
(persist-http-events cfg)))
(handle-error [cause]
(let [xdata (ex-data cause)]
(if (= :spec-validation (:code xdata))
(l/error ::l/raw (str "spec validation on persist-events:\n" (us/pretty-explain xdata)))
(l/error :hint "error on persist-events" :cause cause))))]
(fn [request respond _]
;; Fire and forget, log error in case of errro
(-> (px/submit! executor #(handler request))
(p/catch handle-error))
(respond (yrs/response 204))))))
(defn- persist-http-events
[{:keys [pool events ip-addr source] :as cfg}]
(let [columns [:id :name :source :type :tracked-at :profile-id :ip-addr :props :context]
prepare-xf (map (fn [event]
[(uuid/next)
(:name event)
source
(:type event)
(:timestamp event)
(:profile-id event)
(db/inet ip-addr)
(db/tjson (:props event))
(db/tjson (d/without-nils (:context event)))]))]
(when (seq events)
(->> (into [] prepare-xf events)
(db/insert-multi! pool :audit-log columns)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Collector
@@ -76,39 +156,57 @@
;; an external storage and data cleared.
(declare persist-events)
(s/def ::enabled ::us/boolean)
(defmethod ig/pre-init-spec ::collector [_]
(s/keys :req-un [::db/pool ::wrk/executor ::enabled]))
(s/keys :req-un [::db/pool ::wrk/executor]))
(def event-xform
(s/def ::ip-addr string?)
(s/def ::backend-event
(s/keys :req-un [::type ::name ::profile-id]
:opt-un [::ip-addr ::props]))
(def ^:private backend-event-xform
(comp
(filter :profile-id)
(filter #(us/valid? ::backend-event %))
(map clean-props)))
(defmethod ig/init-key ::collector
[_ {:keys [enabled] :as cfg}]
(when enabled
(l/info :msg "intializing audit collector")
(let [input (a/chan 512 event-xform)
[_ {:keys [pool] :as cfg}]
(cond
(not (contains? cf/flags :audit-log))
(do
(l/info :hint "audit log collection disabled")
(constantly nil))
(db/read-only? pool)
(do
(l/warn :hint "audit log collection disabled, db is read-only")
(constantly nil))
:else
(let [input (a/chan 512 backend-event-xform)
buffer (aa/batch input {:max-batch-size 100
:max-batch-age (* 10 1000) ; 10s
:init []})]
(l/info :hint "audit log collector initialized")
(a/go-loop []
(when-let [[_type events] (a/<! buffer)]
(let [res (a/<! (persist-events cfg events))]
(when (ex/exception? res)
(l/error :hint "error on persiting events"
:cause res)))
(recur)))
(l/error :hint "error on persisting events" :cause res))
(recur))))
(fn [& {:keys [cmd] :as params}]
(let [params (dissoc params :cmd)]
(case cmd
:stop (a/close! input)
:submit (when-not (a/offer! input params)
(l/warn :msg "activity channel is full"))))))))
(case cmd
:stop
(a/close! input)
:submit
(let [params (-> params
(dissoc :cmd)
(assoc :tracked-at (dt/now)))]
(when-not (a/offer! input params)
(l/warn :hint "activity channel is full"))))))))
(defn- persist-events
[{:keys [pool executor] :as cfg} events]
@@ -117,69 +215,89 @@
(:name event)
(:type event)
(:profile-id event)
(:tracked-at event)
(some-> (:ip-addr event) db/inet)
(db/tjson (:props event))])]
(db/tjson (:props event))
"backend"])]
(aa/with-thread executor
(db/with-atomic [conn pool]
(db/insert-multi! conn :audit-log
[:id :name :type :profile-id :ip-addr :props]
(sequence (map event->row) events))))))
(when (seq events)
(db/with-atomic [conn pool]
(db/insert-multi! conn :audit-log
[:id :name :type :profile-id :tracked-at :ip-addr :props :source]
(sequence (keep event->row) events)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Archive Task
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; This is a task responsible to send the accomulated events to an
;; This is a task responsible to send the accumulated events to an
;; external service for archival.
(declare archive-events)
(s/def ::http-client fn?)
(s/def ::uri ::us/string)
(s/def ::tokens fn?)
(defmethod ig/pre-init-spec ::archive-task [_]
(s/keys :req-un [::db/pool ::tokens ::enabled]
(s/keys :req-un [::db/pool ::tokens ::http-client]
:opt-un [::uri]))
(defmethod ig/init-key ::archive-task
[_ {:keys [uri enabled] :as cfg}]
(fn [_]
(when (and enabled (not uri))
(ex/raise :type :internal
:code :task-not-configured
:hint "archive task not configured, missing uri"))
(loop []
(let [res (archive-events cfg)]
(when (= res :continue)
(aa/thread-sleep 200)
(recur))))))
[_ {:keys [uri] :as cfg}]
(fn [props]
;; NOTE: this let allows overwrite default configured values from
;; the repl, when manually invoking the task.
(let [enabled (or (contains? cf/flags :audit-log-archive)
(:enabled props false))
uri (or uri (:uri props))
cfg (assoc cfg :uri uri)]
(when (and enabled (not uri))
(ex/raise :type :internal
:code :task-not-configured
:hint "archive task not configured, missing uri"))
(when enabled
(loop [total 0]
(let [n (archive-events cfg)]
(if n
(do
(aa/thread-sleep 200)
(recur (+ total n)))
(when (pos? total)
(l/trace :hint "events chunk archived" :num total)))))))))
(def sql:retrieve-batch-of-audit-log
"select * from audit_log
where archived_at is null
order by created_at asc
limit 100
limit 256
for update skip locked;")
(defn archive-events
[{:keys [pool uri tokens] :as cfg}]
(letfn [(decode-row [{:keys [props ip-addr] :as row}]
[{:keys [pool uri tokens http-client] :as cfg}]
(letfn [(decode-row [{:keys [props ip-addr context] :as row}]
(cond-> row
(db/pgobject? props)
(assoc :props (db/decode-transit-pgobject props))
(db/pgobject? context)
(assoc :context (db/decode-transit-pgobject context))
(db/pgobject? ip-addr "inet")
(assoc :ip-addr (db/decode-inet ip-addr))))
(row->event [{:keys [name type created-at profile-id props ip-addr]}]
(cond-> {:type type
:name name
:timestamp created-at
:profile-id profile-id
:props props}
(some? ip-addr)
(update :context assoc :source-ip ip-addr)))
(row->event [row]
(select-keys row [:type
:name
:source
:created-at
:tracked-at
:profile-id
:ip-addr
:props
:context]))
(send [events]
(let [token (tokens :generate {:iss "authentication"
@@ -194,12 +312,14 @@
:method :post
:headers headers
:body body}
resp (http/send! params)]
(when (not= (:status resp) 204)
(ex/raise :type :internal
:code :unable-to-send-events
:hint "unable to send events"
:context resp))))
resp (http-client params {:sync? true})]
(if (= (:status resp) 204)
true
(do
(l/error :hint "unable to archive events"
:resp-status (:status resp)
:resp-body (:body resp))
false))))
(mark-as-archived [conn rows]
(db/exec-one! conn ["update audit_log set archived_at=now() where id = ANY(?)"
@@ -212,30 +332,16 @@
xform (comp (map decode-row)
(map row->event))
events (into [] xform rows)]
(l/debug :action "archive-events" :uri uri :events (count events))
(if (empty? events)
:empty
(do
(send events)
(when-not (empty? events)
(l/debug :action "archive-events" :uri uri :events (count events))
(when (send events)
(mark-as-archived conn rows)
:continue))))))
(count events)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; GC Task
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare clean-archived)
(s/def ::max-age ::cf/audit-archive-gc-max-age)
(defmethod ig/pre-init-spec ::archive-gc-task [_]
(s/keys :req-un [::db/pool ::enabled ::max-age]))
(defmethod ig/init-key ::archive-gc-task
[_ cfg]
(fn [_]
(clean-archived cfg)))
(def sql:clean-archived
"delete from audit_log
where archived_at is not null
@@ -248,3 +354,13 @@
result (:next.jdbc/update-count result)]
(l/debug :action "clean archived audit log" :removed result)
result))
(s/def ::max-age ::cf/audit-log-gc-max-age)
(defmethod ig/pre-init-spec ::gc-task [_]
(s/keys :req-un [::db/pool ::max-age]))
(defmethod ig/init-key ::gc-task
[_ cfg]
(fn [_]
(clean-archived cfg)))

View File

@@ -0,0 +1,92 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.loggers.database
"A specific logger impl that persists errors on the database."
(:require
[app.common.logging :as l]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.util.async :as aa]
[app.worker :as wrk]
[clojure.core.async :as a]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[integrant.core :as ig]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Error Listener
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare handle-event)
(defonce enabled (atom true))
(defn- persist-on-database!
[{:keys [pool] :as cfg} {:keys [id] :as event}]
(when-not (db/read-only? pool)
(db/insert! pool :server-error-report {:id id :content (db/tjson event)})))
(defn- parse-event-data
[event]
(reduce-kv
(fn [acc k v]
(cond
(= k :id) (assoc acc k (uuid/uuid v))
(= k :profile-id) (assoc acc k (uuid/uuid v))
(str/blank? v) acc
:else (assoc acc k v)))
{}
event))
(defn parse-event
[event]
(-> (parse-event-data event)
(assoc :tenant (cf/get :tenant))
(assoc :host (cf/get :host))
(assoc :public-uri (cf/get :public-uri))
(assoc :version (:full cf/version))
(update :id #(or % (uuid/next)))))
(defn handle-event
[{:keys [executor] :as cfg} event]
(aa/with-thread executor
(try
(let [event (parse-event event)
uri (cf/get :public-uri)]
(l/debug :hint "registering error on database" :id (:id event)
:uri (str uri "/dbg/error/" (:id event)))
(persist-on-database! cfg event))
(catch Exception cause
(l/warn :hint "unexpected exception on database error logger" :cause cause)))))
(defmethod ig/pre-init-spec ::reporter [_]
(s/keys :req-un [::wrk/executor ::db/pool ::receiver]))
(defn error-event?
[event]
(= "error" (:logger/level event)))
(defmethod ig/init-key ::reporter
[_ {:keys [receiver] :as cfg}]
(l/info :msg "initializing database error persistence")
(let [output (a/chan (a/sliding-buffer 5) (filter error-event?))]
(receiver :sub output)
(a/go-loop []
(let [msg (a/<! output)]
(if (nil? msg)
(l/info :msg "stoping error reporting loop")
(do
(a/<! (handle-event cfg msg))
(recur)))))
output))
(defmethod ig/halt-key! ::reporter
[_ output]
(a/close! output))

View File

@@ -7,39 +7,37 @@
(ns app.loggers.loki
"A Loki integration."
(:require
[app.common.logging :as l]
[app.common.spec :as us]
[app.config :as cfg]
[app.util.async :as aa]
[app.util.http :as http]
[app.util.json :as json]
[app.util.logging :as l]
[app.worker :as wrk]
[clojure.core.async :as a]
[clojure.spec.alpha :as s]
[integrant.core :as ig]))
(declare handle-event)
(declare ^:private handle-event)
(declare ^:private start-rcv-loop)
(s/def ::uri ::us/string)
(s/def ::receiver fn?)
(s/def ::http-client fn?)
(defmethod ig/pre-init-spec ::reporter [_]
(s/keys :req-un [::wrk/executor ::receiver]
(s/keys :req-un [ ::receiver ::http-client]
:opt-un [::uri]))
(defmethod ig/init-key ::reporter
[_ {:keys [receiver uri] :as cfg}]
(when uri
(l/info :msg "intializing loki reporter" :uri uri)
(let [input (a/chan (a/dropping-buffer 512))]
(l/info :msg "initializing loki reporter" :uri uri)
(let [input (a/chan (a/dropping-buffer 2048))]
(receiver :sub input)
(a/go-loop []
(let [msg (a/<! input)]
(if (nil? msg)
(l/info :msg "stoping error reporting loop")
(do
(a/<! (handle-event cfg msg))
(recur)))))
(doto (Thread. #(start-rcv-loop cfg input))
(.setDaemon true)
(.setName "penpot/loki-sender")
(.start))
input)))
(defmethod ig/halt-key! ::reporter
@@ -47,53 +45,49 @@
(when output
(a/close! output)))
(defn- start-rcv-loop
[cfg input]
(loop []
(let [msg (a/<!! input)]
(when-not (nil? msg)
(handle-event cfg msg)
(recur))))
(l/info :msg "stoping error reporting loop"))
(defn- prepare-payload
[event]
(let [labels {:host (cfg/get :host)
:tenant (cfg/get :tenant)
:version (:full cfg/version)
:logger (:logger event)
:level (:level event)}]
:logger (:logger/name event)
:level (:logger/level event)}]
{:streams
[{:stream labels
:values [[(str (* (inst-ms (:created-at event)) 1000000))
(str (:message event)
(when-let [error (:error event)]
(str "\n" (:trace error))))]]}]}))
(when-let [error (:trace event)]
(str "\n" error)))]]}]}))
(defn- send-log
[uri payload i]
(try
(let [response (http/send! {:uri uri
:timeout 6000
:method :post
:headers {"content-type" "application/json"}
:body (json/encode payload)})]
(cond
(= (:status response) 204)
true
(= (:status response) 400)
(do
(l/error :hint "error on sending log to loki (no retry)"
:rsp (pr-str response))
true)
:else
(do
(l/error :hint "error on sending log to loki" :try i
:rsp (pr-str response))
false)))
(catch Exception e
(l/error :hint "error on sending message to loki" :cause e :try i)
false)))
(defn- make-request
[{:keys [http-client uri] :as cfg} payload]
(http-client {:uri uri
:timeout 3000
:method :post
:headers {"content-type" "application/json"}
:body (json/write payload)}
{:sync? true}))
(defn- handle-event
[{:keys [executor uri]} event]
(aa/with-thread executor
(let [payload (prepare-payload event)]
(loop [i 1]
(when (and (not (send-log uri payload i)) (< i 20))
(Thread/sleep (* i 2000))
(recur (inc i)))))))
[cfg event]
(try
(let [payload (prepare-payload event)
response (make-request cfg payload)]
(when-not (= 204 (:status response))
(map? response)
(l/error :hint "error on sending log to loki (unexpected response)"
:response (pr-str response))))
(catch Throwable cause
(l/error :hint "error on sending log to loki (unexpected exception)"
:cause cause))))

View File

@@ -7,35 +7,49 @@
(ns app.loggers.mattermost
"A mattermost integration for error reporting."
(:require
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cfg]
[app.db :as db]
[app.util.async :as aa]
[app.util.http :as http]
[app.common.logging :as l]
[app.config :as cf]
[app.loggers.database :as ldb]
[app.util.json :as json]
[app.util.logging :as l]
[app.util.template :as tmpl]
[app.worker :as wrk]
[clojure.core.async :as a]
[clojure.java.io :as io]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[integrant.core :as ig]))
[integrant.core :as ig]
[promesa.core :as p]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Error Listener
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defonce enabled (atom true))
(declare handle-event)
(defn- send-mattermost-notification!
[{:keys [http-client] :as cfg} {:keys [host id public-uri] :as event}]
(let [uri (:uri cfg)
text (str "Exception on (host: " host ", url: " public-uri "/dbg/error/" id ")\n"
(when-let [pid (:profile-id event)]
(str "- profile-id: #uuid-" pid "\n")))]
(p/then
(http-client {:uri uri
:method :post
:headers {"content-type" "application/json"}
:body (json/write-str {:text text})})
(fn [{:keys [status] :as rsp}]
(when (not= status 200)
(l/warn :hint "error on sending data to mattermost"
:response (pr-str rsp)))))))
(defonce enabled-mattermost (atom true))
(defn handle-event
[cfg event]
(let [ch (a/chan)]
(-> (p/let [event (ldb/parse-event event)]
(send-mattermost-notification! cfg event))
(p/finally (fn [_ cause]
(when cause
(l/warn :hint "unexpected exception on error reporter" :cause cause))
(a/close! ch))))
ch))
(s/def ::uri ::us/string)
(s/def ::http-client fn?)
(s/def ::uri ::cf/error-report-webhook)
(defmethod ig/pre-init-spec ::reporter [_]
(s/keys :req-un [::wrk/executor ::db/pool ::receiver]
(s/keys :req-un [::http-client ::receiver]
:opt-un [::uri]))
(defmethod ig/init-key ::reporter
@@ -43,7 +57,8 @@
(when uri
(l/info :msg "initializing mattermost error reporter" :uri uri)
(let [output (a/chan (a/sliding-buffer 128)
(filter #(= (:level %) "error")))]
(filter (fn [event]
(= (:logger/level event) "error"))))]
(receiver :sub output)
(a/go-loop []
(let [msg (a/<! output)]
@@ -58,95 +73,3 @@
[_ output]
(when output
(a/close! output)))
(defn- send-mattermost-notification!
[cfg {:keys [host id] :as cdata}]
(try
(let [uri (:uri cfg)
text (str "Unhandled exception (host: " host ", url: " (cfg/get :public-uri) "/dbg/error-by-id/" id "\n"
"- profile-id: #" (:profile-id cdata) "\n")
rsp (http/send! {:uri uri
:method :post
:headers {"content-type" "application/json"}
:body (json/encode-str {:text text})})]
(when (not= (:status rsp) 200)
(l/error :hint "error on sending data to mattermost"
:response (pr-str rsp))))
(catch Exception e
(l/error :hint "unexpected exception on error reporter"
:cause e))))
(defn- persist-on-database!
[{:keys [pool] :as cfg} {:keys [id] :as cdata}]
(db/with-atomic [conn pool]
(db/insert! conn :server-error-report
{:id id :content (db/tjson cdata)})))
(defn- parse-context
[event]
(reduce-kv
(fn [acc k v]
(cond
(= k :id) (assoc acc k (uuid/uuid v))
(= k :profile-id) (assoc acc k (uuid/uuid v))
(str/blank? v) acc
:else (assoc acc k v)))
{:id (uuid/next)}
(:context event)))
(defn- parse-event
[event]
(-> (parse-context event)
(merge (dissoc event :context))
(assoc :tenant (cfg/get :tenant))
(assoc :host (cfg/get :host))
(assoc :public-uri (cfg/get :public-uri))
(assoc :version (:full cfg/version))))
(defn handle-event
[{:keys [executor] :as cfg} event]
(aa/with-thread executor
(try
(let [cdata (parse-event event)]
(when @enabled-mattermost
(send-mattermost-notification! cfg cdata))
(persist-on-database! cfg cdata))
(catch Exception e
(l/error :hint "unexpected exception on error reporter"
:cause e)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Http Handler
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod ig/pre-init-spec ::handler [_]
(s/keys :req-un [::db/pool]))
(defmethod ig/init-key ::handler
[_ {:keys [pool] :as cfg}]
(letfn [(parse-id [request]
(let [id (get-in request [:path-params :id])
id (us/uuid-conformer id)]
(when (uuid? id)
id)))
(retrieve-report [id]
(ex/ignoring
(when-let [{:keys [content] :as row} (db/get-by-id pool :server-error-report id)]
(assoc row :content (db/decode-transit-pgobject content)))))
(render-template [{:keys [content] :as report}]
(some-> (io/resource "error-report.tmpl")
(tmpl/render content)))]
(fn [request]
(let [result (some-> (parse-id request)
(retrieve-report)
(render-template))]
(if result
{:status 200
:headers {"content-type" "text/html; charset=utf-8"}
:body result}
{:status 404
:body "not found"})))))

View File

@@ -0,0 +1,170 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.loggers.sentry
"A mattermost integration for error reporting."
(:require
[app.common.logging :as l]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.util.async :as aa]
[app.worker :as wrk]
[clojure.core.async :as a]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[integrant.core :as ig])
(:import
io.sentry.Scope
io.sentry.IHub
io.sentry.Hub
io.sentry.NoOpHub
io.sentry.protocol.User
io.sentry.SentryOptions
io.sentry.SentryLevel
io.sentry.ScopeCallback))
(defonce enabled (atom true))
(defn- parse-context
[event]
(reduce-kv
(fn [acc k v]
(cond
(= k :id) (assoc acc k (uuid/uuid v))
(= k :profile-id) (assoc acc k (uuid/uuid v))
(str/blank? v) acc
:else (assoc acc k v)))
{}
(:context event)))
(defn- parse-event
[event]
(assoc event :context (parse-context event)))
(defn- build-sentry-options
[cfg]
(let [version (:base cf/version)]
(doto (SentryOptions.)
(.setDebug (:debug cfg false))
(.setTracesSampleRate (:traces-sample-rate cfg 1.0))
(.setDsn (:dsn cfg))
(.setServerName (cf/get :host))
(.setEnvironment (cf/get :tenant))
(.setAttachServerName true)
(.setAttachStacktrace (:attach-stack-trace cfg false))
(.setRelease (str "backend@" (if (= version "0.0.0") "develop" version))))))
(defn handle-event
[^IHub shub event]
(letfn [(set-user! [^Scope scope {:keys [context] :as event}]
(let [user (User.)]
(.setIpAddress ^User user ^String (:ip-addr context))
(when-let [pid (:profile-id context)]
(.setId ^User user ^String (str pid)))
(.setUser scope ^User user)))
(set-level! [^Scope scope]
(.setLevel scope SentryLevel/ERROR))
(set-context! [^Scope scope {:keys [context] :as event}]
(let [uri (str (cf/get :public-uri) "/dbg/error-by-id/" (:id context))]
(.setContexts scope "detailed_error_uri" ^String uri))
(when-let [vers (:frontend-version event)]
(.setContexts scope "frontend_version" ^String vers))
(when-let [puri (:public-uri event)]
(.setContexts scope "public_uri" ^String (str puri)))
(when-let [uagent (:user-agent context)]
(.setContexts scope "user_agent" ^String uagent))
(when-let [tenant (:tenant event)]
(.setTag scope "tenant" ^String tenant))
(when-let [type (:error-type context)]
(.setTag scope "error_type" ^String (str type)))
(when-let [code (:error-code context)]
(.setTag scope "error_code" ^String (str code)))
)
(capture [^Scope scope {:keys [context error] :as event}]
(let [msg (str (:message error) "\n\n"
"======================================================\n"
"=================== Params ===========================\n"
"======================================================\n"
(:params context) "\n"
(when (:explain context)
(str "======================================================\n"
"=================== Explain ==========================\n"
"======================================================\n"
(:explain context) "\n"))
(when (:data context)
(str "======================================================\n"
"=================== Error Data =======================\n"
"======================================================\n"
(:data context) "\n"))
(str "======================================================\n"
"=================== Stack Trace ======================\n"
"======================================================\n"
(:trace error))
"\n")]
(set-user! scope event)
(set-level! scope)
(set-context! scope event)
(.captureMessage ^IHub shub msg)
))
]
(when @enabled
(.withScope ^IHub shub (reify ScopeCallback
(run [_ scope]
(->> event
(parse-event)
(capture scope))))))
))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Error Listener
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(s/def ::receiver any?)
(s/def ::dsn ::cf/sentry-dsn)
(s/def ::trace-sample-rate ::cf/sentry-trace-sample-rate)
(s/def ::attach-stack-trace ::cf/sentry-attach-stack-trace)
(s/def ::debug ::cf/sentry-debug)
(defmethod ig/pre-init-spec ::reporter [_]
(s/keys :req-un [::wrk/executor ::db/pool ::receiver]
:opt-un [::dsn ::trace-sample-rate ::attach-stack-trace]))
(defmethod ig/init-key ::reporter
[_ {:keys [receiver dsn executor] :as cfg}]
(l/info :msg "initializing sentry reporter" :dsn dsn)
(let [opts (build-sentry-options cfg)
shub (if dsn
(Hub. ^SentryOptions opts)
(NoOpHub/getInstance))
output (a/chan (a/sliding-buffer 128)
(filter #(= (:level %) "error")))]
(receiver :sub output)
(a/go-loop []
(let [event (a/<! output)]
(if (nil? event)
(do
(l/info :msg "stoping error reporting loop")
(.close ^IHub shub))
(do
(a/<! (aa/with-thread executor (handle-event shub event)))
(recur)))))
output))
(defmethod ig/halt-key! ::reporter
[_ output]
(when output
(a/close! output)))

View File

@@ -7,10 +7,10 @@
(ns app.loggers.zmq
"A generic ZMQ listener."
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.spec :as us]
[app.util.json :as json]
[app.util.logging :as l]
[app.util.time :as dt]
[clojure.core.async :as a]
[clojure.spec.alpha :as s]
@@ -31,13 +31,17 @@
(defmethod ig/init-key ::receiver
[_ {:keys [endpoint] :as cfg}]
(l/info :msg "intializing ZMQ receiver" :bind endpoint)
(l/info :msg "initializing ZMQ receiver" :bind endpoint)
(let [buffer (a/chan 1)
output (a/chan 1 (comp (filter map?)
(map prepare)))
(keep prepare)))
mult (a/mult output)]
(when endpoint
(a/thread (start-rcv-loop {:out buffer :endpoint endpoint})))
(let [thread (Thread. #(start-rcv-loop {:out buffer :endpoint endpoint}))]
(.setDaemon thread false)
(.setName thread "penpot/zmq-logger-receiver")
(.start thread)))
(a/pipe buffer output)
(with-meta
(fn [cmd ch]
@@ -53,37 +57,54 @@
[_ f]
(a/close! (::buffer (meta f))))
(def ^:private json-mapper
(json/mapper
{:encode-key-fn str/camel
:decode-key-fn (comp keyword str/kebab)}))
(defn- start-rcv-loop
([] (start-rcv-loop nil))
([{:keys [out endpoint] :or {endpoint "tcp://localhost:5556"}}]
(let [out (or out (a/chan 1))
zctx (ZContext.)
zctx (ZContext. 1)
socket (.. zctx (createSocket SocketType/SUB))]
(.. socket (connect ^String endpoint))
(.. socket (subscribe ""))
(.. socket (setReceiveTimeOut 5000))
(loop []
(let [msg (.recv ^ZMQ$Socket socket)
msg (json/decode msg)
msg (ex/ignoring (json/read msg json-mapper))
msg (if (nil? msg) :empty msg)]
(if (a/>!! out msg)
(recur)
(do
(.close ^java.lang.AutoCloseable socket)
(.close ^java.lang.AutoCloseable zctx))))))))
(.destroy ^ZContext zctx))))))))
(s/def ::logger-name string?)
(s/def ::level string?)
(s/def ::thread string?)
(s/def ::time-millis integer?)
(s/def ::message string?)
(s/def ::context-map map?)
(s/def ::thrown map?)
(s/def ::log4j-event
(s/keys :req-un [::logger-name ::level ::thread ::time-millis ::message]
:opt-un [::context-map ::thrown]))
(defn- prepare
[event]
(d/merge
{:logger (:loggerName event)
:level (str/lower (:level event))
:thread (:thread event)
:created-at (dt/instant (:timeMillis event))
:message (:message event)}
(when-let [ctx (:contextMap event)]
{:context ctx})
(when-let [thrown (:thrown event)]
{:error
{:class (:name thrown)
:message (:message thrown)
:trace (:extendedStackTrace thrown)}})))
(if (s/valid? ::log4j-event event)
(merge {:message (:message event)
:created-at (dt/instant (:time-millis event))
:logger/name (:logger-name event)
:logger/level (str/lower (:level event))}
(when-let [trace (-> event :thrown :extended-stack-trace)]
{:trace trace})
(:context-map event))
(do
(l/warn :hint "invalid event" :event event)
nil)))

View File

@@ -6,233 +6,254 @@
(ns app.main
(:require
[app.auth.oidc]
[app.common.logging :as l]
[app.config :as cf]
[app.util.logging :as l]
[app.util.time :as dt]
[integrant.core :as ig]))
[integrant.core :as ig])
(:gen-class))
(def system-config
{:app.db/pool
{:uri (cf/get :database-uri)
:username (cf/get :database-username)
:password (cf/get :database-password)
:read-only (cf/get :database-readonly false)
:metrics (ig/ref :app.metrics/metrics)
:migrations (ig/ref :app.migrations/all)
:name :main
:min-pool-size 0
:max-pool-size 20}
:name :main
:min-size (cf/get :database-min-pool-size 0)
:max-size (cf/get :database-max-pool-size 30)}
:app.metrics/metrics
{:definitions
{:profile-register
{:name "actions_profile_register_count"
:help "A global counter of user registrations."
:type :counter}
:profile-activation
{:name "actions_profile_activation_count"
:help "A global counter of profile activations"
:type :counter}}}
;; Default thread pool for IO operations
[::default :app.worker/executor]
{:parallelism (cf/get :default-executor-parallelism 60)
:prefix :default}
:app.migrations/all
{:main (ig/ref :app.migrations/migrations)}
;; Constrained thread pool. Should only be used from high resources
;; demanding operations.
[::blocking :app.worker/executor]
{:parallelism (cf/get :blocking-executor-parallelism 10)
:prefix :blocking}
;; Dedicated thread pool for backround tasks execution.
[::worker :app.worker/executor]
{:parallelism (cf/get :worker-executor-parallelism 10)
:prefix :worker}
:app.worker/scheduler
{:parallelism 1
:prefix :scheduler}
:app.worker/executors
{:default (ig/ref [::default :app.worker/executor])
:worker (ig/ref [::worker :app.worker/executor])
:blocking (ig/ref [::blocking :app.worker/executor])}
:app.worker/executors-monitor
{:metrics (ig/ref :app.metrics/metrics)
:scheduler (ig/ref :app.worker/scheduler)
:executors (ig/ref :app.worker/executors)}
:app.migrations/migrations
{}
:app.metrics/metrics
{}
:app.migrations/all
{:main (ig/ref :app.migrations/migrations)}
:app.msgbus/msgbus
{:backend (cf/get :msgbus-backend :redis)
:executor (ig/ref [::default :app.worker/executor])
:redis-uri (cf/get :redis-uri)}
:app.tokens/tokens
{:keys (ig/ref :app.setup/keys)}
:app.storage.tmp/cleaner
{:executor (ig/ref [::worker :app.worker/executor])
:scheduler (ig/ref :app.worker/scheduler)}
:app.storage/gc-deleted-task
{:pool (ig/ref :app.db/pool)
:storage (ig/ref :app.storage/storage)
:min-age (dt/duration {:hours 2})}
:executor (ig/ref [::worker :app.worker/executor])}
:app.storage/gc-touched-task
{:pool (ig/ref :app.db/pool)}
{:pool (ig/ref :app.db/pool)}
:app.storage/recheck-task
:app.http/client
{:executor (ig/ref [::default :app.worker/executor])}
:app.http/session
{:store (ig/ref :app.http.session/store)}
:app.http.session/store
{:pool (ig/ref :app.db/pool)
:storage (ig/ref :app.storage/storage)}
:app.http.session/session
{:pool (ig/ref :app.db/pool)
:tokens (ig/ref :app.tokens/tokens)}
:tokens (ig/ref :app.tokens/tokens)
:executor (ig/ref [::default :app.worker/executor])}
:app.http.session/gc-task
{:pool (ig/ref :app.db/pool)
:max-age (cf/get :http-session-idle-max-age)}
:app.http.session/updater
{:pool (ig/ref :app.db/pool)
:metrics (ig/ref :app.metrics/metrics)
:executor (ig/ref :app.worker/executor)
:session (ig/ref :app.http.session/session)
:max-batch-age (cf/get :http-session-updater-batch-max-age)
:max-batch-size (cf/get :http-session-updater-batch-max-size)}
:max-age (cf/get :auth-token-cookie-max-age)}
:app.http.awsns/handler
{:tokens (ig/ref :app.tokens/tokens)
:pool (ig/ref :app.db/pool)}
{:tokens (ig/ref :app.tokens/tokens)
:pool (ig/ref :app.db/pool)
:http-client (ig/ref :app.http/client)
:executor (ig/ref [::worker :app.worker/executor])}
:app.http/server
{:port (cf/get :http-server-port)
:router (ig/ref :app.http/router)
:metrics (ig/ref :app.metrics/metrics)
:ws {"/ws/notifications" (ig/ref :app.notifications/handler)}}
{:port (cf/get :http-server-port)
:host (cf/get :http-server-host)
:router (ig/ref :app.http/router)
:metrics (ig/ref :app.metrics/metrics)
:executor (ig/ref [::default :app.worker/executor])
:io-threads (cf/get :http-server-io-threads)
:max-body-size (cf/get :http-server-max-body-size)
:max-multipart-body-size (cf/get :http-server-max-multipart-body-size)}
:app.auth.ldap/provider
{:host (cf/get :ldap-host)
:port (cf/get :ldap-port)
:ssl (cf/get :ldap-ssl)
:tls (cf/get :ldap-starttls)
:query (cf/get :ldap-user-query)
:attrs-email (cf/get :ldap-attrs-email)
:attrs-fullname (cf/get :ldap-attrs-fullname)
:attrs-username (cf/get :ldap-attrs-username)
:base-dn (cf/get :ldap-base-dn)
:bind-dn (cf/get :ldap-bind-dn)
:bind-password (cf/get :ldap-bind-password)
:enabled? (contains? cf/flags :login-with-ldap)}
:app.auth.oidc/google-provider
{:enabled? (contains? cf/flags :login-with-google)
:client-id (cf/get :google-client-id)
:client-secret (cf/get :google-client-secret)}
:app.auth.oidc/github-provider
{:enabled? (contains? cf/flags :login-with-github)
:http-client (ig/ref :app.http/client)
:client-id (cf/get :github-client-id)
:client-secret (cf/get :github-client-secret)}
:app.auth.oidc/gitlab-provider
{:enabled? (contains? cf/flags :login-with-gitlab)
:base-uri (cf/get :gitlab-base-uri "https://gitlab.com")
:client-id (cf/get :gitlab-client-id)
:client-secret (cf/get :gitlab-client-secret)}
:app.auth.oidc/generic-provider
{:enabled? (contains? cf/flags :login-with-oidc)
:http-client (ig/ref :app.http/client)
:client-id (cf/get :oidc-client-id)
:client-secret (cf/get :oidc-client-secret)
:base-uri (cf/get :oidc-base-uri)
:token-uri (cf/get :oidc-token-uri)
:auth-uri (cf/get :oidc-auth-uri)
:user-uri (cf/get :oidc-user-uri)
:scopes (cf/get :oidc-scopes)
:roles-attr (cf/get :oidc-roles-attr)
:roles (cf/get :oidc-roles)}
:app.auth.oidc/routes
{:providers {:google (ig/ref :app.auth.oidc/google-provider)
:github (ig/ref :app.auth.oidc/github-provider)
:gitlab (ig/ref :app.auth.oidc/gitlab-provider)
:oidc (ig/ref :app.auth.oidc/generic-provider)}
:tokens (ig/ref :app.tokens/tokens)
:http-client (ig/ref :app.http/client)
:pool (ig/ref :app.db/pool)
:session (ig/ref :app.http/session)
:public-uri (cf/get :public-uri)
:executor (ig/ref [::default :app.worker/executor])}
:app.http/router
{:rpc (ig/ref :app.rpc/rpc)
:session (ig/ref :app.http.session/session)
:tokens (ig/ref :app.tokens/tokens)
:public-uri (cf/get :public-uri)
:metrics (ig/ref :app.metrics/metrics)
:oauth (ig/ref :app.http.oauth/handler)
:assets (ig/ref :app.http.assets/handlers)
:storage (ig/ref :app.storage/storage)
:sns-webhook (ig/ref :app.http.awsns/handler)
:feedback (ig/ref :app.http.feedback/handler)
:error-report-handler (ig/ref :app.loggers.mattermost/handler)}
{:assets (ig/ref :app.http.assets/handlers)
:feedback (ig/ref :app.http.feedback/handler)
:session (ig/ref :app.http/session)
:awsns-handler (ig/ref :app.http.awsns/handler)
:debug-routes (ig/ref :app.http.debug/routes)
:oidc-routes (ig/ref :app.auth.oidc/routes)
:ws (ig/ref :app.http.websocket/handler)
:metrics (ig/ref :app.metrics/metrics)
:public-uri (cf/get :public-uri)
:storage (ig/ref :app.storage/storage)
:tokens (ig/ref :app.tokens/tokens)
:audit-handler (ig/ref :app.loggers.audit/http-handler)
:rpc-routes (ig/ref :app.rpc/routes)
:doc-routes (ig/ref :app.rpc.doc/routes)
:executor (ig/ref [::default :app.worker/executor])}
:app.http.debug/routes
{:pool (ig/ref :app.db/pool)
:executor (ig/ref [::worker :app.worker/executor])
:storage (ig/ref :app.storage/storage)
:session (ig/ref :app.http/session)}
:app.http.websocket/handler
{:pool (ig/ref :app.db/pool)
:metrics (ig/ref :app.metrics/metrics)
:msgbus (ig/ref :app.msgbus/msgbus)}
:app.http.assets/handlers
{:metrics (ig/ref :app.metrics/metrics)
:assets-path (cf/get :assets-path)
:storage (ig/ref :app.storage/storage)
:executor (ig/ref [::default :app.worker/executor])
:cache-max-age (dt/duration {:hours 24})
:signature-max-age (dt/duration {:hours 24 :minutes 5})}
:app.http.feedback/handler
{:pool (ig/ref :app.db/pool)}
{:pool (ig/ref :app.db/pool)
:executor (ig/ref [::default :app.worker/executor])}
:app.http.oauth/handler
{:rpc (ig/ref :app.rpc/rpc)
:session (ig/ref :app.http.session/session)
:pool (ig/ref :app.db/pool)
:tokens (ig/ref :app.tokens/tokens)
:audit (ig/ref :app.loggers.audit/collector)
:public-uri (cf/get :public-uri)}
:app.rpc/methods
{:pool (ig/ref :app.db/pool)
:session (ig/ref :app.http/session)
:tokens (ig/ref :app.tokens/tokens)
:metrics (ig/ref :app.metrics/metrics)
:storage (ig/ref :app.storage/storage)
:msgbus (ig/ref :app.msgbus/msgbus)
:public-uri (cf/get :public-uri)
:audit (ig/ref :app.loggers.audit/collector)
:ldap (ig/ref :app.auth.ldap/provider)
:http-client (ig/ref :app.http/client)
:executors (ig/ref :app.worker/executors)}
;; RLimit definition for password hashing
:app.rlimits/password
(cf/get :rlimits-password)
:app.rpc.doc/routes
{:methods (ig/ref :app.rpc/methods)}
;; RLimit definition for image processing
:app.rlimits/image
(cf/get :rlimits-image)
;; RLimit definition for font processing
:app.rlimits/font
(cf/get :rlimits-font 2)
;; A collection of rlimits as hash-map.
:app.rlimits/all
{:password (ig/ref :app.rlimits/password)
:image (ig/ref :app.rlimits/image)
:font (ig/ref :app.rlimits/font)}
:app.rpc/rpc
{:pool (ig/ref :app.db/pool)
:session (ig/ref :app.http.session/session)
:tokens (ig/ref :app.tokens/tokens)
:metrics (ig/ref :app.metrics/metrics)
:storage (ig/ref :app.storage/storage)
:msgbus (ig/ref :app.msgbus/msgbus)
:rlimits (ig/ref :app.rlimits/all)
:public-uri (cf/get :public-uri)
:audit (ig/ref :app.loggers.audit/collector)}
:app.notifications/handler
{:msgbus (ig/ref :app.msgbus/msgbus)
:pool (ig/ref :app.db/pool)
:session (ig/ref :app.http.session/session)
:metrics (ig/ref :app.metrics/metrics)
:executor (ig/ref :app.worker/executor)}
:app.worker/executor
{:min-threads 0
:max-threads 256
:idle-timeout 60000
:name :worker}
:app.worker/worker
{:executor (ig/ref :app.worker/executor)
:tasks (ig/ref :app.worker/registry)
:metrics (ig/ref :app.metrics/metrics)
:pool (ig/ref :app.db/pool)}
:app.worker/scheduler
{:executor (ig/ref :app.worker/executor)
:tasks (ig/ref :app.worker/registry)
:pool (ig/ref :app.db/pool)
:schedule
[{:cron #app/cron "0 0 0 * * ?" ;; daily
:task :file-media-gc}
{:cron #app/cron "0 0 * * * ?" ;; hourly
:task :file-xlog-gc}
{:cron #app/cron "0 0 0 * * ?" ;; daily
:task :storage-deleted-gc}
{:cron #app/cron "0 0 0 * * ?" ;; daily
:task :storage-touched-gc}
{:cron #app/cron "0 0 0 * * ?" ;; daily
:task :session-gc}
{:cron #app/cron "0 0 * * * ?" ;; hourly
:task :storage-recheck}
{:cron #app/cron "0 0 0 * * ?" ;; daily
:task :objects-gc}
{:cron #app/cron "0 0 0 * * ?" ;; daily
:task :tasks-gc}
(when (cf/get :fdata-storage-backed)
{:cron #app/cron "0 0 * * * ?" ;; hourly
:task :file-offload})
(when (cf/get :audit-archive-enabled)
{:cron #app/cron "0 0 * * * ?" ;; every 1h
:task :audit-archive})
(when (cf/get :audit-archive-gc-enabled)
{:cron #app/cron "0 0 * * * ?" ;; every 1h
:task :audit-archive-gc})
(when (cf/get :telemetry-enabled)
{:cron #app/cron "0 0 */6 * * ?" ;; every 6h
:task :telemetry})]}
:app.rpc/routes
{:methods (ig/ref :app.rpc/methods)}
:app.worker/registry
{:metrics (ig/ref :app.metrics/metrics)
:tasks
{:sendmail (ig/ref :app.emails/sendmail-handler)
:objects-gc (ig/ref :app.tasks.objects-gc/handler)
:delete-object (ig/ref :app.tasks.delete-object/handler)
:delete-profile (ig/ref :app.tasks.delete-profile/handler)
:file-media-gc (ig/ref :app.tasks.file-media-gc/handler)
:file-gc (ig/ref :app.tasks.file-gc/handler)
:file-xlog-gc (ig/ref :app.tasks.file-xlog-gc/handler)
:storage-deleted-gc (ig/ref :app.storage/gc-deleted-task)
:storage-touched-gc (ig/ref :app.storage/gc-touched-task)
:storage-recheck (ig/ref :app.storage/recheck-task)
:storage-gc-deleted (ig/ref :app.storage/gc-deleted-task)
:storage-gc-touched (ig/ref :app.storage/gc-touched-task)
:tasks-gc (ig/ref :app.tasks.tasks-gc/handler)
:telemetry (ig/ref :app.tasks.telemetry/handler)
:session-gc (ig/ref :app.http.session/gc-task)
:file-offload (ig/ref :app.tasks.file-offload/handler)
:audit-archive (ig/ref :app.loggers.audit/archive-task)
:audit-archive-gc (ig/ref :app.loggers.audit/archive-gc-task)}}
:audit-log-archive (ig/ref :app.loggers.audit/archive-task)
:audit-log-gc (ig/ref :app.loggers.audit/gc-task)}}
:app.emails/sendmail-handler
{:host (cf/get :smtp-host)
:port (cf/get :smtp-port)
:ssl (cf/get :smtp-ssl)
:tls (cf/get :smtp-tls)
:enabled (cf/get :smtp-enabled)
:username (cf/get :smtp-username)
:password (cf/get :smtp-password)
:metrics (ig/ref :app.metrics/metrics)
@@ -243,37 +264,22 @@
{:pool (ig/ref :app.db/pool)
:max-age cf/deletion-delay}
:app.tasks.delete-object/handler
:app.tasks.objects-gc/handler
{:pool (ig/ref :app.db/pool)
:storage (ig/ref :app.storage/storage)}
:app.tasks.objects-gc/handler
{:pool (ig/ref :app.db/pool)
:storage (ig/ref :app.storage/storage)
:max-age cf/deletion-delay}
:app.tasks.delete-profile/handler
{:pool (ig/ref :app.db/pool)}
:app.tasks.file-media-gc/handler
{:pool (ig/ref :app.db/pool)
:max-age cf/deletion-delay}
:app.tasks.file-gc/handler
{:pool (ig/ref :app.db/pool)}
:app.tasks.file-xlog-gc/handler
{:pool (ig/ref :app.db/pool)
:max-age (dt/duration {:hours 72})}
:app.tasks.file-offload/handler
{:pool (ig/ref :app.db/pool)
:max-age (dt/duration {:seconds 5})
:storage (ig/ref :app.storage/storage)
:backend (cf/get :fdata-storage-backed :fdata-s3)}
{:pool (ig/ref :app.db/pool)}
:app.tasks.telemetry/handler
{:pool (ig/ref :app.db/pool)
:version (:full cf/version)
:uri (cf/get :telemetry-uri)
:sprops (ig/ref :app.setup/props)}
:sprops (ig/ref :app.setup/props)
:http-client (ig/ref :app.http/client)}
:app.srepl/server
{:port (cf/get :srepl-port)
@@ -289,77 +295,117 @@
:app.loggers.zmq/receiver
{:endpoint (cf/get :loggers-zmq-uri)}
:app.loggers.audit/http-handler
{:pool (ig/ref :app.db/pool)
:executor (ig/ref [::default :app.worker/executor])}
:app.loggers.audit/collector
{:enabled (cf/get :audit-enabled false)
:pool (ig/ref :app.db/pool)
:executor (ig/ref :app.worker/executor)}
{:pool (ig/ref :app.db/pool)
:executor (ig/ref [::worker :app.worker/executor])}
:app.loggers.audit/archive-task
{:uri (cf/get :audit-archive-uri)
:enabled (cf/get :audit-archive-enabled false)
:tokens (ig/ref :app.tokens/tokens)
:pool (ig/ref :app.db/pool)}
{:uri (cf/get :audit-log-archive-uri)
:tokens (ig/ref :app.tokens/tokens)
:pool (ig/ref :app.db/pool)
:http-client (ig/ref :app.http/client)}
:app.loggers.audit/archive-gc-task
{:enabled (cf/get :audit-archive-gc-enabled false)
:max-age (cf/get :audit-archive-gc-max-age cf/deletion-delay)
:app.loggers.audit/gc-task
{:max-age (cf/get :audit-log-gc-max-age cf/deletion-delay)
:pool (ig/ref :app.db/pool)}
:app.loggers.loki/reporter
{:uri (cf/get :loggers-loki-uri)
:receiver (ig/ref :app.loggers.zmq/receiver)
:executor (ig/ref :app.worker/executor)}
{:uri (cf/get :loggers-loki-uri)
:receiver (ig/ref :app.loggers.zmq/receiver)
:http-client (ig/ref :app.http/client)}
:app.loggers.mattermost/reporter
{:uri (cf/get :error-report-webhook)
:receiver (ig/ref :app.loggers.zmq/receiver)
:pool (ig/ref :app.db/pool)
:executor (ig/ref :app.worker/executor)}
{:uri (cf/get :error-report-webhook)
:receiver (ig/ref :app.loggers.zmq/receiver)
:http-client (ig/ref :app.http/client)}
:app.loggers.mattermost/handler
{:pool (ig/ref :app.db/pool)}
:app.loggers.database/reporter
{:receiver (ig/ref :app.loggers.zmq/receiver)
:pool (ig/ref :app.db/pool)
:executor (ig/ref [::worker :app.worker/executor])}
:app.storage/storage
{:pool (ig/ref :app.db/pool)
:executor (ig/ref :app.worker/executor)
:executor (ig/ref [::default :app.worker/executor])
:backends {
:assets-s3 (ig/ref [::assets :app.storage.s3/backend])
:assets-db (ig/ref [::assets :app.storage.db/backend])
:assets-fs (ig/ref [::assets :app.storage.fs/backend])
:tmp (ig/ref [::tmp :app.storage.fs/backend])
:fdata-s3 (ig/ref [::fdata :app.storage.s3/backend])
:backends
{:assets-s3 (ig/ref [::assets :app.storage.s3/backend])
:assets-fs (ig/ref [::assets :app.storage.fs/backend])
;; keep this for backward compatibility
:s3 (ig/ref [::assets :app.storage.s3/backend])
:fs (ig/ref [::assets :app.storage.fs/backend])}}
[::fdata :app.storage.s3/backend]
{:region (cf/get :storage-fdata-s3-region)
:bucket (cf/get :storage-fdata-s3-bucket)
:prefix (cf/get :storage-fdata-s3-prefix)}
;; keep this for backward compatibility
:s3 (ig/ref [::assets :app.storage.s3/backend])
:fs (ig/ref [::assets :app.storage.fs/backend])}}
[::assets :app.storage.s3/backend]
{:region (cf/get :storage-assets-s3-region)
:bucket (cf/get :storage-assets-s3-bucket)}
{:region (cf/get :storage-assets-s3-region)
:endpoint (cf/get :storage-assets-s3-endpoint)
:bucket (cf/get :storage-assets-s3-bucket)
:executor (ig/ref [::default :app.worker/executor])}
[::assets :app.storage.fs/backend]
{:directory (cf/get :storage-assets-fs-directory)}
})
[::tmp :app.storage.fs/backend]
{:directory "/tmp/penpot"}
[::assets :app.storage.db/backend]
{:pool (ig/ref :app.db/pool)}})
(def worker-config
{ :app.worker/cron
{:executor (ig/ref [::worker :app.worker/executor])
:scheduler (ig/ref :app.worker/scheduler)
:tasks (ig/ref :app.worker/registry)
:pool (ig/ref :app.db/pool)
:entries
[{:cron #app/cron "0 0 0 * * ?" ;; daily
:task :file-gc}
{:cron #app/cron "0 0 * * * ?" ;; hourly
:task :file-xlog-gc}
{:cron #app/cron "0 0 0 * * ?" ;; daily
:task :storage-gc-deleted}
{:cron #app/cron "0 0 0 * * ?" ;; daily
:task :storage-gc-touched}
{:cron #app/cron "0 0 0 * * ?" ;; daily
:task :session-gc}
{:cron #app/cron "0 0 0 * * ?" ;; daily
:task :objects-gc}
{:cron #app/cron "0 0 0 * * ?" ;; daily
:task :tasks-gc}
{:cron #app/cron "0 30 */3,23 * * ?"
:task :telemetry}
(when (contains? cf/flags :audit-log-archive)
{:cron #app/cron "0 */5 * * * ?" ;; every 5m
:task :audit-log-archive})
(when (contains? cf/flags :audit-log-gc)
{:cron #app/cron "0 0 0 * * ?" ;; daily
:task :audit-log-gc})]}
:app.worker/worker
{:executor (ig/ref [::worker :app.worker/executor])
:tasks (ig/ref :app.worker/registry)
:metrics (ig/ref :app.metrics/metrics)
:pool (ig/ref :app.db/pool)}})
(def system nil)
(defn start
[]
(ig/load-namespaces system-config)
(ig/load-namespaces (merge system-config worker-config))
(alter-var-root #'system (fn [sys]
(when sys (ig/halt! sys))
(-> system-config
(cond-> (contains? cf/flags :backend-worker)
(merge worker-config))
(ig/prep)
(ig/init))))
(l/info :msg "welcome to penpot"

View File

@@ -12,45 +12,44 @@
[app.common.media :as cm]
[app.common.spec :as us]
[app.config :as cf]
[app.rlimits :as rlm]
[app.rpc.queries.svg :as svg]
[app.storage.tmp :as tmp]
[app.util.bytes :as bs]
[app.util.svg :as svg]
[buddy.core.bytes :as bb]
[buddy.core.codecs :as bc]
[clojure.java.io :as io]
[clojure.java.shell :as sh]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[datoteka.core :as fs])
(:import
java.io.ByteArrayInputStream
java.io.OutputStream
org.apache.commons.io.IOUtils
org.im4java.core.ConvertCmd
org.im4java.core.IMOperation
org.im4java.core.Info))
(s/def ::image-content-type cm/valid-image-types)
(s/def ::font-content-type cm/valid-font-types)
(s/def :internal.http.upload/filename ::us/string)
(s/def :internal.http.upload/size ::us/integer)
(s/def :internal.http.upload/content-type ::us/string)
(s/def :internal.http.upload/tempfile any?)
(s/def ::path fs/path?)
(s/def ::filename string?)
(s/def ::size integer?)
(s/def ::headers (s/map-of string? string?))
(s/def ::mtype string?)
(s/def ::upload
(s/keys :req-un [:internal.http.upload/filename
:internal.http.upload/size
:internal.http.upload/tempfile
:internal.http.upload/content-type]))
(s/keys :req-un [::filename ::size ::path]
:opt-un [::mtype ::headers]))
(defn validate-media-type
([mtype] (validate-media-type mtype cm/valid-image-types))
([mtype allowed]
(when-not (contains? allowed mtype)
;; A subset of fields from the ::upload spec
(s/def ::input
(s/keys :req-un [::path]
:opt-un [::mtype]))
(defn validate-media-type!
([upload] (validate-media-type! upload cm/valid-image-types))
([upload allowed]
(when-not (contains? allowed (:mtype upload))
(ex/raise :type :validation
:code :media-type-not-allowed
:hint "Seems like you are uploading an invalid media object"))))
:hint "Seems like you are uploading an invalid media object"))
upload))
(defmulti process :cmd)
(defmulti process-error class)
@@ -66,39 +65,23 @@
(throw error))
(defn run
[{:keys [rlimits] :as cfg} {:keys [rlimit] :or {rlimit :image} :as params}]
(us/assert map? rlimits)
(let [rlimit (get rlimits rlimit)]
(when-not rlimit
(ex/raise :type :internal
:code :rlimit-not-configured
:hint ":image rlimit not configured"))
(try
(rlm/execute rlimit (process params))
(catch Throwable e
(process-error e)))))
[params]
(try
(process params)
(catch Throwable e
(process-error e))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Thumbnails Generation
;; IMAGE THUMBNAILS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(s/def ::cmd keyword?)
(s/def ::path (s/or :path fs/path?
:string string?
:file fs/file?))
(s/def ::input
(s/keys :req-un [::path]
:opt-un [::cm/mtype]))
(s/def ::width integer?)
(s/def ::height integer?)
(s/def ::format #{:jpeg :webp :png})
(s/def ::quality #(< 0 % 101))
(s/def ::thumbnail-params
(s/keys :req-un [::cmd ::input ::format ::width ::height]))
(s/keys :req-un [::input ::format ::width ::height]))
;; Related info on how thumbnails generation
;; http://www.imagemagick.org/Usage/thumbnails/
@@ -108,18 +91,16 @@
(let [{:keys [path mtype]} input
format (or (cm/mtype->format mtype) format)
ext (cm/format->extension format)
tmp (fs/create-tempfile :suffix ext)]
tmp (tmp/tempfile :prefix "penpot.media." :suffix ext)]
(doto (ConvertCmd.)
(.run operation (into-array (map str [path tmp]))))
(let [thumbnail-data (fs/slurp-bytes tmp)]
(fs/delete tmp)
(assoc params
:format format
:mtype (cm/format->mtype format)
:size (alength ^bytes thumbnail-data)
:data (ByteArrayInputStream. thumbnail-data)))))
(assoc params
:format format
:mtype (cm/format->mtype format)
:size (fs/size tmp)
:data tmp)))
(defmethod process :generic-thumbnail
[{:keys [quality width height] :as params}]
@@ -180,12 +161,12 @@
(us/assert ::input input)
(let [{:keys [path mtype]} input]
(if (= mtype "image/svg+xml")
(let [info (some-> path slurp svg/parse get-basic-info-from-svg)]
(let [info (some-> path slurp svg/pre-process svg/parse get-basic-info-from-svg)]
(when-not info
(ex/raise :type :validation
:code :invalid-svg-file
:hint "uploaded svg does not provides dimensions"))
(assoc info :mtype mtype))
(merge input info))
(let [instance (Info. (str path))
mtype' (.getProperty instance "Mime type")]
@@ -198,9 +179,9 @@
;; For an animated GIF, getImageWidth/Height returns the delta size of one frame (if no frame given
;; it returns size of the last one), whereas getPageWidth/Height always return the full size of
;; any frame.
{:width (.getPageWidth instance)
:height (.getPageHeight instance)
:mtype mtype}))))
(assoc input
:width (.getPageWidth instance)
:height (.getPageHeight instance))))))
(defmethod process-error org.im4java.core.InfoException
[error]
@@ -210,67 +191,60 @@
:cause error))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Fonts Generation
;; FONTS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def all-fotmats #{"font/woff2", "font/woff", "font/otf", "font/ttf"})
(defmethod process :generate-fonts
[{:keys [input] :as params}]
(letfn [(ttf->otf [data]
(let [input-file (fs/create-tempfile :prefix "penpot")
output-file (fs/path (str input-file ".otf"))
_ (with-open [out (io/output-stream input-file)]
(IOUtils/writeChunked ^bytes data ^OutputStream out)
(.flush ^OutputStream out))
res (sh/sh "fontforge" "-lang=ff" "-c"
(str/fmt "Open('%s'); Generate('%s')"
(str input-file)
(str output-file)))]
(let [finput (tmp/tempfile :prefix "penpot.font." :suffix "")
foutput (fs/path (str finput ".otf"))
_ (bs/write-to-file! data finput)
res (sh/sh "fontforge" "-lang=ff" "-c"
(str/fmt "Open('%s'); Generate('%s')"
(str finput)
(str foutput)))]
(when (zero? (:exit res))
(fs/slurp-bytes output-file))))
foutput)))
(otf->ttf [data]
(let [input-file (fs/create-tempfile :prefix "penpot")
output-file (fs/path (str input-file ".ttf"))
_ (with-open [out (io/output-stream input-file)]
(IOUtils/writeChunked ^bytes data ^OutputStream out)
(.flush ^OutputStream out))
res (sh/sh "fontforge" "-lang=ff" "-c"
(str/fmt "Open('%s'); Generate('%s')"
(str input-file)
(str output-file)))]
(let [finput (tmp/tempfile :prefix "penpot.font." :suffix "")
foutput (fs/path (str finput ".ttf"))
_ (bs/write-to-file! data finput)
res (sh/sh "fontforge" "-lang=ff" "-c"
(str/fmt "Open('%s'); Generate('%s')"
(str finput)
(str foutput)))]
(when (zero? (:exit res))
(fs/slurp-bytes output-file))))
foutput)))
(ttf-or-otf->woff [data]
(let [input-file (fs/create-tempfile :prefix "penpot" :suffix "")
output-file (fs/path (str input-file ".woff"))
_ (with-open [out (io/output-stream input-file)]
(IOUtils/writeChunked ^bytes data ^OutputStream out)
(.flush ^OutputStream out))
res (sh/sh "sfnt2woff" (str input-file))]
;; NOTE: foutput is not used directly, it represents the
;; default output of the exection of the underlying
;; command.
(let [finput (tmp/tempfile :prefix "penpot.font." :suffix "")
foutput (fs/path (str finput ".woff"))
_ (bs/write-to-file! data finput)
res (sh/sh "sfnt2woff" (str finput))]
(when (zero? (:exit res))
(fs/slurp-bytes output-file))))
foutput)))
(ttf-or-otf->woff2 [data]
(let [input-file (fs/create-tempfile :prefix "penpot" :suffix "")
output-file (fs/path (str input-file ".woff2"))
_ (with-open [out (io/output-stream input-file)]
(IOUtils/writeChunked ^bytes data ^OutputStream out)
(.flush ^OutputStream out))
res (sh/sh "woff2_compress" (str input-file))]
;; NOTE: foutput is not used directly, it represents the
;; default output of the exection of the underlying
;; command.
(let [finput (tmp/tempfile :prefix "penpot.font." :suffix ".tmp")
foutput (fs/path (str (fs/base finput) ".woff2"))
_ (bs/write-to-file! data finput)
res (sh/sh "woff2_compress" (str finput))]
(when (zero? (:exit res))
(fs/slurp-bytes output-file))))
foutput)))
(woff->sfnt [data]
(let [input-file (fs/create-tempfile :prefix "penpot" :suffix "")
_ (with-open [out (io/output-stream input-file)]
(IOUtils/writeChunked ^bytes data ^OutputStream out)
(.flush ^OutputStream out))
res (sh/sh "woff2sfnt" (str input-file)
:out-enc :bytes)]
(let [finput (tmp/tempfile :prefix "penpot" :suffix "")
_ (bs/write-to-file! data finput)
res (sh/sh "woff2sfnt" (str finput)
:out-enc :bytes)]
(when (zero? (:exit res))
(:out res))))
@@ -334,10 +308,11 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn configure-assets-storage
"Given storage map, returns a storage configured with the apropriate
backend for assets."
[storage conn]
(-> storage
(assoc :conn conn)
(assoc :backend (cf/get :assets-storage-backend :assets-fs))))
"Given storage map, returns a storage configured with the appropriate
backend for assets and optional connection attached."
([storage]
(assoc storage :backend (cf/get :assets-storage-backend :assets-fs)))
([storage conn]
(-> storage
(assoc :conn conn)
(assoc :backend (cf/get :assets-storage-backend :assets-fs)))))

View File

@@ -5,48 +5,136 @@
;; Copyright (c) UXBOX Labs SL
(ns app.metrics
(:refer-clojure :exclude [run!])
(:require
[app.common.exceptions :as ex]
[app.util.logging :as l]
[app.common.logging :as l]
[clojure.spec.alpha :as s]
[integrant.core :as ig])
(:import
io.prometheus.client.CollectorRegistry
io.prometheus.client.Counter
io.prometheus.client.Counter$Child
io.prometheus.client.Gauge
io.prometheus.client.Gauge$Child
io.prometheus.client.Summary
io.prometheus.client.Summary$Child
io.prometheus.client.Summary$Builder
io.prometheus.client.Histogram
io.prometheus.client.Histogram$Child
io.prometheus.client.exporter.common.TextFormat
io.prometheus.client.hotspot.DefaultExports
io.prometheus.client.jetty.JettyStatisticsCollector
org.eclipse.jetty.server.handler.StatisticsHandler
java.io.StringWriter))
(declare instrument-vars!)
(declare instrument)
(set! *warn-on-reflection* true)
(declare create-registry)
(declare create)
(declare handler)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Entry Point
;; METRICS SERVICE PROVIDER
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- handler
[registry _request]
(let [samples (.metricFamilySamples ^CollectorRegistry registry)
writer (StringWriter.)]
(TextFormat/write004 writer samples)
{:headers {"content-type" TextFormat/CONTENT_TYPE_004}
:body (.toString writer)}))
(def default-metrics
{:update-file-changes
{:name "rpc_update_file_changes_total"
:help "A total number of changes submitted to update-file."
:type :counter}
(s/def ::definitions
(s/map-of keyword? map?))
:update-file-bytes-processed
{:name "rpc_update_file_bytes_processed_total"
:help "A total number of bytes processed by update-file."
:type :counter}
(defmethod ig/pre-init-spec ::metrics [_]
(s/keys :opt-un [::definitions]))
:rpc-mutation-timing
{:name "rpc_mutation_timing"
:help "RPC mutation method call timming."
:labels ["name"]
:type :histogram}
:rpc-command-timing
{:name "rpc_command_timing"
:help "RPC command method call timming."
:labels ["name"]
:type :histogram}
:rpc-query-timing
{:name "rpc_query_timing"
:help "RPC query method call timing."
:labels ["name"]
:type :histogram}
:websocket-active-connections
{:name "websocket_active_connections"
:help "Active websocket connections gauge"
:type :gauge}
:websocket-messages-total
{:name "websocket_message_total"
:help "Counter of processed messages."
:labels ["op"]
:type :counter}
:websocket-session-timing
{:name "websocket_session_timing"
:help "Websocket session timing (seconds)."
:type :summary}
:session-update-total
{:name "http_session_update_total"
:help "A counter of session update batch events."
:type :counter}
:tasks-timing
{:name "penpot_tasks_timing"
:help "Background tasks timing (milliseconds)."
:labels ["name"]
:type :summary}
:rlimit-queued-submissions
{:name "penpot_rlimit_queued_submissions"
:help "Current number of queued submissions on RLIMIT."
:labels ["name"]
:type :gauge}
:rlimit-used-permits
{:name "penpot_rlimit_used_permits"
:help "Current number of used permits on RLIMIT."
:labels ["name"]
:type :gauge}
:rlimit-acquires-total
{:name "penpot_rlimit_acquires_total"
:help "Total number of acquire operations on RLIMIT."
:labels ["name"]
:type :counter}
:executors-active-threads
{:name "penpot_executors_active_threads"
:help "Current number of threads available in the executor service."
:labels ["name"]
:type :gauge}
:executors-completed-tasks
{:name "penpot_executors_completed_tasks_total"
:help "Aproximate number of completed tasks by the executor."
:labels ["name"]
:type :counter}
:executors-running-threads
{:name "penpot_executors_running_threads"
:help "Current number of threads with state RUNNING."
:labels ["name"]
:type :gauge}
:executors-queued-submissions
{:name "penpot_executors_queued_submissions"
:help "Current number of queued submissions."
:labels ["name"]
:type :gauge}})
(defmethod ig/init-key ::metrics
[_ {:keys [definitions] :as cfg}]
[_ _]
(l/info :action "initialize metrics")
(let [registry (create-registry)
definitions (reduce-kv (fn [res k v]
@@ -54,7 +142,7 @@
(create)
(assoc res k)))
{}
definitions)]
default-metrics)]
{:handler (partial handler registry)
:definitions definitions
:registry registry}))
@@ -64,24 +152,45 @@
(s/def ::metrics
(s/keys :req-un [::registry ::handler]))
(defn- handler
[registry _ respond _]
(let [samples (.metricFamilySamples ^CollectorRegistry registry)
writer (StringWriter.)]
(TextFormat/write004 writer samples)
(respond {:headers {"content-type" TextFormat/CONTENT_TYPE_004}
:body (.toString writer)})))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Implementation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def default-empty-labels (into-array String []))
(def default-quantiles
[[0.5 0.01]
[0.90 0.01]
[0.99 0.001]])
(def default-histogram-buckets
[1 5 10 25 50 75 100 250 500 750 1000 2500 5000 7500])
(defn run!
[{:keys [definitions]} {:keys [id] :as params}]
(when-let [mobj (get definitions id)]
((::fn mobj) params)
true))
(defn create-registry
[]
(let [registry (CollectorRegistry.)]
(DefaultExports/register registry)
registry))
(defmacro with-measure
[& {:keys [expr cb]}]
`(let [start# (System/nanoTime)
tdown# ~cb]
(try
~expr
(finally
(tdown# (/ (- (System/nanoTime) start#) 1000000))))))
(defn- is-array?
[o]
(let [oc (class o)]
(and (.isArray ^Class oc)
(= (.getComponentType oc) String))))
(defn make-counter
[{:keys [name help registry reg labels] :as props}]
@@ -92,18 +201,11 @@
_ (when (seq labels)
(.labelNames instance (into-array String labels)))
instance (.register instance registry)]
(reify
clojure.lang.IDeref
(deref [_] instance)
clojure.lang.IFn
(invoke [_ cmd]
(.inc ^Counter instance))
(invoke [_ cmd labels]
(.. ^Counter instance
(labels (into-array String labels))
(inc))))))
{::instance instance
::fn (fn [{:keys [inc labels] :or {inc 1 labels default-empty-labels}}]
(let [instance (.labels instance (if (is-array? labels) labels (into-array String labels)))]
(.inc ^Counter$Child instance (double inc))))}))
(defn make-gauge
[{:keys [name help registry reg labels] :as props}]
@@ -114,57 +216,33 @@
_ (when (seq labels)
(.labelNames instance (into-array String labels)))
instance (.register instance registry)]
(reify
clojure.lang.IDeref
(deref [_] instance)
clojure.lang.IFn
(invoke [_ cmd]
(case cmd
:inc (.inc ^Gauge instance)
:dec (.dec ^Gauge instance)))
(invoke [_ cmd labels]
(let [labels (into-array String [labels])]
(case cmd
:inc (.. ^Gauge instance (labels labels) (inc))
:dec (.. ^Gauge instance (labels labels) (dec))))))))
(def default-quantiles
[[0.75 0.02]
[0.99 0.001]])
{::instance instance
::fn (fn [{:keys [inc dec labels val] :or {labels default-empty-labels}}]
(let [instance (.labels ^Gauge instance (if (is-array? labels) labels (into-array String labels)))]
(cond (number? inc) (.inc ^Gauge$Child instance (double inc))
(number? dec) (.dec ^Gauge$Child instance (double dec))
(number? val) (.set ^Gauge$Child instance (double val)))))}))
(defn make-summary
[{:keys [name help registry reg labels max-age quantiles buckets]
:or {max-age 3600 buckets 6 quantiles default-quantiles} :as props}]
:or {max-age 3600 buckets 12 quantiles default-quantiles} :as props}]
(let [registry (or registry reg)
instance (doto (Summary/build)
builder (doto (Summary/build)
(.name name)
(.help help))
_ (when (seq quantiles)
(.maxAgeSeconds ^Summary instance max-age)
(.ageBuckets ^Summary instance buckets))
(.maxAgeSeconds ^Summary$Builder builder ^long max-age)
(.ageBuckets ^Summary$Builder builder buckets))
_ (doseq [[q e] quantiles]
(.quantile ^Summary instance q e))
(.quantile ^Summary$Builder builder q e))
_ (when (seq labels)
(.labelNames instance (into-array String labels)))
instance (.register instance registry)]
(reify
clojure.lang.IDeref
(deref [_] instance)
(.labelNames ^Summary$Builder builder (into-array String labels)))
instance (.register ^Summary$Builder builder registry)]
clojure.lang.IFn
(invoke [_ cmd val]
(.observe ^Summary instance val))
(invoke [_ cmd val labels]
(.. ^Summary instance
(labels (into-array String labels))
(observe val))))))
(def default-histogram-buckets
[1 5 10 25 50 75 100 250 500 750 1000 2500 5000 7500])
{::instance instance
::fn (fn [{:keys [val labels] :or {labels default-empty-labels}}]
(let [instance (.labels ^Summary instance (if (is-array? labels) labels (into-array String labels)))]
(.observe ^Summary$Child instance val)))}))
(defn make-histogram
[{:keys [name help registry reg labels buckets]
@@ -177,18 +255,11 @@
_ (when (seq labels)
(.labelNames instance (into-array String labels)))
instance (.register instance registry)]
(reify
clojure.lang.IDeref
(deref [_] instance)
clojure.lang.IFn
(invoke [_ cmd val]
(.observe ^Histogram instance val))
(invoke [_ cmd val labels]
(.. ^Histogram instance
(labels (into-array String labels))
(observe val))))))
{::instance instance
::fn (fn [{:keys [val labels] :or {labels default-empty-labels}}]
(let [instance (.labels ^Histogram instance (if (is-array? labels) labels (into-array String labels)))]
(.observe ^Histogram$Child instance val)))}))
(defn create
[{:keys [type] :as props}]
@@ -197,118 +268,3 @@
:gauge (make-gauge props)
:summary (make-summary props)
:histogram (make-histogram props)))
(defn wrap-counter
([rootf mobj]
(let [mdata (meta rootf)
origf (::original mdata rootf)]
(with-meta
(fn
([a]
(mobj :inc)
(origf a))
([a b]
(mobj :inc)
(origf a b))
([a b c]
(mobj :inc)
(origf a b c))
([a b c d]
(mobj :inc)
(origf a b c d))
([a b c d & more]
(mobj :inc)
(apply origf a b c d more)))
(assoc mdata ::original origf))))
([rootf mobj labels]
(let [mdata (meta rootf)
origf (::original mdata rootf)]
(with-meta
(fn
([a]
(mobj :inc labels)
(origf a))
([a b]
(mobj :inc labels)
(origf a b))
([a b & more]
(mobj :inc labels)
(apply origf a b more)))
(assoc mdata ::original origf)))))
(defn wrap-summary
([rootf mobj]
(let [mdata (meta rootf)
origf (::original mdata rootf)]
(with-meta
(fn
([a]
(with-measure
:expr (origf a)
:cb #(mobj :observe %)))
([a b]
(with-measure
:expr (origf a b)
:cb #(mobj :observe %)))
([a b & more]
(with-measure
:expr (apply origf a b more)
:cb #(mobj :observe %))))
(assoc mdata ::original origf))))
([rootf mobj labels]
(let [mdata (meta rootf)
origf (::original mdata rootf)]
(with-meta
(fn
([a]
(with-measure
:expr (origf a)
:cb #(mobj :observe % labels)))
([a b]
(with-measure
:expr (origf a b)
:cb #(mobj :observe % labels)))
([a b & more]
(with-measure
:expr (apply origf a b more)
:cb #(mobj :observe % labels))))
(assoc mdata ::original origf)))))
(defn instrument-vars!
[vars {:keys [wrap] :as props}]
(let [obj (create props)]
(cond
(instance? Counter @obj)
(doseq [var vars]
(alter-var-root var (or wrap wrap-counter) obj))
(instance? Summary @obj)
(doseq [var vars]
(alter-var-root var (or wrap wrap-summary) obj))
:else
(ex/raise :type :not-implemented))))
(defn instrument
[f {:keys [wrap] :as props}]
(let [obj (create props)]
(cond
(instance? Counter @obj)
((or wrap wrap-counter) f obj)
(instance? Summary @obj)
((or wrap wrap-summary) f obj)
(instance? Histogram @obj)
((or wrap wrap-summary) f obj)
:else
(ex/raise :type :not-implemented))))
(defn instrument-jetty!
[^CollectorRegistry registry ^StatisticsHandler handler]
(doto (JettyStatisticsCollector. handler)
(.register registry))
nil)

View File

@@ -6,7 +6,7 @@
(ns app.migrations
(:require
[app.migrations.migration-0023 :as mg0023]
[app.migrations.clj.migration-0023 :as mg0023]
[app.util.migrations :as mg]
[integrant.core :as ig]))
@@ -193,6 +193,57 @@
{:name "0061-mod-file-table"
:fn (mg/resource "app/migrations/sql/0061-mod-file-table.sql")}
{:name "0062-fix-metadata-media"
:fn (mg/resource "app/migrations/sql/0062-fix-metadata-media.sql")}
{:name "0063-add-share-link-table"
:fn (mg/resource "app/migrations/sql/0063-add-share-link-table.sql")}
{:name "0064-mod-audit-log-table"
:fn (mg/resource "app/migrations/sql/0064-mod-audit-log-table.sql")}
{:name "0065-add-trivial-spelling-fixes"
:fn (mg/resource "app/migrations/sql/0065-add-trivial-spelling-fixes.sql")}
{:name "0066-add-frame-thumbnail-table"
:fn (mg/resource "app/migrations/sql/0066-add-frame-thumbnail-table.sql")}
{:name "0067-add-team-invitation-table"
:fn (mg/resource "app/migrations/sql/0067-add-team-invitation-table.sql")}
{:name "0068-mod-storage-object-table"
:fn (mg/resource "app/migrations/sql/0068-mod-storage-object-table.sql")}
{:name "0069-add-file-thumbnail-table"
:fn (mg/resource "app/migrations/sql/0069-add-file-thumbnail-table.sql")}
{:name "0070-del-frame-thumbnail-table"
:fn (mg/resource "app/migrations/sql/0070-del-frame-thumbnail-table.sql")}
{:name "0071-add-file-object-thumbnail-table"
:fn (mg/resource "app/migrations/sql/0071-add-file-object-thumbnail-table.sql")}
{:name "0072-mod-file-object-thumbnail-table"
:fn (mg/resource "app/migrations/sql/0072-mod-file-object-thumbnail-table.sql")}
{:name "0073-mod-file-media-object-constraints"
:fn (mg/resource "app/migrations/sql/0073-mod-file-media-object-constraints.sql")}
{:name "0074-mod-file-library-rel-constraints"
:fn (mg/resource "app/migrations/sql/0074-mod-file-library-rel-constraints.sql")}
{:name "0075-mod-share-link-table"
:fn (mg/resource "app/migrations/sql/0075-mod-share-link-table.sql")}
{:name "0076-mod-storage-object-table"
:fn (mg/resource "app/migrations/sql/0076-mod-storage-object-table.sql")}
{:name "0077-mod-comment-thread-table"
:fn (mg/resource "app/migrations/sql/0077-mod-comment-thread-table.sql")}
{:name "0078-mod-file-media-object-table-drop-cascade"
:fn (mg/resource "app/migrations/sql/0078-mod-file-media-object-table-drop-cascade.sql")}
])

View File

@@ -4,7 +4,7 @@
;;
;; Copyright (c) UXBOX Labs SL
(ns app.migrations.migration-0023
(ns app.migrations.clj.migration-0023
(:require
[app.db :as db]
[app.util.blob :as blob]))

View File

@@ -22,7 +22,7 @@ CREATE TABLE storage_data (
CREATE INDEX storage_data__id__idx ON storage_data(id);
-- Table used for store inflight upload ids, for later recheck and
-- delete possible staled files that exists on the phisical storage
-- delete possible staled files that exists on the physical storage
-- but does not exists in the 'storage_object' table.
CREATE TABLE storage_pending (

View File

@@ -1,4 +1,4 @@
-- Fix problem with content-type inconherence
-- Fix problem with content-type incoherence
UPDATE storage_object so
SET metadata = jsonb_set(metadata, '{~:content-type}', to_jsonb(fmo.mtype))

View File

@@ -0,0 +1,12 @@
CREATE TABLE share_link (
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE DEFERRABLE,
owner_id uuid NULL REFERENCES profile(id) ON DELETE SET NULL DEFERRABLE,
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
pages uuid[],
flags text[]
);
CREATE INDEX share_link_file_id_idx ON share_link(file_id);
CREATE INDEX share_link_owner_id_idx ON share_link(owner_id);

View File

@@ -0,0 +1,13 @@
ALTER TABLE audit_log
ADD COLUMN tracked_at timestamptz NULL DEFAULT clock_timestamp(),
ADD COLUMN source text NULL,
ADD COLUMN context jsonb NULL;
ALTER TABLE audit_log
ALTER COLUMN source SET STORAGE external,
ALTER COLUMN context SET STORAGE external;
UPDATE audit_log SET source = 'backend', tracked_at=created_at;
-- ALTER TABLE audit_log ALTER COLUMN source SET NOT NULL;
-- ALTER TABLE audit_log ALTER COLUMN tracked_at SET NOT NULL;

View File

@@ -0,0 +1,2 @@
ALTER INDEX file__modified_at__has_media_trimed__idx RENAME TO file__modified_at__has_media_trimmed__idx;
ALTER INDEX media_bject__file_id__idx RENAME TO media_object__file_id__idx;

View File

@@ -0,0 +1,13 @@
CREATE TABLE file_frame_thumbnail (
file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE,
frame_id uuid NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT clock_timestamp(),
data text NULL,
PRIMARY KEY(file_id, frame_id)
);
ALTER TABLE file_frame_thumbnail
ALTER COLUMN data SET STORAGE external;

View File

@@ -0,0 +1,14 @@
CREATE TABLE team_invitation (
team_id uuid NOT NULL REFERENCES team(id) ON DELETE CASCADE,
email_to text NOT NULL,
role text NOT NULL,
valid_until timestamptz NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY(team_id, email_to)
);
ALTER TABLE team_invitation
ALTER COLUMN email_to SET STORAGE external,
ALTER COLUMN role SET STORAGE external;

View File

@@ -0,0 +1,3 @@
CREATE INDEX storage_object__hash_backend_bucket__idx
ON storage_object ((metadata->>'~:hash'), (metadata->>'~:bucket'), backend)
WHERE deleted_at IS NULL;

View File

@@ -0,0 +1,14 @@
CREATE TABLE file_thumbnail (
file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE,
revn bigint NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz NULL,
data text NULL,
props jsonb NULL,
PRIMARY KEY(file_id, revn)
);
ALTER TABLE file_thumbnail
ALTER COLUMN data SET STORAGE external,
ALTER COLUMN props SET STORAGE external;

View File

@@ -0,0 +1 @@
DROP TABLE file_frame_thumbnail;

View File

@@ -0,0 +1,11 @@
CREATE TABLE file_object_thumbnail (
file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE,
object_id uuid NOT NULL,
created_at timestamptz NOT NULL DEFAULT now(),
data text NULL,
PRIMARY KEY(file_id, object_id)
);
ALTER TABLE file_object_thumbnail
ALTER COLUMN data SET STORAGE external;

View File

@@ -0,0 +1,4 @@
TRUNCATE TABLE file_object_thumbnail;
ALTER TABLE file_object_thumbnail
ALTER COLUMN object_id TYPE text;

View File

@@ -0,0 +1,11 @@
ALTER TABLE file_media_object
ALTER CONSTRAINT file_media_object_media_id_fkey DEFERRABLE INITIALLY IMMEDIATE;
ALTER TABLE file_media_object
ALTER CONSTRAINT file_media_object_thumbnail_id_fkey DEFERRABLE INITIALLY IMMEDIATE;
ALTER TABLE file_media_object
RENAME CONSTRAINT media_object_file_id_fkey TO file_media_object_file_id_fkey;
ALTER TABLE file_media_object
ALTER CONSTRAINT file_media_object_file_id_fkey DEFERRABLE INITIALLY IMMEDIATE;

View File

@@ -0,0 +1,5 @@
ALTER TABLE file_library_rel
ALTER CONSTRAINT file_library_rel_file_id_fkey DEFERRABLE INITIALLY IMMEDIATE;
ALTER TABLE file_library_rel
ALTER CONSTRAINT file_library_rel_library_file_id_fkey DEFERRABLE INITIALLY IMMEDIATE;

View File

@@ -0,0 +1,5 @@
ALTER TABLE share_link
ADD COLUMN who_comment text NOT NULL DEFAULT('team'),
ADD COLUMN who_inspect text NOT NULL DEFAULT('team');
--- TODO: remove flags column in 1.15.x

View File

@@ -0,0 +1,10 @@
-- Renames the old, already deprecated backend name with new one on
-- all storage object rows.
UPDATE storage_object
SET backend = 'assets-fs'
WHERE backend = 'fs';
UPDATE storage_object
SET backend = 'assets-s3'
WHERE backend = 's3';

View File

@@ -0,0 +1,3 @@
--- Add frame_id field.
ALTER TABLE comment_thread
ADD COLUMN frame_id uuid NULL DEFAULT '00000000-0000-0000-0000-000000000000';

View File

@@ -0,0 +1,9 @@
ALTER TABLE file_media_object
DROP CONSTRAINT file_media_object_media_id_fkey,
ADD CONSTRAINT file_media_object_media_id_fkey
FOREIGN KEY (media_id) REFERENCES storage_object(id) ON DELETE NO ACTION DEFERRABLE;
ALTER TABLE file_media_object
DROP CONSTRAINT file_media_object_thumbnail_id_fkey,
ADD CONSTRAINT file_media_object_thumbnail_id_fkey
FOREIGN KEY (thumbnail_id) REFERENCES storage_object(id) ON DELETE NO ACTION DEFERRABLE;

View File

@@ -1,5 +1,5 @@
--- This is a second migration but it should be applied when manual
--- migration intervention is alteady executed.
--- migration intervention is already executed.
ALTER TABLE file_media_object ALTER COLUMN media_id SET NOT NULL;
DROP TABLE file_media_thumbnail;

View File

@@ -7,18 +7,20 @@
(ns app.msgbus
"The msgbus abstraction implemented using redis as underlying backend."
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.spec :as us]
[app.common.transit :as t]
[app.config :as cfg]
[app.util.blob :as blob]
[app.util.logging :as l]
[app.util.async :as aa]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.core.async :as a]
[clojure.spec.alpha :as s]
[integrant.core :as ig]
[promesa.core :as p])
(:import
java.time.Duration
io.lettuce.core.RedisClient
io.lettuce.core.RedisURI
io.lettuce.core.api.StatefulConnection
@@ -29,7 +31,12 @@
io.lettuce.core.codec.StringCodec
io.lettuce.core.pubsub.RedisPubSubListener
io.lettuce.core.pubsub.StatefulRedisPubSubConnection
io.lettuce.core.pubsub.api.async.RedisPubSubAsyncCommands))
io.lettuce.core.pubsub.api.sync.RedisPubSubCommands
io.lettuce.core.resource.ClientResources
io.lettuce.core.resource.DefaultClientResources
java.time.Duration))
(set! *warn-on-reflection* true)
(def ^:private prefix (cfg/get :tenant))
@@ -37,266 +44,253 @@
[topic]
(str prefix "." topic))
(def xform-prefix (map prefix-topic))
(def xform-topics (map (fn [m] (update m :topics #(into #{} xform-prefix %)))))
(def xform-topic (map (fn [m] (update m :topic prefix-topic))))
(def ^:private xform-prefix-topic
(map (fn [obj] (update obj :topic prefix-topic))))
(s/def ::redis-uri ::us/string)
(s/def ::buffer-size ::us/integer)
(defmulti init-backend :backend)
(defmulti stop-backend :backend)
(defmulti init-pub-loop :backend)
(defmulti init-sub-loop :backend)
(defmethod ig/pre-init-spec ::msgbus [_]
(s/keys :opt-un [::buffer-size ::redis-uri]))
(declare ^:private redis-connect)
(declare ^:private redis-disconnect)
(declare ^:private start-io-loop)
(declare ^:private subscribe)
(declare ^:private purge)
(declare ^:private redis-pub)
(declare ^:private redis-sub)
(declare ^:private redis-unsub)
(defmethod ig/prep-key ::msgbus
[_ cfg]
(merge {:buffer-size 128} cfg))
(merge {:buffer-size 128
:timeout (dt/duration {:seconds 30})}
(d/without-nils cfg)))
(s/def ::timeout ::dt/duration)
(s/def ::redis-uri ::us/string)
(s/def ::buffer-size ::us/integer)
(defmethod ig/pre-init-spec ::msgbus [_]
(s/keys :req-un [::buffer-size ::redis-uri ::timeout ::wrk/executor]))
(defmethod ig/init-key ::msgbus
[_ {:keys [backend buffer-size] :as cfg}]
(l/debug :action "initialize msgbus"
:backend (name backend))
(let [cfg (init-backend cfg)
[_ {:keys [buffer-size redis-uri] :as cfg}]
(l/info :hint "initialize msgbus"
:buffer-size buffer-size
:redis-uri redis-uri)
(let [cmd-ch (a/chan buffer-size)
rcv-ch (a/chan (a/dropping-buffer buffer-size))
pub-ch (a/chan (a/dropping-buffer buffer-size) xform-prefix-topic)
state (agent {} :error-handler #(l/error :cause % :hint "unexpected error on agent" ::l/async false))
cfg (-> (redis-connect cfg)
(assoc ::cmd-ch cmd-ch)
(assoc ::rcv-ch rcv-ch)
(assoc ::pub-ch pub-ch)
(assoc ::state state))]
;; Channel used for receive publications from the application.
pub-ch (-> (a/dropping-buffer buffer-size)
(a/chan xform-topic))
;; Channel used for receive subscription requests.
sub-ch (a/chan 1 xform-topics)
cfg (-> cfg
(assoc ::pub-ch pub-ch)
(assoc ::sub-ch sub-ch))]
(init-pub-loop cfg)
(init-sub-loop cfg)
(start-io-loop cfg)
(with-meta
(fn run
([command] (run command nil))
([command params]
(a/go
(case command
:pub (a/>! pub-ch params)
:sub (a/>! sub-ch params)))))
(fn [& {:keys [cmd] :as params}]
(a/go
(case cmd
:pub (a/>! pub-ch params)
:sub (a/<! (subscribe cfg params))
:purge (a/<! (purge cfg params))
(l/error :hint "unexpeced error on msgbus command processing" :params params))))
cfg)))
(defmethod ig/halt-key! ::msgbus
[_ f]
(let [mdata (meta f)]
(stop-backend mdata)
(a/close! (::pub-ch mdata))
(a/close! (::sub-ch mdata))))
(redis-disconnect mdata)
(a/close! (::cmd-ch mdata))
(a/close! (::rcv-ch mdata))))
;; --- IN-MEMORY BACKEND IMPL
;; --- IMPL
(defmethod init-backend :memory [cfg] cfg)
(defmethod stop-backend :memory [_])
(defmethod init-pub-loop :memory [_])
(defn- redis-connect
[{:keys [redis-uri timeout] :as cfg}]
(let [codec (RedisCodec/of StringCodec/UTF8 ByteArrayCodec/INSTANCE)
(defmethod init-sub-loop :memory
[{:keys [::sub-ch ::pub-ch]}]
(a/go-loop [state {}]
(let [[val port] (a/alts! [pub-ch sub-ch])]
(cond
(and (= port sub-ch) (some? val))
(let [{:keys [topics chan]} val]
(recur (reduce #(update %1 %2 (fnil conj #{}) chan) state topics)))
resources (.. (DefaultClientResources/builder)
(ioThreadPoolSize 4)
(computationThreadPoolSize 4)
(build))
(and (= port pub-ch) (some? val))
(let [topic (:topic val)
message (:message val)
state (loop [state state
chans (get state topic)]
(if-let [c (first chans)]
(if (a/>! c message)
(recur state (rest chans))
(recur (update state topic disj c)
(rest chans)))
state))]
(recur state))
uri (RedisURI/create redis-uri)
rclient (RedisClient/create ^ClientResources resources ^RedisURI uri)
:else
(->> (vals state)
(mapcat identity)
(run! a/close!))))))
pconn (.connect ^RedisClient rclient ^RedisCodec codec)
sconn (.connectPubSub ^RedisClient rclient ^RedisCodec codec)]
;; Add a unique listener to connection
;; --- REDIS BACKEND IMPL
(declare impl-redis-open?)
(declare impl-redis-pub)
(declare impl-redis-sub)
(declare impl-redis-unsub)
(defmethod init-backend :redis
[{:keys [redis-uri] :as cfg}]
(let [codec (RedisCodec/of StringCodec/UTF8 ByteArrayCodec/INSTANCE)
uri (RedisURI/create redis-uri)
rclient (RedisClient/create ^RedisURI uri)
pub-conn (.connect ^RedisClient rclient ^RedisCodec codec)
sub-conn (.connectPubSub ^RedisClient rclient ^RedisCodec codec)]
(.setTimeout ^StatefulRedisConnection pub-conn ^Duration (dt/duration {:seconds 10}))
(.setTimeout ^StatefulRedisPubSubConnection sub-conn ^Duration (dt/duration {:seconds 10}))
(.setTimeout ^StatefulRedisConnection pconn ^Duration timeout)
(.setTimeout ^StatefulRedisPubSubConnection sconn ^Duration timeout)
(-> cfg
(assoc ::pub-conn pub-conn)
(assoc ::sub-conn sub-conn))))
(assoc ::resources resources)
(assoc ::pconn pconn)
(assoc ::sconn sconn))))
(defmethod stop-backend :redis
[{:keys [::pub-conn ::sub-conn] :as cfg}]
(.close ^StatefulRedisConnection pub-conn)
(.close ^StatefulRedisPubSubConnection sub-conn))
(defn- redis-disconnect
[{:keys [::pconn ::sconn ::resources] :as cfg}]
(.. ^StatefulConnection pconn close)
(.. ^StatefulConnection sconn close)
(.shutdown ^ClientResources resources))
(defmethod init-pub-loop :redis
[{:keys [::pub-conn ::pub-ch]}]
(let [rac (.async ^StatefulRedisConnection pub-conn)]
(a/go-loop []
(when-let [val (a/<! pub-ch)]
(let [result (a/<! (impl-redis-pub rac val))]
(when (and (impl-redis-open? pub-conn)
(ex/exception? result))
(l/error :cause result
:hint "unexpected error on publish message to redis")))
(recur)))))
(defn- conj-subscription
"A low level function that is responsible to create on-demand
subscriptions on redis. It reuses the same subscription if it is
already established. Intended to be executed in agent."
[nsubs cfg topic chan]
(let [nsubs (if (nil? nsubs) #{chan} (conj nsubs chan))]
(when (= 1 (count nsubs))
(l/trace :hint "open subscription" :topic topic ::l/async false)
(redis-sub cfg topic))
nsubs))
(defmethod init-sub-loop :redis
[{:keys [::sub-conn ::sub-ch buffer-size]}]
(let [rcv-ch (a/chan (a/dropping-buffer buffer-size))
chans (agent {} :error-handler #(l/error :cause % :hint "unexpected error on agent"))
rac (.async ^StatefulRedisPubSubConnection sub-conn)]
(defn- disj-subscription
"A low level function responsible on removing subscriptions. The
subscription is trully removed from redis once no single local
subscription is look for it. Intended to be executed in agent."
[nsubs cfg topic chan]
(let [nsubs (disj nsubs chan)]
(when (empty? nsubs)
(l/trace :hint "close subscription" :topic topic ::l/async false)
(redis-unsub cfg topic))
nsubs))
;; Add a unique listener to connection
(.addListener sub-conn
(reify RedisPubSubListener
(message [it pattern topic message])
(message [it topic message]
;; There are no back pressure, so we use a slidding
;; buffer for cases when the pubsub broker sends
;; more messages that we can process.
(let [val {:topic topic :message (blob/decode message)}]
(when-not (a/offer! rcv-ch val)
(l/warn :msg "dropping message on subscription loop"))))
(psubscribed [it pattern count])
(punsubscribed [it pattern count])
(subscribed [it topic count])
(unsubscribed [it topic count])))
(defn- subscribe-to-topics
"Function responsible to attach local subscription to the
state. Intended to be used in agent."
[state cfg topics chan done-ch]
(aa/with-closing done-ch
(let [state (update state :chans assoc chan topics)]
(reduce (fn [state topic]
(update-in state [:topics topic] conj-subscription cfg topic chan))
state
topics))))
(letfn [(subscribe-to-single-topic [nsubs topic chan]
(let [nsubs (if (nil? nsubs) #{chan} (conj nsubs chan))]
(when (= 1 (count nsubs))
(let [result (a/<!! (impl-redis-sub rac topic))]
(l/trace :action "open subscription"
:topic topic)
(when (ex/exception? result)
(l/error :cause result
:hint "unexpected exception on subscribing"
:topic topic))))
nsubs))
(defn- unsubscribe-single-channel
"Auxiliar function responsible on removing a single local
subscription from the state."
[state cfg chan]
(let [topics (get-in state [:chans chan])
state (update state :chans dissoc chan)]
(reduce (fn [state topic]
(update-in state [:topics topic] disj-subscription cfg topic chan))
state
topics)))
(subscribe-to-topics [state topics chan]
(let [state (update state :chans assoc chan topics)]
(reduce (fn [state topic]
(update-in state [:topics topic] subscribe-to-single-topic topic chan))
state
topics)))
(unsubscribe-from-single-topic [nsubs topic chan]
(let [nsubs (disj nsubs chan)]
(when (empty? nsubs)
(let [result (a/<!! (impl-redis-unsub rac topic))]
(l/trace :action "close subscription"
:topic topic)
(when (and (impl-redis-open? sub-conn)
(ex/exception? result))
(l/error :cause result
:hint "unexpected exception on unsubscribing"
:topic topic))))
nsubs))
(unsubscribe-channels [state pending]
(reduce (fn [state ch]
(let [topics (get-in state [:chans ch])
state (update state :chans dissoc ch)]
(reduce (fn [state topic]
(update-in state [:topics topic] unsubscribe-from-single-topic topic ch))
state
topics)))
state
pending))]
;; Asynchronous subscription loop;
(a/go-loop []
(if-let [{:keys [topics chan]} (a/<! sub-ch)]
(do
(send-off chans subscribe-to-topics topics chan)
(recur))
(a/close! rcv-ch)))
;; Asyncrhonous message processing loop;x
(a/go-loop []
(if-let [{:keys [topic message]} (a/<! rcv-ch)]
;; This means we receive data from redis and we need to
;; forward it to the underlying subscriptions.
(let [pending (loop [chans (seq (get-in @chans [:topics topic]))
pending #{}]
(if-let [ch (first chans)]
(if (a/>! ch message)
(recur (rest chans) pending)
(recur (rest chans) (conj pending ch)))
pending))]
(some->> (seq pending)
(send-off chans unsubscribe-channels))
(recur))
;; Stop condition; close all underlying subscriptions and
;; exit. The close operation is performed asynchronously.
(send-off chans (fn [state]
(->> (vals state)
(mapcat identity)
(filter some?)
(run! a/close!)))))))))
(defn- unsubscribe-channels
"Function responsible from detach from state a seq of channels,
useful when client disconnects or in-bulk unsubscribe
operations. Intended to be executed in agent."
[state cfg channels done-ch]
(aa/with-closing done-ch
(reduce #(unsubscribe-single-channel %1 cfg %2) state channels)))
(defn- impl-redis-open?
[^StatefulConnection conn]
(.isOpen conn))
(defn- subscribe
[{:keys [::state executor] :as cfg} {:keys [topic topics chan]}]
(let [done-ch (a/chan)
topics (into [] (map prefix-topic) (if topic [topic] topics))]
(l/debug :hint "subscribe" :topics topics)
(send-via executor state subscribe-to-topics cfg topics chan done-ch)
done-ch))
(defn- impl-redis-pub
[^RedisAsyncCommands rac {:keys [topic message]}]
(let [message (blob/encode message)
res (a/chan 1)]
(-> (.publish rac ^String topic ^bytes message)
(p/finally (fn [_ e]
(when e (a/>!! res e))
(defn- purge
[{:keys [::state executor] :as cfg} {:keys [chans]}]
(l/trace :hint "purge" :chans (count chans))
(let [done-ch (a/chan)]
(send-via executor state unsubscribe-channels cfg chans done-ch)
done-ch))
(defn- create-listener
[rcv-ch]
(reify RedisPubSubListener
(message [_ _pattern _topic _message])
(message [_ topic message]
;; There are no back pressure, so we use a slidding
;; buffer for cases when the pubsub broker sends
;; more messages that we can process.
(let [val {:topic topic :message (t/decode message)}]
(when-not (a/offer! rcv-ch val)
(l/warn :msg "dropping message on subscription loop"))))
(psubscribed [_ _pattern _count])
(punsubscribed [_ _pattern _count])
(subscribed [_ _topic _count])
(unsubscribed [_ _topic _count])))
(defn start-io-loop
[{:keys [::sconn ::rcv-ch ::pub-ch ::state executor] :as cfg}]
;; Add a single listener to the pubsub connection
(.addListener ^StatefulRedisPubSubConnection sconn
^RedisPubSubListener (create-listener rcv-ch))
(letfn [(send-to-topic [topic message]
(a/go-loop [chans (seq (get-in @state [:topics topic]))
closed #{}]
(if-let [ch (first chans)]
(if (a/>! ch message)
(recur (rest chans) closed)
(recur (rest chans) (conj closed ch)))
(seq closed))))
(process-incoming [{:keys [topic message]}]
(a/go
(when-let [closed (a/<! (send-to-topic topic message))]
(send-via executor state unsubscribe-channels cfg closed nil))))
]
(a/go-loop []
(let [[val port] (a/alts! [pub-ch rcv-ch])]
(cond
(nil? val)
(do
(l/trace :hint "stoping io-loop, nil received")
(send-via executor state (fn [state]
(->> (vals state)
(mapcat identity)
(filter some?)
(run! a/close!))
nil)))
(= port rcv-ch)
(do
(a/<! (process-incoming val))
(recur))
(= port pub-ch)
(let [result (a/<! (redis-pub cfg val))]
(when (ex/exception? result)
(l/error :hint "unexpected error on publishing" :message val
:cause result))
(recur)))))))
(defn- redis-pub
"Publish a message to the redis server. Asynchronous operation,
intended to be used in core.async go blocks."
[{:keys [::pconn] :as cfg} {:keys [topic message]}]
(let [message (t/encode message)
res (a/chan 1)
pcomm (.async ^StatefulRedisConnection pconn)]
(-> (.publish ^RedisAsyncCommands pcomm ^String topic ^bytes message)
(p/finally (fn [_ cause]
(when (and cause (.isOpen ^StatefulConnection pconn))
(a/offer! res cause))
(a/close! res))))
res))
(defn impl-redis-sub
[^RedisPubSubAsyncCommands rac topic]
(let [res (a/chan 1)]
(-> (.subscribe rac (into-array String [topic]))
(p/finally (fn [_ e]
(when e (a/>!! res e))
(a/close! res))))
res))
(defn redis-sub
"Create redis subscription. Blocking operation, intended to be used
inside an agent."
[{:keys [::sconn] :as cfg} topic]
(let [topic (into-array String [topic])
scomm (.sync ^StatefulRedisPubSubConnection sconn)]
(.subscribe ^RedisPubSubCommands scomm topic)))
(defn impl-redis-unsub
[rac topic]
(let [res (a/chan 1)]
(-> (.unsubscribe rac (into-array String [topic]))
(p/finally (fn [_ e]
(when e (a/>!! res e))
(a/close! res))))
res))
(defn redis-unsub
"Removes redis subscription. Blocking operation, intended to be used
inside an agent."
[{:keys [::sconn] :as cfg} topic]
(let [topic (into-array String [topic])
scomm (.sync ^StatefulRedisPubSubConnection sconn)]
(.unsubscribe ^RedisPubSubCommands scomm topic)))

View File

@@ -1,280 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.notifications
"A websocket based notifications mechanism."
(:require
[app.common.spec :as us]
[app.common.transit :as t]
[app.db :as db]
[app.metrics :as mtx]
[app.util.async :as aa]
[app.util.logging :as l]
[app.util.time :as dt]
[app.worker :as wrk]
[clojure.core.async :as a]
[clojure.spec.alpha :as s]
[integrant.core :as ig]
[ring.adapter.jetty9 :as jetty]
[ring.middleware.cookies :refer [wrap-cookies]]
[ring.middleware.keyword-params :refer [wrap-keyword-params]]
[ring.middleware.params :refer [wrap-params]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Http Handler
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare retrieve-file)
(declare websocket)
(declare handler)
(s/def ::session map?)
(s/def ::msgbus fn?)
(defmethod ig/pre-init-spec ::handler [_]
(s/keys :req-un [::msgbus ::db/pool ::session ::mtx/metrics ::wrk/executor]))
(defmethod ig/init-key ::handler
[_ {:keys [session metrics] :as cfg}]
(let [wrap-session (:middleware session)
mtx-active-connections
(mtx/create
{:name "websocket_active_connections"
:registry (:registry metrics)
:type :gauge
:help "Active websocket connections."})
mtx-messages
(mtx/create
{:name "websocket_message_total"
:registry (:registry metrics)
:labels ["op"]
:type :counter
:help "Counter of processed messages."})
mtx-sessions
(mtx/create
{:name "websocket_session_timing"
:registry (:registry metrics)
:quantiles []
:help "Websocket session timing (seconds)."
:type :summary})
cfg (assoc cfg
:mtx-active-connections mtx-active-connections
:mtx-messages mtx-messages
:mtx-sessions mtx-sessions
)]
(-> #(handler cfg %)
(wrap-session)
(wrap-keyword-params)
(wrap-cookies)
(wrap-params))))
(s/def ::file-id ::us/uuid)
(s/def ::session-id ::us/uuid)
(s/def ::websocket-handler-params
(s/keys :req-un [::file-id ::session-id]))
(defn- handler
[{:keys [pool] :as cfg} {:keys [profile-id params] :as req}]
(let [params (us/conform ::websocket-handler-params params)
file (retrieve-file pool (:file-id params))
cfg (merge cfg params
{:profile-id profile-id
:team-id (:team-id file)})]
(cond
(not profile-id)
{:error {:code 403 :message "Authentication required"}}
(not file)
{:error {:code 404 :message "File does not exists"}}
:else
(websocket cfg))))
(def ^:private
sql:retrieve-file
"select f.id as id,
p.team_id as team_id
from file as f
join project as p on (p.id = f.project_id)
where f.id = ?")
(defn- retrieve-file
[conn id]
(db/exec-one! conn [sql:retrieve-file id]))
;; --- WEBSOCKET INIT
(declare handle-connect)
(defn- ws-send
[conn data]
(try
(when (jetty/connected? conn)
(jetty/send! conn data)
true)
(catch java.lang.NullPointerException _e
false)))
(defn websocket
[{:keys [file-id team-id msgbus executor] :as cfg}]
(let [rcv-ch (a/chan 32)
out-ch (a/chan 32)
mtx-aconn (:mtx-active-connections cfg)
mtx-messages (:mtx-messages cfg)
mtx-sessions (:mtx-sessions cfg)
created-at (dt/now)
ws-send (mtx/wrap-counter ws-send mtx-messages ["send"])]
(letfn [(on-connect [conn]
(mtx-aconn :inc)
;; A subscription channel should use a lossy buffer
;; because we can't penalize normal clients when one
;; slow client is connected to the room.
(let [sub-ch (a/chan (a/dropping-buffer 128))
cfg (assoc cfg
:conn conn
:rcv-ch rcv-ch
:out-ch out-ch
:sub-ch sub-ch)]
(l/trace :event "connect" :session (:session-id cfg))
;; Forward all messages from out-ch to the websocket
;; connection
(a/go-loop []
(let [val (a/<! out-ch)]
(when (some? val)
(when (a/<! (aa/thread-call executor #(ws-send conn (t/encode-str val))))
(recur)))))
(a/go
;; Subscribe to corresponding topics
(a/<! (msgbus :sub {:topics [file-id team-id] :chan sub-ch}))
(a/<! (handle-connect cfg))
;; when connection is closed
(mtx-aconn :dec)
(mtx-sessions :observe (/ (inst-ms (dt/diff created-at (dt/now))) 1000.0))
;; close subscription
(a/close! sub-ch))))
(on-error [_conn _e]
(l/trace :event "error" :session (:session-id cfg))
(a/close! out-ch)
(a/close! rcv-ch))
(on-close [_conn _status _reason]
(l/trace :event "close" :session (:session-id cfg))
(a/close! out-ch)
(a/close! rcv-ch))
(on-message [_ws message]
(let [message (t/decode-str message)]
(when-not (a/offer! rcv-ch message)
(l/warn :msg "drop messages"))))]
{:on-connect on-connect
:on-error on-error
:on-close on-close
:on-text (mtx/wrap-counter on-message mtx-messages ["recv"])
:on-bytes (constantly nil)})))
;; --- CONNECTION INIT
(declare send-presence)
(declare handle-message)
(declare start-loop!)
(defn- handle-connect
[cfg]
(a/go
(a/<! (handle-message cfg {:type :connect}))
(a/<! (start-loop! cfg))
(a/<! (handle-message cfg {:type :disconnect}))))
(defn- start-loop!
[{:keys [rcv-ch out-ch sub-ch session-id] :as cfg}]
(a/go-loop []
(let [timeout (a/timeout 30000)
[val port] (a/alts! [rcv-ch sub-ch timeout])]
(cond
;; Process message coming from connected client
(and (= port rcv-ch) (some? val))
(do
(a/<! (handle-message cfg val))
(recur))
;; Process message coming from pubsub.
(and (= port sub-ch) (some? val))
(do
(when-not (= (:session-id val) session-id)
;; If we receive a connect message of other user, we need
;; to send an update presence to all participants.
(when (= :connect (:type val))
(a/<! (send-presence cfg :presence)))
;; Then, just forward the message
(a/>! out-ch val))
(recur))
;; When timeout channel is signaled, we need to send a ping
;; message to the output channel. TODO: we need to make this
;; more smart.
(= port timeout)
(do
(a/>! out-ch {:type :ping})
(recur))))))
(defn send-presence
([cfg] (send-presence cfg :presence))
([{:keys [msgbus session-id profile-id file-id]} type]
(a/go
(a/<! (msgbus :pub {:topic file-id
:message {:type type
:session-id session-id
:profile-id profile-id}})))))
;; --- INCOMING MSG PROCESSING
(defmulti handle-message
(fn [_ message] (:type message)))
(defmethod handle-message :connect
[cfg _]
(send-presence cfg :connect))
(defmethod handle-message :disconnect
[cfg _]
(send-presence cfg :disconnect))
(defmethod handle-message :keepalive
[_ _]
(a/go :nothing))
(defmethod handle-message :pointer-update
[{:keys [profile-id file-id session-id msgbus] :as cfg} message]
(let [message (assoc message
:profile-id profile-id
:session-id session-id)]
(msgbus :pub {:topic file-id
:message message})))
(defmethod handle-message :default
[_ws message]
(a/go
(l/log :level :warn
:msg "received unexpected message"
:message message)))

View File

@@ -1,45 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.rlimits
"Resource usage limits (in other words: semaphores)."
(:require
[app.common.spec :as us]
[clojure.spec.alpha :as s]
[integrant.core :as ig])
(:import
java.util.concurrent.Semaphore))
(s/def ::rlimit #(instance? Semaphore %))
(s/def ::rlimits (s/map-of ::us/keyword ::rlimit))
(derive ::password ::instance)
(derive ::image ::instance)
(derive ::font ::instance)
(defmethod ig/pre-init-spec ::instance [_]
(s/spec int?))
(defmethod ig/init-key ::instance
[_ permits]
(Semaphore. (int permits)))
(defn acquire!
[sem]
(.acquire ^Semaphore sem))
(defn release!
[sem]
(.release ^Semaphore sem))
(defmacro execute
[rlinst & body]
`(try
(acquire! ~rlinst)
~@body
(finally
(release! ~rlinst))))

View File

@@ -6,192 +6,300 @@
(ns app.rpc
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.spec :as us]
[app.db :as db]
[app.loggers.audit :as audit]
[app.metrics :as mtx]
[app.rlimits :as rlm]
[app.util.logging :as l]
[app.rpc.retry :as retry]
[app.rpc.rlimit :as rlimit]
[app.util.async :as async]
[app.util.services :as sv]
[app.worker :as wrk]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[integrant.core :as ig]))
[integrant.core :as ig]
[promesa.core :as p]
[promesa.exec :as px]
[yetti.response :as yrs]))
(defn- default-handler
[_]
(ex/raise :type :not-found))
(p/rejected (ex/error :type :not-found)))
(defn- run-hook
[hook-fn response]
(ex/ignoring (hook-fn))
(defn- handle-response-transformation
[response request mdata]
(if-let [transform-fn (:transform-response mdata)]
(p/do (transform-fn request response))
(p/resolved response)))
(defn- handle-before-comple-hook
[response mdata]
(when-let [hook-fn (:before-complete mdata)]
(ex/ignoring (hook-fn)))
response)
(defn- rpc-query-handler
[methods {:keys [profile-id] :as request}]
(let [type (keyword (get-in request [:path-params :type]))
"Ring handler that dispatches query requests and convert between
internal async flow into ring async flow."
[methods {:keys [profile-id session-id params] :as request} respond raise]
(letfn [(handle-response [result]
(let [mdata (meta result)]
(-> (yrs/response 200 result)
(handle-response-transformation request mdata))))]
data (merge (:params request)
(:body-params request)
(:uploads request)
{::request request})
(let [type (keyword (:type params))
data (into {::request request} params)
data (if profile-id
(assoc data :profile-id profile-id ::session-id session-id)
(dissoc data :profile-id))
method (get methods type default-handler)]
data (if profile-id
(assoc data :profile-id profile-id)
(dissoc data :profile-id))
result ((get methods type default-handler) data)
mdata (meta result)]
(cond->> {:status 200 :body result}
(fn? (:transform-response mdata))
((:transform-response mdata) request))))
(-> (method data)
(p/then handle-response)
(p/then respond)
(p/catch (fn [cause]
(let [context {:profile-id profile-id}]
(raise (ex/wrap-with-context cause context)))))))))
(defn- rpc-mutation-handler
[methods {:keys [profile-id] :as request}]
(let [type (keyword (get-in request [:path-params :type]))
data (merge (:params request)
(:body-params request)
(:uploads request)
{::request request})
"Ring handler that dispatches mutation requests and convert between
internal async flow into ring async flow."
[methods {:keys [profile-id session-id params] :as request} respond raise]
(letfn [(handle-response [result]
(let [mdata (meta result)]
(p/-> (yrs/response 200 result)
(handle-response-transformation request mdata)
(handle-before-comple-hook mdata))))]
data (if profile-id
(assoc data :profile-id profile-id)
(dissoc data :profile-id))
(let [type (keyword (:type params))
data (into {::request request} params)
data (if profile-id
(assoc data :profile-id profile-id ::session-id session-id)
(dissoc data :profile-id))
result ((get methods type default-handler) data)
mdata (meta result)]
(cond->> {:status 200 :body result}
(fn? (:transform-response mdata))
((:transform-response mdata) request)
method (get methods type default-handler)]
(-> (method data)
(p/then handle-response)
(p/then respond)
(p/catch (fn [cause]
(let [context {:profile-id profile-id}]
(raise (ex/wrap-with-context cause context)))))))))
(fn? (:before-complete mdata))
(run-hook (:before-complete mdata)))))
(defn- rpc-command-handler
"Ring handler that dispatches cmd requests and convert between
internal async flow into ring async flow."
[methods {:keys [profile-id session-id params] :as request} respond raise]
(letfn [(handle-response [result]
(let [mdata (meta result)]
(p/-> (yrs/response 200 result)
(handle-response-transformation request mdata)
(handle-before-comple-hook mdata))))]
(defn- wrap-with-metrics
[cfg f mdata]
(mtx/wrap-summary f (::mobj cfg) [(::sv/name mdata)]))
(let [cmd (keyword (:command params))
data (into {::request request} params)
data (if profile-id
(assoc data :profile-id profile-id ::session-id session-id)
(dissoc data :profile-id))
;; Wrap the rpc handler with a semaphore if it is specified in the
;; metadata asocciated with the handler.
(defn- wrap-with-rlimits
[cfg f mdata]
(if-let [key (:rlimit mdata)]
(let [rlinst (get-in cfg [:rlimits key])]
(when-not rlinst
(ex/raise :type :internal
:code :rlimit-not-configured
:hint (str/fmt "%s rlimit not configured" key)))
(l/trace :action "add rlimit"
:handler (::sv/name mdata))
(fn [cfg params]
(rlm/execute rlinst (f cfg params))))
method (get methods cmd default-handler)]
(-> (method data)
(p/then handle-response)
(p/then respond)
(p/catch (fn [cause]
(let [context {:profile-id profile-id}]
(raise (ex/wrap-with-context cause context)))))))))
(defn- wrap-metrics
"Wrap service method with metrics measurement."
[{:keys [metrics ::metrics-id]} f mdata]
(let [labels (into-array String [(::sv/name mdata)])]
(fn [cfg params]
(let [start (System/nanoTime)]
(p/finally
(f cfg params)
(fn [_ _]
(mtx/run! metrics
{:id metrics-id
:val (/ (- (System/nanoTime) start) 1000000)
:labels labels})))))))
(defn- wrap-dispatch
"Wraps service method into async flow, with the ability to dispatching
it to a preconfigured executor service."
[{:keys [executors] :as cfg} f mdata]
(let [dname (::async/dispatch mdata :default)]
(if (= :none dname)
(with-meta
(fn [cfg params]
(p/do (f cfg params)))
mdata)
(let [executor (get executors dname)]
(when-not executor
(ex/raise :type :internal
:code :executor-not-configured
:hint (format "executor %s not configured" dname)))
(with-meta
(fn [cfg params]
(-> (px/submit! executor #(f cfg params))
(p/bind p/wrap)))
mdata)))))
(defn- wrap-audit
[{:keys [audit] :as cfg} f mdata]
(if audit
(with-meta
(fn [cfg {:keys [::request] :as params}]
(p/finally (f cfg params)
(fn [result _]
(when result
(let [resultm (meta result)
profile-id (or (::audit/profile-id resultm)
(:profile-id result)
(:profile-id params))
props (or (::audit/replace-props resultm)
(-> params
(merge (::audit/props resultm))
(dissoc :type)))]
(audit :cmd :submit
:type (or (::audit/type resultm)
(::type cfg))
:name (or (::audit/name resultm)
(::sv/name mdata))
:profile-id profile-id
:ip-addr (some-> request audit/parse-client-ip)
:props (dissoc props ::request)))))))
mdata)
f))
(defn- wrap-impl
[{:keys [audit] :as cfg} f mdata]
(let [f (wrap-with-rlimits cfg f mdata)
f (wrap-with-metrics cfg f mdata)
spec (or (::sv/spec mdata) (s/spec any?))
auth? (:auth mdata true)]
(defn- wrap
[cfg f mdata]
(let [f (as-> f $
(wrap-dispatch cfg $ mdata)
(rlimit/wrap-rlimit cfg $ mdata)
(retry/wrap-retry cfg $ mdata)
(wrap-audit cfg $ mdata)
(wrap-metrics cfg $ mdata)
)
(l/trace :action "register" :name (::sv/name mdata))
(fn [params]
spec (or (::sv/spec mdata) (s/spec any?))
auth? (:auth mdata true)]
;; Raise authentication error when rpc method requires auth but
;; no profile-id is found in the request.
(when (and auth? (not (uuid? (:profile-id params))))
(ex/raise :type :authentication
:code :authentication-required
:hint "authentication required for this endpoint"))
(l/debug :hint "register method" :name (::sv/name mdata))
(with-meta
(fn [{:keys [::request] :as params}]
;; Raise authentication error when rpc method requires auth but
;; no profile-id is found in the request.
(p/do!
(if (and auth? (not (uuid? (:profile-id params))))
(ex/raise :type :authentication
:code :authentication-required
:hint "authentication required for this endpoint")
(let [params (us/conform spec (dissoc params ::request))]
(f cfg (assoc params ::request request))))))
(let [params' (dissoc params ::request)
params' (us/conform spec params')
result (f cfg params')]
;; When audit log is enabled (default false).
(when (fn? audit)
(let [resultm (meta result)
request (::request params)
profile-id (or (:profile-id params')
(:profile-id result)
(::audit/profile-id resultm))
props (d/merge params (::audit/props resultm))]
(audit :cmd :submit
:type (::type cfg)
:name (or (::audit/name resultm)
(::sv/name mdata))
:profile-id profile-id
:ip-addr (audit/parse-client-ip request)
:props (audit/profile->props props))))
result))))
mdata)))
(defn- process-method
[cfg vfn]
(let [mdata (meta vfn)]
[(keyword (::sv/name mdata))
(wrap-impl cfg (deref vfn) mdata)]))
(wrap cfg vfn mdata)]))
(defn- resolve-query-methods
[cfg]
(let [mobj (mtx/create
{:name "rpc_query_timing"
:labels ["name"]
:registry (get-in cfg [:metrics :registry])
:type :histogram
:help "Timing of query services."})
cfg (assoc cfg ::mobj mobj ::type "query")]
(let [cfg (assoc cfg ::type "query" ::metrics-id :rpc-query-timing)]
(->> (sv/scan-ns 'app.rpc.queries.projects
'app.rpc.queries.files
'app.rpc.queries.teams
'app.rpc.queries.comments
'app.rpc.queries.profile
'app.rpc.queries.recent-files
'app.rpc.queries.viewer
'app.rpc.queries.fonts
'app.rpc.queries.svg)
'app.rpc.queries.fonts)
(map (partial process-method cfg))
(into {}))))
(defn- resolve-mutation-methods
[cfg]
(let [mobj (mtx/create
{:name "rpc_mutation_timing"
:labels ["name"]
:registry (get-in cfg [:metrics :registry])
:type :histogram
:help "Timing of mutation services."})
cfg (assoc cfg ::mobj mobj ::type "mutation")]
(->> (sv/scan-ns 'app.rpc.mutations.demo
'app.rpc.mutations.media
(let [cfg (assoc cfg ::type "mutation" ::metrics-id :rpc-mutation-timing)]
(->> (sv/scan-ns 'app.rpc.mutations.media
'app.rpc.mutations.profile
'app.rpc.mutations.files
'app.rpc.mutations.comments
'app.rpc.mutations.projects
'app.rpc.mutations.viewer
'app.rpc.mutations.teams
'app.rpc.mutations.management
'app.rpc.mutations.ldap
'app.rpc.mutations.fonts
'app.rpc.mutations.share-link
'app.rpc.mutations.verify-token)
(map (partial process-method cfg))
(into {}))))
(s/def ::storage some?)
(s/def ::session map?)
(s/def ::tokens fn?)
(defn- resolve-command-methods
[cfg]
(let [cfg (assoc cfg ::type "command" ::metrics-id :rpc-command-timing)]
(->> (sv/scan-ns 'app.rpc.commands.binfile
'app.rpc.commands.comments
'app.rpc.commands.auth
'app.rpc.commands.ldap
'app.rpc.commands.demo
'app.rpc.commands.files)
(map (partial process-method cfg))
(into {}))))
(s/def ::audit (s/nilable fn?))
(s/def ::executors (s/map-of keyword? ::wrk/executor))
(s/def ::executors map?)
(s/def ::http-client fn?)
(s/def ::ldap (s/nilable map?))
(s/def ::msgbus fn?)
(s/def ::public-uri ::us/not-empty-string)
(s/def ::session map?)
(s/def ::storage some?)
(s/def ::tokens fn?)
(defmethod ig/pre-init-spec ::rpc [_]
(s/keys :req-un [::storage ::session ::tokens ::audit
::mtx/metrics ::rlm/rlimits ::db/pool]))
(defmethod ig/pre-init-spec ::methods [_]
(s/keys :req-un [::storage
::session
::tokens
::audit
::executors
::public-uri
::msgbus
::http-client
::mtx/metrics
::db/pool
::ldap]))
(defmethod ig/init-key ::rpc
(defmethod ig/init-key ::methods
[_ cfg]
(let [mq (resolve-query-methods cfg)
mm (resolve-mutation-methods cfg)]
{:methods {:query mq :mutation mm}
:query-handler #(rpc-query-handler mq %)
:mutation-handler #(rpc-mutation-handler mm %)}))
{:mutations (resolve-mutation-methods cfg)
:queries (resolve-query-methods cfg)
:commands (resolve-command-methods cfg)})
(s/def ::mutations
(s/map-of keyword? fn?))
(s/def ::queries
(s/map-of keyword? fn?))
(s/def ::commands
(s/map-of keyword? fn?))
(s/def ::methods
(s/keys :req-un [::mutations
::queries
::commands]))
(defmethod ig/pre-init-spec ::routes [_]
(s/keys :req-un [::methods]))
(defmethod ig/init-key ::routes
[_ {:keys [methods] :as cfg}]
[["/rpc"
["/command/:command" {:handler (partial rpc-command-handler (:commands methods))}]
["/query/:type" {:handler (partial rpc-query-handler (:queries methods))}]
["/mutation/:type" {:handler (partial rpc-mutation-handler (:mutations methods))
:allowed-methods #{:post}}]]])

View File

@@ -0,0 +1,428 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.rpc.commands.auth
(:require
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.emails :as eml]
[app.loggers.audit :as audit]
[app.rpc.doc :as-alias doc]
[app.rpc.mutations.teams :as teams]
[app.rpc.queries.profile :as profile]
[app.rpc.rlimit :as rlimit]
[app.util.services :as sv]
[app.util.time :as dt]
[buddy.hashers :as hashers]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]))
(s/def ::email ::us/email)
(s/def ::fullname ::us/not-empty-string)
(s/def ::lang ::us/string)
(s/def ::path ::us/string)
(s/def ::profile-id ::us/uuid)
(s/def ::password ::us/not-empty-string)
(s/def ::old-password ::us/not-empty-string)
(s/def ::theme ::us/string)
(s/def ::invitation-token ::us/not-empty-string)
(s/def ::token ::us/not-empty-string)
;; ---- HELPERS
(defn derive-password
[password]
(hashers/derive password
{:alg :argon2id
:memory 16384
:iterations 20
:parallelism 2}))
(defn verify-password
[attempt password]
(try
(hashers/verify attempt password)
(catch Exception _e
{:update false
:valid false})))
(defn email-domain-in-whitelist?
"Returns true if email's domain is in the given whitelist or if
given whitelist is an empty string."
[domains email]
(if (or (empty? domains)
(nil? domains))
true
(let [[_ candidate] (-> (str/lower email)
(str/split #"@" 2))]
(contains? domains candidate))))
(def ^:private sql:profile-existence
"select exists (select * from profile
where email = ?
and deleted_at is null) as val")
(defn check-profile-existence!
[conn {:keys [email] :as params}]
(let [email (str/lower email)
result (db/exec-one! conn [sql:profile-existence email])]
(when (:val result)
(ex/raise :type :validation
:code :email-already-exists))
params))
;; ---- COMMAND: login with password
(defn login-with-password
[{:keys [pool session tokens] :as cfg} {:keys [email password] :as params}]
(when-not (contains? cf/flags :login)
(ex/raise :type :restriction
:code :login-disabled
:hint "login is disabled in this instance"))
(letfn [(check-password [profile password]
(when (= (:password profile) "!")
(ex/raise :type :validation
:code :account-without-password
:hint "the current account does not have password"))
(:valid (verify-password password (:password profile))))
(validate-profile [profile]
(when-not (:is-active profile)
(ex/raise :type :validation
:code :wrong-credentials))
(when-not profile
(ex/raise :type :validation
:code :wrong-credentials))
(when-not (check-password profile password)
(ex/raise :type :validation
:code :wrong-credentials))
profile)]
(db/with-atomic [conn pool]
(let [profile (->> (profile/retrieve-profile-data-by-email conn email)
(validate-profile)
(profile/strip-private-attrs)
(profile/populate-additional-data conn)
(profile/decode-profile-row))
invitation (when-let [token (:invitation-token params)]
(tokens :verify {:token token :iss :team-invitation}))
;; If invitation member-id does not matches the profile-id, we just proceed to ignore the
;; invitation because invitations matches exactly; and user can't loging with other email and
;; accept invitation with other email
response (if (and (some? invitation) (= (:id profile) (:member-id invitation)))
{:invitation-token (:invitation-token params)}
profile)]
(with-meta response
{:transform-response ((:create session) (:id profile))
::audit/props (audit/profile->props profile)
::audit/profile-id (:id profile)})))))
(s/def ::login-with-password
(s/keys :req-un [::email ::password]
:opt-un [::invitation-token]))
(sv/defmethod ::login-with-password
"Performs authentication using penpot password."
{:auth false
::rlimit/permits (cf/get :rlimit-password)
::doc/added "1.15"}
[cfg params]
(login-with-password cfg params))
;; ---- COMMAND: Logout
(s/def ::logout
(s/keys :opt-un [::profile-id]))
(sv/defmethod ::logout
"Clears the authentication cookie and logout the current session."
{:auth false
::doc/added "1.15"}
[{:keys [session] :as cfg} _]
(with-meta {}
{:transform-response (:delete session)}))
;; ---- COMMAND: Recover Profile
(defn recover-profile
[{:keys [pool tokens] :as cfg} {:keys [token password]}]
(letfn [(validate-token [token]
(let [tdata (tokens :verify {:token token :iss :password-recovery})]
(:profile-id tdata)))
(update-password [conn profile-id]
(let [pwd (derive-password password)]
(db/update! conn :profile {:password pwd} {:id profile-id})))]
(db/with-atomic [conn pool]
(->> (validate-token token)
(update-password conn))
nil)))
(s/def ::token ::us/not-empty-string)
(s/def ::recover-profile
(s/keys :req-un [::token ::password]))
(sv/defmethod ::recover-profile
{:auth false
::rlimit/permits (cf/get :rlimit-password)
::doc/added "1.15"}
[cfg params]
(recover-profile cfg params))
;; ---- COMMAND: Prepare Register
(defn prepare-register
[{:keys [pool tokens] :as cfg} params]
(when-not (contains? cf/flags :registration)
(if-not (contains? params :invitation-token)
(ex/raise :type :restriction
:code :registration-disabled)
(let [invitation (tokens :verify {:token (:invitation-token params) :iss :team-invitation})]
(when-not (= (:email params) (:member-email invitation))
(ex/raise :type :restriction
:code :email-does-not-match-invitation
:hint "email should match the invitation")))))
(when-let [domains (cf/get :registration-domain-whitelist)]
(when-not (email-domain-in-whitelist? domains (:email params))
(ex/raise :type :validation
:code :email-domain-is-not-allowed)))
;; Don't allow proceed in preparing registration if the profile is
;; already reported as spammer.
(when (eml/has-bounce-reports? pool (:email params))
(ex/raise :type :validation
:code :email-has-permanent-bounces
:hint "looks like the email has one or many bounces reported"))
(check-profile-existence! pool params)
(when (= (str/lower (:email params))
(str/lower (:password params)))
(ex/raise :type :validation
:code :email-as-password
:hint "you can't use your email as password"))
(let [params {:email (:email params)
:password (:password params)
:invitation-token (:invitation-token params)
:backend "penpot"
:iss :prepared-register
:exp (dt/in-future "48h")}
token (tokens :generate params)]
(with-meta {:token token}
{::audit/profile-id uuid/zero})))
(s/def ::prepare-register-profile
(s/keys :req-un [::email ::password]
:opt-un [::invitation-token]))
(sv/defmethod ::prepare-register-profile
{:auth false
::doc/added "1.15"}
[cfg params]
(prepare-register cfg params))
;; ---- COMMAND: Register Profile
(defn create-profile
"Create the profile entry on the database with limited input filling
all the other fields with defaults."
[conn params]
(let [id (or (:id params) (uuid/next))
props (-> (audit/extract-utm-params params)
(merge (:props params))
(db/tjson))
password (if-let [password (:password params)]
(derive-password password)
"!")
locale (:locale params)
locale (when (and (string? locale) (not (str/blank? locale)))
locale)
backend (:backend params "penpot")
is-demo (:is-demo params false)
is-muted (:is-muted params false)
is-active (:is-active params false)
email (str/lower (:email params))
params {:id id
:fullname (:fullname params)
:email email
:auth-backend backend
:lang locale
:password password
:deleted-at (:deleted-at params)
:props props
:is-active is-active
:is-muted is-muted
:is-demo is-demo}]
(try
(-> (db/insert! conn :profile params)
(profile/decode-profile-row))
(catch org.postgresql.util.PSQLException e
(let [state (.getSQLState e)]
(if (not= state "23505")
(throw e)
(ex/raise :type :validation
:code :email-already-exists
:cause e)))))))
(defn create-profile-relations
[conn profile]
(let [team (teams/create-team conn {:profile-id (:id profile)
:name "Default"
:is-default true})]
(-> profile
(profile/strip-private-attrs)
(assoc :default-team-id (:id team))
(assoc :default-project-id (:default-project-id team)))))
(defn register-profile
[{:keys [conn tokens session] :as cfg} {:keys [token] :as params}]
(let [claims (tokens :verify {:token token :iss :prepared-register})
params (merge params claims)]
(check-profile-existence! conn params)
(let [is-active (or (:is-active params)
(contains? cf/flags :insecure-register))
profile (->> (assoc params :is-active is-active)
(create-profile conn)
(create-profile-relations conn)
(profile/decode-profile-row))
invitation (when-let [token (:invitation-token params)]
(tokens :verify {:token token :iss :team-invitation}))]
(cond
;; If invitation token comes in params, this is because the user comes from team-invitation process;
;; in this case, regenerate token and send back to the user a new invitation token (and mark current
;; session as logged). This happens only if the invitation email matches with the register email.
(and (some? invitation) (= (:email profile) (:member-email invitation)))
(let [claims (assoc invitation :member-id (:id profile))
token (tokens :generate claims)
resp {:invitation-token token}]
(with-meta resp
{:transform-response ((:create session) (:id profile))
::audit/replace-props (audit/profile->props profile)
::audit/profile-id (:id profile)}))
;; If auth backend is different from "penpot" means user is
;; registering using third party auth mechanism; in this case
;; we need to mark this session as logged.
(not= "penpot" (:auth-backend profile))
(with-meta (profile/strip-private-attrs profile)
{:transform-response ((:create session) (:id profile))
::audit/replace-props (audit/profile->props profile)
::audit/profile-id (:id profile)})
;; If the `:enable-insecure-register` flag is set, we proceed
;; to sign in the user directly, without email verification.
(true? is-active)
(with-meta (profile/strip-private-attrs profile)
{:transform-response ((:create session) (:id profile))
::audit/replace-props (audit/profile->props profile)
::audit/profile-id (:id profile)})
;; In all other cases, send a verification email.
:else
(let [vtoken (tokens :generate
{:iss :verify-email
:exp (dt/in-future "48h")
:profile-id (:id profile)
:email (:email profile)})
ptoken (tokens :generate-predefined
{:iss :profile-identity
:profile-id (:id profile)})]
(eml/send! {::eml/conn conn
::eml/factory eml/register
:public-uri (:public-uri cfg)
:to (:email profile)
:name (:fullname profile)
:token vtoken
:extra-data ptoken})
(with-meta profile
{::audit/replace-props (audit/profile->props profile)
::audit/profile-id (:id profile)}))))))
(s/def ::register-profile
(s/keys :req-un [::token ::fullname]))
(sv/defmethod ::register-profile
{:auth false
::rlimit/permits (cf/get :rlimit-password)
::doc/added "1.15"}
[{:keys [pool] :as cfg} params]
(db/with-atomic [conn pool]
(-> (assoc cfg :conn conn)
(register-profile params))))
;; ---- COMMAND: Request Profile Recovery
(defn request-profile-recovery
[{:keys [pool tokens] :as cfg} {:keys [email] :as params}]
(letfn [(create-recovery-token [{:keys [id] :as profile}]
(let [token (tokens :generate
{:iss :password-recovery
:exp (dt/in-future "15m")
:profile-id id})]
(assoc profile :token token)))
(send-email-notification [conn profile]
(let [ptoken (tokens :generate-predefined
{:iss :profile-identity
:profile-id (:id profile)})]
(eml/send! {::eml/conn conn
::eml/factory eml/password-recovery
:public-uri (:public-uri cfg)
:to (:email profile)
:token (:token profile)
:name (:fullname profile)
:extra-data ptoken})
nil))]
(db/with-atomic [conn pool]
(when-let [profile (profile/retrieve-profile-data-by-email conn email)]
(when-not (eml/allow-send-emails? conn profile)
(ex/raise :type :validation
:code :profile-is-muted
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces."))
(when-not (:is-active profile)
(ex/raise :type :validation
:code :profile-not-verified
:hint "the user need to validate profile before recover password"))
(when (eml/has-bounce-reports? conn (:email profile))
(ex/raise :type :validation
:code :email-has-permanent-bounces
:hint "looks like the email you invite has been repeatedly reported as spam or permanent bounce"))
(->> profile
(create-recovery-token)
(send-email-notification conn))))))
(s/def ::request-profile-recovery
(s/keys :req-un [::email]))
(sv/defmethod ::request-profile-recovery
{:auth false
::doc/added "1.15"}
[cfg params]
(request-profile-recovery cfg params))

View File

@@ -0,0 +1,868 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.rpc.commands.binfile
(:refer-clojure :exclude [assert])
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.pages.migrations :as pmg]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.media :as media]
[app.rpc.doc :as-alias doc]
[app.rpc.queries.files :as files]
[app.rpc.queries.projects :as projects]
[app.storage :as sto]
[app.storage.tmp :as tmp]
[app.tasks.file-gc]
[app.util.blob :as blob]
[app.util.bytes :as bs]
[app.util.fressian :as fres]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.java.io :as io]
[clojure.spec.alpha :as s]
[clojure.walk :as walk]
[cuerdas.core :as str]
[yetti.adapter :as yt])
(:import
java.io.DataInputStream
java.io.DataOutputStream
java.io.InputStream
java.io.OutputStream
java.lang.AutoCloseable))
(set! *warn-on-reflection* true)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; DEFAULTS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Threshold in MiB when we pass from using
;; in-memory byte-array's to use temporal files.
(def temp-file-threshold
(* 1024 1024 2))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; LOW LEVEL STREAM IO API
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:const buffer-size (:xnio/buffer-size yt/defaults))
(def ^:const penpot-magic-number 800099563638710213)
(def ^:const max-object-size (* 1024 1024 100)) ; Only allow 100MiB max file size.
(def ^:dynamic *position* nil)
(defn get-mark
[id]
(case id
:header 1
:stream 2
:uuid 3
:label 4
:obj 5
(ex/raise :type :validation
:code :invalid-mark-id
:hint (format "invalid mark id %s" id))))
(defmacro assert
[expr hint]
`(when-not ~expr
(ex/raise :type :validation
:code :unexpected-condition
:hint ~hint)))
(defmacro assert-mark
[v type]
`(let [expected# (get-mark ~type)
val# (long ~v)]
(when (not= val# expected#)
(ex/raise :type :validation
:code :unexpected-mark
:hint (format "received mark %s, expected %s" val# expected#)))))
(defmacro assert-label
[expr label]
`(let [v# ~expr]
(when (not= v# ~label)
(ex/raise :type :assertion
:code :unexpected-label
:hint (format "received label %s, expected %s" v# ~label)))))
;; --- PRIMITIVE IO
(defn write-byte!
[^DataOutputStream output data]
(l/trace :fn "write-byte!" :data data :position @*position* ::l/async false)
(.writeByte output (byte data))
(swap! *position* inc))
(defn read-byte!
[^DataInputStream input]
(let [v (.readByte input)]
(l/trace :fn "read-byte!" :val v :position @*position* ::l/async false)
(swap! *position* inc)
v))
(defn write-long!
[^DataOutputStream output data]
(l/trace :fn "write-long!" :data data :position @*position* ::l/async false)
(.writeLong output (long data))
(swap! *position* + 8))
(defn read-long!
[^DataInputStream input]
(let [v (.readLong input)]
(l/trace :fn "read-long!" :val v :position @*position* ::l/async false)
(swap! *position* + 8)
v))
(defn write-bytes!
[^DataOutputStream output ^bytes data]
(let [size (alength data)]
(l/trace :fn "write-bytes!" :size size :position @*position* ::l/async false)
(.write output data 0 size)
(swap! *position* + size)))
(defn read-bytes!
[^InputStream input ^bytes buff]
(let [size (alength buff)
readed (.readNBytes input buff 0 size)]
(l/trace :fn "read-bytes!" :expected (alength buff) :readed readed :position @*position* ::l/async false)
(swap! *position* + readed)
readed))
;; --- COMPOSITE IO
(defn write-uuid!
[^DataOutputStream output id]
(l/trace :fn "write-uuid!" :position @*position* :WRITTEN? (.size output) ::l/async false)
(doto output
(write-byte! (get-mark :uuid))
(write-long! (uuid/get-word-high id))
(write-long! (uuid/get-word-low id))))
(defn read-uuid!
[^DataInputStream input]
(l/trace :fn "read-uuid!" :position @*position* ::l/async false)
(let [m (read-byte! input)]
(assert-mark m :uuid)
(let [a (read-long! input)
b (read-long! input)]
(uuid/custom a b))))
(defn write-obj!
[^DataOutputStream output data]
(l/trace :fn "write-obj!" :position @*position* ::l/async false)
(let [^bytes data (fres/encode data)]
(doto output
(write-byte! (get-mark :obj))
(write-long! (alength data))
(write-bytes! data))))
(defn read-obj!
[^DataInputStream input]
(l/trace :fn "read-obj!" :position @*position* ::l/async false)
(let [m (read-byte! input)]
(assert-mark m :obj)
(let [size (read-long! input)]
(assert (pos? size) "incorrect header size found on reading header")
(let [buff (byte-array size)]
(read-bytes! input buff)
(fres/decode buff)))))
(defn write-label!
[^DataOutputStream output label]
(l/trace :fn "write-label!" :label label :position @*position* ::l/async false)
(doto output
(write-byte! (get-mark :label))
(write-obj! label)))
(defn read-label!
[^DataInputStream input]
(l/trace :fn "read-label!" :position @*position* ::l/async false)
(let [m (read-byte! input)]
(assert-mark m :label)
(read-obj! input)))
(defn write-header!
[^OutputStream output version]
(l/trace :fn "write-header!"
:version version
:position @*position*
::l/async false)
(let [vers (-> version name (subs 1) parse-long)
output (bs/data-output-stream output)]
(doto output
(write-byte! (get-mark :header))
(write-long! penpot-magic-number)
(write-long! vers))))
(defn read-header!
[^InputStream input]
(l/trace :fn "read-header!" :position @*position* ::l/async false)
(let [input (bs/data-input-stream input)
mark (read-byte! input)
mnum (read-long! input)
vers (read-long! input)]
(when (or (not= mark (get-mark :header))
(not= mnum penpot-magic-number))
(ex/raise :type :validation
:code :invalid-penpot-file
:hint "invalid penpot file"))
(keyword (str "v" vers))))
(defn copy-stream!
[^OutputStream output ^InputStream input ^long size]
(let [written (bs/copy! input output :size size)]
(l/trace :fn "copy-stream!" :position @*position* :size size :written written ::l/async false)
(swap! *position* + written)
written))
(defn write-stream!
[^DataOutputStream output stream size]
(l/trace :fn "write-stream!" :position @*position* ::l/async false :size size)
(doto output
(write-byte! (get-mark :stream))
(write-long! size))
(copy-stream! output stream size))
(defn read-stream!
[^DataInputStream input]
(l/trace :fn "read-stream!" :position @*position* ::l/async false)
(let [m (read-byte! input)
s (read-long! input)
p (tmp/tempfile :prefix "penpot.binfile.")]
(assert-mark m :stream)
(when (> s max-object-size)
(ex/raise :type :validation
:code :max-file-size-reached
:hint (str/ffmt "unable to import storage object with size % bytes" s)))
(if (> s temp-file-threshold)
(with-open [^OutputStream output (io/output-stream p)]
(let [readed (bs/copy! input output :offset 0 :size s)]
(l/trace :fn "read-stream*!" :expected s :readed readed :position @*position* ::l/async false)
(swap! *position* + readed)
[s p]))
[s (bs/read-as-bytes input :size s)])))
(defmacro assert-read-label!
[input expected-label]
`(let [readed# (read-label! ~input)
expected# ~expected-label]
(when (not= readed# expected#)
(ex/raise :type :validation
:code :unexpected-label
:hint (format "unxpected label found: %s, expected: %s" readed# expected#)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; API
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- HELPERS
(defn- retrieve-file
[pool file-id]
(->> (db/query pool :file {:id file-id})
(map files/decode-row)
(first)))
(def ^:private sql:file-media-objects
"SELECT * FROM file_media_object WHERE id = ANY(?)")
(defn- retrieve-file-media
[pool {:keys [data id] :as file}]
(with-open [^AutoCloseable conn (db/open pool)]
(let [ids (app.tasks.file-gc/collect-used-media data)
ids (db/create-array conn "uuid" ids)]
;; We assoc the file-id again to the file-media-object row
;; because there are cases that used objects refer to other
;; files and we need to ensure in the exportation process that
;; all ids matches
(->> (db/exec! conn [sql:file-media-objects ids])
(mapv #(assoc % :file-id id))))))
(def ^:private storage-object-id-xf
(comp
(mapcat (juxt :media-id :thumbnail-id))
(filter uuid?)))
(def ^:private sql:file-libraries
"WITH RECURSIVE libs AS (
SELECT fl.id, fl.deleted_at
FROM file AS fl
JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id)
WHERE flr.file_id = ANY(?)
UNION
SELECT fl.id, fl.deleted_at
FROM file AS fl
JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id)
JOIN libs AS l ON (flr.file_id = l.id)
)
SELECT DISTINCT l.id
FROM libs AS l
WHERE l.deleted_at IS NULL OR l.deleted_at > now();")
(defn- retrieve-libraries
[pool ids]
(with-open [^AutoCloseable conn (db/open pool)]
(let [ids (db/create-array conn "uuid" ids)]
(map :id (db/exec! pool [sql:file-libraries ids])))))
(def ^:private sql:file-library-rels
"SELECT * FROM file_library_rel
WHERE file_id = ANY(?)")
(defn- retrieve-library-relations
[pool ids]
(with-open [^AutoCloseable conn (db/open pool)]
(db/exec! conn [sql:file-library-rels (db/create-array conn "uuid" ids)])))
(defn- create-or-update-file
[conn params]
(let [sql (str "INSERT INTO file (id, project_id, name, revn, is_shared, data, created_at, modified_at) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?) "
"ON CONFLICT (id) DO UPDATE SET data=?")]
(db/exec-one! conn [sql
(:id params)
(:project-id params)
(:name params)
(:revn params)
(:is-shared params)
(:data params)
(:created-at params)
(:modified-at params)
(:data params)])))
;; --- GENERAL PURPOSE DYNAMIC VARS
(def ^:dynamic *state*)
(def ^:dynamic *options*)
;; --- EXPORT WRITTER
(defn- embed-file-assets
[data conn file-id]
(letfn [(walk-map-form [form state]
(cond
(uuid? (:fill-color-ref-file form))
(do
(vswap! state conj [(:fill-color-ref-file form) :colors (:fill-color-ref-id form)])
(assoc form :fill-color-ref-file file-id))
(uuid? (:stroke-color-ref-file form))
(do
(vswap! state conj [(:stroke-color-ref-file form) :colors (:stroke-color-ref-id form)])
(assoc form :stroke-color-ref-file file-id))
(uuid? (:typography-ref-file form))
(do
(vswap! state conj [(:typography-ref-file form) :typographies (:typography-ref-id form)])
(assoc form :typography-ref-file file-id))
(uuid? (:component-file form))
(do
(vswap! state conj [(:component-file form) :components (:component-id form)])
(assoc form :component-file file-id))
:else
form))
(process-group-of-assets [data [lib-id items]]
;; NOTE: there are a posibility that shape refers to a not
;; existing file because the file was removed. In this
;; case we just ignore the asset.
(if-let [lib (retrieve-file conn lib-id)]
(reduce (partial process-asset lib) data items)
data))
(process-asset [lib data [bucket asset-id]]
(let [asset (get-in lib [:data bucket asset-id])
;; Add a special case for colors that need to have
;; correctly set the :file-id prop (pending of the
;; refactor that will remove it).
asset (cond-> asset
(= bucket :colors) (assoc :file-id file-id))]
(update data bucket assoc asset-id asset)))]
(let [assets (volatile! [])]
(walk/postwalk #(cond-> % (map? %) (walk-map-form assets)) data)
(->> (deref assets)
(filter #(as-> (first %) $ (and (uuid? $) (not= $ file-id))))
(d/group-by first rest)
(reduce (partial process-group-of-assets) data)))))
(defmulti write-export ::version)
(defmulti write-section ::section)
(s/def ::output bs/output-stream?)
(s/def ::file-ids (s/every ::us/uuid :kind vector? :min-count 1))
(s/def ::include-libraries? (s/nilable ::us/boolean))
(s/def ::embed-assets? (s/nilable ::us/boolean))
(s/def ::write-export-options
(s/keys :req-un [::db/pool ::sto/storage]
:req [::output ::file-ids]
:opt [::include-libraries? ::embed-assets?]))
(defn write-export!
"Do the exportation of a speficied file in custom penpot binary
format. There are some options available for customize the output:
`::include-libraries?`: additionaly to the specified file, all the
linked libraries also will be included (including transitive
dependencies).
`::embed-assets?`: instead of including the libraryes, embedd in the
same file library all assets used from external libraries."
[{:keys [::include-libraries? ::embed-assets?] :as options}]
(us/assert! ::write-export-options options)
(us/verify!
:expr (not (and include-libraries? embed-assets?))
:hint "the `include-libraries?` and `embed-assets?` are mutally excluding options")
(write-export options))
(defmethod write-export :default
[{:keys [::output] :as options}]
(write-header! output :v1)
(with-open [output (bs/zstd-output-stream output :level 12)]
(with-open [output (bs/data-output-stream output)]
(binding [*state* (volatile! {})]
(run! (fn [section]
(l/debug :hint "write section" :section section ::l/async false)
(write-label! output section)
(let [options (-> options
(assoc ::output output)
(assoc ::section section))]
(binding [*options* options]
(write-section options))))
[:v1/metadata :v1/files :v1/rels :v1/sobjects])))))
(defmethod write-section :v1/metadata
[{:keys [pool ::output ::file-ids ::include-libraries?]}]
(let [libs (when include-libraries?
(retrieve-libraries pool file-ids))
files (into file-ids libs)]
(write-obj! output {:version cf/version :files files})
(vswap! *state* assoc :files files)))
(defmethod write-section :v1/files
[{:keys [pool ::output ::embed-assets?]}]
;; Initialize SIDS with empty vector
(vswap! *state* assoc :sids [])
(doseq [file-id (-> *state* deref :files)]
(let [file (cond-> (retrieve-file pool file-id)
embed-assets?
(update :data embed-file-assets pool file-id))
media (retrieve-file-media pool file)]
(l/debug :hint "write penpot file"
:id file-id
:media (count media)
::l/async false)
(doto output
(write-obj! file)
(write-obj! media))
(vswap! *state* update :sids into storage-object-id-xf media))))
(defmethod write-section :v1/rels
[{:keys [pool ::output ::include-libraries?]}]
(let [rels (when include-libraries?
(retrieve-library-relations pool (-> *state* deref :files)))]
(l/debug :hint "found rels" :total (count rels) ::l/async false)
(write-obj! output rels)))
(defmethod write-section :v1/sobjects
[{:keys [storage ::output]}]
(let [sids (-> *state* deref :sids)
storage (media/configure-assets-storage storage)]
(l/debug :hint "found sobjects"
:items (count sids)
::l/async false)
;; Write all collected storage objects
(write-obj! output sids)
(doseq [id sids]
(let [{:keys [size] :as obj} @(sto/get-object storage id)]
(l/debug :hint "write sobject" :id id ::l/async false)
(doto output
(write-uuid! id)
(write-obj! (meta obj)))
(with-open [^InputStream stream @(sto/get-object-data storage obj)]
(let [written (write-stream! output stream size)]
(when (not= written size)
(ex/raise :type :validation
:code :mismatch-readed-size
:hint (str/ffmt "found unexpected object size; size=% written=%" size written)))))))))
;; --- EXPORT READER
(declare lookup-index)
(declare update-index)
(declare relink-media)
(declare relink-shapes)
(defmulti read-import ::version)
(defmulti read-section ::section)
(s/def ::project-id ::us/uuid)
(s/def ::input bs/input-stream?)
(s/def ::overwrite? (s/nilable ::us/boolean))
(s/def ::migrate? (s/nilable ::us/boolean))
(s/def ::ignore-index-errors? (s/nilable ::us/boolean))
(s/def ::read-import-options
(s/keys :req-un [::db/pool ::sto/storage]
:req [::project-id ::input]
:opt [::overwrite? ::migrate? ::ignore-index-errors?]))
(defn read-import!
"Do the importation of the specified resource in penpot custom binary
format. There are some options for customize the importation
behavior:
`::overwrite?`: if true, instead of creating new files and remaping id references,
it reuses all ids and updates existing objects; defaults to `false`.
`::migrate?`: if true, applies the migration before persisting the
file data; defaults to `false`.
`::ignore-index-errors?`: if true, do not fail on index lookup errors, can
happen with broken files; defaults to: `false`.
"
[{:keys [::input ::timestamp] :or {timestamp (dt/now)} :as options}]
(us/verify! ::read-import-options options)
(let [version (read-header! input)]
(read-import (assoc options ::version version ::timestamp timestamp))))
(defmethod read-import :v1
[{:keys [pool ::input] :as options}]
(with-open [input (bs/zstd-input-stream input)]
(with-open [input (bs/data-input-stream input)]
(db/with-atomic [conn pool]
(db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED;"])
(binding [*state* (volatile! {:media [] :index {}})]
(run! (fn [section]
(l/debug :hint "reading section" :section section ::l/async false)
(assert-read-label! input section)
(let [options (-> options
(assoc ::section section)
(assoc ::input input)
(assoc :conn conn))]
(binding [*options* options]
(read-section options))))
[:v1/metadata :v1/files :v1/rels :v1/sobjects])
;; Knowing that the ids of the created files are in
;; index, just lookup them and return it as a set
(let [files (-> *state* deref :files)]
(into #{} (keep #(get-in @*state* [:index %])) files)))))))
(defmethod read-section :v1/metadata
[{:keys [::input]}]
(let [{:keys [version files]} (read-obj! input)]
(l/debug :hint "metadata readed" :version (:full version) :files files ::l/async false)
(vswap! *state* update :index update-index files)
(vswap! *state* assoc :version version :files files)))
(defmethod read-section :v1/files
[{:keys [conn ::input ::migrate? ::project-id ::timestamp ::overwrite?]}]
(doseq [expected-file-id (-> *state* deref :files)]
(let [file (read-obj! input)
media' (read-obj! input)
file-id (:id file)]
(when (not= file-id expected-file-id)
(ex/raise :type :validation
:code :inconsistent-penpot-file
:hint "the penpot file seems corrupt, found unexpected uuid (file-id)"))
;; Update index using with media
(l/debug :hint "update index with media" ::l/async false)
(vswap! *state* update :index update-index (map :id media'))
;; Store file media for later insertion
(l/debug :hint "update media references" ::l/async false)
(vswap! *state* update :media into (map #(update % :id lookup-index)) media')
(l/debug :hint "procesing file" :file-id file-id ::l/async false)
(let [file-id' (lookup-index file-id)
data (-> (:data file)
(assoc :id file-id')
(cond-> migrate? (pmg/migrate-data))
(update :pages-index relink-shapes)
(update :components relink-shapes)
(update :media relink-media))
params {:id file-id'
:project-id project-id
:name (str "Imported: " (:name file))
:revn (:revn file)
:is-shared (:is-shared file)
:data (blob/encode data)
:created-at timestamp
:modified-at timestamp}]
(l/debug :hint "create file" :id file-id' ::l/async false)
(if overwrite?
(create-or-update-file conn params)
(db/insert! conn :file params))
(when overwrite?
(db/delete! conn :file-thumbnail {:file-id file-id'}))))))
(defmethod read-section :v1/rels
[{:keys [conn ::input ::timestamp]}]
(let [rels (read-obj! input)]
;; Insert all file relations
(doseq [rel rels]
(let [rel (-> rel
(assoc :synced-at timestamp)
(update :file-id lookup-index)
(update :library-file-id lookup-index))]
(l/debug :hint "create file library link"
:file-id (:file-id rel)
:lib-id (:library-file-id rel)
::l/async false)
(db/insert! conn :file-library-rel rel)))))
(defmethod read-section :v1/sobjects
[{:keys [storage conn ::input ::overwrite?]}]
(let [storage (media/configure-assets-storage storage)
ids (read-obj! input)]
(doseq [expected-storage-id ids]
(let [id (read-uuid! input)
mdata (read-obj! input)]
(when (not= id expected-storage-id)
(ex/raise :type :validation
:code :inconsistent-penpot-file
:hint "the penpot file seems corrupt, found unexpected uuid (storage-object-id)"))
(l/debug :hint "readed storage object" :id id ::l/async false)
(let [[size resource] (read-stream! input)
hash (sto/calculate-hash resource)
content (-> (sto/content resource size)
(sto/wrap-with-hash hash))
params (-> mdata
(assoc ::sto/deduplicate? true)
(assoc ::sto/content content)
(assoc ::sto/touched-at (dt/now))
(assoc :bucket "file-media-object"))
sobject @(sto/put-object! storage params)]
(l/debug :hint "persisted storage object" :id id :new-id (:id sobject) ::l/async false)
(vswap! *state* update :index assoc id (:id sobject)))))
(doseq [item (:media @*state*)]
(l/debug :hint "inserting file media object"
:id (:id item)
:file-id (:file-id item)
::l/async false)
(let [file-id (lookup-index (:file-id item))]
(if (= file-id (:file-id item))
(l/warn :hint "ignoring file media object" :file-id (:file-id item) ::l/async false)
(db/insert! conn :file-media-object
(-> item
(assoc :file-id file-id)
(d/update-when :media-id lookup-index)
(d/update-when :thumbnail-id lookup-index))
{:on-conflict-do-nothing overwrite?}))))))
(defn- lookup-index
[id]
(let [val (get-in @*state* [:index id])]
(l/trace :fn "lookup-index" :id id :val val ::l/async false)
(when (and (not (::ignore-index-errors? *options*)) (not val))
(ex/raise :type :validation
:code :incomplete-index
:hint "looks like index has missing data"))
(or val id)))
(defn- update-index
[index coll]
(loop [items (seq coll)
index index]
(if-let [id (first items)]
(let [new-id (if (::overwrite? *options*) id (uuid/next))]
(l/trace :fn "update-index" :id id :new-id new-id ::l/async false)
(recur (rest items)
(assoc index id new-id)))
index)))
(defn- relink-shapes
"A function responsible to analyze all file data and
replace the old :component-file reference with the new
ones, using the provided file-index."
[data]
(letfn [(process-map-form [form]
(cond-> form
;; Relink image shapes
(and (map? (:metadata form))
(= :image (:type form)))
(update-in [:metadata :id] lookup-index)
;; Relink paths with fill image
(and (map? (:fill-image form))
(= :path (:type form)))
(update-in [:fill-image :id] lookup-index)
;; This covers old shapes and the new :fills.
(uuid? (:fill-color-ref-file form))
(update :fill-color-ref-file lookup-index)
;; This covers the old shapes and the new :strokes
(uuid? (:storage-color-ref-file form))
(update :stroke-color-ref-file lookup-index)
;; This covers all text shapes that have typography referenced
(uuid? (:typography-ref-file form))
(update :typography-ref-file lookup-index)
;; This covers the shadows and grids (they have directly
;; the :file-id prop)
(uuid? (:file-id form))
(update :file-id lookup-index)))]
(walk/postwalk (fn [form]
(if (map? form)
(try
(process-map-form form)
(catch Throwable cause
(l/warn :hint "failed form" :form (pr-str form) ::l/async false)
(throw cause)))
form))
data)))
(defn- relink-media
"A function responsible of process the :media attr of file data and
remap the old ids with the new ones."
[media]
(reduce-kv (fn [res k v]
(let [id (lookup-index k)]
(if (uuid? id)
(-> res
(assoc id (assoc v :id id))
(dissoc k))
res)))
media
media))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HIGH LEVEL API
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn export!
[cfg]
(let [path (tmp/tempfile :prefix "penpot.export.")
id (uuid/next)
ts (dt/now)
cs (volatile! nil)]
(try
(l/info :hint "start exportation" :export-id id)
(with-open [output (io/output-stream path)]
(binding [*position* (atom 0)]
(write-export! (assoc cfg ::output output))
path))
(catch Throwable cause
(vreset! cs cause)
(throw cause))
(finally
(l/info :hint "exportation finished" :export-id id
:elapsed (str (inst-ms (dt/diff ts (dt/now))) "ms")
:cause @cs)))))
(defn import!
[{:keys [::input] :as cfg}]
(let [id (uuid/next)
ts (dt/now)
cs (volatile! nil)]
(try
(l/info :hint "start importation" :import-id id)
(binding [*position* (atom 0)]
(with-open [input (io/input-stream input)]
(read-import! (assoc cfg ::input input))))
(catch Throwable cause
(vreset! cs cause)
(throw cause))
(finally
(l/info :hint "importation finished" :import-id id
:elapsed (str (inst-ms (dt/diff ts (dt/now))) "ms")
:error? (some? @cs)
:cause @cs)))))
;; --- Command: export-binfile
(s/def ::file-id ::us/uuid)
(s/def ::profile-id ::us/uuid)
(s/def ::include-libraries? ::us/boolean)
(s/def ::embed-assets? ::us/boolean)
(s/def ::export-binfile
(s/keys :req-un [::profile-id ::file-id ::include-libraries? ::embed-assets?]))
(sv/defmethod ::export-binfile
"Export a penpot file in a binary format."
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id file-id include-libraries? embed-assets?] :as params}]
(db/with-atomic [conn pool]
(files/check-read-permissions! conn profile-id file-id)
(let [path (export! (assoc cfg
::file-ids [file-id]
::embed-assets? embed-assets?
::include-libraries? include-libraries?))]
(with-meta {}
{:transform-response (fn [_ response]
(assoc response
:body (io/input-stream path)
:headers {"content-type" "application/octet-stream"}))}))))
(s/def ::file ::media/upload)
(s/def ::import-binfile
(s/keys :req-un [::profile-id ::project-id ::file]))
(sv/defmethod ::import-binfile
"Import a penpot file in a binary format."
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id project-id file] :as params}]
(db/with-atomic [conn pool]
(projects/check-read-permissions! conn profile-id project-id)
(import! (assoc cfg
::input (:path file)
::project-id project-id
::ignore-index-errors? true))))

View File

@@ -0,0 +1,532 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.rpc.commands.comments
(:require
[app.common.exceptions :as ex]
[app.common.geom.point :as gpt]
[app.common.spec :as us]
[app.db :as db]
[app.rpc.doc :as-alias doc]
[app.rpc.queries.files :as files]
[app.rpc.queries.teams :as teams]
[app.rpc.retry :as retry]
[app.util.blob :as blob]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.spec.alpha :as s]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; QUERY COMMANDS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn decode-row
[{:keys [participants position] :as row}]
(cond-> row
(db/pgpoint? position) (assoc :position (db/decode-pgpoint position))
(db/pgobject? participants) (assoc :participants (db/decode-transit-pgobject participants))))
;; --- COMMAND: Get Comment Threads
(declare retrieve-comment-threads)
(s/def ::team-id ::us/uuid)
(s/def ::file-id ::us/uuid)
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::get-comment-threads
(s/and (s/keys :req-un [::profile-id]
:opt-un [::file-id ::share-id ::team-id])
#(or (:file-id %) (:team-id %))))
(sv/defmethod ::get-comment-threads
[{:keys [pool] :as cfg} params]
(with-open [conn (db/open pool)]
(retrieve-comment-threads conn params)))
(def sql:comment-threads
"select distinct on (ct.id)
ct.*,
f.name as file_name,
f.project_id as project_id,
first_value(c.content) over w as content,
(select count(1)
from comment as c
where c.thread_id = ct.id) as count_comments,
(select count(1)
from comment as c
where c.thread_id = ct.id
and c.created_at >= coalesce(cts.modified_at, ct.created_at)) as count_unread_comments
from comment_thread as ct
inner join comment as c on (c.thread_id = ct.id)
inner join file as f on (f.id = ct.file_id)
left join comment_thread_status as cts
on (cts.thread_id = ct.id and
cts.profile_id = ?)
where ct.file_id = ?
window w as (partition by c.thread_id order by c.created_at asc)")
(defn retrieve-comment-threads
[conn {:keys [profile-id file-id share-id]}]
(files/check-comment-permissions! conn profile-id file-id share-id)
(->> (db/exec! conn [sql:comment-threads profile-id file-id])
(into [] (map decode-row))))
;; --- COMMAND: Get Unread Comment Threads
(declare retrieve-unread-comment-threads)
(s/def ::team-id ::us/uuid)
(s/def ::get-unread-comment-threads
(s/keys :req-un [::profile-id ::team-id]))
(sv/defmethod ::get-unread-comment-threads
[{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}]
(with-open [conn (db/open pool)]
(teams/check-read-permissions! conn profile-id team-id)
(retrieve-unread-comment-threads conn params)))
(def sql:comment-threads-by-team
"select distinct on (ct.id)
ct.*,
f.name as file_name,
f.project_id as project_id,
first_value(c.content) over w as content,
(select count(1)
from comment as c
where c.thread_id = ct.id) as count_comments,
(select count(1)
from comment as c
where c.thread_id = ct.id
and c.created_at >= coalesce(cts.modified_at, ct.created_at)) as count_unread_comments
from comment_thread as ct
inner join comment as c on (c.thread_id = ct.id)
inner join file as f on (f.id = ct.file_id)
inner join project as p on (p.id = f.project_id)
left join comment_thread_status as cts
on (cts.thread_id = ct.id and
cts.profile_id = ?)
where p.team_id = ?
window w as (partition by c.thread_id order by c.created_at asc)")
(def sql:unread-comment-threads-by-team
(str "with threads as (" sql:comment-threads-by-team ")"
"select * from threads where count_unread_comments > 0"))
(defn retrieve-unread-comment-threads
[conn {:keys [profile-id team-id]}]
(->> (db/exec! conn [sql:unread-comment-threads-by-team profile-id team-id])
(into [] (map decode-row))))
;; --- COMMAND: Get Single Comment Thread
(s/def ::id ::us/uuid)
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::get-comment-thread
(s/keys :req-un [::profile-id ::file-id ::id]
:opt-un [::share-id]))
(sv/defmethod ::get-comment-thread
[{:keys [pool] :as cfg} {:keys [profile-id file-id id share-id] :as params}]
(with-open [conn (db/open pool)]
(files/check-comment-permissions! conn profile-id file-id share-id)
(let [sql (str "with threads as (" sql:comment-threads ")"
"select * from threads where id = ?")]
(-> (db/exec-one! conn [sql profile-id file-id id])
(decode-row)))))
(defn get-comment-thread
[conn {:keys [profile-id file-id id] :as params}]
(let [sql (str "with threads as (" sql:comment-threads ")"
"select * from threads where id = ?")]
(-> (db/exec-one! conn [sql profile-id file-id id])
(decode-row))))
;; --- COMMAND: Retrieve Comments
(declare get-comments)
(s/def ::file-id ::us/uuid)
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::thread-id ::us/uuid)
(s/def ::get-comments
(s/keys :req-un [::profile-id ::thread-id]
:opt-un [::share-id]))
(sv/defmethod ::get-comments
[{:keys [pool] :as cfg} {:keys [profile-id thread-id share-id] :as params}]
(with-open [conn (db/open pool)]
(let [thread (db/get-by-id conn :comment-thread thread-id)]
(files/check-comment-permissions! conn profile-id (:file-id thread) share-id))
(get-comments conn thread-id)))
(def sql:comments
"select c.* from comment as c
where c.thread_id = ?
order by c.created_at asc")
(defn get-comments
[conn thread-id]
(->> (db/query conn :comment
{:thread-id thread-id}
{:order-by [[:created-at :asc]]})
(into [] (map decode-row))))
;; --- COMMAND: Get file comments users
(declare get-file-comments-users)
(s/def ::file-id ::us/uuid)
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::get-profiles-for-file-comments
(s/keys :req-un [::profile-id ::file-id]
:opt-un [::share-id]))
(sv/defmethod ::get-profiles-for-file-comments
"Retrieves a list of profiles with limited set of properties of all
participants on comment threads of the file."
{::doc/added "1.15"
::doc/changes ["1.15" "Imported from queries and renamed."]}
[{:keys [pool] :as cfg} {:keys [profile-id file-id share-id]}]
(with-open [conn (db/open pool)]
(files/check-comment-permissions! conn profile-id file-id share-id)
(get-file-comments-users conn file-id profile-id)))
;; All the profiles that had comment the file, plus the current
;; profile.
(def sql:file-comment-users
"WITH available_profiles AS (
SELECT DISTINCT owner_id AS id
FROM comment
WHERE thread_id IN (SELECT id FROM comment_thread WHERE file_id=?)
)
SELECT p.id,
p.email,
p.fullname AS name,
p.fullname AS fullname,
p.photo_id,
p.is_active
FROM profile AS p
WHERE p.id IN (SELECT id FROM available_profiles) OR p.id=?")
(defn get-file-comments-users
[conn file-id profile-id]
(db/exec! conn [sql:file-comment-users file-id profile-id]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; MUTATION COMMANDS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- COMMAND: Create Comment Thread
(declare upsert-comment-thread-status!)
(declare create-comment-thread)
(declare retrieve-page-name)
(s/def ::page-id ::us/uuid)
(s/def ::file-id ::us/uuid)
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::profile-id ::us/uuid)
(s/def ::position ::gpt/point)
(s/def ::content ::us/string)
(s/def ::frame-id ::us/uuid)
(s/def ::create-comment-thread
(s/keys :req-un [::profile-id ::file-id ::position ::content ::page-id ::frame-id]
:opt-un [::share-id]))
(sv/defmethod ::create-comment-thread
{::retry/max-retries 3
::retry/matches retry/conflict-db-insert?
::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}]
(db/with-atomic [conn pool]
(files/check-comment-permissions! conn profile-id file-id share-id)
(create-comment-thread conn params)))
(defn- retrieve-next-seqn
[conn file-id]
(let [sql "select (f.comment_thread_seqn + 1) as next_seqn from file as f where f.id = ?"
res (db/exec-one! conn [sql file-id])]
(:next-seqn res)))
(defn create-comment-thread
[conn {:keys [profile-id file-id page-id position content frame-id] :as params}]
(let [seqn (retrieve-next-seqn conn file-id)
now (dt/now)
pname (retrieve-page-name conn params)
thread (db/insert! conn :comment-thread
{:file-id file-id
:owner-id profile-id
:participants (db/tjson #{profile-id})
:page-name pname
:page-id page-id
:created-at now
:modified-at now
:seqn seqn
:position (db/pgpoint position)
:frame-id frame-id})]
;; Create a comment entry
(db/insert! conn :comment
{:thread-id (:id thread)
:owner-id profile-id
:created-at now
:modified-at now
:content content})
;; Make the current thread as read.
(upsert-comment-thread-status! conn profile-id (:id thread))
;; Optimistic update of current seq number on file.
(db/update! conn :file
{:comment-thread-seqn seqn}
{:id file-id})
(select-keys thread [:id :file-id :page-id])))
(defn- retrieve-page-name
[conn {:keys [file-id page-id]}]
(let [{:keys [data]} (db/get-by-id conn :file file-id)
data (blob/decode data)]
(get-in data [:pages-index page-id :name])))
;; --- COMMAND: Update Comment Thread Status
(s/def ::id ::us/uuid)
(s/def ::share-id (s/nilable ::us/uuid))
(s/def ::update-comment-thread-status
(s/keys :req-un [::profile-id ::id]
:opt-un [::share-id]))
(sv/defmethod ::update-comment-thread-status
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id id share-id] :as params}]
(db/with-atomic [conn pool]
(let [cthr (db/get-by-id conn :comment-thread id {:for-update true})]
(when-not cthr
(ex/raise :type :not-found))
(files/check-comment-permissions! conn profile-id (:file-id cthr) share-id)
(upsert-comment-thread-status! conn profile-id (:id cthr)))))
(def sql:upsert-comment-thread-status
"insert into comment_thread_status (thread_id, profile_id)
values (?, ?)
on conflict (thread_id, profile_id)
do update set modified_at = clock_timestamp()
returning modified_at;")
(defn upsert-comment-thread-status!
[conn profile-id thread-id]
(db/exec-one! conn [sql:upsert-comment-thread-status thread-id profile-id]))
;; --- COMMAND: Update Comment Thread
(s/def ::is-resolved ::us/boolean)
(s/def ::update-comment-thread
(s/keys :req-un [::profile-id ::id ::is-resolved]
:opt-un [::share-id]))
(sv/defmethod ::update-comment-thread
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id id is-resolved share-id] :as params}]
(db/with-atomic [conn pool]
(let [thread (db/get-by-id conn :comment-thread id {:for-update true})]
(when-not thread
(ex/raise :type :not-found))
(files/check-comment-permissions! conn profile-id (:file-id thread) share-id)
(db/update! conn :comment-thread
{:is-resolved is-resolved}
{:id id})
nil)))
;; --- COMMAND: Add Comment
(declare create-comment)
(s/def ::create-comment
(s/keys :req-un [::profile-id ::thread-id ::content]
:opt-un [::share-id]))
(sv/defmethod ::create-comment
{::doc/added "1.15"}
[{:keys [pool] :as cfg} params]
(db/with-atomic [conn pool]
(create-comment conn params)))
(defn create-comment
[conn {:keys [profile-id thread-id content share-id] :as params}]
(let [thread (-> (db/get-by-id conn :comment-thread thread-id {:for-update true})
(decode-row))
pname (retrieve-page-name conn thread)]
;; Standard Checks
(when-not thread (ex/raise :type :not-found))
;; Permission Checks
(files/check-comment-permissions! conn profile-id (:file-id thread) share-id)
;; Update the page-name cachedattribute on comment thread table.
(when (not= pname (:page-name thread))
(db/update! conn :comment-thread
{:page-name pname}
{:id thread-id}))
;; NOTE: is important that all timestamptz related fields are
;; created or updated on the database level for avoid clock
;; inconsistencies (some user sees something read that is not
;; read, etc...)
(let [ppants (:participants thread #{})
comment (db/insert! conn :comment
{:thread-id thread-id
:owner-id profile-id
:content content})]
;; NOTE: this is done in SQL instead of using db/update!
;; helper because currently the helper does not allow pass raw
;; function call parameters to the underlying prepared
;; statement; in a future when we fix/improve it, this can be
;; changed to use the helper.
;; Update thread modified-at attribute and assoc the current
;; profile to the participant set.
(let [ppants (conj ppants profile-id)
sql "update comment_thread
set modified_at = clock_timestamp(),
participants = ?
where id = ?"]
(db/exec-one! conn [sql (db/tjson ppants) thread-id]))
;; Update the current profile status in relation to the
;; current thread.
(upsert-comment-thread-status! conn profile-id thread-id)
;; Return the created comment object.
comment)))
;; --- COMMAND: Update Comment
(declare update-comment)
(s/def ::update-comment
(s/keys :req-un [::profile-id ::id ::content]
:opt-un [::share-id]))
(sv/defmethod ::update-comment
{::doc/added "1.15"}
[{:keys [pool] :as cfg} params]
(db/with-atomic [conn pool]
(update-comment conn params)))
(defn update-comment
[conn {:keys [profile-id id content share-id] :as params}]
(let [comment (db/get-by-id conn :comment id {:for-update true})
_ (when-not comment (ex/raise :type :not-found))
thread (db/get-by-id conn :comment-thread (:thread-id comment) {:for-update true})
_ (when-not thread (ex/raise :type :not-found))
pname (retrieve-page-name conn thread)]
(files/check-comment-permissions! conn profile-id (:file-id thread) share-id)
;; Don't allow edit comments to not owners
(when-not (= (:owner-id thread) profile-id)
(ex/raise :type :validation
:code :not-allowed))
(db/update! conn :comment
{:content content
:modified-at (dt/now)}
{:id (:id comment)})
(db/update! conn :comment-thread
{:modified-at (dt/now)
:page-name pname}
{:id (:id thread)})
nil))
;; --- COMMAND: Delete Comment Thread
(s/def ::delete-comment-thread
(s/keys :req-un [::profile-id ::id]))
(sv/defmethod ::delete-comment-thread
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id id] :as params}]
(db/with-atomic [conn pool]
(let [thread (db/get-by-id conn :comment-thread id {:for-update true})]
(when-not (= (:owner-id thread) profile-id)
(ex/raise :type :validation
:code :not-allowed))
(db/delete! conn :comment-thread {:id id})
nil)))
;; --- COMMAND: Delete comment
(s/def ::delete-comment
(s/keys :req-un [::profile-id ::id]))
(sv/defmethod ::delete-comment
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id id] :as params}]
(db/with-atomic [conn pool]
(let [comment (db/get-by-id conn :comment id {:for-update true})]
(when-not (= (:owner-id comment) profile-id)
(ex/raise :type :validation
:code :not-allowed))
(db/delete! conn :comment {:id id}))))
;; --- COMMAND: Update comment thread position
(s/def ::update-comment-thread-position
(s/keys :req-un [::profile-id ::id ::position ::frame-id]
:opt-un [::share-id]))
(sv/defmethod ::update-comment-thread-position
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id id position frame-id share-id] :as params}]
(db/with-atomic [conn pool]
(let [thread (db/get-by-id conn :comment-thread id {:for-update true})]
(files/check-comment-permissions! conn profile-id (:file-id thread) share-id)
(db/update! conn :comment-thread
{:modified-at (dt/now)
:position (db/pgpoint position)
:frame-id frame-id}
{:id (:id thread)})
nil)))
;; --- COMMAND: Update comment frame
(s/def ::update-comment-thread-frame
(s/keys :req-un [::profile-id ::id ::frame-id]
:opt-un [::share-id]))
(sv/defmethod ::update-comment-thread-frame
{::doc/added "1.15"}
[{:keys [pool] :as cfg} {:keys [profile-id id frame-id share-id] :as params}]
(db/with-atomic [conn pool]
(let [thread (db/get-by-id conn :comment-thread id {:for-update true})]
(files/check-comment-permissions! conn profile-id (:file-id thread) share-id)
(db/update! conn :comment-thread
{:modified-at (dt/now)
:frame-id frame-id}
{:id (:id thread)})
nil)))

View File

@@ -4,16 +4,16 @@
;;
;; Copyright (c) UXBOX Labs SL
(ns app.rpc.mutations.demo
(ns app.rpc.commands.demo
"A demo specific mutations."
(:require
[app.common.exceptions :as ex]
[app.common.uuid :as uuid]
[app.config :as cfg]
[app.config :as cf]
[app.db :as db]
[app.loggers.audit :as audit]
[app.rpc.mutations.profile :as profile]
[app.setup.initial-data :as sid]
[app.rpc.commands.auth :as cmd.auth]
[app.rpc.doc :as-alias doc]
[app.util.services :as sv]
[app.util.time :as dt]
[buddy.core.codecs :as bc]
@@ -22,7 +22,13 @@
(s/def ::create-demo-profile any?)
(sv/defmethod ::create-demo-profile {:auth false}
(sv/defmethod ::create-demo-profile
"A command that is responsible of creating a demo purpose
profile. It only works if the `demo-users` flag is inabled in the
configuration."
{:auth false
::doc/added "1.15"
::doc/changes ["1.15" "This methos is migrated from mutations to commands."]}
[{:keys [pool] :as cfg} _]
(let [id (uuid/next)
sem (System/currentTimeMillis)
@@ -34,20 +40,20 @@
params {:id id
:email email
:fullname fullname
:is-demo true
:deleted-at (dt/in-future cfg/deletion-delay)
:is-active true
:deleted-at (dt/in-future cf/deletion-delay)
:password password
:props {:onboarding-viewed true}}]
:props {}
}]
(when-not (cfg/get :allow-demo-users)
(when-not (contains? cf/flags :demo-users)
(ex/raise :type :validation
:code :demo-users-not-allowed
:hint "Demo users are disabled by config."))
(db/with-atomic [conn pool]
(->> (#'profile/create-profile conn params)
(#'profile/create-profile-relations conn)
(sid/load-initial-project! conn))
(->> (cmd.auth/create-profile conn params)
(cmd.auth/create-profile-relations conn))
(with-meta {:email email
:password password}

View File

@@ -0,0 +1,50 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.rpc.commands.files
(:require
[app.common.spec :as us]
[app.db :as db]
[app.rpc.doc :as-alias doc]
[app.rpc.queries.files :as files]
[app.util.services :as sv]
[clojure.spec.alpha :as s]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; QUERY COMMANDS
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Query: File Libraries used by a File
(declare retrieve-has-file-libraries)
(s/def ::file-id ::us/uuid)
(s/def ::profile-id ::us/uuid)
(s/def ::has-file-libraries
(s/keys :req-un [::profile-id ::file-id]))
(sv/defmethod ::has-file-libraries
"Checks if the file has libraries. Returns a boolean"
{::doc/added "1.15.1"}
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
(with-open [conn (db/open pool)]
(files/check-read-permissions! pool profile-id file-id)
(retrieve-has-file-libraries conn params)))
(def ^:private sql:has-file-libraries
"SELECT COUNT(*) > 0 AS has_libraries
FROM file_library_rel AS flr
JOIN file AS fl ON (flr.library_file_id = fl.id)
WHERE flr.file_id = ?::uuid
AND (fl.deleted_at IS NULL OR
fl.deleted_at > now())")
(defn- retrieve-has-file-libraries
[conn {:keys [file-id]}]
(let [row (db/exec-one! conn [sql:has-file-libraries file-id])]
(:has-libraries row)))

View File

@@ -0,0 +1,80 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.rpc.commands.ldap
(:require
[app.auth.ldap :as ldap]
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.db :as db]
[app.loggers.audit :as-alias audit]
[app.rpc.commands.auth :as cmd.auth]
[app.rpc.doc :as-alias doc]
[app.rpc.queries.profile :as profile]
[app.util.services :as sv]
[clojure.spec.alpha :as s]))
;; --- COMMAND: login-with-ldap
(declare login-or-register)
(s/def ::email ::us/email)
(s/def ::password ::us/string)
(s/def ::invitation-token ::us/string)
(s/def ::login-with-ldap
(s/keys :req-un [::email ::password]
:opt-un [::invitation-token]))
(sv/defmethod ::login-with-ldap
"Performs the authentication using LDAP backend. Only works if LDAP
is properly configured and enabled with `login-with-ldap` flag."
{:auth false
::doc/added "1.15"}
[{:keys [session tokens ldap] :as cfg} params]
(when-not ldap
(ex/raise :type :restriction
:code :ldap-not-initialized
:hide "ldap auth provider is not initialized"))
(let [info (ldap/authenticate ldap params)]
(when-not info
(ex/raise :type :validation
:code :wrong-credentials))
(let [profile (login-or-register cfg info)]
(if-let [token (:invitation-token params)]
;; If invitation token comes in params, this is because the
;; user comes from team-invitation process; in this case,
;; regenerate token and send back to the user a new invitation
;; token (and mark current session as logged).
(let [claims (tokens :verify {:token token :iss :team-invitation})
claims (assoc claims
:member-id (:id profile)
:member-email (:email profile))
token (tokens :generate claims)]
(with-meta {:invitation-token token}
{:transform-response ((:create session) (:id profile))
::audit/props (:props profile)
::audit/profile-id (:id profile)}))
(with-meta profile
{:transform-response ((:create session) (:id profile))
::audit/props (:props profile)
::audit/profile-id (:id profile)})))))
(defn- login-or-register
[{:keys [pool] :as cfg} info]
(db/with-atomic [conn pool]
(or (some->> (:email info)
(profile/retrieve-profile-data-by-email conn)
(profile/populate-additional-data conn)
(profile/decode-profile-row))
(->> (assoc info :is-active true :is-demo false)
(cmd.auth/create-profile conn)
(cmd.auth/create-profile-relations conn)
(profile/strip-private-attrs)))))

View File

@@ -0,0 +1,77 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.rpc.doc
"API autogenerated documentation."
(:require
[app.common.data :as d]
[app.config :as cf]
[app.rpc :as-alias rpc]
[app.util.services :as sv]
[app.util.template :as tmpl]
[clojure.java.io :as io]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[integrant.core :as ig]
[pretty-spec.core :as ps]
[yetti.response :as yrs]))
(defn- get-spec-str
[k]
(with-out-str
(ps/pprint (s/form k)
{:ns-aliases {"clojure.spec.alpha" "s"
"clojure.core.specs.alpha" "score"
"clojure.core" nil}})))
(defn- prepare-context
[methods]
(letfn [(gen-doc [type [name f]]
(let [mdata (meta f)]
{:type (d/name type)
:name (d/name name)
:module (-> (:ns mdata) (str/split ".") last)
:auth (:auth mdata true)
:docs (::sv/docstring mdata)
:deprecated (::deprecated mdata)
:added (::added mdata)
:changes (some->> (::changes mdata) (partition-all 2) (map vec))
:spec (get-spec-str (::sv/spec mdata))}))]
{:version (:main cf/version)
:command-methods
(->> (:commands methods)
(map (partial gen-doc :command))
(sort-by (juxt :module :name)))
:query-methods
(->> (:queries methods)
(map (partial gen-doc :query))
(sort-by (juxt :module :name)))
:mutation-methods
(->> (:mutations methods)
(map (partial gen-doc :query))
(sort-by (juxt :module :name)))}))
(defn- handler
[methods]
(if (contains? cf/flags :backend-api-doc)
(let [context (prepare-context methods)]
(fn [_ respond _]
(respond (yrs/response 200 (-> (io/resource "api-doc.tmpl")
(tmpl/render context))))))
(fn [_ respond _]
(respond (yrs/response 404)))))
(defmethod ig/pre-init-spec ::routes [_]
(s/keys :req-un [::rpc/methods]))
(defmethod ig/init-key ::routes
[_ {:keys [methods] :as cfg}]
["/_doc" {:handler (handler methods)
:allowed-methods #{:get}}])

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