Compare commits

...

543 Commits

Author SHA1 Message Date
elhombretecla
6b724d9572 Add new optimized gifs 2023-04-03 11:22:59 +02:00
elhombretecla
2789ecc22a Add new spacing optimized gif 2023-04-03 11:07:37 +02:00
Alejandro
2eba317797 Merge pull request #3099 from penpot/alotor-bugfixing-15
🐛 Fix problem with opacity in imported SVG's
2023-04-03 09:49:52 +02:00
alonso.torres
5856e3cc03 🐛 Fix problem with opacity in imported SVG's 2023-04-03 09:24:54 +02:00
Alejandro
cc469b116d Merge pull request #3093 from penpot/alotor-bugfixing-14
Alotor bugfixing 14
2023-03-31 13:56:25 +02:00
Andrey Antukh
9fe49b5546 🐛 Fix unexpected responste truncation related to shared links
that contains old data that is not used but can be still present
on the table; we should consider right now to proceed to delete
the row completly
2023-03-31 12:48:24 +02:00
Andrey Antukh
0c89b7cdb1 🐛 Fix some issues on read-only database connection 2023-03-31 12:48:24 +02:00
Andrey Antukh
90d48c1d30 Add the ability to check read-only state of connection
on the db ns helper; previously it only worked with datasource
instances
2023-03-31 12:48:24 +02:00
alonso.torres
2792c22ec9 🐛 Fix problem with overlays positioning 2023-03-31 12:22:16 +02:00
alonso.torres
a838dac01b 🐛 Fix problem when calculating group bounds 2023-03-31 11:20:42 +02:00
Alejandro Alonso
d5bbc7b1aa 🐛 Fix hide grid keyboard shortcut 2023-03-31 09:57:24 +02:00
Alejandro
e1e6816544 Merge pull request #3089 from penpot/alotor-bugfixing-13
Alotor bugfixing 13
2023-03-31 08:49:46 +02:00
alonso.torres
64c0273554 🐛 Fix problem when reorder layers removes show in viewer 2023-03-30 16:30:21 +02:00
alonso.torres
532caea169 🐛 Fix relative position overlay positioning 2023-03-30 16:20:37 +02:00
alonso.torres
0c8d8d92ba 🐛 Fix precision for wrap in flex 2023-03-30 16:20:36 +02:00
Alejandro Alonso
af428ab0ae 🐛 Fix view mode header buttons overlapping in small resolutions 2023-03-30 16:16:24 +02:00
Alejandro Alonso
85b3605c33 🐛 Fix dashboard scrolling using 'Page Up' and 'Page Down' 2023-03-30 16:16:24 +02:00
Alejandro
f1431b7b77 Merge pull request #3086 from penpot/release-info-1.18
Add new onboarding slides 1.18 info
2023-03-30 14:37:38 +02:00
elhombretecla
1ea1d53971 Add new 1.18 info 2023-03-30 14:35:10 +02:00
Alejandro Alonso
8bf01858bb 🐛 Fix alt+P combination while left bar buttons focused 2023-03-29 16:26:34 +02:00
Alejandro Alonso
f05f527336 🐛 Fix allow change team image for editor role users 2023-03-29 16:26:34 +02:00
Alejandro Alonso
fa4c7a1eb7 🐛 Fix last update project timer update after creating new file 2023-03-29 16:26:34 +02:00
Alejandro Alonso
3e6b3bcdc4 🐛 Fix unpublish and delete shared library warning messages 2023-03-29 16:26:34 +02:00
Alejandro
aca242046e Merge pull request #3082 from penpot/alotor-bugfixing-12
🐛 Fix problem with invalid SVG shape
2023-03-29 16:05:57 +02:00
alonso.torres
be27ce4914 🐛 Fix problem with invalid SVG shape 2023-03-29 16:00:07 +02:00
Alejandro
190b77ff95 Merge pull request #3080 from penpot/alotor-bugfixing-11
🐛 Fix problem with SVG and flex layout
2023-03-29 11:47:09 +02:00
alonso.torres
6e78745ed5 🐛 Fix problem with SVG and flex layout 2023-03-29 11:22:26 +02:00
Alejandro
f03def32fd Merge pull request #3078 from penpot/alotor-bugfixing-10
Alotor bugfixing 10
2023-03-29 09:32:18 +02:00
alonso.torres
a98ae69a03 🐛 Disable empty names on rename files 2023-03-29 09:18:22 +02:00
alonso.torres
43fe2390c8 🐛 Fix problem with copy/paste shapes 2023-03-29 09:12:03 +02:00
alonso.torres
d54e152a3d 🐛 Fix problem creating files in project page 2023-03-29 09:12:03 +02:00
alonso.torres
ac23c7bb4a 🐛 Remove "show in view mode" flag when moving frame to frame 2023-03-29 09:12:03 +02:00
alonso.torres
66444e27b1 🐛 Fix problem with selection colors and texts 2023-03-29 09:12:03 +02:00
Alejandro Alonso
92baf75ccd 🐛 Fix import typo 2023-03-29 08:53:10 +02:00
Alejandro Alonso
0714dc34c5 🐛 Fix spelling mistake in confirmation after importing only 1 file 2023-03-28 17:25:44 +02:00
Alejandro Alonso
aa068c70c2 🐛 Fix expanded typography on assets sidebar is moving 2023-03-28 17:25:44 +02:00
Alejandro Alonso
70974efc74 🐛 Fix dashboard left sidebar, the [x] overlaps the field 2023-03-28 17:25:44 +02:00
Alejandro Alonso
acccba6ed4 🐛 Fix invalid files amount after moving on dashboard 2023-03-28 17:25:44 +02:00
Alejandro Alonso
2e549b164f 🐛 Fix internal error on imported svgs 2023-03-28 17:25:44 +02:00
Alejandro Alonso
3df2b80427 🐛 Fix rename option is absent in RMB menu for file 2023-03-28 17:25:44 +02:00
Alejandro Alonso
0ec89e8bbe 🐛 Fix enter emails on onboarding new user creating team 2023-03-28 17:25:44 +02:00
Alejandro Alonso
694497803b 🐛 Fix don't show invite user hero to users with editor role 2023-03-28 17:25:44 +02:00
Alejandro
88db456127 Merge pull request #3075 from penpot/alotor-bugfixes-9
Alotor bugfixes 9
2023-03-28 09:08:45 +02:00
alonso.torres
6832b4a304 🐛 Fix problem with text carring over next line when changing to fixed 2023-03-27 17:06:45 +02:00
alonso.torres
5079582e1f 🐛 Fix problem with round corners scaling 2023-03-27 13:26:24 +02:00
alonso.torres
4313c45870 🐛 Fix sending invitation to existing members 2023-03-27 13:18:52 +02:00
alonso.torres
1f9e7f2ae8 🐛 Fix markdown message 2023-03-27 13:18:52 +02:00
alonso.torres
f7bba745ab 🐛 Changes to the header menu 2023-03-27 13:18:52 +02:00
alonso.torres
391ba77da9 🐛 Fix scaling of texts 2023-03-27 13:18:52 +02:00
alonso.torres
1d7b43ffbc 🐛 Fix problem with outer stroke in texts 2023-03-27 13:18:52 +02:00
alonso.torres
7256759488 🐛 Fix problem with color picker not able to change hue 2023-03-27 13:18:52 +02:00
alonso.torres
f11c782c0f 🐛 Fix problem when copy/pasting shapes 2023-03-27 13:18:52 +02:00
Pablo Alba
26aec7d129 🐛 Fix usiong padding/marging value on updating with shift 2023-03-27 13:18:08 +02:00
Pablo Alba
d61c799846 🐛 Fix padding/gap/margin remain glowing when the shape is deselected and selected again 2023-03-27 13:18:08 +02:00
Pablo Alba
c3c41c5b7d 🐛 Fix rotate board breaks paddings 2023-03-27 13:18:08 +02:00
Pablo Alba
eeb76b1e50 🐛 Fix during scale paddings glow 2023-03-27 13:18:08 +02:00
Pablo Alba
caf462e9b8 🐛 Fix padding prediction does not work with one shape 2023-03-27 13:18:08 +02:00
Pablo Alba
4d70d3b909 🐛 Bad padding gui on nil sizing 2023-03-27 13:18:08 +02:00
Alejandro Alonso
91e81823a5 🐛 Fix deleted files appear in search results 2023-03-24 12:20:00 +01:00
Alejandro Alonso
d0ab0bccb9 🐛 Fix drag and drop files from browser or file explorer under circumstances 2023-03-24 12:20:00 +01:00
Alejandro Alonso
b2b91bfa57 🐛 Fix change email and password for users signed in via social login 2023-03-24 12:20:00 +01:00
Alejandro
fc857aad08 Merge pull request #3068 from penpot/alotor-bugfixes-8
Alotor bugfixes 8
2023-03-24 08:32:38 +01:00
alonso.torres
5874922367 🐛 Fix problem with guides not showing when moving over nested frames 2023-03-23 17:13:04 +01:00
alonso.torres
1657f06a48 🐛 Select children after ungroup action 2023-03-23 16:41:00 +01:00
alonso.torres
2ad9c3cc72 🐛 Forbid empty names for assets 2023-03-23 16:11:23 +01:00
alonso.torres
fae76f6d4e 🐛 Fix problem with geometry of groups 2023-03-23 12:03:25 +01:00
alonso.torres
d0878aa805 🐛 Fix visual problem in select options 2023-03-23 12:03:25 +01:00
alonso.torres
020454e701 🐛 Fix header not showing when exiting fullscreen mode in viewer 2023-03-23 12:03:25 +01:00
alonso.torres
eedb83e863 🐛 Fix problem with text out of borders when changing from auto-width to fixed 2023-03-23 12:03:25 +01:00
alonso.torres
8a6809848e 🐛 Show warning when trying to invite a user that is already in members 2023-03-23 12:03:25 +01:00
Alejandro Alonso
3b2083134e 🐛 Fix multiplayer username sometimes is not displayed correctly 2023-03-23 09:14:58 +01:00
Alejandro Alonso
b5fc074e35 🐛 Fix horizontal margins drag don't always start from place 2023-03-23 09:14:58 +01:00
Alejandro
bc794816db Merge pull request #3066 from penpot/alotor-bugfixes-7
Alotor bugfixes 7
2023-03-22 16:28:19 +01:00
alonso.torres
f1b5ac27a9 🐛 Fix path options not showing when editing rects or ellipses 2023-03-22 14:45:19 +01:00
alonso.torres
ea438d3626 🐛 Add tooltip for text alignment options 2023-03-22 14:25:32 +01:00
alonso.torres
6d93501dc7 🐛 Fix shortcuts for alignment 2023-03-22 14:25:24 +01:00
alonso.torres
09d0a9e3f8 🐛 Fix problem when assigning color from palette or assets 2023-03-22 12:36:40 +01:00
alonso.torres
2fef90e7eb 🐛 Fix problem with selected colors and texts 2023-03-22 11:58:33 +01:00
Alejandro Alonso
c851f60de4 🐛 Fix deleted files appear in search results 2023-03-22 09:47:03 +01:00
Alejandro Alonso
6b4bca50ee bug: Fix manipulate duplicated project 2023-03-22 09:47:03 +01:00
Alejandro Alonso
f05e37590a 🐛 Fix font kerning on export 2023-03-22 09:30:07 +01:00
Alejandro Alonso
fbf06a4de0 Use tabulators to navigate layers 2023-03-22 09:23:06 +01:00
Alejandro
25014a81c3 Merge pull request #3062 from penpot/alotor-bugfixes-6
Bugfixes
2023-03-22 07:29:43 +01:00
alonso.torres
5d77f7e5b1 🐛 Fix linter issues 2023-03-21 17:11:23 +01:00
alonso.torres
131e4f2446 🐛 Fix nested frame interaction created flow in wrong frame 2023-03-21 16:50:59 +01:00
alonso.torres
8ab264af80 🐛 Fix problem in Firefox with scroll jumping when changin pages 2023-03-21 16:43:04 +01:00
Alejandro
b32e0f458c Merge pull request #3050 from penpot/alotor-fixes-layout
Alotor fixes layout
2023-03-17 08:24:30 +01:00
Alejandro
484a50949a Merge pull request #3049 from penpot/alotor-bugfixes-5
Bugfixes
2023-03-17 08:23:39 +01:00
alonso.torres
a118f34b49 🐛 Add version to presence and fixes off-page updates 2023-03-17 08:23:03 +01:00
alonso.torres
120d3005ea 🐛 Fix change layer index when moving absolute positioned shape 2023-03-16 17:39:11 +01:00
alonso.torres
2272977d67 🐛 Fix problem when editing gap/margins in layout 2023-03-16 17:11:46 +01:00
alonso.torres
cbe8587db3 🐛 Fix problem with z positioning of elements 2023-03-16 15:08:49 +01:00
alonso.torres
6a4d505033 🐛 Fix problem with alt getting stuck when alt+tab 2023-03-16 15:08:03 +01:00
alonso.torres
bd44f49175 🐛 Fix problem with board titles misplaced 2023-03-15 15:51:30 +01:00
alonso.torres
acdcf82c6c 🐛 Fix filter in layers z-index 2023-03-15 15:48:45 +01:00
Alejandro
bda2468a86 Merge pull request #3046 from penpot/alotor-bugfixes-4
Alotor bugfixes 4
2023-03-15 09:43:48 +01:00
alonso.torres
2dea2d9d27 🐛 Ignore remote changes in size 2023-03-15 09:28:46 +01:00
alonso.torres
107d607d37 🐛 Fix error with empty curves 2023-03-15 09:28:46 +01:00
alonso.torres
2c6513ac85 🐛 Fix problems with touch devices and Wacom tablets 2023-03-15 09:28:46 +01:00
alonso.torres
5bd4be1950 🐛 No select frames without fill should happen only on ctrl click 2023-03-14 15:42:46 +01:00
alonso.torres
dad88cb42e 🐛 Fix close colorpicker on Firefox when mouse-up is outside the picker 2023-03-14 15:42:45 +01:00
Alejandro
b6e01077ed Merge pull request #3044 from penpot/azazeln28-improve-rotate-matrix
Improve rotate matrix
2023-03-14 13:42:31 +01:00
Aitor
538a05b359 improve rotate matrix 2023-03-14 13:05:52 +01:00
Alejandro
1b3281457e Merge pull request #3042 from penpot/azazeln28-fix-scaling-frame-proportionally
Fix scaling frame proportionally
2023-03-14 12:33:04 +01:00
Alejandro
37b20571d2 Merge pull request #3041 from penpot/azazeln28-improve-scale-matrix
Improve scale matrix
2023-03-14 12:27:32 +01:00
Alejandro
4661fb26dc Merge pull request #3039 from penpot/alotor-fix-text-sync
Fix text sync problems
2023-03-14 12:22:14 +01:00
Aitor
b9559d99da Improve scale matrix computation 2023-03-14 11:24:15 +01:00
alonso.torres
aa4a3ef940 🐛 Fix apply structure modifiers to children 2023-03-14 11:02:11 +01:00
alonso.torres
3a2e1b5c94 Adapt scale to flex elements 2023-03-14 11:02:10 +01:00
Aitor
44c35e6aee 🐛 Fix scaling frame proportionally 2023-03-14 11:01:57 +01:00
alonso.torres
a56dc25fae 🐛 Fix problems with text synchronization 2023-03-13 13:15:36 +01:00
Pablo Alba
4eeef41ed4 🐛 Fix flex layout gaps showing for nested items 2023-03-13 11:52:00 +01:00
Alejandro Alonso
9cd207595f 📎 Prepare new development cycle 2023-03-13 10:37:34 +01:00
Alejandro
c21e0739f2 Merge pull request #3037 from penpot/alotor-bugfixes-3
Bug fixes
2023-03-13 10:34:35 +01:00
alonso.torres
83367dd519 🐛 Fix viewer layers styles 2023-03-13 10:30:12 +01:00
alonso.torres
0d9695de1d 🐛 Fix shortcuts for zoom now take into account the mouse position 2023-03-13 10:30:12 +01:00
alonso.torres
468e61e1e0 🐛 Fix snap pixel when moving path points on high zoom 2023-03-13 10:30:12 +01:00
alonso.torres
481e9b0d32 🐛 Fix unlink library color when blur color picker input 2023-03-13 10:30:12 +01:00
alonso.torres
ce85a1b1d5 🐛 Fix problem with text editor in Safari 2023-03-13 10:22:57 +01:00
Alejandro Alonso
da74d0d732 🐛 Fix viewer wrong translations 2023-03-13 10:21:53 +01:00
Pablo Alba
e6306e5109 Add visualization and mouse control to paddings, margins and gaps in frames with layout 2023-03-10 13:59:50 +01:00
Alejandro
5fae9526d6 Merge pull request #3028 from penpot/alotor-bugfixes-fixes
Fixes after QA revision
2023-03-09 17:39:23 +01:00
alonso.torres
37f52cafc9 🐛 Fix problem with rules when changing pages 2023-03-09 17:28:21 +01:00
alonso.torres
2a632512b3 🐛 Fix select in area of hidden children elements 2023-03-09 16:15:11 +01:00
alonso.torres
079cff0bc0 🐛 Fix problem with undo transactions 2023-03-09 15:53:10 +01:00
Alejandro
7954ad0edf Merge pull request #3025 from penpot/alotor-bugfixes2
Bug fixes
2023-03-09 13:21:32 +01:00
alonso.torres
2500d192e8 🐛 Changed the text dominant-baseline to use ideographic 2023-03-09 10:57:49 +01:00
Pablo Alba
480a72b6e2 🐛 Fix paddings and gaps prediction on create layout 2023-03-09 09:25:10 +01:00
alonso.torres
b2c3dc1504 🐛 Fix problem when loading fonts 2023-03-08 15:22:31 +01:00
alonso.torres
e170011e3c 🐛 Fix problem on selection numeric inputs on Firefox 2023-03-08 15:22:31 +01:00
alonso.torres
f3f611848c 🐛 Improve deeps selection of nested arboards 2023-03-08 15:22:31 +01:00
Alejandro Alonso
c3ce0eb794 Merge remote-tracking branch 'origin/staging' into develop 2023-03-08 07:25:19 +01:00
alonso.torres
1643287775 🐛 Fix problem with area selection 2023-03-07 15:55:39 +01:00
Alejandro Alonso
9e35229ebd 🐛 Fix components texts not displayed in assets panel 2023-03-07 15:22:24 +01:00
alonso.torres
046bd59726 🐛 Fix style for absolute positioning 2023-03-07 14:16:42 +01:00
Alejandro
e8027d3316 Merge pull request #3010 from penpot/niwinz-docker-frontend-2
🐳 Add backend and exporter uri env vars to frontend docker image
2023-03-07 13:08:59 +01:00
Andrey Antukh
ad34ebff89 🐳 Add backend and exporter uri env vars to frontend docker image 2023-03-07 13:08:38 +01:00
Alejandro Alonso
f733497f0f 🐛 Fix some typos on english translation 2023-03-07 10:57:37 +01:00
Alejandro Alonso
ed917fa194 🐛 Fix font translations not detected as markdown 2023-03-07 10:57:37 +01:00
Alejandro Alonso
313df74202 🐛 Fix handle correctly slashes in emails 2023-03-07 10:51:31 +01:00
Alejandro
db7c234053 Merge pull request #3019 from penpot/alotor-bugfixes
Alotor bugfixes
2023-03-07 10:49:57 +01:00
Alejandro Alonso
91c12ca34f 🐛 Fix change colors from selected colors 2023-03-07 10:42:58 +01:00
Alejandro Alonso
9f66e8e5d1 🐛 Fix search field shared styles 2023-03-07 10:37:11 +01:00
alonso.torres
b5be938480 🐛 Improve behavior for undo on text edition 2023-03-07 09:11:51 +01:00
alonso.torres
36583d1171 🐛 Allow selection of empty board by partial rect 2023-03-06 16:27:50 +01:00
alonso.torres
05e13ad05f 🐛 Fix problem when undoing multiple selected colors 2023-03-06 16:27:50 +01:00
alonso.torres
475ce08d3e 🐛 Fix selecting children from hidden parent layers 2023-03-06 16:27:50 +01:00
alonso.torres
6962e15b6d 🐛 Fix error streen when uploading wrong SVG 2023-03-06 16:27:50 +01:00
alonso.torres
7b72906096 🐛 Fix problem on finalize page 2023-03-06 16:27:13 +01:00
Eva Marco
9d43bb4252 Merge pull request #3011 from penpot/alotor-poc-css-modules
 Adds CSS modules to the build pipeline
2023-03-06 15:59:13 +01:00
alonso.torres
7dd24bb79b Merge remote-tracking branch 'origin/staging' into develop 2023-03-06 14:52:43 +01:00
Alejandro
82e402c271 Merge pull request #3012 from penpot/alotor-bug-redo
🐛 Fix problem with redo shortcut
2023-03-06 14:37:28 +01:00
alonso.torres
827ce6c42a 🐛 Fix problem with redo shortcut 2023-03-06 14:23:26 +01:00
alonso.torres
94a98a1866 Adds CSS modules to the build pipeline 2023-03-06 14:20:18 +01:00
Alejandro
0e585cd585 Merge pull request #3002 from penpot/alotor-fixes-rules
🐛 Fix problem with rules position on changing pages
2023-03-06 09:56:50 +01:00
alonso.torres
cd505ecced 🐛 Fix problem with rules position on changing pages 2023-03-03 14:20:53 +01:00
Alejandro
c8360b1994 Merge pull request #2996 from penpot/alotor-grid-layout
Partial merge of the grid layout infrastructure
2023-03-03 11:15:32 +01:00
alonso.torres
a12baf684c Review fixes 2023-03-03 10:53:46 +01:00
Alejandro Alonso
910352280c Merge remote-tracking branch 'origin/staging' into develop 2023-03-03 10:36:29 +01:00
Alejandro Alonso
dec854a012 🐛 Fix full screen not clickable on inspect mode after user entered full screen 2023-03-03 10:31:04 +01:00
Alejandro
03d4e97ad7 Merge pull request #2997 from penpot/alotor-fix-shadow-multi-selection
🐛 Fix problem withs shadows and blur on multiple selection
2023-03-02 16:35:24 +01:00
alonso.torres
e061ba8123 🐛 Fix problem with shadows and blur on multiple selection 2023-03-02 16:32:21 +01:00
alonso.torres
23104b28b6 Edition mode for grid 2023-03-02 14:05:51 +01:00
alonso.torres
b497de0dae UI Integration 2023-03-02 13:56:11 +01:00
Eva
284fc2acbc Add grid cell options 2023-03-02 13:56:11 +01:00
Eva
cc8347a871 Add options to sidebar 2023-03-02 13:56:11 +01:00
alonso.torres
eb425dc4f2 Edit cell panel 2023-03-02 13:56:11 +01:00
alonso.torres
4b7e93ab84 First draft of cell display 2023-03-02 13:56:11 +01:00
alonso.torres
6f99209a62 Grid layout editor interface 2023-03-02 13:51:41 +01:00
alonso.torres
a0cd94cfae Grid layout infrastructure 2023-03-02 13:51:27 +01:00
alonso.torres
2030f987db Performance improvements 2023-03-01 16:38:09 +01:00
Alejandro Alonso
94e87f8a7d Merge remote-tracking branch 'origin/staging' into develop 2023-03-01 16:08:57 +01:00
Alejandro Alonso
9a272f69c7 🐛 Fix height 100% cropped 2023-03-01 14:19:48 +01:00
Alejandro Alonso
fc1f2b2a9f 🐛 Fix some layout tooltips cropped 2023-03-01 14:19:48 +01:00
Alejandro Alonso
89fbe28ed1 🐛 Fix wrap and nowrap spelling issues 2023-03-01 14:19:48 +01:00
Alejandro Alonso
216d101e56 🐛 Fix flex layout min height bigger than board when height is 100% 2023-03-01 14:19:48 +01:00
Aitor
e57262136c Scale content now scales strokes, shadows, blur and corners 2023-03-01 14:11:03 +01:00
Alejandro
0b9bef066b Merge pull request #2989 from penpot/alotor-fix-position-absolute-auto
Fix position absolute auto
2023-03-01 08:38:43 +01:00
alonso.torres
4111cee3d6 🐛 Fix clipping overlay 2023-02-28 15:22:04 +01:00
alonso.torres
0ef5a37e33 🐛 Allow set position when position absolute 2023-02-28 15:22:04 +01:00
alonso.torres
8b5a36a49f 🐛 Fix problem with auto layout an absolute positioning 2023-02-28 15:22:04 +01:00
Eva
c6d1f80af2 🐛 Fix toggle collapse layer icon 2023-02-28 13:09:43 +01:00
Alejandro Alonso
b73b40b23c Merge remote-tracking branch 'origin/staging' into develop 2023-02-28 10:53:09 +01:00
Alejandro Alonso
ccf91a129c 🐛 Fix custom fonts not rendered correctly 2023-02-28 10:43:59 +01:00
Alejandro
1f3f6ce1e9 Merge pull request #2980 from penpot/eva-fix-paste-nested-boards
🐛 Fix copy paste a very nested boards inside itself
2023-02-28 09:51:21 +01:00
Eva
8f2e3d5fe4 🐛 Fix copy paste a very nested boards inside itself 2023-02-28 09:51:12 +01:00
Alejandro
b581752bd5 Merge pull request #2981 from penpot/alotor-small-fixes-flex
🐛 Fix problem when moving absolute positioned element
2023-02-28 09:46:06 +01:00
Alejandro
47481986a1 Merge pull request #2987 from penpot/alotor-fix-layout-from-selected
🐛 Fix problem when creating layout from selection
2023-02-28 09:40:18 +01:00
alonso.torres
9af0e6ca44 🐛 Fix problem when creating layout from selection 2023-02-27 16:43:59 +01:00
Alejandro Alonso
9c419ef114 Merge remote-tracking branch 'origin/staging' into develop 2023-02-27 10:39:11 +01:00
Alejandro Alonso
24fa4f71ad 📎 Update version.txt file 2023-02-27 10:37:39 +01:00
Andrey Antukh
fa21dc4cf9 📎 Fix tests 2023-02-25 10:35:00 +01:00
Andrey Antukh
9b5a321a62 📎 Fix tests 2023-02-25 10:24:41 +01:00
Andrey Antukh
738cf6407c 📎 Fix liner issue 2023-02-25 10:24:22 +01:00
Andrey Antukh
1d21ee7089 Merge remote-tracking branch 'origin/staging' into develop 2023-02-24 18:30:05 +01:00
Alejandro
2460f36bab Merge pull request #2983 from penpot/niwinz-invitations-fixes
Fix issues with invitation user flow
2023-02-24 15:50:40 +01:00
Andrey Antukh
4d627f8993 🐛 Fix incorrect invitation flow 2023-02-24 15:44:29 +01:00
Andrey Antukh
7771467aa0 🐛 Fix missing member-id field on invitation copy-link 2023-02-24 15:41:15 +01:00
Andrey Antukh
01b361fd3c Fix minor issue on contributing.md rendering output 2023-02-24 14:58:56 +01:00
alonso.torres
4d46460f90 🐛 Fix problem when moving absolute positioned element 2023-02-24 14:26:33 +01:00
alonso.torres
e9942e5527 🐛 Fix position absolute showing on first-level flex containers 2023-02-24 13:18:21 +01:00
Alejandro
8aa0e96377 Merge pull request #2979 from penpot/alotor-small-fixes-absolute
🐛 Fix problems with position absolute
2023-02-24 12:05:56 +01:00
alonso.torres
a12fce1c1f Change names for flex items 2023-02-24 11:24:23 +01:00
alonso.torres
e9d50eb10d 🐛 Fix problems with position absolute 2023-02-24 10:57:56 +01:00
Alejandro
0e97182ef0 Merge pull request #2977 from penpot/niwinz-invitations-1
 Add proper audit log for invitations
2023-02-24 10:57:13 +01:00
Andrey Antukh
f0c0e5e43a Add proper audit log for invitations 2023-02-24 10:28:07 +01:00
Alejandro
8c618f95f7 Merge pull request #2976 from penpot/alotor-flex-position-absolute
Flex position absolute & z-index
2023-02-24 07:42:43 +01:00
alonso.torres
d309628e1d Add z-index option to flex items elements 2023-02-24 07:37:37 +01:00
alonso.torres
f3f1dbc2d1 Allow for absolute positioned elements inside layout 2023-02-24 07:37:35 +01:00
alonso.torres
664f73b8a5 🐛 Fix problem when converting an empty frame to layout 2023-02-24 07:37:11 +01:00
Alejandro
94f2681223 Merge pull request #2970 from penpot/palba-layout-padding-display
 Add visualization and mouse control to paddings in frames with layout
2023-02-24 07:32:48 +01:00
Eva
a182ca3ab7 🚑 Fix CI 2023-02-23 11:03:54 +01:00
Ondřej Konečný
be865af1fc ♻️ connect values with variables in CSS and remove unused code
Signed-off-by: Ondřej Konečný <ondrej.konecny@gmail.com>
2023-02-23 10:58:27 +01:00
Prithvi Tharun
c6ad8ee110 Improves tooltip content for Corner and Padding options (#2971)
Improves tooltip content for Corner and Padding options

Closes #2964

Signed-off-by: Prithvi Tharun <ptrithu8@gmail.com>
2023-02-23 10:41:43 +01:00
Pablo Alba
4d90d36225 Add visualization and mouse control to paddings in frames with layout 2023-02-22 17:19:29 +01:00
Alejandro
fd673b39a4 Merge pull request #2959 from penpot/azazeln28-visual-feedback-scale-k
 add visual feedback to scale text
2023-02-22 12:39:19 +01:00
Aitor
1758b34eed add visual feedback to scale text 2023-02-22 12:38:55 +01:00
Alejandro Alonso
16bd5e2ebc Merge remote-tracking branch 'origin/staging' into develop 2023-02-22 11:48:41 +01:00
Alejandro
475b6ff6e0 Merge pull request #2969 from penpot/alotor-fix-redo-curve-tool
🐛 Fix problem with redo curve drawings
2023-02-22 11:25:40 +01:00
alonso.torres
a1f41c80a2 🐛 Fix problem with redo curve drawings 2023-02-22 10:43:51 +01:00
Eva Marco
4297b6fda8 Merge pull request #2968 from penpot/alotor-bug-width-fill
🐛 Fix problem with align center and width 100%
2023-02-21 16:25:08 +01:00
Eva
c892411484 📎 Update changes file 2023-02-21 15:56:23 +01:00
alonso.torres
28dce3cc8b 🐛 Fix problem with align center and width 100% 2023-02-21 15:44:13 +01:00
Aitor
96ce475206 Merge pull request #2952 from penpot/eva-ally-context-3
Add new accessibility functionality to the dashboard
2023-02-20 13:30:42 +01:00
Andrey Antukh
788dc9b3f8 Merge branch 'staging' into develop 2023-02-20 13:29:03 +01:00
Andrey Antukh
3c650ae47e Merge branch 'main' into staging 2023-02-20 13:28:51 +01:00
Andrey Antukh
80af0bb148 Merge branch 'main' into develop 2023-02-20 13:28:01 +01:00
Eva
fcb8b15ef2 Add new accessibility functionalities to dashboard 2023-02-17 15:17:24 +01:00
Alejandro
1806200613 Merge pull request #2947 from penpot/alotor-performance-improvement
 Performance improvement
2023-02-16 09:38:05 +01:00
alonso.torres
ed22e2c6d1 Performance improvement 2023-02-15 15:17:50 +01:00
Alejandro
0487539b23 Merge pull request #2946 from penpot/alotor-bug-new-frame
🐛 Fix problem with new frame inside layout
2023-02-15 13:48:57 +01:00
Alejandro
9e190d9810 Merge pull request #2945 from penpot/palba-layout-predictive-gap2
 Adds paddings and gaps prediction on layout creation
2023-02-15 13:43:01 +01:00
alonso.torres
fd15ff940f 🐛 Fix problem with new frame inside layout 2023-02-15 13:38:03 +01:00
Pablo Alba
85a47e36b5 Adds paddings and gaps prediction on layout creation 2023-02-15 12:42:23 +01:00
Alejandro
ece6193260 Merge pull request #2939 from penpot/palba-fix-undo-duplicate-with-alt
Fix duplicate with alt and undo only undo one step
2023-02-15 12:21:00 +01:00
Pablo Alba
813a188e24 🐛 Fix duplicate with alt and undo only undo one step 2023-02-15 12:20:47 +01:00
Alejandro
0f07def536 Merge pull request #2940 from penpot/alotor-layout-improvements
 Add space-evenly option
2023-02-15 12:08:36 +01:00
alonso.torres
490f5f19f1 Add space-evenly option 2023-02-15 12:08:22 +01:00
Alejandro
b3216000fd Merge pull request #2941 from penpot/alotor-fix-frame-opacity
🐛 Fix problem with opacity in frames
2023-02-15 11:57:10 +01:00
Alejandro
2ef3e4b325 Merge pull request #2944 from penpot/alotor-fix-unhandled-error
🐛 Fix crash when resizing frame
2023-02-15 11:49:17 +01:00
alonso.torres
70edd2c290 🐛 Fix crash when resizing frame 2023-02-15 09:59:28 +01:00
alonso.torres
02543b1a4f 🐛 Fix problem with opacity in frames 2023-02-14 17:54:51 +01:00
Alejandro
4852882c28 Merge pull request #2938 from penpot/alotor-fix-size-auto-center-align
🐛 Fix problem with align center and size auto
2023-02-13 16:58:15 +01:00
Alejandro
094556926e Merge pull request #2932 from penpot/eva-change-onboarding-images
💄 Update onboarding images with new style
2023-02-13 16:54:23 +01:00
alonso.torres
f3c5aed5d0 🐛 Fix problem with align center and size auto 2023-02-13 16:47:28 +01:00
Andrey Antukh
c0eb20d31d 🐛 Add missing require on rpc ns 2023-02-11 00:59:08 +01:00
Andrey Antukh
f23d29deb7 🐛 Fix unexpected exception on logger caused by log4j2 plugin 2023-02-11 00:52:23 +01:00
alonso.torres
cdd268afbc Merge remote-tracking branch 'origin/staging' into develop 2023-02-10 15:06:23 +01:00
Andrey Antukh
1ed3b3cf75 📎 Add missing restart policy to some containers
on default compose file
2023-02-10 14:07:12 +01:00
Eva
1637e82018 💄 Update onboarding images with new style 2023-02-10 13:52:53 +01:00
Andrey Antukh
c467d04d50 🐛 Fix permission issue on docker images 2023-02-10 13:37:33 +01:00
Andrey Antukh
8d19c067e8 🐛 Fix incorrect mountpoint on docker compose 2023-02-10 13:23:22 +01:00
Alejandro
a99fb7ada3 Merge pull request #2922 from penpot/palba-fix-middle-button-drags-guides
🐛 Fix middle button panning can drag guides
2023-02-09 14:27:28 +01:00
Alejandro
2f1d1a6c41 Merge pull request #2921 from penpot/eva-fix-invite-members-btn
🐛 Fix invite members text on modal button
2023-02-09 14:23:40 +01:00
Eva
7f963edf9e 🐛 Fix invite members text on modal button 2023-02-09 13:51:43 +01:00
Eva Marco
9c99d86e08 Merge pull request #2927 from penpot/alotor-fix-auto-size
Fix auto size
2023-02-09 13:51:03 +01:00
Eva
6a5bfdd7fb ❤️ Add thanks for ondrejkonec 2023-02-09 13:36:26 +01:00
Ondřej Konečný
a98ba72c12 added width property to avoid shrinking on icons 2023-02-09 13:33:06 +01:00
Eva Marco
b2b224e5a7 Merge pull request #2923 from ondrejkonec/BUG-fix-icon-resizing-on-small-displays
🐛 Add min-width property to avoid shrinking on icons
2023-02-09 13:17:34 +01:00
alonso.torres
ee42dd8b01 🐛 Fix layout on multiple selection 2023-02-09 11:18:37 +01:00
alonso.torres
da209b7507 🐛 Fix problem with auto sizes 2023-02-09 10:41:18 +01:00
Pablo Alba
d49e1f1641 🐛 Fix middle button panning can drag guides 2023-02-09 08:53:42 +01:00
Ondřej Konečný
4b9d6fc794 added width property to avoid shrinking on icons 2023-02-08 12:16:34 +01:00
Pablo Alba
8e35ad0f7f Merge pull request #2896 from penpot/eva-bugfixing-6
🐛 Fix paste a frame inside itself
2023-02-08 12:16:09 +01:00
Eva
be3a973d09 🐛 Fix paste a frame inside itself 2023-02-08 12:01:11 +01:00
Pablo Alba
c3c6e533e3 Merge pull request #2903 from ondrejkonec/a11y-udpate-change-hover-color-for-zoom-widget-button
 Added darker color for hover button in zoom widget to improve a11y
2023-02-08 11:40:38 +01:00
Pablo Alba
af30df58dc Merge pull request #2902 from glippi/drop-shadow-negative-spread
 Allow negative values for shadow spread
2023-02-08 11:31:23 +01:00
Andrey Antukh
78aea0f24e 🐛 Fix incorrect props cleaning on auditlog 2023-02-08 10:35:57 +01:00
Alejandro
3587362c4a Merge pull request #2909 from penpot/niwinz-refactor-remove-legacy
🔥 Remove legacy code and internal refactor of storage module
2023-02-08 10:34:18 +01:00
Andrey Antukh
06a30316c2 🐛 Fix logging initialization on exporter 2023-02-07 19:10:57 +01:00
Andrey Antukh
8161d3ae09 🐛 Fix incorrect props cleaning on auditlog 2023-02-07 18:38:54 +01:00
Andrey Antukh
ea470068bb ⬆️ Update jdk and postgresql-client on devenv 2023-02-07 18:17:36 +01:00
Andrey Antukh
e3378181ee 📎 Update docker log4j config 2023-02-07 18:17:08 +01:00
Andrey Antukh
9162f0e1fd 🐛 Fix auth flag handling on rpc metadata 2023-02-07 18:16:55 +01:00
Andrey Antukh
69556f19ac Add more logging to OIDC providers 2023-02-07 18:16:55 +01:00
Andrey Antukh
ab3b9cba45 ♻️ Refactor storage and assets related modules
- improve internal error handling
- add more specs and more asserts
2023-02-07 18:16:55 +01:00
Andrey Antukh
4b4f78b4cc Add minor change to srepl module options
Replace unqualified attrs with fully qualified
2023-02-07 18:16:55 +01:00
Andrey Antukh
0c48f76911 Add better spec validation on http module 2023-02-07 18:16:55 +01:00
Andrey Antukh
3cf4a3facc Improve db/pool initialization and reusability 2023-02-07 18:16:55 +01:00
Andrey Antukh
41d34de9e1 🔥 Remove zmq mentions on devenv log4j config 2023-02-07 18:16:55 +01:00
Andrey Antukh
dfdebc35c8 💄 Improve readability on error report templates 2023-02-07 18:16:55 +01:00
Andrey Antukh
bd2745d1fe ♻️ Clean email related namespaces
- Remove legacy and outdated mjml directory
- Rename namespace to a proper name
- Add more specs
2023-02-07 18:16:55 +01:00
Andrey Antukh
64f2d874fe Merge remote-tracking branch 'origin/staging' into develop 2023-02-07 18:16:37 +01:00
Andrey Antukh
6e1ce62aad Merge branch 'staging' 2023-02-07 17:06:42 +01:00
Alejandro
070ea135e5 Merge pull request #2919 from penpot/niwinz-docker-oidc-fixes
🐛 Docker & OIDC fixes
2023-02-07 16:56:22 +01:00
Andrey Antukh
5ae1fe5867 📎 Add nano editor to backend docker image 2023-02-07 16:50:58 +01:00
Andrey Antukh
eef2cba976 🐛 Fix incorrect registration flag handling on frontend
registration flag should not prevent include register on the
router because a registration process can be started from oidc
auth process
2023-02-07 16:50:52 +01:00
Andrey Antukh
1c4dcf1574 Add minor improvements to logging on docker images 2023-02-07 15:06:35 +01:00
Andrey Antukh
220b80799d Add more logging to OIDC providers 2023-02-07 14:49:12 +01:00
Andrey Antukh
58668c11f3 Merge remote-tracking branch 'origin/staging' into develop 2023-02-07 14:46:18 +01:00
elhombretecla
bab1a417df Update README.md 2023-02-07 11:55:32 +01:00
elhombretecla
b16718bfe4 Update README.md 2023-02-07 11:54:48 +01:00
elhombretecla
8f58bb4f2c Update README.md 2023-02-07 11:51:16 +01:00
elhombretecla
9cdb25344b Update README.md 2023-02-07 11:49:45 +01:00
Alejandro Alonso
22b6d4241d 📎 Update version.txt file 2023-02-07 11:47:50 +01:00
elhombretecla
96ce631784 review readme 2023-02-07 11:32:59 +01:00
Alejandro
fa02df7106 Merge pull request #2914 from penpot/alotor-small-fixes
Small fixes
2023-02-07 11:23:54 +01:00
Andrey Antukh
5d6462b2a7 🐛 Fix compatibility issues on docker upgrade path 2023-02-07 11:19:46 +01:00
Alejandro
3464842c1e Merge pull request #2917 from penpot/eva-bugfixing-7
🐛 Fix tooltips on left toolbar
2023-02-07 11:18:43 +01:00
Pablo Alba
d74af6ddc1 Revert "🐛 Fix line-height inconsistent"
This reverts commit 3974a4778a.
2023-02-07 11:18:01 +01:00
Alejandro
8cb33dc19c Merge pull request #2908 from penpot/niwinz-bugfix-oidc-autidiscover
🐛 Fix issue with oidc autodiscover
2023-02-07 11:16:00 +01:00
Eva
4912107fcc 🐛 Fix tooltips on left toolbar 2023-02-07 10:03:18 +01:00
alonso.torres
d5c7a6e547 🐛 Fix problem with auto-width and space-around 2023-02-07 00:17:16 +01:00
Andrey Antukh
f1085aadd1 🐛 Fix compatibility issues on docker upgrade path 2023-02-06 19:21:55 +01:00
alonso.torres
ca5b59f102 🐛 Fix sizing when moving shapes into/out of a layout 2023-02-06 17:50:59 +01:00
alonso.torres
a0898fbabd 🐛 Named redis volume 2023-02-06 17:00:42 +01:00
Andrey Antukh
aaf332ed18 🐛 Fix issue with oidc autodiscover 2023-02-06 14:20:57 +01:00
Christian Clauss
b05ca4bb82 🐛 Fix undefined name RuntimeException on manage.py script
Python defines [`RuntimeError`](https://docs.python.org/3.7/library/exceptions.html#RuntimeError)
but it does not define `RuntimeException` so a `NameError` will be raised when any of these lines
are executed.

% `python3 -c "RuntimeException('This is a test...')"`
```
Traceback (most recent call last):
  File "<string>", line 1, in <module>
NameError: name 'RuntimeException' is not defined
```

% `flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics`
```
./backend/scripts/manage.py:22:15: F821 undefined name 'RuntimeException'
        raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
              ^
./backend/scripts/manage.py:25:15: F821 undefined name 'RuntimeException'
        raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
              ^
./backend/scripts/manage.py:49:23: F821 undefined name 'RuntimeException'
                raise RuntimeException("unexpected response from PREPL")
                      ^
3     F821 undefined name 'RuntimeException'
3
```
2023-02-05 11:19:41 +01:00
Christian Clauss
b46b23b027 🐛 Fix undefined name RuntimeException on manage.py script
Python defines [`RuntimeError`](https://docs.python.org/3.7/library/exceptions.html#RuntimeError)
but it does not define `RuntimeException` so a `NameError` will be raised when any of these lines
are executed.

% `python3 -c "RuntimeException('This is a test...')"`
```
Traceback (most recent call last):
  File "<string>", line 1, in <module>
NameError: name 'RuntimeException' is not defined
```

% `flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics`
```
./backend/scripts/manage.py:22:15: F821 undefined name 'RuntimeException'
        raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
              ^
./backend/scripts/manage.py:25:15: F821 undefined name 'RuntimeException'
        raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
              ^
./backend/scripts/manage.py:49:23: F821 undefined name 'RuntimeException'
                raise RuntimeException("unexpected response from PREPL")
                      ^
3     F821 undefined name 'RuntimeException'
3
```
2023-02-05 11:19:28 +01:00
Andrey Antukh
01d463b4aa Merge branch 'cclauss-patch-1' into develop 2023-02-05 11:19:12 +01:00
Christian Clauss
58001f367a 🐛 Fix undefined name RuntimeException on manage.py script
Python defines [`RuntimeError`](https://docs.python.org/3.7/library/exceptions.html#RuntimeError)
but it does not define `RuntimeException` so a `NameError` will be raised when any of these lines
are executed.

% `python3 -c "RuntimeException('This is a test...')"`
```
Traceback (most recent call last):
  File "<string>", line 1, in <module>
NameError: name 'RuntimeException' is not defined
```

% `flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics`
```
./backend/scripts/manage.py:22:15: F821 undefined name 'RuntimeException'
        raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
              ^
./backend/scripts/manage.py:25:15: F821 undefined name 'RuntimeException'
        raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
              ^
./backend/scripts/manage.py:49:23: F821 undefined name 'RuntimeException'
                raise RuntimeException("unexpected response from PREPL")
                      ^
3     F821 undefined name 'RuntimeException'
3
```
2023-02-05 11:18:01 +01:00
Mario Bašić
29c0190b7a 🐛 Add mailcatch to penpot network on docker compose
Without this the backend complains that it cannot connect to the smtp host (when using mailcatcher). The reason is because the mailcatcher is not on the same network as the backend application.
2023-02-05 11:16:34 +01:00
Mario Bašić
f1b09e763e 🐛 Add mailcatch to penpot network on docker compose
Without this the backend complains that it cannot connect to the smtp host (when using mailcatcher). The reason is because the mailcatcher is not on the same network as the backend application.
2023-02-05 11:16:07 +01:00
Andrey Antukh
517210eeb5 Merge branch 'mabasic-patch-1' into develop 2023-02-05 11:15:49 +01:00
Mario Bašić
22034c22c6 🐛 Add mailcatch to penpot network on docker compose
Without this the backend complains that it cannot connect to the smtp host (when using mailcatcher). The reason is because the mailcatcher is not on the same network as the backend application.
2023-02-05 11:15:01 +01:00
Ondřej Konečný
9fae26765a added darker color for hover button in zoom widget to improve a11y 2023-02-03 13:19:21 +01:00
Alejandro
2e5e772392 Merge pull request #2900 from penpot/alotor-fix-space-around-bounds
🐛 Fix problem with space-around
2023-02-02 19:47:41 +01:00
alonso.torres
ecd4bb54c9 🐛 Fix problem with space-around 2023-02-02 19:31:02 +01:00
Alejandro Alonso
3cfc432c23 Merge remote-tracking branch 'origin/staging' 2023-02-02 18:07:20 +01:00
Alejandro Alonso
2ea81c0114 Merge remote-tracking branch 'origin/staging' into develop 2023-02-02 18:06:59 +01:00
Alejandro
a4cef16ef2 Merge pull request #2895 from penpot/niwinz-refactor-logging
♻️ Refactor logging and error reporting
2023-02-02 18:05:47 +01:00
Alejandro
e426425cb5 Merge pull request #2898 from penpot/alotor-fix-safari-thumbnails
🐛 Temporary deactivate thumbnails in Safari
2023-02-02 17:37:25 +01:00
alonso.torres
3a0cc63fa7 🐛 Temporary deactivate thumbnails in Safari 2023-02-02 17:32:12 +01:00
Alejandro
88a8370e8d Merge pull request #2897 from penpot/alotor-pro-fixes
Fixes
2023-02-02 16:08:30 +01:00
alonso.torres
e8972dd802 🐛 Fix problem with thumbnail updating 2023-02-02 15:34:35 +01:00
Andrey Antukh
1325e46192 Improve internal state validation on db module 2023-02-02 14:20:13 +01:00
Andrey Antukh
071ecca875 🐛 Fix internal executor naming issue 2023-02-02 13:38:04 +01:00
Andrey Antukh
d91e6e381e 🔧 Do not compile clj source (allow dynamic instrumentation on runtime) 2023-02-02 13:38:04 +01:00
Andrey Antukh
b54bf2bba4 📎 Add helpers for instrumenting vars 2023-02-02 13:38:04 +01:00
Andrey Antukh
32b8a2c243 ⬆️ Update dependencies on backend and common 2023-02-02 13:38:04 +01:00
Andrey Antukh
bb055a3c84 ♻️ Refactor logging subsystem and error reporting 2023-02-02 13:38:04 +01:00
alonso.torres
3e52bef6d4 🐛 Fix problem with multiple selection of layout items 2023-02-02 13:37:34 +01:00
alonso.torres
7c215dc11b 🐛 Align-items center/end weren't respected when layout was outside bounds 2023-02-02 13:37:34 +01:00
alonso.torres
48c3e3e00b 🐛 Fix problem with Safari canvas behavior 2023-02-02 13:37:34 +01:00
Alejandro
412dcae01a Merge pull request #2894 from penpot/fix-social-urls
🐛 Fix social links broken
2023-02-02 13:20:25 +01:00
Pablo Alba
cc5f245209 🐛 Fix social links broken 2023-02-02 13:13:24 +01:00
Alejandro
dc4aabe263 Merge pull request #2893 from penpot/palba-fix-can-move-shape-with-lens-zoom
🐛 Fix can move shape with lens zoom active
2023-02-02 13:12:34 +01:00
Pablo Alba
708a8ce27b 🐛 Fix can move shape with lens zoom active 2023-02-02 12:59:02 +01:00
Alejandro
7c1d9ce06f Merge pull request #2892 from penpot/alotor-fix-safari-problem
🐛 Fix problem with thumbnails in safari
2023-02-02 11:36:19 +01:00
Aitor
b0cbf09950 Merge pull request #2885 from penpot/eva-bugfixing-5
Some bug fixing
2023-02-02 11:33:25 +01:00
Aitor
f31bc7457f Merge branch 'staging' into eva-bugfixing-5 2023-02-02 11:31:49 +01:00
alonso.torres
e47ce3235e 🐛 Fix problem with thumbnails in safari 2023-02-02 11:31:10 +01:00
Alejandro
fe76e0fab6 Merge pull request #2884 from penpot/alotor-post-release-fixes
Post release fixes
2023-02-02 11:22:53 +01:00
glippi
57a89b733e Allow negative values for shadow spread 2023-02-02 10:56:58 +01:00
Alejandro
297ba10e9d Merge pull request #2886 from penpot/hiru-dashboard-names
🐛 Fix weird numeration creating elements in dashboard
2023-02-02 10:51:26 +01:00
Andrés Moya
dd2321a37b 🐛 Fix weird numeration creating elements in dashboard 2023-02-02 10:19:36 +01:00
Eva
f98630a46b 🐛 Fix invitations input on team management and onboarding modal 2023-02-02 09:37:21 +01:00
Eva
82d6ba790c 🐛 Fix button spacing on delete account modal 2023-02-02 09:37:19 +01:00
Eva
575aec209c 🐛 Fix button spacing on delete acount modal 2023-02-02 09:37:00 +01:00
alonso.torres
00e265695c Change parent/children constraint for problematic configurations 2023-02-02 09:18:53 +01:00
alonso.torres
071ac0366c 🐛 Fix problem with max-size 0 2023-02-02 09:18:53 +01:00
alonso.torres
1a2a90f829 🐛 Fix problems with strange file 2023-02-02 09:18:53 +01:00
alonso.torres
028c084b22 🐛 Add limit to growth fill shapes to the bounds of the layout 2023-02-02 09:18:53 +01:00
alonso.torres
e7e80e99bd 🐛 Fix thumbnail not updating when changing pages 2023-02-02 09:18:53 +01:00
alonso.torres
70fa169d0d 🐛 Fix problem with selection on nested boards 2023-02-02 09:18:53 +01:00
Andrey Antukh
50ee0ad3fd Merge remote-tracking branch 'origin/staging' into develop 2023-02-01 23:25:25 +01:00
Alejandro
6be83fc6d6 Merge pull request #2889 from penpot/palba-fix-commad-z-in-mac
🐛 Fix typing CMD+Z on MacOS turns the cursor into a Zoom cursor
2023-02-01 22:45:26 +01:00
Pablo Alba
1e9ece43d0 🐛 Fix typing CMD+Z on MacOS turns the cursor into a Zoom cursor 2023-02-01 21:56:30 +01:00
Andrés Moya
b7c55b4700 💄 Move all README images to a subfolder 2023-02-01 21:09:17 +01:00
Alejandro
965c0d6fa2 Merge pull request #2888 from penpot/hiru-move-readme-images
💄 Move all README images to a subfolder
2023-02-01 20:40:14 +01:00
Andrés Moya
950d5dcc2f 💄 Move all README images to a subfolder 2023-02-01 19:31:12 +01:00
Andrey Antukh
43d034798c Merge branch 'staging' 2023-02-01 18:06:50 +01:00
Andrey Antukh
86712f977d 🐛 Skip unnecesary mutation events from audit log 2023-02-01 18:06:12 +01:00
Andrey Antukh
6240323704 💄 Add minor cosmetic changes to common.spec ns 2023-02-01 16:39:59 +01:00
Andrey Antukh
d666564112 🐛 Fix loading issue on app.rpc ns 2023-02-01 16:39:59 +01:00
Andrey Antukh
f4d4559cd4 💄 Add cosmetic improvemnts on http client validation 2023-02-01 16:39:59 +01:00
Alejandro Alonso
e9c3b0567b Merge remote-tracking branch 'origin/staging' into develop 2023-02-01 13:24:39 +01:00
Alejandro Alonso
707e6c2a33 Merge remote-tracking branch 'origin/staging' 2023-02-01 13:12:34 +01:00
Alejandro
3dfd87eee1 Merge pull request #2883 from penpot/palba-fix-components-groups-names-in-list-mode
🐛 Fix components groups items show the component name in list mode
2023-02-01 10:41:33 +01:00
Alejandro
037ba19e87 Merge pull request #2882 from penpot/niwinz-webhooks-fix
🐛 Fix incorrect state management on webhooks crud
2023-02-01 10:38:33 +01:00
Pablo Alba
cdbab2c098 🐛 Fix components groups items show the component name in list mode 2023-02-01 10:23:51 +01:00
Andrey Antukh
e8ea61ee78 🐛 Fix incorrect state management on webhooks crud 2023-02-01 10:15:25 +01:00
Andrey Antukh
56cf7064f5 Merge remote-tracking branch 'origin/staging' into develop 2023-01-31 23:04:26 +01:00
Andrey Antukh
7ab91f68af Merge branch 'staging' 2023-01-31 23:02:22 +01:00
Andrey Antukh
91ececa59e 🐛 Fix backend flags on docker compose sample file 2023-01-31 23:01:58 +01:00
Andrey Antukh
8758723200 Merge pull request #2874 from penpot/niwinz-hotfix-1
🐛 Fix docker images issues in the latest release
2023-01-31 23:00:30 +01:00
Alejandro Alonso
8a968dc081 🐛 Fix upload team image 2023-01-31 22:29:05 +01:00
Andrey Antukh
f8cb505196 🐛 Fix arguments handling on docker/images/build.sh script 2023-01-31 19:48:41 +01:00
Andrey Antukh
14e3439cae 🔥 Remove admin mention from compose, still not working correctly 2023-01-31 19:08:38 +01:00
Andrey Antukh
7dd55c7f9d Revert JRE/JLINK changes from Dockerfile.backend file 2023-01-31 18:50:53 +01:00
Alejandro Alonso
e8e3398a74 🐛 Fix default nginx configuration for docker images 2023-01-31 17:42:28 +01:00
Alejandro Alonso
95cad24c18 Merge remote-tracking branch 'origin/staging' 2023-01-31 13:57:30 +01:00
Alejandro
d31138db72 Merge pull request #2868 from penpot/alotor-fix-layout-problems
Fix layout problems
2023-01-31 12:19:20 +01:00
Alejandro Alonso
2c5f35e192 🐛 Fix penpot.app urls 2023-01-31 12:10:06 +01:00
Alejandro Alonso
5a8f8ba349 🐛 Fix create team and invite 2023-01-31 12:09:13 +01:00
alonso.torres
3fe5cd3752 🐛 Fix problem when resizing layout to zero size 2023-01-31 12:07:17 +01:00
alonso.torres
da60911d81 🐛 Create new layouts without clip content 2023-01-31 12:03:23 +01:00
Alejandro Alonso
a905f49721 Merge remote-tracking branch 'origin/staging' into develop 2023-01-31 10:29:41 +01:00
Alejandro
f4f1f80050 Merge pull request #2864 from penpot/alotor-fix-hug-compacting
🐛 Fix problem with size 100% and auto parent
2023-01-31 10:29:06 +01:00
alonso.torres
18445ea5f4 🐛 Fix problem with size 100% and auto parent 2023-01-31 09:40:01 +01:00
Alejandro
2d28e02742 Merge pull request #2865 from penpot/superalex-fix-onboarding-poll
🐛 Fix onboarding poll
2023-01-31 09:30:32 +01:00
Alejandro Alonso
b0b963fb7c 🐛 Fix onboarding poll 2023-01-31 09:24:50 +01:00
Alejandro Alonso
5cfee13956 🐛 Remove beta word 2023-01-30 17:35:18 +01:00
Alejandro Alonso
11db7590eb Merge remote-tracking branch 'origin/staging' into develop 2023-01-30 15:39:17 +01:00
Alejandro
7271e98df3 Merge pull request #2862 from penpot/niwinz-multipart-encodign-bugfix
⬆️ Update yetti to v9.12
2023-01-30 15:38:44 +01:00
Andrey Antukh
f0386ef7b0 ⬆️ Update yetti to v9.12
Fixes encoding bug on multipart uploads
2023-01-30 15:29:53 +01:00
Alejandro
185cabb2fa Merge pull request #2861 from penpot/alotor-regenerate-empty-thumbnails
🐛 Try to refresh thumbnails on empty stored data in back
2023-01-30 14:05:57 +01:00
alonso.torres
3a19223264 🐛 Try to refresh thumbnails on empty stored data in back 2023-01-30 13:45:56 +01:00
Alejandro Alonso
2c38f31aa9 🐛 Fix clean archived auditlog 2023-01-30 13:11:50 +01:00
Andrés Moya
e1d1ecbc24 Merge remote-tracking branch 'origin/staging' into develop 2023-01-30 12:47:20 +01:00
Alejandro
a1dcb11261 Merge pull request #2860 from penpot/palba-fix-paste-texts-typos
🐛 Fix copy paste texts with typography assets linked
2023-01-30 12:32:32 +01:00
Pablo Alba
9f8d86a80e 🐛 Fix copy paste texts with typography assets linked 2023-01-30 12:26:31 +01:00
Alejandro Alonso
c59fc87fc4 🐛 Fix styling info at the libraries modal 2023-01-30 12:22:50 +01:00
Alejandro Alonso
3421e6ef57 🐛 Fix viewer misalignment when expanding code tab 2023-01-30 11:54:18 +01:00
Alejandro Alonso
40349c8ece 🐛 Fix line-height inconsistent 2023-01-30 11:44:20 +01:00
Alejandro
5a53376b01 Merge pull request #2859 from penpot/alotor-fix-code-generator-hangs
🐛 Fix problem with code view hanging
2023-01-30 11:18:55 +01:00
Alejandro
d4dfdaff57 Merge pull request #2854 from penpot/palba-fix-incorrect-state-after-undo-page-creation
🐛 Fix incorrect state after undo page creation
2023-01-30 11:14:17 +01:00
Pablo Alba
c7f87d0f26 🐛 Fix incorrect state after undo page creation 2023-01-30 11:13:59 +01:00
alonso.torres
c7954990f0 🐛 Fix problem with code view hanging 2023-01-30 11:13:05 +01:00
Alejandro
fe118819ce Merge pull request #2858 from penpot/info-release-1.17
Info release 1.17
2023-01-30 10:42:39 +01:00
Alejandro
073ec9ea2b Merge pull request #2856 from penpot/alotor-fix-transform-precision
🐛 Fix problem with transform when a coordinate was very close to …
2023-01-30 09:57:50 +01:00
Alejandro
f85a731969 Merge pull request #2855 from penpot/alotor-migration-fix-frame-id
🐛 Add migration to fix problem with frame-id
2023-01-30 09:39:18 +01:00
Alejandro
a3a88d7a0a Merge pull request #2850 from penpot/alotor-fix-frame-overlay
🐛 Fix frame overlays in workspace
2023-01-30 07:19:08 +01:00
Alejandro
1660dd634e Merge pull request #2857 from penpot/palba-fix-resend-invitation-doesnt-reset-expiration
🐛 Fix resend invitation doesn't reset the expiration date
2023-01-30 06:55:47 +01:00
Pablo Alba
6e698110d6 🐛 Fix resend invitation doesn't reset the expiration date 2023-01-27 16:56:19 +01:00
alonso.torres
951c67a2d5 🐛 Fix problem with transform when a coordinate was very close to zero 2023-01-27 16:09:44 +01:00
Alejandro
50b7337b8c Merge pull request #2852 from penpot/eva-bugfixing-4
Eva bugfixing 4
2023-01-27 15:07:20 +01:00
Eva
15e62ff649 🐛 Remove copy all button in fills and strokes 2023-01-27 14:58:53 +01:00
Eva
e7ddd6055f ♻️ Improve a case 2023-01-27 14:53:32 +01:00
Eva
aa3438f800 ♻️ Improve some functions 2023-01-27 14:53:31 +01:00
Eva
a45380a91c 🐛 Fix typo 2023-01-27 14:53:08 +01:00
alonso.torres
86b68aeca4 🐛 Add migration to fix problem with frame-id 2023-01-27 14:43:55 +01:00
alonso.torres
d69d392362 🐛 Fix duplicate frames 2023-01-27 13:41:20 +01:00
Andrés Moya
506c2b8d7b 🔧 Add script to rename some layout attrs in existing files 2023-01-27 13:11:46 +01:00
Eva
b463ebc17b 🐛 Fix layout spec with proper names 2023-01-27 13:11:46 +01:00
alonso.torres
f90fda2c90 🐛 Fix frame overlays in workspace 2023-01-27 11:26:35 +01:00
Eva Marco
87c5aa71a3 Merge pull request #2847 from penpot/superalex-text-weight-inspect-code
🐛 Fix text weight on inspect code
2023-01-27 10:09:11 +01:00
Alejandro Alonso
4f82f6bde4 🐛 Fix text weight on inspect code 2023-01-27 10:00:54 +01:00
Alejandro
545b3860b4 Merge pull request #2844 from penpot/alotor-fix-transparent-thumbnails
🐛 Fix problem with transparent frame thumbnails
2023-01-27 09:29:26 +01:00
alonso.torres
d4921c8eb9 🐛 Fix problem with transparent frame thumbnails 2023-01-27 09:27:18 +01:00
Alejandro Alonso
18652d0b6f 🐛 Fix outline corner radius 2023-01-27 09:26:10 +01:00
Alejandro Alonso
2dbeda1d8f 🐛 Fix outline corner radius 2023-01-27 09:14:11 +01:00
elhombretecla
9422d1e9e2 Fix wording 2023-01-27 07:45:31 +01:00
Alejandro
e0441bc16a Merge pull request #2845 from penpot/palba-text-shortcuts-multi-layer
 Apply text format shortcuts to several layers
2023-01-27 07:03:11 +01:00
Pablo Alba
d7d6166232 Apply text format shortcuts to several layers (even inside groups) 2023-01-26 18:46:16 +01:00
Alejandro
6fd6205634 Merge pull request #2841 from penpot/alotor-polishing-11
Polishing
2023-01-26 16:27:05 +01:00
Eva
7cd6f5ba70 🐛 User icons are not centered 2023-01-26 15:56:21 +01:00
Eva
9cc3cceb06 💄 Change layout flex by flex layout text 2023-01-26 15:56:21 +01:00
Eva
6f6bcd2f7e 💄 Improve warning message css in fonts 2023-01-26 15:56:21 +01:00
Alejandro Alonso
f9f3b3951f 🐛 Fix external borders not considered for thumbnails 2023-01-26 15:43:11 +01:00
Andrés Moya
22ded62000 🐛 Fix paths not flagged as modified when resized 2023-01-26 15:29:20 +01:00
Alejandro Alonso
71d104f768 🐛 Fix fills and strokes on inspect code 2023-01-26 15:18:26 +01:00
alonso.torres
5a36cbceb7 Enter to select children allow for multiselection 2023-01-26 14:27:14 +01:00
Pablo Alba
f2033c46f3 🐛 Fix ctrl+z shows zoom icon 2023-01-26 14:07:21 +01:00
alonso.torres
6b225a10b5 🐛 Fix problem with align and flex layout 2023-01-26 13:44:40 +01:00
alonso.torres
38fe6e856a 🐛 Fix problems with content between/around and auto-width 2023-01-26 12:55:40 +01:00
alonso.torres
1984109436 🐛 Fix problem with change frame groups 2023-01-26 12:55:40 +01:00
alonso.torres
9f9d9277a6 🐛 Fix problem with space-around and auto-width/height 2023-01-26 12:55:40 +01:00
alonso.torres
e041f93680 🐛 Fix space-between preserves gap distances 2023-01-26 12:55:40 +01:00
alonso.torres
2d779a4414 🐛 Fix problem with empty text rendering 2023-01-26 12:55:40 +01:00
Alejandro
21fc9289a6 Merge pull request #2835 from penpot/palba-fix-multiplayer-shadow
🐛 Fix multiuser - "Shadow" element is not updating immediately
2023-01-26 07:33:47 +01:00
Pablo Alba
b40ea3fb2a 🐛 Fix multiuser - "Shadow" element is not updating immediately 2023-01-25 17:48:40 +01:00
Pablo Alba
444e9a3081 Merge pull request #2833 from penpot/hiru-fix-unwanted-popup
🐛 Fix unneeded popup when updating local components
2023-01-25 16:51:29 +01:00
Andrés Moya
f93d305545 🐛 Fix unneeded popup when updating local components 2023-01-25 16:50:41 +01:00
Pablo Alba
09a91c87be Merge pull request #2834 from penpot/superalex-fix-ctrl-c-in-inspect-code
🐛 Fix ctrl+c on inspect code
2023-01-25 16:23:38 +01:00
Alejandro Alonso
e71d569cda 🐛 Fix ctrl+c on inspect code 2023-01-25 16:11:58 +01:00
alonso.torres
a56a9868dc 🐛 Fix error on thumbnail generation 2023-01-25 13:20:06 +01:00
Pablo Alba
a09198b46e 🐛 Fix wrong pop on setup shortcuts 2023-01-25 13:05:03 +01:00
Alejandro
c7e9c658cd Merge pull request #2827 from penpot/eva-flex-bugfixing-2
🐛 Fix missing flex props on code generation
2023-01-25 11:46:34 +01:00
Alejandro
58d7bc5c14 Merge pull request #2831 from penpot/azazeln28-fix-viewer-all-mouse-wheel-issues
Fix all viewer mouse wheel issues
2023-01-25 11:38:31 +01:00
Alejandro
e939db927e Merge pull request #2825 from penpot/palba-text-formatting-shortcuts
🎉 Shortcuts for text formatting
2023-01-25 11:33:17 +01:00
Pablo Alba
efe50479de 🎉 Shortcuts for text formatting 2023-01-25 11:32:59 +01:00
Eva
ea1b3bd058 🐛 Fix missing flex props on code generation 2023-01-25 08:19:33 +01:00
Aitor
4751d7d385 🐛 Fix all viewer mouse wheel issues 2023-01-24 17:44:15 +01:00
Hosted Weblate
bc88e30efa Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/
2023-01-24 16:17:30 +01:00
Andrés Moya
9623dbfbd6 📚 Validate translations 2023-01-24 16:17:30 +01:00
Andrés Moya
f177de6661 Merge remote-tracking branch 'weblate/develop' into translations 2023-01-24 16:17:30 +01:00
Alejandro
43043e2dc1 Merge pull request #2830 from penpot/alotor-polishing-10
Small bugfixes
2023-01-24 15:53:16 +01:00
Hosted Weblate
4a46cf2ab7 Update translation files
Updated by "Cleanup translation files" hook in Weblate.

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/
2023-01-24 15:44:15 +01:00
Andrés Moya
30725af367 📚 Validate translations 2023-01-24 15:42:34 +01:00
Andrés Moya
ece324a76f Merge remote-tracking branch 'weblate/develop' into translations 2023-01-24 15:39:47 +01:00
alonso.torres
05d21d7d07 🐛 Fix reorder layers with keys not refreshing layout 2023-01-24 15:30:20 +01:00
Amerey.eu
2ea69a84b2 🌐 Add translations for: Czech.
Currently translated at 100.0% (1215 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/cs/
2023-01-24 15:27:43 +01:00
Mikel Larreategi
f2f0d292e0 🌐 Add translations for: Basque.
Currently translated at 100.0% (1215 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/eu/
2023-01-24 15:27:40 +01:00
Ahmad HosseinBor
fc0fad29d0 🌐 Add translations for: Persian.
Currently translated at 59.9% (728 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2023-01-24 15:27:40 +01:00
Linerly
9a954ab430 🌐 Add translations for: Indonesian.
Currently translated at 100.0% (1215 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/id/
2023-01-24 15:27:39 +01:00
Vin
90caaaa14a 🌐 Add translations for: Russian.
Currently translated at 68.3% (831 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/
2023-01-24 15:27:36 +01:00
GradelerM
98360ed9e8 🌐 Add translations for: French.
Currently translated at 93.1% (1132 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2023-01-24 15:27:35 +01:00
Rubén
f64a74e7b9 🌐 Add translations for: Catalan.
Currently translated at 95.8% (1164 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ca/
2023-01-24 15:27:35 +01:00
alonso.torres
02aab37ee7 🐛 Fix bold typefaces in thumbnails 2023-01-24 15:08:58 +01:00
elhombretecla
d3aee1afa3 Add new images 2023-01-24 15:01:18 +01:00
elhombretecla
ac361cdb36 Adds new 1.17 onboarding info 2023-01-24 14:53:12 +01:00
Aitor
7ac6f49c08 Merge pull request #2808 from penpot/superalex-fix-font-vertical-metrics
🐛 Fix font vertical metrics
2023-01-24 14:26:14 +01:00
Alejandro Alonso
d3e11433bf 🐛 Fix font vertical metrics 2023-01-24 14:21:16 +01:00
Pablo Alba
771d1d9194 🐛 Fix double click and lens zoom 2023-01-24 14:19:14 +01:00
Alejandro
4a3a53182b Merge pull request #2797 from penpot/palba-lens-tool
🎉 Zoom lense tool
2023-01-24 13:34:23 +01:00
Pablo Alba
c25cf043fa 🎉 Zoom lense tool 2023-01-24 13:34:04 +01:00
elhombretecla
7440d38c94 Add new login image 2023-01-24 13:19:16 +01:00
Alejandro
a8c0d437ce Merge pull request #2828 from penpot/superalex-update-changes
 Update changes
2023-01-24 12:55:35 +01:00
Alejandro
8d683beae4 Merge pull request #2829 from penpot/fix-safari-thumbnails
🐛 Fix thumbnails for Safari browsers
2023-01-24 12:36:15 +01:00
Alejandro Alonso
4007d8713c Update changes 2023-01-24 12:26:17 +01:00
alonso.torres
ead64a1820 🐛 Fix thumbnails for Safari browsers 2023-01-24 11:56:59 +01:00
Alejandro Alonso
aae78055c8 Merge remote-tracking branch 'origin/staging' into develop 2023-01-24 10:00:51 +01:00
Alejandro
88e2a5c56e Merge pull request #2826 from penpot/alotor-thumbnails-performance
Thumbnails performance
2023-01-24 09:59:20 +01:00
alonso.torres
9782d9077f Improved and fixed thumbnail generation 2023-01-24 09:44:56 +01:00
Alejandro
b4c4511d9d Merge pull request #2823 from penpot/alotor-polishing-9
Improved thumbnails behavior
2023-01-23 17:18:53 +01:00
alonso.torres
316b3d4539 🐛 Try to remove cases when the thumbnail could be empty 2023-01-23 14:07:51 +01:00
alonso.torres
1c54e9fa4d Allow debug in for release build 2023-01-23 14:03:28 +01:00
alonso.torres
3d064b804b Improve performance on multiple options 2023-01-23 14:03:02 +01:00
Andrés Moya
77cd645e25 🔧 Update docker-compose without needing config file 2023-01-23 10:34:00 +01:00
Ahmad HosseinBor
6bdd25b5d1 🌐 Add translations for: Persian.
Currently translated at 56.3% (685 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2023-01-18 06:48:14 +01:00
Rubén
42b8c3669f 🌐 Add translations for: Catalan.
Currently translated at 95.5% (1161 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ca/
2023-01-16 15:50:44 +01:00
Alejandro
04dc9f7881 Merge pull request #2736 from penpot/superalex-fix-text-sync-hotfix
🐛 Fix text content sync and touched detection in shape displacement
2023-01-09 11:35:59 +01:00
K.B.Dharun Krishna
0039585848 🌐 Add translations for: Tamil.
Currently translated at 2.7% (34 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ta/
2023-01-06 11:48:00 +01:00
Andrés Moya
0863a96f93 🐛 Fix text content sync and touched detection in shape displacement 2023-01-05 13:26:33 +01:00
Alejandro
216a43cc43 Merge pull request #2731 from penpot/superalex-fix-enter-events-hotfix
🐛 Fix enter events
2023-01-05 07:02:34 +01:00
Alejandro Alonso
05431cc757 🐛 Fix enter events 2023-01-04 13:23:05 +01:00
Fernando Krik
5e6d079fea 🌐 Add translations for: Portuguese (Portugal).
Currently translated at 99.9% (1214 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_PT/
2022-12-29 00:47:37 +01:00
Fernando Krik
4c392e3a31 🌐 Add translations for: Portuguese (Brazil).
Currently translated at 100.0% (1215 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/
2022-12-27 23:22:16 +01:00
matl-17
8aa1f29865 🌐 Add translations for: Czech.
Currently translated at 15.8% (192 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/cs/
2022-12-22 01:45:30 +01:00
Midka
62b730f5f0 🌐 Add translations for: Finnish (fin_FI).
Currently translated at 5.0% (61 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fin_FI/
2022-12-20 21:48:51 +01:00
andy
de7fb393c9 🌐 Added translation for: Finnish (fin_FI). 2022-12-19 10:22:15 +01:00
Ahmad HosseinBor
fed320be36 🌐 Add translations for: Persian.
Currently translated at 55.3% (673 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2022-12-17 17:49:47 +01:00
Ahmad HosseinBor
1b30d023ef 🌐 Add translations for: Persian.
Currently translated at 54.9% (668 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/
2022-12-10 16:48:15 +01:00
Alex Howell
806a818cb3 🌐 Add translations for: Romanian.
Currently translated at 99.5% (1210 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ro/
2022-12-10 16:48:15 +01:00
GradelerM
4014fec195 🌐 Add translations for: French.
Currently translated at 92.0% (1119 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-12-10 16:48:14 +01:00
Pablo Alba
cae0311db6 🌐 Added translation for: Korean. 2022-12-09 16:36:55 +01:00
María Ozámiz
7c6dfef1c6 🌐 Add translations for: Galician.
Currently translated at 33.9% (412 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/gl/
2022-12-06 22:48:04 +01:00
Alex Howell
51440964a7 🌐 Add translations for: Romanian.
Currently translated at 76.4% (929 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ro/
2022-12-06 22:48:04 +01:00
Ally Tiago
f7a819fd57 🌐 Add translations for: Portuguese (Brazil).
Currently translated at 99.9% (1214 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/
2022-12-06 22:48:03 +01:00
Salman Hossain Saif
378b9f3f67 🌐 Add translations for: Bengali.
Currently translated at 1.5% (19 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/bn/
2022-12-03 15:47:26 +01:00
María Ozámiz
cb3a7a1da0 🌐 Add translations for: Galician.
Currently translated at 31.1% (379 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/gl/
2022-12-03 15:47:25 +01:00
andy
6f4b533fc7 🌐 Added translation for: Bengali. 2022-12-02 14:10:07 +01:00
GradelerM
dbdc656e3e 🌐 Add translations for: French.
Currently translated at 89.1% (1083 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-12-01 11:47:02 +01:00
Dário
797aa68bfa 🌐 Add translations for: Chinese (Simplified).
Currently translated at 100.0% (1215 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2022-11-29 15:48:08 +01:00
HIYIZI
80c17e5dcf 🌐 Add translations for: Chinese (Simplified).
Currently translated at 100.0% (1215 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2022-11-29 15:48:07 +01:00
K.B.Dharun Krishna
7083c4e111 🌐 Add translations for: Tamil.
Currently translated at 2.1% (26 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ta/
2022-11-28 15:06:27 +01:00
HIYIZI
e0e0f0a9b1 🌐 Add translations for: Chinese (Simplified).
Currently translated at 99.5% (1210 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2022-11-28 15:06:27 +01:00
Maemolee
b57c5ec92a 🌐 Add translations for: Chinese (Simplified).
Currently translated at 99.5% (1210 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/
2022-11-28 15:06:27 +01:00
Bogi Napoleon Wennerstrøm
08eb2bceb1 🌐 Add translations for: Faroese.
Currently translated at 14.1% (172 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fo/
2022-11-18 20:48:29 +01:00
Tummas Jóhan Sigvardsen
f439d10128 🌐 Add translations for: Faroese.
Currently translated at 13.2% (161 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fo/
2022-11-17 20:21:01 +01:00
Bogi Napoleon Wennerstrøm
b87022ef28 🌐 Add translations for: Faroese.
Currently translated at 13.2% (161 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fo/
2022-11-17 20:21:01 +01:00
Stas Haas
17d1c16d9c 🌐 Add translations for: German.
Currently translated at 100.0% (1215 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2022-11-05 11:09:39 +01:00
Tummas Jóhan Sigvardsen
0e3675ce1f 🌐 Add translations for: Faroese.
Currently translated at 11.6% (141 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fo/
2022-11-01 15:29:29 +01:00
Bogi Napoleon Wennerstrøm
92cd4693f4 🌐 Add translations for: Faroese.
Currently translated at 11.6% (141 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fo/
2022-11-01 15:29:28 +01:00
Tatsuto Yamamoto
7905b9fbeb 🌐 Add translations for: Japanese (jpn_JP).
Currently translated at 21.8% (265 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/jpn_JP/
2022-11-01 15:29:28 +01:00
Mikel Larreategi
0b4318b32c 🌐 Add translations for: Basque.
Currently translated at 99.5% (1210 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/eu/
2022-10-29 15:03:18 +02:00
Stas Haas
0fd80bedf2 🌐 Add translations for: German.
Currently translated at 100.0% (1215 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2022-10-29 15:03:17 +02:00
Marius
380f297af3 🌐 Add translations for: German.
Currently translated at 100.0% (1215 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2022-10-29 15:03:17 +02:00
Henrik Steffens
f8f0944816 🌐 Add translations for: German.
Currently translated at 100.0% (1215 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2022-10-29 15:03:17 +02:00
nautilusx
a5f833759a 🌐 Add translations for: German.
Currently translated at 100.0% (1215 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/
2022-10-29 15:03:16 +02:00
Tummas Jóhan Sigvardsen
7ab90c6b6f 🌐 Add translations for: Faroese.
Currently translated at 9.0% (110 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fo/
2022-10-27 15:02:46 +02:00
Pablo Alba
a5a0d51ca7 🌐 Add translations for: French.
Currently translated at 87.9% (1069 of 1215 strings)

Translation: Penpot/frontend
Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/
2022-10-27 15:02:46 +02:00
446 changed files with 24742 additions and 10793 deletions

View File

@@ -1,17 +1,131 @@
# CHANGELOG
## :rocket: Next
### :boom: Breaking changes & Deprecations
## 1.18.0
### :sparkles: New features
- Adds more accessibility improvements in dashboard [Taiga #4577](https://tree.taiga.io/project/penpot/us/4577)
- Adds paddings and gaps prediction on layout creation [Taiga #4838](https://tree.taiga.io/project/penpot/task/4838)
- Add visual feedback when proportionally scaling text elements with **K** [Taiga #3415](https://tree.taiga.io/project/penpot/us/3415)
- Add visualization and mouse control to paddings, margins and gaps in frames with layout [Taiga #4839](https://tree.taiga.io/project/penpot/task/4839)
- Allow for absolute positioned elements inside layout [Taiga #4834](https://tree.taiga.io/project/penpot/us/4834)
- Add z-index option for flex layout items [Taiga #2980](https://tree.taiga.io/project/penpot/us/2980)
- Scale content proportionally affects strokes, shadows, blurs and corners [Taiga #1951](https://tree.taiga.io/project/penpot/us/1951)
- Use tabulators to navigate layers [Taiga #5010](https://tree.taiga.io/project/penpot/issue/5010)
### :bug: Bugs fixed
### :arrow_up: Deps updates
- Fix problem with rules position on changing pages [Taiga #4847](https://tree.taiga.io/project/penpot/issue/4847)
- Fix error streen when uploading wrong SVG [#2995](https://github.com/penpot/penpot/issues/2995)
- Fix selecting children from hidden parent layers [Taiga #4934](https://tree.taiga.io/project/penpot/issue/4934)
- Fix problem when undoing multiple selected colors [Taiga #4920](https://tree.taiga.io/project/penpot/issue/4920)
- Allow selection of empty board by partial rect [Taiga #4806](https://tree.taiga.io/project/penpot/issue/4806)
- Improve behavior for undo on text edition [Taiga #4693](https://tree.taiga.io/project/penpot/issue/4693)
- Improve deeps selection of nested arboards [Taiga #4913](https://tree.taiga.io/project/penpot/issue/4913)
- Fix problem on selection numeric inputs on Firefox [#2991](https://github.com/penpot/penpot/issues/2991)
- Changed the text dominant-baseline to use ideographic [Taiga #4791](https://tree.taiga.io/project/penpot/issue/4791)
- Viewer wrong translations [Github #3035](https://github.com/penpot/penpot/issues/3035)
- Fix problem with text editor in Safari
- Fix unlink library color when blur color picker input [#3026](https://github.com/penpot/penpot/issues/3026)
- Fix snap pixel when moving path points on high zoom [#2930](https://github.com/penpot/penpot/issues/2930)
- Fix shortcuts for zoom now take into account the mouse position [#2924](https://github.com/penpot/penpot/issues/2924)
- Fix close colorpicker on Firefox when mouse-up is outside the picker [#2911](https://github.com/penpot/penpot/issues/2911)
- Fix problems with touch devices and Wacom tablets [#2216](https://github.com/penpot/penpot/issues/2216)
- Fix problem with board titles misplaced [Taiga #4738](https://tree.taiga.io/project/penpot/issue/4738)
- Fix problem with alt getting stuck when alt+tab [Taiga #5013](https://tree.taiga.io/project/penpot/issue/5013)
- Fix problem with z positioning of elements [Taiga #5014](https://tree.taiga.io/project/penpot/issue/5014)
- Fix problem in Firefox with scroll jumping when changin pages [#3052](https://github.com/penpot/penpot/issues/3052)
- Fix nested frame interaction created flow in wrong frame [Taiga #5043](https://tree.taiga.io/project/penpot/issue/5043)
- Font-Kerning does not work on Artboard Export to PNG/JPG/PDF [#3029](https://github.com/penpot/penpot/issues/3029)
- Fix manipulate duplicated project (delete, duplicate, rename, pin/unpin...) [Taiga #5027](https://tree.taiga.io/project/penpot/issue/5027)
- Fix deleted files appear in search results [Taiga #5002](https://tree.taiga.io/project/penpot/issue/5002)
- Fix problem with selected colors and texts [Taiga #5051](https://tree.taiga.io/project/penpot/issue/5051)
- Fix problem when assigning color from palette or assets [Taiga #5050](https://tree.taiga.io/project/penpot/issue/5050)
- Fix shortcuts for alignment [Taiga #5030](https://tree.taiga.io/project/penpot/issue/5030)
- Fix path options not showing when editing rects or ellipses [Taiga #5053](https://tree.taiga.io/project/penpot/issue/5053)
- Fix tooltips for some alignment options are truncated on design tab [Taiga #5040](https://tree.taiga.io/project/penpot/issue/5040)
- Fix horizontal margins drag don't always start from place [Taiga #5020](https://tree.taiga.io/project/penpot/issue/5020)
- Fix multiplayer username sometimes is not displayed correctly [Taiga #4400](https://tree.taiga.io/project/penpot/issue/4400)
- Show warning when trying to invite a user that is already in members [Taiga #4147](https://tree.taiga.io/project/penpot/issue/4147)
- Fix problem with text out of borders when changing from auto-width to fixed [Taiga #4308](https://tree.taiga.io/project/penpot/issue/4308)
- Fix header not showing when exiting fullscreen mode in viewer [Taiga #4244](https://tree.taiga.io/project/penpot/issue/4244)
- Fix visual problem in select options [Taiga #5028](https://tree.taiga.io/project/penpot/issue/5028)
- Forbid empty names for assets [Taiga #5056](https://tree.taiga.io/project/penpot/issue/5056)
- Select children after ungroup action [Taiga #4917](https://tree.taiga.io/project/penpot/issue/4917)
- Fix problem with guides not showing when moving over nested frames [Taiga #4905](https://tree.taiga.io/project/penpot/issue/4905)
- Fix change email and password for users signed in via social login [Taiga #4273](https://tree.taiga.io/project/penpot/issue/4273)
- Fix drag and drop files from browser or file explorer under circumstances [Taiga #5054](https://tree.taiga.io/project/penpot/issue/5054)
- Fix problem when copy/pasting shapes [Taiga #4931](https://tree.taiga.io/project/penpot/issue/4931)
- Fix problem with color picker not able to change hue [Taiga #5065](https://tree.taiga.io/project/penpot/issue/5065)
- Fix problem with outer stroke in texts [Taiga #5078](https://tree.taiga.io/project/penpot/issue/5078)
- Fix problem with text carring over next line when changing to fixed [Taiga #5067](https://tree.taiga.io/project/penpot/issue/5067)
- Fix don't show invite user hero to users with editor role [Taiga #5086](https://tree.taiga.io/project/penpot/issue/5086)
- Fix enter emails on onboarding new user creating team [Taiga #5089](https://tree.taiga.io/project/penpot/issue/5089)
- Fix invalid files amount after moving on dashboard [Taiga #5080](https://tree.taiga.io/project/penpot/issue/5080)
- Fix dashboard left sidebar, the [x] overlaps the field [Taiga #5064](https://tree.taiga.io/project/penpot/issue/5064)
- Fix expanded typography on assets sidebar is moving [Taiga #5063](https://tree.taiga.io/project/penpot/issue/5063)
- Fix spelling mistake in confirmation after importing only 1 file [Taiga #5095](https://tree.taiga.io/project/penpot/issue/5095)
- Fix problem with selection colors and texts [Taiga #5079](https://tree.taiga.io/project/penpot/issue/5079)
- Remove "show in view mode" flag when moving frame to frame [Taiga #5091](https://tree.taiga.io/project/penpot/issue/5091)
- Fix problem creating files in project page [Taiga #5060](https://tree.taiga.io/project/penpot/issue/5060)
- Disable empty names on rename files [Taiga #5088](https://tree.taiga.io/project/penpot/issue/5088)
- Fix problem with SVG and flex layout [Taiga #](https://tree.taiga.io/project/penpot/issue/5099)
- Fix unpublish and delete shared library warning messages [Taiga #5090](https://tree.taiga.io/project/penpot/issue/5090)
- Fix last update project timer update after creating new file [Taiga #5096](https://tree.taiga.io/project/penpot/issue/5096)
- Fix dashboard scrolling using 'Page Up' and 'Page Down' [Taiga #5081](https://tree.taiga.io/project/penpot/issue/5081)
- Fix view mode header buttons overlapping in small resolutions [Taiga #5058](https://tree.taiga.io/project/penpot/issue/5058)
- Fix precision for wrap in flex [Taiga #5072](https://tree.taiga.io/project/penpot/issue/5072)
- Fix relative position overlay positioning [Taiga #5092](https://tree.taiga.io/project/penpot/issue/5092)
- Fix hide grid keyboard shortcut [Github #3071](https://github.com/penpot/penpot/pull/3071)
- Fix problem with opacity in imported SVG's [Taiga #4923](https://tree.taiga.io/project/penpot/issue/4923)
### :heart: Community contributions by (Thank you!)
- To @ondrejkonec: for contributing to the code with:
- Refactor CSS variables [Github #2948](https://github.com/penpot/penpot/pull/2948)
## 1.17.3
### :bug: Bugs fixed
- Fix copy and paste very nested inside itself [Taiga #4848](https://tree.taiga.io/project/penpot/issue/4848)
- Fix custom fonts not rendered correctly [Taiga #4874](https://tree.taiga.io/project/penpot/issue/4874)
- Fix problem with shadows and blur on multiple selection
- Fix problem with redo shortcut
- Fix Component texts not displayed in assets panel [Taiga #4907](https://tree.taiga.io/project/penpot/issue/4907)
- Fix search field has implemented shared styles for "close icon" and "search icon" [Taiga #4927](https://tree.taiga.io/project/penpot/issue/4927)
- Fix Handling correctly slashes "/" in emails [Taiga #4906](https://tree.taiga.io/project/penpot/issue/4906)
- Fix Change text color from selected colors [Taiga #4933](https://tree.taiga.io/project/penpot/issue/4933)
### :sparkles: Enhancements
- Adds environment variables for specifying the export and backend URI for the frontend docker image, thanks to @Supernova3339 for the initial PR and suggestion [Github #2984](https://github.com/penpot/penpot/issues/2984)
## 1.17.2
### :bug: Bugs fixed
- Fix invite members button text [Taiga #4794](https://tree.taiga.io/project/penpot/issue/4794)
- Fix problem with opacity in frames [Taiga #4795](https://tree.taiga.io/project/penpot/issue/4795)
- Fix correct behaviour for space-around and added space-evenly option
- Fix duplicate with alt and undo only undo one step [Taiga #4746](https://tree.taiga.io/project/penpot/issue/4746)
- Fix problem creating frames inside layout [Taiga #4844](https://tree.taiga.io/project/penpot/issue/4844)
- Fix paste board inside itself [Taiga #4775](https://tree.taiga.io/project/penpot/issue/4775)
- Fix middle button panning can drag guides [Taiga #4266](https://tree.taiga.io/project/penpot/issue/4266)
### :heart: Community contributions by (Thank you!)
- To @ondrejkonec: for some code contributions on this release.
## 1.17.1
### :bug: Bugs fixed
- Fix components groups items show the component name in list mode [Taiga #4770](https://tree.taiga.io/project/penpot/issue/4770)
- Fix typing CMD+Z on MacOS turns the cursor into a Zoom cursor [Taiga #4778](https://tree.taiga.io/project/penpot/issue/4778)
- Fix white space on small screens [Taiga #4774](https://tree.taiga.io/project/penpot/issue/4774)
- Fix button spacing on delete acount modal [Taiga #4762](https://tree.taiga.io/project/penpot/issue/4762)
- Fix invitations input on team management and onboarding modal [Taiga #4760](https://tree.taiga.io/project/penpot/issue/4760)
- Fix weird numeration creating new elements in dashboard [Taiga #4755](https://tree.taiga.io/project/penpot/issue/4755)
- Fix can move shape with lens zoom active [Taiga #4787](https://tree.taiga.io/project/penpot/issue/4787)
- Fix social links broken [Taiga #4759](https://tree.taiga.io/project/penpot/issue/4759)
- Fix tooltips on left toolbar [Taiga #4793](https://tree.taiga.io/project/penpot/issue/4793)
## 1.17.0
@@ -23,6 +137,16 @@
- Handoff visual improvements [Taiga #3124](https://tree.taiga.io/project/penpot/us/3124)
- Dynamic alignment only in sight [Github 1971](https://github.com/penpot/penpot/issues/1971)
- Add some accessibility to shortcut panel [Taiga #4713](https://tree.taiga.io/project/penpot/issue/4713)
- Add shortcuts for text editing [Taiga #2052](https://tree.taiga.io/project/penpot/us/2052)
- Second level boards treated as groups in terms of selection [Taiga #4269](https://tree.taiga.io/project/penpot/us/4269)
- Performance improvements both for backend and frontend
- Accessibility improvements for login area [Taiga #4353](https://tree.taiga.io/project/penpot/us/4353)
- Outbound webhooks [Taiga #4577](https://tree.taiga.io/project/penpot/us/4577)
- Add copy invitation link to the invitation options [Taiga #4213](https://tree.taiga.io/project/penpot/us/4213)
- Dynamic alignment only in sight [Taiga #3537](https://tree.taiga.io/project/penpot/us/3537)
- Improve naming of layers [Taiga #4036](https://tree.taiga.io/project/penpot/us/4036)
- Add zoom lense [Taiga #4691](https://tree.taiga.io/project/penpot/us/4691)
- Detect potential problems with custom font vertical metrics [Taiga #4697](https://tree.taiga.io/project/penpot/us/4697)
### :bug: Bugs fixed
@@ -63,6 +187,18 @@
- Fix hidden layers inside groups become visible after the group visibility is changed[Taiga #4710](https://tree.taiga.io/project/penpot/issue/4710)
- Fix format of HSLA color on viewer [Taiga #4393](https://tree.taiga.io/project/penpot/issue/4393)
- Fix some typos [Taiga #4724](https://tree.taiga.io/project/penpot/issue/4724)
- Fix ctrl+c for inspect code [Taiga #4739](https://tree.taiga.io/project/penpot/issue/4739)
- Fix text in custom font is not at the expected position at export [Taiga #4394](https://tree.taiga.io/project/penpot/issue/4394)
- Fix unneeded popup when updating local components [Taiga #4430](https://tree.taiga.io/project/penpot/issue/4430)
- Fix multiuser - "Shadow" element is not updating immediately [Taiga #4709](https://tree.taiga.io/project/penpot/issue/4709)
- Fix paths not flagged as modified when resized [Taiga #4742](https://tree.taiga.io/project/penpot/issue/4742)
- Fix resend invitation doesn't reset the expiration date [Taiga #4741](https://tree.taiga.io/project/penpot/issue/4741)
- Fix incorrect state after undo page creation [Taiga #4690](https://tree.taiga.io/project/penpot/issue/4690)
- Fix copy paste texts with typography assets linked [Taiga #4750](https://tree.taiga.io/project/penpot/issue/4750)
### :heart: Community contributions by (Thank you!)
- To @iprithvitharun: let's make UX Writing contributions in Open Source a trend!
## 1.16.2-beta

View File

@@ -101,14 +101,14 @@ Each commit should have:
Examples of good commit messages:
- :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
- `: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

@@ -4,7 +4,7 @@
<h1 align="center">
<br>
<img src="https://penpot.app/images/readme/readme-logo.jpg" alt="PENPOT">
<img src="https://penpot.app/images/readme/git-readme-header.png" alt="PENPOT">
</h1>
<p align="center"><a href="https://www.mozilla.org/en-US/MPL/2.0" rel="nofollow"><img src="https://camo.githubusercontent.com/3fcf3d6b678ea15fde3cf7d6af0e242160366282d62a7c182d83a50bfee3f45e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4d504c2d322e302d626c75652e737667" alt="License: MPL-2.0" data-canonical-src="https://img.shields.io/badge/MPL-2.0-blue.svg" style="max-width:100%;"></a>
@@ -50,7 +50,7 @@ Being web based, Penpot is not dependent on operating systems or local installat
Using SVG as no other design and prototyping tool does, Penpot files sport compatibility with most of the vectorial tools, are tech friendly and extremely easy to use on the web. We make sure you will always own your work.
<p align="center">
<img src="https://penpot.app/images/open-source.png" alt="Open Source">
<img src="https://penpot.app/images/readme/git-open.png" alt="Open Source" style="width: 65%;">
</p>
@@ -74,7 +74,7 @@ Heres a step-by-step guide on [getting started with Docker.](https://help.pen
If you prefer not to install Penpot in a local environment, [login or register on our Penpot cloud app](https://design.penpot.app). Create a team to work together on projects and share design assets or jump right away into Penpot and **start designing** on your own.
<p align="center">
<img src="https://help.penpot.app/img/home-techguide.png" alt="Getting started">
<img src="https://penpot.app/images/readme/git-self-host.png" alt="Getting started" style="width: 65%;">
</p>
## Community ##
@@ -93,7 +93,7 @@ You will find the following categories:
- [Penpot in your language](https://community.penpot.app/c/penpot-in-your-language/12)
<p align="center">
<img src="https://penpot.app/images/cross-teams.webp" alt="Community">
<img src="https://penpot.app/images/readme/git-collaborate.png" alt="Communnity" style="width: 65%;">
</p>
## Contributing ##
@@ -111,7 +111,7 @@ Every sort of contribution will be very helpful to enhance Penpot. How youll
To find (almost) everything you need to know on how to contribute to Penpot, refer to the [contributing-guide](https://help.penpot.app/contributing-guide/).
<p align="center">
<img src="https://help.penpot.app/img/home-contributing.png" alt="Contributing">
<img src="https://penpot.app/images/readme/git-community.png" alt="Contributing" style="width: 65%;">
</p>
## Resources ##

View File

@@ -16,16 +16,11 @@
{: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.*"]
:exclude [#".*Log4j2Plugins\.dat$"]
:basis basis}))
(defn compile [_]

View File

@@ -3,9 +3,6 @@
org.clojure/clojure {:mvn/version "1.11.1"}
org.clojure/core.async {:mvn/version "1.6.673"}
;; Logging
org.zeromq/jeromq {:mvn/version "0.5.3"}
com.github.luben/zstd-jni {:mvn/version "1.5.2-5"}
org.clojure/data.fressian {:mvn/version "1.0.0"}
@@ -22,14 +19,14 @@
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
funcool/yetti
{:git/tag "v9.11"
:git/sha "6f9197a"
{:git/tag "v9.12"
:git/sha "51646d8"
:git/url "https://github.com/funcool/yetti.git"
:exclusions [org.slf4j/slf4j-api]}
com.github.seancorfield/next.jdbc {:mvn/version "1.3.847"}
metosin/reitit-core {:mvn/version "0.5.18"}
org.postgresql/postgresql {:mvn/version "42.5.1"}
org.postgresql/postgresql {:mvn/version "42.5.2"}
com.zaxxer/HikariCP {:mvn/version "5.0.1"}
io.whitfin/siphash {:mvn/version "2.0.0"}
@@ -55,7 +52,7 @@
;; Pretty Print specs
pretty-spec/pretty-spec {:mvn/version "0.1.4"}
software.amazon.awssdk/s3 {:mvn/version "2.19.8"}
software.amazon.awssdk/s3 {:mvn/version "2.19.29"}
}
:paths ["src" "resources" "target/classes"]
@@ -70,10 +67,9 @@
mockery/mockery {:mvn/version "RELEASE"}}
:extra-paths ["test" "dev"]}
:build
{:extra-deps
{io.github.clojure/tools.build {:git/tag "v0.9.0" :git/sha "8c93e0c"}}
{io.github.clojure/tools.build {:git/tag "v0.9.3" :git/sha "e537cd1"}}
:ns-default build}
:test

View File

@@ -103,9 +103,9 @@
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -129,9 +129,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -143,7 +143,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -157,9 +157,9 @@
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -211,9 +211,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -225,7 +225,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -239,9 +239,9 @@
<td style="direction:ltr;font-size:0px;padding:24px 0 0 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:425px;"
>
@@ -257,9 +257,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -271,7 +271,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -285,9 +285,9 @@
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -301,7 +301,7 @@
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
>
<tr>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -321,7 +321,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -341,7 +341,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -361,7 +361,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -370,7 +370,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
<tr>
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
<a href="https://instagram.com/penpotapp/" target="_blank">
<a href="https://www.instagram.com/penpot.app/" target="_blank">
<img height="24" src="{{ public-uri }}/images/email/logo-instagram.png" style="border-radius:3px;display:block;" width="24" />
</a>
</td>
@@ -381,7 +381,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -390,7 +390,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
<tr>
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
<a href="https://tree.taiga.io/project/uxbox" target="_blank">
<a href="https://tree.taiga.io/project/penpot" target="_blank">
<img height="24" src="{{ public-uri }}/images/email/logo-taiga.png" style="border-radius:3px;display:block;" width="24" />
</a>
</td>
@@ -401,7 +401,7 @@
</table>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
@@ -411,9 +411,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -425,7 +425,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -439,9 +439,9 @@
<td style="direction:ltr;font-size:0px;padding:0 0 24px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -457,9 +457,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>

View File

@@ -103,9 +103,9 @@
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -129,9 +129,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -143,7 +143,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -157,9 +157,9 @@
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -201,9 +201,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -215,7 +215,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -229,9 +229,9 @@
<td style="direction:ltr;font-size:0px;padding:24px 0 0 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:425px;"
>
@@ -247,9 +247,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -261,7 +261,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -275,9 +275,9 @@
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -291,7 +291,7 @@
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
>
<tr>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -311,7 +311,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -331,7 +331,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -351,7 +351,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -360,7 +360,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
<tr>
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
<a href="https://instagram.com/penpotapp/" target="_blank">
<a href="https://www.instagram.com/penpot.app/" target="_blank">
<img height="24" src="{{ public-uri }}/images/email/logo-instagram.png" style="border-radius:3px;display:block;" width="24" />
</a>
</td>
@@ -371,7 +371,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -380,7 +380,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
<tr>
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
<a href="https://tree.taiga.io/project/uxbox" target="_blank">
<a href="https://tree.taiga.io/project/penpot" target="_blank">
<img height="24" src="{{ public-uri }}/images/email/logo-taiga.png" style="border-radius:3px;display:block;" width="24" />
</a>
</td>
@@ -391,7 +391,7 @@
</table>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
@@ -401,9 +401,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -415,7 +415,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -429,9 +429,9 @@
<td style="direction:ltr;font-size:0px;padding:0 0 24px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -447,9 +447,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>

View File

@@ -103,9 +103,9 @@
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -129,9 +129,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -143,7 +143,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -157,9 +157,9 @@
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -206,9 +206,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -220,7 +220,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -234,9 +234,9 @@
<td style="direction:ltr;font-size:0px;padding:24px 0 0 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:425px;"
>
@@ -252,9 +252,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -266,7 +266,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -280,9 +280,9 @@
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -296,7 +296,7 @@
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
>
<tr>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -316,7 +316,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -336,7 +336,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -356,7 +356,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -365,7 +365,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
<tr>
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
<a href="https://instagram.com/penpotapp/" target="_blank">
<a href="https://www.instagram.com/penpot.app/" target="_blank">
<img height="24" src="{{ public-uri }}/images/email/logo-instagram.png" style="border-radius:3px;display:block;" width="24" />
</a>
</td>
@@ -376,7 +376,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -385,7 +385,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
<tr>
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
<a href="https://tree.taiga.io/project/uxbox" target="_blank">
<a href="https://tree.taiga.io/project/penpot" target="_blank">
<img height="24" src="{{ public-uri }}/images/email/logo-taiga.png" style="border-radius:3px;display:block;" width="24" />
</a>
</td>
@@ -396,7 +396,7 @@
</table>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
@@ -406,9 +406,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -420,7 +420,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -434,9 +434,9 @@
<td style="direction:ltr;font-size:0px;padding:0 0 24px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -452,9 +452,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>

View File

@@ -103,9 +103,9 @@
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -129,9 +129,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -143,7 +143,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -157,9 +157,9 @@
<td style="direction:ltr;font-size:0px;padding:20px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -201,9 +201,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -215,7 +215,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -229,9 +229,9 @@
<td style="direction:ltr;font-size:0px;padding:24px 0 0 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:425px;"
>
@@ -247,9 +247,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -261,7 +261,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -275,9 +275,9 @@
<td style="direction:ltr;font-size:0px;padding:0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -291,7 +291,7 @@
align="center" border="0" cellpadding="0" cellspacing="0" role="presentation"
>
<tr>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -311,7 +311,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -331,7 +331,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -351,7 +351,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -360,7 +360,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
<tr>
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
<a href="https://instagram.com/penpotapp/" target="_blank">
<a href="https://www.instagram.com/penpot.app/" target="_blank">
<img height="24" src="{{ public-uri }}/images/email/logo-instagram.png" style="border-radius:3px;display:block;" width="24" />
</a>
</td>
@@ -371,7 +371,7 @@
</table>
<!--[if mso | IE]>
</td>
<td>
<![endif]-->
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
@@ -380,7 +380,7 @@
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-radius:3px;width:24px;">
<tr>
<td style="font-size:0;height:24px;vertical-align:middle;width:24px;">
<a href="https://tree.taiga.io/project/uxbox" target="_blank">
<a href="https://tree.taiga.io/project/penpot" target="_blank">
<img height="24" src="{{ public-uri }}/images/email/logo-taiga.png" style="border-radius:3px;display:block;" width="24" />
</a>
</td>
@@ -391,7 +391,7 @@
</table>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
@@ -401,9 +401,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>
@@ -415,7 +415,7 @@
</td>
</tr>
</table>
<table
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
>
@@ -429,9 +429,9 @@
<td style="direction:ltr;font-size:0px;padding:0 0 24px 0;text-align:center;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0">
<tr>
<td
class="" style="vertical-align:top;width:600px;"
>
@@ -447,9 +447,9 @@
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</td>

View File

@@ -1,66 +0,0 @@
<mjml>
<mj-head>
<mj-font name="Source Sans Pro" href="https://fonts.googleapis.com/css?family=Source%20Sans%20Pro" />
<mj-attributes>
<mj-text font-family="Source Sans Pro, sans-serif" font-size="16px" color="#000000" line-height="150%" />
<mj-button background-color="#31EFB8" color="#1F1F1F" font-family="Source Sans Pro, sans-serif" font-size="16px" />
</mj-attributes>
</mj-head>
<mj-body background-color="#E5E5E5">
<mj-section padding="0">
<mj-column>
<mj-image src="{{ public-uri }}/images/email/uxbox-title.png"
width="97px" height="32px" align="left" padding="16px" />
</mj-column>
</mj-section>
<mj-section background-color="#FFFFFF">
<mj-column>
<mj-text font-size="24px" font-weight="600">Hello {{name}}!</mj-text>
<mj-text>We received a request to change your current email to {{ pending-email }}.</mj-text>
<mj-text>Click to the link below to confirm the change:</mj-text>
<mj-button href="{{ public-uri }}/#/auth/verify-token?token={{token}}">
Confirm email change
</mj-button>
<mj-text>
If you received this email by mistake, please consider changing your password
for security reasons.
</mj-text>
<mj-text>Enjoy!</mj-text>
<mj-text>The Penpot team.</mj-text>
</mj-column>
</mj-section>
<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 design and prototyping platform meant for cross-domain teams.
</mj-text>
</mj-column>
</mj-section>
<mj-section padding="0">
<mj-column>
<mj-social icon-size="24px" mode="horizontal">
<mj-social-element src="{{ public-uri }}/images/email/logo-uxbox.png" href="https://penpot.app/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-twitter.png" href="https://twitter.com/penpotapp" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-github.png" href="https://github.com/penpot/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-instagram.png" href="https://instagram.com/penpotapp/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-taiga.png" href="https://tree.taiga.io/project/uxbox" padding="0 8px" />
</mj-social>
</mj-column>
</mj-section>
<mj-section padding="0 0 24px 0">
<mj-column>
<mj-text align="center" font-size="14px" color="#64666A" line-height="150%">
Penpot © 2020 | Made with &lt;3 and Open Source
</mj-text>
</mj-column>
</mj-section>
</mg-body>
</mjml>

View File

@@ -1,59 +0,0 @@
<mjml>
<mj-head>
<mj-font name="Source Sans Pro" href="https://fonts.googleapis.com/css?family=Source%20Sans%20Pro" />
<mj-attributes>
<mj-text font-family="Source Sans Pro, sans-serif" font-size="16px" color="#000000" line-height="150%" />
<mj-button background-color="#31EFB8" color="#1F1F1F" font-family="Source Sans Pro, sans-serif" font-size="16px" />
</mj-attributes>
</mj-head>
<mj-body background-color="#E5E5E5">
<mj-section padding="0">
<mj-column>
<mj-image src="{{ public-uri }}/images/email/uxbox-title.png"
width="97px" height="32px" align="left" padding="16px" />
</mj-column>
</mj-section>
<mj-section background-color="#FFFFFF">
<mj-column>
<mj-text font-size="24px" font-weight="600">Hello!</mj-text>
<mj-text>
{{invited-by}} has invited you to join the team “{{ team }}”.
</mj-text>
<mj-button href="{{ public-uri }}/#/auth/verify-token?token={{token}}">
Accept invite
</mj-button>
<mj-text>Enjoy!</mj-text>
<mj-text>The Penpot team.</mj-text>
</mj-column>
</mj-section>
<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 design and prototyping platform meant for cross-domain teams.
</mj-text>
</mj-column>
</mj-section>
<mj-section padding="0">
<mj-column>
<mj-social icon-size="24px" mode="horizontal">
<mj-social-element src="{{ public-uri }}/images/email/logo-uxbox.png" href="https://penpot.app/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-twitter.png" href="https://twitter.com/penpotapp" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-github.png" href="https://github.com/penpot/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-instagram.png" href="https://instagram.com/penpotapp/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-taiga.png" href="https://tree.taiga.io/project/uxbox" padding="0 8px" />
</mj-social>
</mj-column>
</mj-section>
<mj-section padding="0 0 24px 0">
<mj-column>
<mj-text align="center" font-size="14px" color="#64666A" line-height="150%">
Penpot © 2020 | Made with &lt;3 and Open Source
</mj-text>
</mj-column>
</mj-section>
</mg-body>
</mjml>

View File

@@ -1,68 +0,0 @@
<mjml>
<mj-head>
<mj-font name="Source Sans Pro" href="https://fonts.googleapis.com/css?family=Source%20Sans%20Pro" />
<mj-attributes>
<mj-text font-family="Source Sans Pro, sans-serif" font-size="16px" color="#000000" line-height="150%" />
<mj-button background-color="#31EFB8" color="#1F1F1F" font-family="Source Sans Pro, sans-serif" font-size="16px" />
</mj-attributes>
</mj-head>
<mj-body background-color="#E5E5E5">
<mj-section padding="0">
<mj-column>
<mj-image src="{{ public-uri }}/images/email/uxbox-title.png"
width="97px" height="32px" align="left" padding="16px" />
</mj-column>
</mj-section>
<mj-section background-color="#FFFFFF">
<mj-column>
<mj-text font-size="24px" font-weight="600">Hello {{name}}!</mj-text>
<mj-text>
We have received a request to reset your password. Click the link
below to choose a new one:
</mj-text>
<mj-button href="{{ public-uri }}/#/auth/recovery?token={{token}}">
Reset password
</mj-button>
<mj-text>
If you received this email by mistake, you can safely ignore
it. Your password won't be changed.
</mj-text>
<mj-text>Enjoy!</mj-text>
<mj-text>The Penpot team.</mj-text>
</mj-column>
</mj-section>
<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 design and prototyping platform meant for cross-domain teams.
</mj-text>
</mj-column>
</mj-section>
<mj-section padding="0">
<mj-column>
<mj-social icon-size="24px" mode="horizontal">
<mj-social-element src="{{ public-uri }}/images/email/logo-uxbox.png" href="https://penpot.app/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-twitter.png" href="https://twitter.com/penpotapp" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-github.png" href="https://github.com/penpot/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-instagram.png" href="https://instagram.com/penpotapp/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-taiga.png" href="https://tree.taiga.io/project/uxbox" padding="0 8px" />
</mj-social>
</mj-column>
</mj-section>
<mj-section padding="0 0 24px 0">
<mj-column>
<mj-text align="center" font-size="14px" color="#64666A" line-height="150%">
Penpot © 2020 | Made with &lt;3 and Open Source
</mj-text>
</mj-column>
</mj-section>
</mg-body>
</mjml>

View File

@@ -1,65 +0,0 @@
<mjml>
<mj-head>
<mj-font name="Source Sans Pro" href="https://fonts.googleapis.com/css?family=Source%20Sans%20Pro" />
<mj-attributes>
<mj-text font-family="Source Sans Pro, sans-serif" font-size="16px" color="#000000" line-height="150%" />
<mj-button background-color="#31EFB8" color="#1F1F1F" font-family="Source Sans Pro, sans-serif" font-size="16px" />
</mj-attributes>
</mj-head>
<mj-body background-color="#E5E5E5">
<mj-section padding="0">
<mj-column>
<mj-image src="{{ public-uri }}/images/email/uxbox-title.png"
width="97px" height="32px" align="left" padding="16px" />
</mj-column>
</mj-section>
<mj-section background-color="#FFFFFF">
<mj-column>
<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 and get started building mockups and
prototypes today!
</mj-text>
<mj-button href="{{ public-uri }}/#/auth/verify-token?token={{token}}">
Verify email
</mj-button>
<mj-text>Enjoy!</mj-text>
<mj-text>The Penpot team.</mj-text>
</mj-column>
</mj-section>
<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 design and prototyping platform meant for cross-domain teams.
</mj-text>
</mj-column>
</mj-section>
<mj-section padding="0">
<mj-column>
<mj-social icon-size="24px" mode="horizontal">
<mj-social-element src="{{ public-uri }}/images/email/logo-uxbox.png" href="https://penpot.app/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-twitter.png" href="https://twitter.com/penpotapp" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-github.png" href="https://github.com/penpot/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-instagram.png" href="https://instagram.com/penpotapp/" padding="0 8px" />
<mj-social-element src="{{ public-uri }}/images/email/logo-taiga.png" href="https://tree.taiga.io/project/uxbox" padding="0 8px" />
</mj-social>
</mj-column>
</mj-section>
<mj-section padding="0 0 24px 0">
<mj-column>
<mj-text align="center" font-size="14px" color="#64666A" line-height="150%">
Penpot © 2020 | Made with &lt;3 and Open Source
</mj-text>
</mj-column>
</mj-section>
</mg-body>
</mjml>

View File

@@ -0,0 +1,112 @@
{% extends "app/templates/base.tmpl" %}
{% block title %}
penpot - error report v2 {{id}}
{% endblock %}
{% block content %}
<nav>
<div>[<a href="/dbg/error">⮜</a>]</div>
<div>[<a href="#message">message</a>]</div>
<div>[<a href="#props">props</a>]</div>
<div>[<a href="#context">context</a>]</div>
{% if params %}
<div>[<a href="#params">request params</a>]</div>
{% endif %}
{% 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="message" class="table-key">MESSAGE: </div>
<div class="table-val">
<h1>{{hint}}</h1>
</div>
</div>
<div class="table-row multiline">
<div id="props" class="table-key">LOG PROPS: </div>
<div class="table-val">
<pre>{{props}}</pre>
</div>
</div>
<div class="table-row multiline">
<div id="context" class="table-key">CONTEXT: </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

@@ -23,6 +23,10 @@ input[type=text], input[type=submit] {
padding: 3px;
}
pre {
white-space: pre-wrap;
}
main {
margin: 20px;
}

View File

@@ -14,11 +14,6 @@
</Policies>
<DefaultRolloverStrategy max="9"/>
</RollingFile>
<JeroMQ name="zmq">
<Property name="endpoint">tcp://localhost:45556</Property>
<JsonLayout complete="false" compact="true" includeTimeMillis="true" stacktraceAsString="true" properties="true" />
</JeroMQ>
</Appenders>
<Loggers>
@@ -37,17 +32,12 @@
<Logger name="app.rpc.climit" level="info" />
<Logger name="app.rpc.mutations.files" level="info" />
<Logger name="app.cli" level="debug" additivity="false">
<AppenderRef ref="console"/>
</Logger>
<Logger name="app.loggers" level="debug" additivity="false">
<AppenderRef ref="main" level="debug" />
</Logger>
<Logger name="app" level="all" additivity="false">
<AppenderRef ref="main" level="trace" />
<AppenderRef ref="zmq" level="debug" />
</Logger>
<Logger name="user" level="trace" additivity="false">

View File

@@ -12,6 +12,7 @@
<Logger name="com.zaxxer.hikari" level="error" />
<Logger name="org.postgresql" level="error" />
<Logger name="app.util" level="info" />
<Logger name="app" level="info" additivity="false">
<AppenderRef ref="console" />
</Logger>

View File

@@ -12,6 +12,7 @@ cp ../CHANGES.md target/classes/changelog.md;
clojure -T:build jar;
mv target/penpot.jar target/dist/penpot.jar
cp resources/log4j2.xml target/dist/log4j2.xml
cp scripts/run.template.sh target/dist/run.sh;
cp scripts/manage.py target/dist/manage.py
chmod +x target/dist/run.sh;

View File

@@ -19,10 +19,10 @@ PREPL_URI = "tcp://localhost:6063"
def get_prepl_conninfo():
uri_data = urlparse(PREPL_URI)
if uri_data.scheme != "tcp":
raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
raise RuntimeError(f"invalid PREPL_URI: {PREPL_URI}")
if not isinstance(uri_data.netloc, str):
raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
raise RuntimeError(f"invalid PREPL_URI: {PREPL_URI}")
host, port = uri_data.netloc.split(":", 2)
@@ -46,7 +46,7 @@ def send_eval(expr):
result = json.load(f)
tag = result.get("tag", None)
if tag != "ret":
raise RuntimeException("unexpected response from PREPL")
raise RuntimeError("unexpected response from PREPL")
return result.get("val", None), result.get("exception", None)
def encode(val):

View File

@@ -18,5 +18,7 @@ if [ -f ./environ ]; then
source ./environ
fi
export JVM_OPTS="-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -Dlog4j2.configurationFile=log4j2.xml -XX:-OmitStackTraceInFastThrow $JVM_OPTS"
set -x
exec $JAVA_CMD $JVM_OPTS "$@" -jar penpot.jar -m app.main

View File

@@ -50,7 +50,7 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- discover-oidc-config
[cfg {:keys [::base-uri] :as opts}]
[cfg {:keys [base-uri] :as opts}]
(let [discovery-uri (u/join base-uri ".well-known/openid-configuration")
response (ex/try! (http/req! cfg
{:method :get :uri (str discovery-uri)}
@@ -64,10 +64,17 @@
nil)
(= 200 (:status response))
(let [data (json/decode (:body response))]
{:token-uri (get data :token_endpoint)
:auth-uri (get data :authorization_endpoint)
:user-uri (get data :userinfo_endpoint)})
(let [data (json/decode (:body response))
token-uri (get data :token_endpoint)
auth-uri (get data :authorization_endpoint)
user-uri (get data :userinfo_endpoint)]
(l/debug :hint "oidc uris discovered"
:token-uri token-uri
:auth-uri auth-uri
:user-uri user-uri)
{:token-uri token-uri
:auth-uri auth-uri
:user-uri user-uri})
:else
(do
@@ -110,7 +117,7 @@
(if-let [opts (prepare-oidc-opts cfg)]
(do
(l/info :hint "provider initialized"
:provider :oidc
:provider "oidc"
:method (if (:discover? opts) "discover" "manual")
:client-id (:client-id opts)
:client-secret (obfuscate-string (:client-secret opts))
@@ -122,7 +129,7 @@
:roles (:roles opts))
opts)
(do
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :oidc)
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider "oidc")
nil))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -144,13 +151,13 @@
(string? (:client-secret opts)))
(do
(l/info :hint "provider initialized"
:provider :google
: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)
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider "google")
nil)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -196,13 +203,13 @@
(string? (:client-secret opts)))
(do
(l/info :hint "provider initialized"
:provider :github
: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)
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider "github")
nil)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -225,14 +232,14 @@
(string? (:client-secret opts)))
(do
(l/info :hint "provider initialized"
:provider :gitlab
: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)
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider "gitlab")
nil)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -275,8 +282,19 @@
"accept" "application/json"}
:uri (:token-uri provider)
:body (u/map->query-string params)}]
(l/trace :hint "request access token"
:provider (:name provider)
:client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider))
:grant-type (:grant_type params)
:redirect-uri (:redirect_uri params))
(->> (http/req! cfg req)
(p/map (fn [{:keys [status body] :as res}]
(l/trace :hint "access token response"
:status status
:body body)
(if (= status 200)
(let [data (json/decode body)]
{:token (get data :access_token)
@@ -289,12 +307,19 @@
(defn- retrieve-user-info
[{:keys [provider] :as cfg} tdata]
(letfn [(retrieve []
(l/trace :hint "request user info"
:uri (:user-uri provider)
:token (obfuscate-string (:token tdata))
:token-type (:type tdata))
(http/req! cfg
{:uri (:user-uri provider)
:headers {"Authorization" (str (:type tdata) " " (:token tdata))}
:timeout 6000
:method :get}))
(validate-response [response]
(l/trace :hint "user info response"
:status (:status response)
:body (:body response))
(when-not (s/int-in-range? 200 300 (:status response))
(ex/raise :type :internal
:code :unable-to-retrieve-user-info
@@ -309,7 +334,7 @@
(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))))
(p/resolved (get info attr-kw)))))
(get-name [info]
(let [attr-kw (cf/get :oidc-name-attr :name)]
@@ -325,6 +350,7 @@
(qualify-props provider))}))
(validate-info [info]
(l/trace :hint "authentication info" :info info)
(when-not (s/valid? ::info info)
(l/warn :hint "received incomplete profile info object (please set correct scopes)"
:info (pr-str info))
@@ -334,10 +360,10 @@
:info info))
info)]
(-> (retrieve)
(p/then validate-response)
(p/then process-response)
(p/then validate-info))))
(->> (retrieve)
(p/fmap validate-response)
(p/mcat process-response)
(p/fmap validate-info))))
(s/def ::backend ::us/not-empty-string)
(s/def ::email ::us/not-empty-string)
@@ -432,12 +458,11 @@
(ex/raise :type :restriction
:code :profile-blocked))
(when-let [collector (::audit/collector cfg)]
(audit/submit! collector {:type "command"
:name "login-with-password"
:profile-id (:id profile)
:ip-addr (audit/parse-client-ip request)
:props (audit/profile->props profile)}))
(audit/submit! cfg {:type "command"
:name "login-with-password"
:profile-id (:id profile)
:ip-addr (audit/parse-client-ip request)
:props (audit/profile->props profile)})
(->> (redirect-response uri)
(sxf request)))

View File

@@ -51,7 +51,6 @@
:database-password "penpot"
:default-blob-version 5
:loggers-zmq-uri "tcp://localhost:45556"
:rpc-rlimit-config (fs/path "resources/rlimit.edn")
:rpc-climit-config (fs/path "resources/climit.edn")
@@ -175,8 +174,6 @@
(s/def ::ldap-ssl ::us/boolean)
(s/def ::ldap-starttls ::us/boolean)
(s/def ::ldap-user-query ::us/string)
(s/def ::loggers-loki-uri ::us/string)
(s/def ::loggers-zmq-uri ::us/string)
(s/def ::media-directory ::us/string)
(s/def ::media-uri ::us/string)
(s/def ::profile-bounce-max-age ::dt/duration)
@@ -272,8 +269,6 @@
::ldap-starttls
::ldap-user-query
::local-assets-uri
::loggers-loki-uri
::loggers-zmq-uri
::media-max-file-size
::profile-bounce-max-age
::profile-bounce-threshold
@@ -357,7 +352,7 @@
(merge defaults)
(us/conform ::config))
(catch Throwable e
(when (ex/ex-info? e)
(when (ex/error? e)
(println ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;")
(println "Error on validating configuration:")
(println (some-> e ex-data ex/explain))

View File

@@ -17,7 +17,6 @@
[app.db.sql :as sql]
[app.metrics :as mtx]
[app.util.json :as json]
[app.util.migrations :as mg]
[app.util.time :as dt]
[clojure.java.io :as io]
[clojure.spec.alpha :as s]
@@ -32,7 +31,6 @@
io.whitfin.siphash.SipHasherContainer
java.io.InputStream
java.io.OutputStream
java.lang.AutoCloseable
java.sql.Connection
java.sql.Savepoint
org.postgresql.PGConnection
@@ -50,12 +48,9 @@
;; Initialization
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare apply-migrations!)
(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)
@@ -64,26 +59,26 @@
(s/def ::read-only? ::us/boolean)
(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?]))
(s/keys :opt [::uri
::name
::min-size
::max-size
::connection-timeout
::validation-timeout
::username
::password
::mtx/metrics
::read-only?]))
(def defaults
{:name :main
:min-size 0
:max-size 60
:connection-timeout 10000
:validation-timeout 10000
:idle-timeout 120000 ; 2min
:max-lifetime 1800000 ; 30m
:read-only? false})
{::name :main
::min-size 0
::max-size 60
::connection-timeout 10000
::validation-timeout 10000
::idle-timeout 120000 ; 2min
::max-lifetime 1800000 ; 30m
::read-only? false})
(defmethod ig/prep-key ::pool
[_ cfg]
@@ -93,39 +88,23 @@
(defmethod ig/pre-init-spec ::pool [_] ::pool-options)
(defmethod ig/init-key ::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)))
[_ {:keys [::uri ::read-only?] :as cfg}]
(when uri
(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))
(create-pool cfg)))
(defmethod ig/halt-key! ::pool
[_ pool]
(when pool
(.close ^HikariDataSource pool)))
(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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -135,19 +114,19 @@
"SET idle_in_transaction_session_timeout = 300000;"))
(defn- create-datasource-config
[{:keys [metrics uri] :as cfg}]
[{:keys [::mtx/metrics ::uri] :as cfg}]
(let [config (HikariConfig.)]
(doto config
(.setJdbcUrl (str "jdbc:" uri))
(.setPoolName (d/name (:name cfg)))
(.setPoolName (d/name (::name cfg)))
(.setAutoCommit true)
(.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))
(.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))
@@ -157,8 +136,8 @@
(PrometheusMetricsTrackerFactory.)
(.setMetricsTrackerFactory config)))
(some->> ^String (:username cfg) (.setUsername config))
(some->> ^String (:password cfg) (.setPassword config))
(some->> ^String (::username cfg) (.setUsername config))
(some->> ^String (::password cfg) (.setPassword config))
config))
@@ -166,16 +145,28 @@
[v]
(instance? javax.sql.DataSource v))
(s/def ::conn some?)
(s/def ::nilable-pool (s/nilable ::pool))
(s/def ::pool pool?)
(s/def ::conn-or-pool some?)
(s/def ::pool-or-conn some?)
(defn closed?
[pool]
(.isClosed ^HikariDataSource pool))
(defn read-only?
[pool]
(.isReadOnly ^HikariDataSource pool))
[pool-or-conn]
(cond
(instance? HikariDataSource pool-or-conn)
(.isReadOnly ^HikariDataSource pool-or-conn)
(instance? Connection pool-or-conn)
(.isReadOnly ^Connection pool-or-conn)
:else
(ex/raise :type :internal
:code :invalid-connection
:hint "invalid connection provided")))
(defn create-pool
[cfg]
@@ -451,6 +442,11 @@
(.setType "jsonb")
(.setValue (json/encode-str data)))))
(defn get-update-count
[result]
(:next.jdbc/update-count result))
;; --- Locks
(def ^:private siphash-state

View File

@@ -4,7 +4,7 @@
;;
;; Copyright (c) KALEIDOS INC
(ns app.emails
(ns app.email
"Main api for send emails."
(:require
[app.common.exceptions :as ex]
@@ -14,7 +14,7 @@
[app.config :as cf]
[app.db :as db]
[app.db.sql :as sql]
[app.emails.invite-to-team :as-alias emails.invite-to-team]
[app.email.invite-to-team :as-alias email.invite-to-team]
[app.metrics :as mtx]
[app.util.template :as tmpl]
[app.worker :as wrk]
@@ -71,7 +71,7 @@
(.addFrom ^MimeMessage mmsg from)))))
(defn- assign-reply-to
[mmsg {:keys [default-reply-to] :as cfg} {:keys [reply-to] :as params}]
[mmsg {:keys [::default-reply-to] :as cfg} {:keys [reply-to] :as params}]
(let [reply-to (or reply-to default-reply-to)]
(when reply-to
(let [reply-to (parse-address reply-to)]
@@ -127,9 +127,8 @@
mmsg))
(defn- opts->props
[{:keys [username tls host port timeout default-from]
:or {timeout 30000}
:as opts}]
[{:keys [::username ::tls ::host ::port ::timeout ::default-from]
:or {timeout 30000}}]
(reduce-kv
(fn [^Properties props k v]
(if (nil? v)
@@ -150,8 +149,8 @@
"mail.smtp.connectiontimeout" timeout}))
(defn- create-smtp-session
[opts]
(let [props (opts->props opts)]
[cfg]
(let [props (opts->props cfg)]
(Session/getInstance props)))
(defn- create-smtp-message
@@ -171,7 +170,7 @@
;; TEMPLATE EMAIL IMPL
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def ^:private email-path "app/emails/%(id)s/%(lang)s.%(type)s")
(def ^:private email-path "app/email/%(id)s/%(lang)s.%(type)s")
(defn- render-email-template-part
[type id context]
@@ -283,14 +282,14 @@
(s/def ::default-from ::cf/smtp-default-from)
(s/def ::smtp-config
(s/keys :opt-un [::username
::password
::tls
::ssl
::host
::port
::default-from
::default-reply-to]))
(s/keys :opt [::username
::password
::tls
::ssl
::host
::port
::default-from
::default-reply-to]))
(declare send-to-logger!)
@@ -306,8 +305,8 @@
(let [session (create-smtp-session cfg)]
(with-open [transport (.getTransport session (if (:ssl cfg) "smtps" "smtp"))]
(.connect ^Transport transport
^String (:username cfg)
^String (:password cfg))
^String (::username cfg)
^String (::password cfg))
(let [^MimeMessage message (create-smtp-message cfg session params)]
(.sendMessage ^Transport transport
@@ -319,10 +318,10 @@
(send-to-logger! cfg params))))
(defmethod ig/pre-init-spec ::handler [_]
(s/keys :req-un [::sendmail ::mtx/metrics]))
(s/keys :req [::sendmail ::mtx/metrics]))
(defmethod ig/init-key ::handler
[_ {:keys [sendmail]}]
[_ {:keys [::sendmail]}]
(fn [{:keys [props] :as task}]
(sendmail props)))
@@ -380,14 +379,14 @@
"Password change confirmation email"
(template-factory ::change-email))
(s/def ::emails.invite-to-team/invited-by ::us/string)
(s/def ::emails.invite-to-team/team ::us/string)
(s/def ::emails.invite-to-team/token ::us/string)
(s/def ::email.invite-to-team/invited-by ::us/string)
(s/def ::email.invite-to-team/team ::us/string)
(s/def ::email.invite-to-team/token ::us/string)
(s/def ::invite-to-team
(s/keys :req-un [::emails.invite-to-team/invited-by
::emails.invite-to-team/token
::emails.invite-to-team/team]))
(s/keys :req-un [::email.invite-to-team/invited-by
::email.invite-to-team/token
::email.invite-to-team/team]))
(def invite-to-team
"Teams member invitation email."

View File

@@ -46,46 +46,53 @@
(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"
:port 6060
:host "0.0.0.0"
:max-body-size (* 1024 1024 30) ; 30 MiB
:max-multipart-body-size (* 1024 1024 120)} ; 120 MiB
(merge {::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)))))
(s/keys :req [::port ::host]
:opt [::max-body-size
::max-multipart-body-size
::router
::handler
::io-threads
::wrk/executor]))
(defmethod ig/init-key ::server
[_ {:keys [handler router port name host] :as cfg}]
(l/info :hint "starting http server" :port port :host host :name name)
[_ {:keys [::handler ::router ::host ::port] :as cfg}]
(l/info :hint "starting http server" :port port :host host)
(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/dispatch (:executor cfg)
: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/dispatch (::wrk/executor cfg)
:ring/async true}
handler (if (some? router)
handler (cond
(some? router)
(wrap-router router)
handler)
server (yt/server handler (d/without-nils options))]
(assoc cfg :server (yt/start! server))))
(some? handler)
handler
:else
(throw (UnsupportedOperationException. "handler or router are required")))
options (d/without-nils options)
server (yt/server handler options)]
(assoc cfg ::server (yt/start! server))))
(defmethod ig/halt-key! ::server
[_ {:keys [server name port] :as cfg}]
(l/info :msg "stopping http server" :name name :port port)
[_ {:keys [::server ::port] :as cfg}]
(l/info :msg "stopping http server" :port port)
(yt/stop! server))
(defn- not-found-handler

View File

@@ -7,18 +7,17 @@
(ns app.http.assets
"Assets related handlers."
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.common.uri :as u]
[app.db :as db]
[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]
[promesa.core :as p]
[promesa.exec :as px]
[yetti.response :as yrs]))
(def ^:private cache-max-age
@@ -27,104 +26,96 @@
(def ^:private signature-max-age
(dt/duration {:hours 24 :minutes 15}))
(defn coerce-id
[id]
(let [res (parse-uuid id)]
(when-not (uuid? res)
(ex/raise :type :not-found
:hint "object not found"))
res))
(defn get-id
[{:keys [path-params]}]
(if-let [id (some-> path-params :id d/parse-uuid)]
(p/resolved id)
(p/rejected (ex/error :type :not-found
:hunt "object not found"))))
(defn- get-file-media-object
[{: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)))
[pool id]
(db/get pool :file-media-object {:id id}))
(defn- serve-object-from-s3
[{:keys [::sto/storage] :as cfg} obj]
(let [mdata (meta obj)]
(->> (sto/get-object-url storage obj {:max-age signature-max-age})
(p/fmap (fn [{:keys [host port] :as url}]
(let [headers {"location" (str url)
"x-host" (cond-> host port (str ":" port))
"x-mtype" (:content-type mdata)
"cache-control" (str "max-age=" (inst-ms cache-max-age))}]
(yrs/response
:status 307
:headers headers)))))))
(defn- serve-object-from-fs
[{:keys [::path]} obj]
(let [purl (u/join (u/uri path)
(sto/object->relative-path obj))
mdata (meta obj)
headers {"x-accel-redirect" (:path purl)
"content-type" (:content-type mdata)
"cache-control" (str "max-age=" (inst-ms cache-max-age))}]
(p/resolved
(yrs/response :status 204 :headers headers))))
(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)
:s3
(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))
"x-mtype" (:content-type mdata)
"cache-control" (str "max-age=" (inst-ms cache-max-age))}))
:fs
(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))})))))
[{:keys [::sto/storage] :as cfg} {:keys [backend] :as obj}]
(let [backend (sto/resolve-backend storage backend)]
(case (::sto/type backend)
:s3 (serve-object-from-s3 cfg obj)
:fs (serve-object-from-fs cfg obj))))
(defn objects-handler
"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)))
[{:keys [::sto/storage ::wrk/executor] :as cfg} request respond raise]
(->> (get-id request)
(p/mcat executor (fn [id] (sto/get-object storage id)))
(p/mcat executor (fn [obj]
(if (some? obj)
(serve-object cfg obj)
(p/resolved (yrs/response 404)))))
(p/fnly executor (fn [result cause]
(if cause (raise cause) (respond result))))))
(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))))
[{:keys [::sto/storage ::wrk/executor] :as cfg} request kf]
(let [pool (::db/pool storage)]
(->> (get-id request)
(p/fmap executor (fn [id] (get-file-media-object pool id)))
(p/mcat executor (fn [mobj] (sto/get-object storage (kf mobj))))
(p/mcat executor (fn [sobj]
(if sobj
(serve-object cfg sobj)
(p/resolved (yrs/response 404))))))))
(defn file-objects-handler
"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)))
(->> (generic-handler cfg request :media-id)
(p/fnly (fn [result cause]
(if cause (raise cause) (respond result))))))
(defn file-thumbnails-handler
"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)))
(->> (generic-handler cfg request #(or (:thumbnail-id %) (:media-id %)))
(p/fnly (fn [result cause]
(if cause (raise cause) (respond result))))))
;; --- Initialization
(s/def ::storage some?)
(s/def ::assets-path ::us/string)
(s/def ::cache-max-age ::dt/duration)
(s/def ::signature-max-age ::dt/duration)
(s/def ::path ::us/string)
(s/def ::routes vector?)
;; FIXME: namespace qualified params
(defmethod ig/pre-init-spec ::routes [_]
(s/keys :req-un [::storage
::wrk/executor
::mtx/metrics
::assets-path
::cache-max-age
::signature-max-age]))
(s/keys :req [::sto/storage ::wrk/executor ::path]))
(defmethod ig/init-key ::routes
[_ cfg]

View File

@@ -43,9 +43,9 @@
(defn req!
"A convencience toplevel function for gradual migration to a new API
convention."
([{:keys [::client] :as holder} request]
(us/assert! ::client-holder holder)
([{:keys [::client]} request]
(us/assert! ::client client)
(send! client request {}))
([{:keys [::client] :as holder} request options]
(us/assert! ::client-holder holder)
([{:keys [::client]} request options]
(us/assert! ::client client)
(send! client request options)))

View File

@@ -205,45 +205,47 @@
(defn error-handler
[{:keys [::db/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]
(letfn [(get-report [{:keys [path-params]}]
(ex/ignoring
(some-> (db/get-by-id pool :server-error-report id) :content db/decode-transit-pgobject)))
(let [report-id (some-> path-params :id parse-uuid)]
(some-> (db/get-by-id pool :server-error-report report-id)
(update :content db/decode-transit-pgobject)))))
(render-template [report]
(let [context (dissoc report
(render-template-v1 [{:keys [content]}]
(let [context (dissoc content
:trace :cause :params :data :spec-problems :message
: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)}]
:hint (:hint content)
:spec-explain (:spec-explain content)
:spec-problems (:spec-problems content)
:spec-value (:spec-value content)
:data (:data content)
:trace (or (:trace content)
(some-> content :error :trace))
:params (:params content)}]
(-> (io/resource "app/templates/error-report.tmpl")
(tmpl/render params))))]
(tmpl/render params))))
(render-template-v2 [{report :content}]
(-> (io/resource "app/templates/error-report.v2.tmpl")
(tmpl/render report)))
]
(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
(if-let [report (get-report request)]
(let [result (if (= 1 (:version report))
(render-template-v1 report)
(render-template-v2 report))]
(yrs/response :status 200
:body result
:headers {"content-type" "text/html; charset=utf-8"
"x-robots-tag" "noindex"})
(yrs/response 404 "not found")))))
"x-robots-tag" "noindex"}))
(yrs/response 404 "not found"))))
(def sql:error-reports
"SELECT id, created_at,

View File

@@ -7,7 +7,6 @@
(ns app.http.errors
"A errors handling for the http server."
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.http :as-alias http]
@@ -18,30 +17,26 @@
[yetti.request :as yrq]
[yetti.response :as yrs]))
(def ^:dynamic *context* {})
(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
(defn request->context
"Extracts error report relevant context data from request."
[request]
(let [claims (-> {}
(into (::session/token-claims request))
(into (::actoken/token-claims request)))]
(merge
*context*
{:path (:path request)
:method (:method request)
:params (:params request)
:ip-addr (parse-client-ip request)}
(d/without-nils
{:user-agent (yrq/get-header request "user-agent")
:frontend-version (or (yrq/get-header request "x-frontend-version")
"unknown")
:profile-id (:uid claims)}))))
{:path (:path request)
:method (:method request)
:params (:params request)
:ip-addr (parse-client-ip request)
:user-agent (yrq/get-header request "user-agent")
:profile-id (:uid claims)
:version (or (yrq/get-header request "x-frontend-version")
"unknown")}))
(defmulti handle-exception
(fn [err & _rest]
@@ -87,15 +82,14 @@
[error request]
(let [edata (ex-data error)
explain (ex/explain edata)]
(l/error :hint (ex-message error)
:cause error
::l/context (get-context request))
(yrs/response :status 500
:body {:type :server-error
:code :assertion
:data (-> edata
(dissoc ::s/problems ::s/value ::s/spec)
(cond-> explain (assoc :explain explain)))})))
(binding [l/*context* (request->context request)]
(l/error :hint "Assertion error" :message (ex-message error) :cause error)
(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 _]
@@ -109,10 +103,8 @@
(yrs/response 429)
:else
(do
(l/error :hint (ex-message error)
:cause error
::l/context (get-context request))
(binding [l/*context* (request->context request)]
(l/error :hint "Internal error" :message (ex-message error) :cause error)
(yrs/response 500 {:type :server-error
:code :unhandled
:hint (ex-message error)
@@ -121,25 +113,24 @@
(defmethod handle-exception org.postgresql.util.PSQLException
[error request]
(let [state (.getSQLState ^java.sql.SQLException error)]
(l/error :hint (ex-message error)
:cause error
::l/context (get-context request))
(cond
(= state "57014")
(yrs/response 504 {:type :server-error
:code :statement-timeout
:hint (ex-message error)})
(binding [l/*context* (request->context request)]
(l/error :hint "PSQL error" :message (ex-message error) :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)})
(= 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}))))
:else
(yrs/response 500 {:type :server-error
:code :unexpected
:hint (ex-message error)
:state state})))))
(defmethod handle-exception :default
[error request]
@@ -147,10 +138,8 @@
(cond
;; This means that exception is not a controlled exception.
(nil? edata)
(do
(l/error :hint (ex-message error)
:cause error
::l/context (get-context request))
(binding [l/*context* (request->context request)]
(l/error :hint "Unexpected error" :message (ex-message error) :cause error)
(yrs/response 500 {:type :server-error
:code :unexpected
:hint (ex-message error)}))
@@ -165,10 +154,8 @@
(handle-exception (:handling edata) request)
:else
(do
(l/error :hint (ex-message error)
:cause error
::l/context (get-context request))
(binding [l/*context* (request->context request)]
(l/error :hint "Unhandled error" :message (ex-message error) :cause error)
(yrs/response 500 {:type :server-error
:code :unhandled
:hint (ex-message error)
@@ -176,16 +163,7 @@
(defn handle
[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
(if (or (instance? java.util.concurrent.CompletionException cause)
(instance? java.util.concurrent.ExecutionException cause))
(handle-exception (ex-cause cause) request)
(handle-exception cause request)))

View File

@@ -80,8 +80,8 @@
(fn [request respond raise]
(let [request (ex/try! (process-request request))]
(if (ex/exception? request)
(if (instance? RuntimeException request)
(handle-error raise (or (ex/cause request) request))
(if (ex/runtime-exception? request)
(handle-error raise (or (ex-cause request) request))
(handle-error raise request))
(handler request respond raise))))))

View File

@@ -8,6 +8,7 @@
(:refer-clojure :exclude [read])
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.spec :as us]
[app.config :as cf]
@@ -167,7 +168,7 @@
(->> (write! manager token params)
(p/fmap (fn [session]
(l/trace :hint "create" :profile-id profile-id)
(l/trace :hint "create" :profile-id (str profile-id))
(-> response
(assign-auth-token-cookie session)
(assign-authenticated-cookie session)))))))))
@@ -230,17 +231,18 @@
(let [{:keys [::wrk/executor ::main/props]} (meta manager)]
(fn [request respond raise]
(let [token (get-token request)]
(->> (px/submit! executor (partial decode-token props token))
(p/fnly (fn [claims cause]
(when cause
(l/trace :hint "exception on decoding malformed token" :cause cause))
(let [request (cond-> request
(map? claims)
(-> (assoc ::token-claims claims)
(assoc ::token token)))]
(handler request respond raise)))))))))
(let [token (ex/try! (get-token request))]
(if (ex/exception? token)
(raise token)
(->> (px/submit! executor (partial decode-token props token))
(p/fnly (fn [claims cause]
(when cause
(l/trace :hint "exception on decoding malformed token" :cause cause))
(let [request (cond-> request
(map? claims)
(-> (assoc ::token-claims claims)
(assoc ::token token)))]
(handler request respond raise))))))))))
(defn- wrap-authz
[handler {:keys [::manager]}]
@@ -299,13 +301,16 @@
(defn- assign-authenticated-cookie
[response {updated-at :updated-at}]
(let [max-age (cf/get :auth-token-cookie-max-age default-cookie-max-age)
domain (cf/get :authenticated-cookie-domain)
cname (cf/get :authenticated-cookie-name "authenticated")
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 "/"
@@ -315,20 +320,20 @@
:secure secure?}]
(cond-> response
(string? domain)
(update :cookies assoc name cookie))))
(update :cookies assoc cname cookie))))
(defn- clear-auth-token-cookie
[response]
(let [cname (cf/get :auth-token-cookie-name default-auth-token-cookie-name)]
(update response :cookies assoc cname {:path "/" :value "" :max-age -1})))
(update response :cookies assoc cname {:path "/" :value "" :max-age 0})))
(defn- clear-authenticated-cookie
[response]
(let [cname (cf/get :authenticated-cookie-name default-authenticated-cookie-name)
(let [cname (cf/get :authenticated-cookie-name default-authenticated-cookie-name)
domain (cf/get :authenticated-cookie-domain)]
(cond-> response
(string? domain)
(update :cookies assoc cname {:domain domain :path "/" :value "" :max-age -1}))))
(update :cookies assoc cname {:domain domain :path "/" :value "" :max-age 0}))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@@ -204,7 +204,7 @@
(a/<! (mbus/sub! msgbus :topic team-id :chan channel)))))
(defmethod handle-message :subscribe-file
[cfg wsp {:keys [file-id] :as params}]
[cfg wsp {:keys [file-id version] :as params}]
(let [msgbus (::mbus/msgbus cfg)
conn-id (::ws/id @wsp)
profile-id (::profile-id @wsp)
@@ -239,7 +239,8 @@
(let [message {:type :presence
:file-id file-id
:session-id session-id
:profile-id profile-id}]
:profile-id profile-id
:version version}]
(a/<! (mbus/pub! msgbus :topic file-id :message message))))
(a/>! output-ch message)
(recur))))

View File

@@ -20,7 +20,6 @@
[app.loggers.audit.tasks :as-alias tasks]
[app.loggers.webhooks :as-alias webhooks]
[app.main :as-alias main]
[app.metrics :as mtx]
[app.rpc :as-alias rpc]
[app.tokens :as tokens]
[app.util.retry :as rtry]
@@ -30,7 +29,6 @@
[cuerdas.core :as str]
[integrant.core :as ig]
[lambdaisland.uri :as u]
[promesa.core :as p]
[promesa.exec :as px]
[yetti.request :as yrq]))
@@ -77,28 +75,20 @@
(merge (:props profile))
(d/without-nils)))
(defn clean-props
[{:keys [profile-id] :as event}]
(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)))))]
(def reserved-props
#{:session-id
:password
:old-password
:token})
(update event :props #(into {} xform %))))
(defn clean-props
[props]
(into {}
(comp
(d/without-nils)
(d/without-qualified)
(remove #(contains? reserved-props (key %))))
props))
;; --- SPECS
@@ -132,7 +122,7 @@
(s/keys :req [::wrk/executor ::db/pool]))
(defmethod ig/pre-init-spec ::collector [_]
(s/keys :req [::db/pool ::wrk/executor ::mtx/metrics]))
(s/keys :req [::db/pool ::wrk/executor]))
(defmethod ig/init-key ::collector
[_ {:keys [::db/pool] :as cfg}]
@@ -143,8 +133,8 @@
:else
cfg))
(defn- persist-event!
[pool event]
(defn- handle-event!
[conn-or-pool event]
(us/verify! ::event event)
(let [params {:id (uuid/next)
:name (:name event)
@@ -161,7 +151,7 @@
::rtry/max-retries 6
::rtry/label "persist-audit-log"}
(let [now (dt/now)]
(db/insert! pool :audit-log
(db/insert! conn-or-pool :audit-log
(-> params
(update :props db/tjson)
(update :ip-addr db/inet)
@@ -180,7 +170,7 @@
:else label)
dedupe? (boolean (and batch-key batch-timeout))]
(wrk/submit! ::wrk/conn pool
(wrk/submit! ::wrk/conn conn-or-pool
::wrk/task :process-webhook-event
::wrk/queue :webhooks
::wrk/max-retries 0
@@ -191,16 +181,19 @@
::webhooks/event
(-> params
(dissoc :ip-addr)
(dissoc :type)))))))
(dissoc :type)))))
params))
(defn submit!
"Submit audit event to the collector."
[{:keys [::wrk/executor ::db/pool] :as collector} params]
(us/assert! ::collector collector)
(->> (px/submit! executor (partial persist-event! pool (d/without-nils params)))
(p/merr (fn [cause]
(l/error :hint "audit: unexpected error processing event" :cause cause)
(p/resolved nil)))))
[{:keys [::wrk/executor] :as cfg} params]
(let [conn (or (::db/conn cfg) (::db/pool cfg))]
(us/assert! ::wrk/executor executor)
(us/assert! ::db/pool-or-conn conn)
(try
(handle-event! conn (d/without-nils params))
(catch Throwable cause
(l/error :hint "audit: unexpected error processing event" :cause cause)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TASK: ARCHIVE
@@ -247,7 +240,7 @@
from audit_log
where archived_at is null
order by created_at asc
limit 256
limit 128
for update skip locked;")
(defn archive-events
@@ -323,7 +316,7 @@
where archived_at is not null")
(defn- clean-archived
[{:keys [pool]}]
[{:keys [::db/pool]}]
(let [result (db/exec-one! pool [sql:clean-archived])
result (:next.jdbc/update-count result)]
(l/debug :hint "delete archived audit log entries" :deleted result)

View File

@@ -7,16 +7,17 @@
(ns app.loggers.database
"A specific logger impl that persists errors on the database."
(:require
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.uuid :as uuid]
[app.common.pprint :as pp]
[app.common.spec :as us]
[app.config :as cf]
[app.db :as db]
[app.loggers.zmq :as lzmq]
[clojure.core.async :as a]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[integrant.core :as ig]
[promesa.exec :as px]))
[promesa.exec :as px]
[promesa.exec.csp :as sp]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Error Listener
@@ -27,73 +28,79 @@
(defonce enabled (atom true))
(defn- persist-on-database!
[{:keys [::db/pool] :as cfg} {:keys [id] :as event}]
[pool id report]
(when-not (db/read-only? pool)
(db/insert! pool :server-error-report {:id id :content (db/tjson event)})))
(db/insert! pool :server-error-report
{:id id
:version 2
:content (db/tjson report)})))
(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 record->report
[{:keys [::l/context ::l/message ::l/props ::l/logger ::l/level ::l/cause] :as record}]
(us/assert! ::l/record record)
(defn parse-event
[event]
(-> (parse-event-data event)
(assoc :hint (or (:hint event) (:message 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)))))
(merge
{:context (-> context
(assoc :tenant (cf/get :tenant))
(assoc :host (cf/get :host))
(assoc :public-uri (cf/get :public-uri))
(assoc :version (:full cf/version))
(assoc :logger-name logger)
(assoc :logger-level level)
(dissoc :params)
(pp/pprint-str :width 200))
:params (some-> (:params context)
(pp/pprint-str :width 200))
:props (pp/pprint-str props :width 200)
:hint (or (ex-message cause) @message)
:trace (ex/format-throwable cause :data? false :explain? false :header? false :summary? false)}
(when-let [data (ex-data cause)]
{:spec-value (some-> (::s/value data) (pp/pprint-str :width 200))
:spec-explain (ex/explain data)
:data (-> data
(dissoc ::s/problems ::s/value ::s/spec :hint)
(pp/pprint-str :width 200))})))
(defn- handle-event
[cfg event]
[{:keys [::db/pool]} {:keys [::l/id] :as record}]
(try
(let [event (parse-event event)
uri (cf/get :public-uri)]
(let [uri (cf/get :public-uri)
report (-> record record->report d/without-nils)]
(l/debug :hint "registering error on database" :id id
:uri (str uri "/dbg/error/" id))
(l/debug :hint "registering error on database" :id (:id event)
:uri (str uri "/dbg/error/" (:id event)))
(persist-on-database! cfg event))
(persist-on-database! pool id report))
(catch Throwable cause
(l/warn :hint "unexpected exception on database error logger" :cause cause))))
(defn- error-event?
[event]
(= "error" (:logger/level event)))
(defn error-record?
[{:keys [::l/level ::l/cause]}]
(and (= :error level)
(ex/exception? cause)))
(defmethod ig/pre-init-spec ::reporter [_]
(s/keys :req [::db/pool ::lzmq/receiver]))
(s/keys :req [::db/pool]))
(defmethod ig/init-key ::reporter
[_ {:keys [::lzmq/receiver] :as cfg}]
(px/thread
{:name "penpot/database-reporter"}
(l/info :hint "initializing database error persistence")
(let [input (a/chan (a/sliding-buffer 5)
(filter error-event?))]
[_ cfg]
(let [input (sp/chan (sp/sliding-buffer 32) (filter error-record?))]
(add-watch l/log-record ::reporter #(sp/put! input %4))
(px/thread
{:name "penpot/database-reporter" :virtual true}
(l/info :hint "initializing database error persistence")
(try
(lzmq/sub! receiver input)
(loop []
(when-let [msg (a/<!! input)]
(handle-event cfg msg))
(recur))
(when-let [record (sp/take! input)]
(handle-event cfg record)
(recur)))
(catch InterruptedException _
(l/debug :hint "reporter interrupted"))
(catch Throwable cause
(l/error :hint "unexpected error" :cause cause))
(finally
(a/close! input)
(sp/close! input)
(remove-watch l/log-record ::reporter)
(l/info :hint "reporter terminated"))))))
(defmethod ig/halt-key! ::reporter

View File

@@ -1,89 +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) KALEIDOS INC
(ns app.loggers.loki
"A Loki integration."
(:require
[app.common.logging :as l]
[app.config :as cf]
[app.http.client :as http]
[app.loggers.zmq :as lzmq]
[app.util.json :as json]
[clojure.core.async :as a]
[clojure.spec.alpha :as s]
[integrant.core :as ig]
[promesa.exec :as px]))
(declare ^:private handle-event)
(defmethod ig/pre-init-spec ::reporter [_]
(s/keys :req [::http/client
::lzmq/receiver]))
(defmethod ig/init-key ::reporter
[_ cfg]
(when-let [uri (cf/get :loggers-loki-uri)]
(px/thread
{:name "penpot/loki-reporter"}
(l/info :hint "reporter started" :uri uri)
(let [input (a/chan (a/dropping-buffer 2048))
cfg (assoc cfg ::uri uri)]
(try
(lzmq/sub! (::lzmq/receiver cfg) input)
(loop []
(when-let [msg (a/<!! input)]
(handle-event cfg msg)
(recur)))
(catch InterruptedException _
(l/debug :hint "reporter interrupted"))
(catch Throwable cause
(l/error :hint "unexpected exception"
:cause cause))
(finally
(a/close! input)
(l/info :hint "reporter terminated")))))))
(defmethod ig/halt-key! ::reporter
[_ thread]
(some-> thread px/interrupt!))
(defn- prepare-payload
[event]
(let [labels {:host (cf/get :host)
:tenant (cf/get :tenant)
:version (:full cf/version)
: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 (:trace event)]
(str "\n" error)))]]}]}))
(defn- make-request
[{:keys [::uri] :as cfg} payload]
(http/req! cfg
{:uri uri
:timeout 3000
:method :post
:headers {"content-type" "application/json"}
:body (json/encode payload)}
{:sync? true}))
(defn- handle-event
[cfg event]
(try
(let [payload (prepare-payload event)
response (make-request cfg payload)]
(when-not (= 204 (:status 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,24 +7,35 @@
(ns app.loggers.mattermost
"A mattermost integration for error reporting."
(:require
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.spec :as us]
[app.config :as cf]
[app.http.client :as http]
[app.loggers.database :as ldb]
[app.loggers.zmq :as lzmq]
[app.util.json :as json]
[clojure.core.async :as a]
[clojure.spec.alpha :as s]
[integrant.core :as ig]
[promesa.exec :as px]))
[promesa.exec :as px]
[promesa.exec.csp :as sp]))
(defonce enabled (atom true))
(defonce enabled (atom false))
(defn- send-mattermost-notification!
[cfg {:keys [host id public-uri] :as event}]
(let [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")))
[cfg {:keys [id public-uri] :as report}]
(let [text (str "Exception: " public-uri "/dbg/error/" id " "
(when-let [pid (:profile-id report)]
(str "(pid: #uuid-" pid ")"))
"\n"
"```\n"
"- host: `" (:host report) "`\n"
"- tenant: `" (:tenant report) "`\n"
"- version: `" (:version report) "`\n"
"\n"
"Trace:\n"
(:trace report)
"```")
resp (http/req! cfg
{:uri (cf/get :error-report-webhook)
:method :post
@@ -36,32 +47,41 @@
(l/warn :hint "error on sending data"
:response (pr-str resp)))))
(defn record->report
[{:keys [::l/context ::l/id ::l/cause] :as record}]
(us/assert! ::l/record record)
{:id id
:tenant (cf/get :tenant)
:host (cf/get :host)
:public-uri (cf/get :public-uri)
:version (:full cf/version)
:profile-id (:profile-id context)
:trace (ex/format-throwable cause :detail? false :header? false)})
(defn handle-event
[cfg event]
[cfg record]
(when @enabled
(try
(let [event (ldb/parse-event event)]
(send-mattermost-notification! cfg event))
(let [report (record->report record)]
(send-mattermost-notification! cfg report))
(catch Throwable cause
(l/warn :hint "unhandled error"
:cause cause)))))
(l/warn :hint "unhandled error" :cause cause)))))
(defmethod ig/pre-init-spec ::reporter [_]
(s/keys :req [::http/client
::lzmq/receiver]))
(s/keys :req [::http/client]))
(defmethod ig/init-key ::reporter
[_ cfg]
(when-let [uri (cf/get :error-report-webhook)]
(px/thread
{:name "penpot/mattermost-reporter"}
(l/info :msg "initializing error reporter" :uri uri)
(let [input (a/chan (a/sliding-buffer 128)
(filter #(= (:logger/level %) "error")))]
{:name "penpot/mattermost-reporter"
:virtual true}
(l/info :hint "initializing error reporter" :uri uri)
(let [input (sp/chan (sp/sliding-buffer 128) (filter ldb/error-record?))]
(add-watch l/log-record ::reporter #(sp/put! input %4))
(try
(lzmq/sub! (::lzmq/receiver cfg) input)
(loop []
(when-let [msg (a/<!! input)]
(when-let [msg (sp/take! input)]
(handle-event cfg msg)
(recur)))
(catch InterruptedException _
@@ -69,7 +89,8 @@
(catch Throwable cause
(l/error :hint "unexpected error" :cause cause))
(finally
(a/close! input)
(sp/close! input)
(remove-watch l/log-record ::reporter)
(l/info :hint "reporter terminated")))))))
(defmethod ig/halt-key! ::reporter

View File

@@ -1,130 +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) KALEIDOS INC
(ns app.loggers.zmq
"A generic ZMQ listener."
(:require
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.config :as cf]
[app.loggers.zmq.receiver :as-alias receiver]
[app.util.json :as json]
[app.util.time :as dt]
[clojure.core.async :as a]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[integrant.core :as ig]
[promesa.exec :as px])
(:import
org.zeromq.SocketType
org.zeromq.ZMQ$Socket
org.zeromq.ZContext))
(declare prepare)
(declare start-rcv-loop)
(defmethod ig/init-key ::receiver
[_ cfg]
(let [uri (cf/get :loggers-zmq-uri)
buffer (a/chan 1)
output (a/chan 1 (comp (filter map?)
(keep prepare)))
mult (a/mult output)
thread (when uri
(px/thread
{:name "penpot/zmq-receiver"
:daemon false}
(l/info :hint "receiver started")
(try
(start-rcv-loop buffer uri)
(catch InterruptedException _
(l/debug :hint "receiver interrupted"))
(catch java.lang.IllegalStateException cause
(if (= "errno 4" (ex-message cause))
(l/debug :hint "receiver interrupted")
(l/error :hint "unhandled error" :cause cause)))
(catch Throwable cause
(l/error :hint "unhandled error" :cause cause))
(finally
(l/info :hint "receiver terminated")))))]
(a/pipe buffer output)
(-> cfg
(assoc ::receiver/mult mult)
(assoc ::receiver/thread thread)
(assoc ::receiver/output output)
(assoc ::receiver/buffer buffer))))
(s/def ::receiver/mult some?)
(s/def ::receiver/thread #(instance? Thread %))
(s/def ::receiver/output some?)
(s/def ::receiver/buffer some?)
(s/def ::receiver
(s/keys :req [::receiver/mult
::receiver/thread
::receiver/output
::receiver/buffer]))
(defn sub!
[{:keys [::receiver/mult]} ch]
(a/tap mult ch))
(defmethod ig/halt-key! ::receiver
[_ {:keys [::receiver/buffer ::receiver/thread]}]
(some-> thread px/interrupt!)
(some-> buffer a/close!))
(def ^:private json-mapper
(json/mapper
{:encode-key-fn str/camel
:decode-key-fn (comp keyword str/kebab)}))
(defn- start-rcv-loop
[output endpoint]
(let [zctx (ZContext. 1)
socket (.. zctx (createSocket SocketType/SUB))]
(try
(.. socket (connect ^String endpoint))
(.. socket (subscribe ""))
(.. socket (setReceiveTimeOut 5000))
(loop []
(let [msg (.recv ^ZMQ$Socket socket)
msg (ex/ignoring (json/decode msg json-mapper))
msg (if (nil? msg) :empty msg)]
(when (a/>!! output msg)
(recur))))
(finally
(.close ^java.lang.AutoCloseable socket)
(.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]
(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

@@ -12,6 +12,8 @@
[app.common.logging :as l]
[app.config :as cf]
[app.db :as-alias db]
[app.email :as-alias email]
[app.http :as-alias http]
[app.http.access-token :as-alias actoken]
[app.http.assets :as-alias http.assets]
[app.http.awsns :as http.awsns]
@@ -20,10 +22,8 @@
[app.http.session :as-alias session]
[app.http.session.tasks :as-alias session.tasks]
[app.http.websocket :as http.ws]
[app.loggers.audit :as-alias audit]
[app.loggers.audit.tasks :as-alias audit.tasks]
[app.loggers.webhooks :as-alias webhooks]
[app.loggers.zmq :as-alias lzmq]
[app.metrics :as-alias mtx]
[app.metrics.definition :as-alias mdef]
[app.msgbus :as-alias mbus]
@@ -32,6 +32,8 @@
[app.rpc.doc :as-alias rpc.doc]
[app.srepl :as-alias srepl]
[app.storage :as-alias sto]
[app.storage.fs :as-alias sto.fs]
[app.storage.s3 :as-alias sto.s3]
[app.util.time :as dt]
[app.worker :as-alias wrk]
[cuerdas.core :as str]
@@ -162,15 +164,13 @@
(def system-config
{::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 ::mtx/metrics)
:migrations (ig/ref :app.migrations/all)
:name :main
:min-size (cf/get :database-min-pool-size 0)
:max-size (cf/get :database-max-pool-size 60)}
{::db/uri (cf/get :database-uri)
::db/username (cf/get :database-username)
::db/password (cf/get :database-password)
::db/read-only? (cf/get :database-readonly false)
::db/min-size (cf/get :database-min-pool-size 0)
::db/max-size (cf/get :database-max-pool-size 60)
::mtx/metrics (ig/ref ::mtx/metrics)}
;; Default thread pool for IO operations
::wrk/executor
@@ -185,7 +185,7 @@
::wrk/executor (ig/ref ::wrk/executor)}
:app.migrations/migrations
{}
{::db/pool (ig/ref ::db/pool)}
::mtx/metrics
{:default default-metrics}
@@ -193,9 +193,6 @@
::mtx/routes
{::mtx/metrics (ig/ref ::mtx/metrics)}
:app.migrations/all
{:main (ig/ref :app.migrations/migrations)}
::rds/redis
{::rds/uri (cf/get :redis-uri)
::mtx/metrics (ig/ref ::mtx/metrics)}
@@ -210,12 +207,11 @@
::wrk/scheduled-executor (ig/ref ::wrk/scheduled-executor)}
::sto/gc-deleted-task
{:pool (ig/ref ::db/pool)
:storage (ig/ref ::sto/storage)
:executor (ig/ref ::wrk/executor)}
{::db/pool (ig/ref ::db/pool)
::sto/storage (ig/ref ::sto/storage)}
::sto/gc-touched-task
{:pool (ig/ref ::db/pool)}
{::db/pool (ig/ref ::db/pool)}
::http.client/client
{::wrk/executor (ig/ref ::wrk/executor)}
@@ -239,15 +235,15 @@
::http.client/client (ig/ref ::http.client/client)
::wrk/executor (ig/ref ::wrk/executor)}
:app.http/server
{:port (cf/get :http-server-port)
:host (cf/get :http-server-host)
:router (ig/ref :app.http/router)
:metrics (ig/ref ::mtx/metrics)
:executor (ig/ref ::wrk/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)}
::http/server
{::http/port (cf/get :http-server-port)
::http/host (cf/get :http-server-host)
::http/router (ig/ref ::http/router)
::http/metrics (ig/ref ::mtx/metrics)
::http/executor (ig/ref ::wrk/executor)
::http/io-threads (cf/get :http-server-io-threads)
::http/max-body-size (cf/get :http-server-max-body-size)
::http/max-multipart-body-size (cf/get :http-server-max-multipart-body-size)}
::ldap/provider
{:host (cf/get :ldap-host)
@@ -284,7 +280,6 @@
:github (ig/ref ::oidc.providers/github)
:gitlab (ig/ref ::oidc.providers/gitlab)
:oidc (ig/ref ::oidc.providers/generic)}
::audit/collector (ig/ref ::audit/collector)
::session/manager (ig/ref ::session/manager)}
:app.http/router
@@ -314,12 +309,11 @@
::session/manager (ig/ref ::session/manager)}
:app.http.assets/routes
{:metrics (ig/ref ::mtx/metrics)
:assets-path (cf/get :assets-path)
:storage (ig/ref ::sto/storage)
:executor (ig/ref ::wrk/executor)
:cache-max-age (dt/duration {:hours 24})
:signature-max-age (dt/duration {:hours 24 :minutes 5})}
{::http.assets/path (cf/get :assets-path)
::http.assets/cache-max-age (dt/duration {:hours 24})
::http.assets/cache-max-agesignature-max-age (dt/duration {:hours 24 :minutes 5})
::sto/storage (ig/ref ::sto/storage)
::wrk/executor (ig/ref ::wrk/executor)}
:app.rpc/climit
{::mtx/metrics (ig/ref ::mtx/metrics)
@@ -330,8 +324,7 @@
::wrk/scheduled-executor (ig/ref ::wrk/scheduled-executor)}
:app.rpc/methods
{::audit/collector (ig/ref ::audit/collector)
::http.client/client (ig/ref ::http.client/client)
{::http.client/client (ig/ref ::http.client/client)
::db/pool (ig/ref ::db/pool)
::wrk/executor (ig/ref ::wrk/executor)
::session/manager (ig/ref ::session/manager)
@@ -362,9 +355,9 @@
::props (ig/ref :app.setup/props)}
::wrk/registry
{:metrics (ig/ref ::mtx/metrics)
:tasks
{:sendmail (ig/ref :app.emails/handler)
{::mtx/metrics (ig/ref ::mtx/metrics)
::wrk/tasks
{:sendmail (ig/ref ::email/handler)
:objects-gc (ig/ref :app.tasks.objects-gc/handler)
:file-gc (ig/ref :app.tasks.file-gc/handler)
:file-xlog-gc (ig/ref :app.tasks.file-xlog-gc/handler)
@@ -381,34 +374,32 @@
:run-webhook
(ig/ref ::webhooks/run-webhook-handler)}}
::email/sendmail
{::email/host (cf/get :smtp-host)
::email/port (cf/get :smtp-port)
::email/ssl (cf/get :smtp-ssl)
::email/tls (cf/get :smtp-tls)
::email/username (cf/get :smtp-username)
::email/password (cf/get :smtp-password)
::email/default-reply-to (cf/get :smtp-default-reply-to)
::email/default-from (cf/get :smtp-default-from)}
:app.emails/sendmail
{:host (cf/get :smtp-host)
:port (cf/get :smtp-port)
:ssl (cf/get :smtp-ssl)
:tls (cf/get :smtp-tls)
:username (cf/get :smtp-username)
:password (cf/get :smtp-password)
:default-reply-to (cf/get :smtp-default-reply-to)
:default-from (cf/get :smtp-default-from)}
:app.emails/handler
{:sendmail (ig/ref :app.emails/sendmail)
:metrics (ig/ref ::mtx/metrics)}
::email/handler
{::email/sendmail (ig/ref ::email/sendmail)
::mtx/metrics (ig/ref ::mtx/metrics)}
:app.tasks.tasks-gc/handler
{:pool (ig/ref ::db/pool)
:max-age cf/deletion-delay}
{::db/pool (ig/ref ::db/pool)}
:app.tasks.objects-gc/handler
{::db/pool (ig/ref ::db/pool)
::sto/storage (ig/ref ::sto/storage)}
:app.tasks.file-gc/handler
{:pool (ig/ref ::db/pool)}
{::db/pool (ig/ref ::db/pool)}
:app.tasks.file-xlog-gc/handler
{:pool (ig/ref ::db/pool)}
{::db/pool (ig/ref ::db/pool)}
:app.tasks.telemetry/handler
{::db/pool (ig/ref ::db/pool)
@@ -416,27 +407,23 @@
::props (ig/ref :app.setup/props)}
[::srepl/urepl ::srepl/server]
{:port (cf/get :urepl-port 6062)
:host (cf/get :urepl-host "localhost")}
{::srepl/port (cf/get :urepl-port 6062)
::srepl/host (cf/get :urepl-host "localhost")}
[::srepl/prepl ::srepl/server]
{:port (cf/get :prepl-port 6063)
:host (cf/get :prepl-host "localhost")}
{::srepl/port (cf/get :prepl-port 6063)
::srepl/host (cf/get :prepl-host "localhost")}
:app.setup/builtin-templates
{::http.client/client (ig/ref ::http.client/client)}
:app.setup/props
{:pool (ig/ref ::db/pool)
:key (cf/get :secret-key)}
{::db/pool (ig/ref ::db/pool)
::key (cf/get :secret-key)
::lzmq/receiver
{}
::audit/collector
{::db/pool (ig/ref ::db/pool)
::wrk/executor (ig/ref ::wrk/executor)
::mtx/metrics (ig/ref ::mtx/metrics)}
;; NOTE: this dependency is only necessary for proper initialization ordering, props
;; module requires the migrations to run before initialize.
::migrations (ig/ref :app.migrations/migrations)}
::audit.tasks/archive
{::props (ig/ref :app.setup/props)
@@ -454,38 +441,27 @@
{::db/pool (ig/ref ::db/pool)
::http.client/client (ig/ref ::http.client/client)}
:app.loggers.loki/reporter
{::lzmq/receiver (ig/ref ::lzmq/receiver)
::http.client/client (ig/ref ::http.client/client)}
:app.loggers.mattermost/reporter
{::lzmq/receiver (ig/ref ::lzmq/receiver)
::http.client/client (ig/ref ::http.client/client)}
{::http.client/client (ig/ref ::http.client/client)}
:app.loggers.database/reporter
{::lzmq/receiver (ig/ref :app.loggers.zmq/receiver)
::db/pool (ig/ref ::db/pool)}
{::db/pool (ig/ref ::db/pool)}
::sto/storage
{:pool (ig/ref ::db/pool)
:executor (ig/ref ::wrk/executor)
:backends
{::db/pool (ig/ref ::db/pool)
::wrk/executor (ig/ref ::wrk/executor)
::sto/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])}}
:assets-fs (ig/ref [::assets :app.storage.fs/backend])}}
[::assets :app.storage.s3/backend]
{:region (cf/get :storage-assets-s3-region)
:endpoint (cf/get :storage-assets-s3-endpoint)
:bucket (cf/get :storage-assets-s3-bucket)
:executor (ig/ref ::wrk/executor)}
{::sto.s3/region (cf/get :storage-assets-s3-region)
::sto.s3/endpoint (cf/get :storage-assets-s3-endpoint)
::sto.s3/bucket (cf/get :storage-assets-s3-bucket)
::wrk/executor (ig/ref ::wrk/executor)}
[::assets :app.storage.fs/backend]
{:directory (cf/get :storage-assets-fs-directory)}
{::sto.fs/directory (cf/get :storage-assets-fs-directory)}
})

View File

@@ -12,6 +12,8 @@
[app.common.media :as cm]
[app.common.spec :as us]
[app.config :as cf]
[app.db :as-alias db]
[app.storage :as-alias sto]
[app.storage.tmp :as tmp]
[app.util.svg :as svg]
[buddy.core.bytes :as bb]
@@ -297,8 +299,7 @@
"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)))))
(assoc storage ::sto/backend (cf/get :assets-storage-backend :assets-fs)))
([storage pool-or-conn]
(-> (configure-assets-storage storage)
(assoc ::db/pool-or-conn pool-or-conn))))

View File

@@ -6,8 +6,12 @@
(ns app.migrations
(:require
[app.common.data.macros :as dm]
[app.common.logging :as l]
[app.db :as db]
[app.migrations.clj.migration-0023 :as mg0023]
[app.util.migrations :as mg]
[clojure.spec.alpha :as s]
[integrant.core :as ig]))
(def migrations
@@ -308,7 +312,23 @@
{:name "0100-mod-profile-indexes"
:fn (mg/resource "app/migrations/sql/0100-mod-profile-indexes.sql")}
{:name "0101-mod-server-error-report-table"
:fn (mg/resource "app/migrations/sql/0101-mod-server-error-report-table.sql")}
])
(defn apply-migrations!
[pool name migrations]
(dm/with-open [conn (db/open pool)]
(mg/setup! conn)
(mg/migrate! conn {:name name :steps migrations})))
(defmethod ig/init-key ::migrations [_ _] migrations)
(defmethod ig/pre-init-spec ::migrations
[_]
(s/keys :req [::db/pool]))
(defmethod ig/init-key ::migrations
[module {:keys [::db/pool]}]
(when-not (db/read-only? pool)
(l/info :hint "running migrations" :module module)
(some->> (seq migrations) (apply-migrations! pool "main"))))

View File

@@ -0,0 +1,2 @@
ALTER TABLE server_error_report
ADD COLUMN version integer DEFAULT 1;

View File

@@ -79,7 +79,7 @@
(us/verify! ::msgbus msgbus)
(set-error-handler! state #(l/error :cause % :hint "unexpected error on agent" ::l/async false))
(set-error-handler! state #(l/error :cause % :hint "unexpected error on agent" ::l/sync? true))
(set-error-mode! state :continue)
(start-io-loop! msgbus)
@@ -133,7 +133,7 @@
[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)
(l/trace :hint "open subscription" :topic topic ::l/sync? true)
(redis-sub cfg topic))
nsubs))
@@ -144,7 +144,7 @@
[nsubs cfg topic chan]
(let [nsubs (disj nsubs chan)]
(when (empty? nsubs)
(l/trace :hint "close subscription" :topic topic ::l/async false)
(l/trace :hint "close subscription" :topic topic ::l/sync? true)
(redis-unsub cfg topic))
nsubs))

View File

@@ -15,9 +15,9 @@
[app.config :as cf]
[app.db :as db]
[app.http :as-alias http]
[app.http.access-token :as-alias actoken]
[app.http.access-token :as actoken]
[app.http.client :as-alias http.client]
[app.http.session :as-alias session]
[app.http.session :as session]
[app.loggers.audit :as audit]
[app.loggers.webhooks :as-alias webhooks]
[app.main :as-alias main]
@@ -93,7 +93,7 @@
(p/mcat (partial handle-response request))
(p/fnly (fn [response cause]
(if cause
(raise (ex/wrap-with-context cause {:profile-id profile-id}))
(raise cause)
(respond response)))))))
(defn- rpc-mutation-handler
@@ -117,7 +117,7 @@
(p/mcat (partial handle-response request))
(p/fnly (fn [response cause]
(if cause
(raise (ex/wrap-with-context cause {:profile-id profile-id}))
(raise cause)
(respond response)))))))
(defn- rpc-command-handler
@@ -144,7 +144,7 @@
(p/mcat (partial handle-response request))
(p/fnly (fn [response cause]
(if cause
(raise (ex/wrap-with-context cause {:profile-id profile-id}))
(raise cause)
(respond response))))))))
(defn- wrap-metrics
@@ -162,10 +162,10 @@
(defn- wrap-authentication
[_ f {:keys [::auth] :as mdata}]
[_ f mdata]
(fn [cfg params]
(let [profile-id (::profile-id params)]
(if (and auth (not (uuid? profile-id)))
(if (and (::auth mdata true) (not (uuid? profile-id)))
(p/rejected
(ex/error :type :authentication
:code :authentication-required
@@ -202,7 +202,8 @@
(defn- wrap-audit
[cfg f mdata]
(if-let [collector (::audit/collector cfg)]
(if (or (contains? cf/flags :webhooks)
(contains? cf/flags :audit-log))
(letfn [(handle-audit [params result]
(let [resultm (meta result)
request (::http/request params)
@@ -219,8 +220,7 @@
(merge (::audit/props resultm))
(dissoc :profile-id)
(dissoc :type)))
(d/without-qualified)
(d/without-nils))
(audit/clean-props))
event {:type (or (::audit/type resultm)
(::type cfg))
@@ -248,13 +248,14 @@
(::webhooks/event? resultm)
false)}]
(audit/submit! collector event)))
(audit/submit! cfg event)))
(handle-request [cfg params]
(->> (f cfg params)
(p/mcat (fn [result]
(->> (handle-audit params result)
(p/map (constantly result)))))))]
(p/fnly (fn [result cause]
(when-not cause
(handle-audit params result))))))]
(if-not (::audit/skip mdata)
(with-meta handle-request mdata)
f))
@@ -349,8 +350,7 @@
(into {}))))
(defmethod ig/pre-init-spec ::methods [_]
(s/keys :req [::audit/collector
::session/manager
(s/keys :req [::session/manager
::http.client/client
::db/pool
::mbus/msgbus
@@ -365,9 +365,10 @@
(defmethod ig/init-key ::methods
[_ cfg]
{:mutations (resolve-mutation-methods cfg)
:queries (resolve-query-methods cfg)
:commands (resolve-command-methods cfg)})
(let [cfg (d/without-nils cfg)]
{:mutations (resolve-mutation-methods cfg)
:queries (resolve-query-methods cfg)
:commands (resolve-command-methods cfg)}))
(s/def ::mutations
(s/map-of keyword? fn?))

View File

@@ -32,7 +32,7 @@
(defn- capacity-exception?
[o]
(and (ex/ex-info? o)
(and (ex/error? o)
(let [data (ex-data o)]
(and (= :bulkhead-error (:type data))
(= :capacity-limit-reached (:code data))))))

View File

@@ -13,7 +13,7 @@
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.emails :as eml]
[app.email :as eml]
[app.http.session :as session]
[app.loggers.audit :as audit]
[app.main :as-alias main]
@@ -335,7 +335,7 @@
:extra-data ptoken})))
(defn register-profile
[{:keys [conn] :as cfg} {:keys [token] :as params}]
[{:keys [::db/conn] :as cfg} {:keys [token] :as params}]
(let [claims (tokens/verify (::main/props cfg) {:token token :iss :prepared-register})
params (merge params claims)
@@ -355,11 +355,10 @@
;; accordingly.
(when-let [id (:profile-id claims)]
(db/update! conn :profile {:modified-at (dt/now)} {:id id})
(when-let [collector (::audit/collector cfg)]
(audit/submit! collector
{:type "fact"
:name "register-profile-retry"
:profile-id id})))
(audit/submit! cfg
{:type "fact"
:name "register-profile-retry"
:profile-id id}))
(cond
;; If invitation token comes in params, this is because the
@@ -411,7 +410,7 @@
::doc/added "1.15"}
[{:keys [::db/pool] :as cfg} params]
(db/with-atomic [conn pool]
(-> (assoc cfg :conn conn)
(-> (assoc cfg ::db/conn conn)
(register-profile params))))
;; ---- COMMAND: Request Profile Recovery

View File

@@ -109,20 +109,20 @@
(defn write-byte!
[^DataOutputStream output data]
(l/trace :fn "write-byte!" :data data :position @*position* ::l/async false)
(l/trace :fn "write-byte!" :data data :position @*position* ::l/sync? true)
(.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)
(l/trace :fn "read-byte!" :val v :position @*position* ::l/sync? true)
(swap! *position* inc)
v))
(defn write-long!
[^DataOutputStream output data]
(l/trace :fn "write-long!" :data data :position @*position* ::l/async false)
(l/trace :fn "write-long!" :data data :position @*position* ::l/sync? true)
(.writeLong output (long data))
(swap! *position* + 8))
@@ -130,14 +130,14 @@
(defn read-long!
[^DataInputStream input]
(let [v (.readLong input)]
(l/trace :fn "read-long!" :val v :position @*position* ::l/async false)
(l/trace :fn "read-long!" :val v :position @*position* ::l/sync? true)
(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)
(l/trace :fn "write-bytes!" :size size :position @*position* ::l/sync? true)
(.write output data 0 size)
(swap! *position* + size)))
@@ -145,7 +145,7 @@
[^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)
(l/trace :fn "read-bytes!" :expected (alength buff) :readed readed :position @*position* ::l/sync? true)
(swap! *position* + readed)
readed))
@@ -153,7 +153,7 @@
(defn write-uuid!
[^DataOutputStream output id]
(l/trace :fn "write-uuid!" :position @*position* :WRITTEN? (.size output) ::l/async false)
(l/trace :fn "write-uuid!" :position @*position* :WRITTEN? (.size output) ::l/sync? true)
(doto output
(write-byte! (get-mark :uuid))
@@ -162,7 +162,7 @@
(defn read-uuid!
[^DataInputStream input]
(l/trace :fn "read-uuid!" :position @*position* ::l/async false)
(l/trace :fn "read-uuid!" :position @*position* ::l/sync? true)
(let [m (read-byte! input)]
(assert-mark m :uuid)
(let [a (read-long! input)
@@ -171,7 +171,7 @@
(defn write-obj!
[^DataOutputStream output data]
(l/trace :fn "write-obj!" :position @*position* ::l/async false)
(l/trace :fn "write-obj!" :position @*position* ::l/sync? true)
(let [^bytes data (fres/encode data)]
(doto output
(write-byte! (get-mark :obj))
@@ -180,7 +180,7 @@
(defn read-obj!
[^DataInputStream input]
(l/trace :fn "read-obj!" :position @*position* ::l/async false)
(l/trace :fn "read-obj!" :position @*position* ::l/sync? true)
(let [m (read-byte! input)]
(assert-mark m :obj)
(let [size (read-long! input)]
@@ -191,14 +191,14 @@
(defn write-label!
[^DataOutputStream output label]
(l/trace :fn "write-label!" :label label :position @*position* ::l/async false)
(l/trace :fn "write-label!" :label label :position @*position* ::l/sync? true)
(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)
(l/trace :fn "read-label!" :position @*position* ::l/sync? true)
(let [m (read-byte! input)]
(assert-mark m :label)
(read-obj! input)))
@@ -208,7 +208,7 @@
(l/trace :fn "write-header!"
:version version
:position @*position*
::l/async false)
::l/sync? true)
(let [vers (-> version name (subs 1) parse-long)
output (io/data-output-stream output)]
(doto output
@@ -218,7 +218,7 @@
(defn read-header!
[^InputStream input]
(l/trace :fn "read-header!" :position @*position* ::l/async false)
(l/trace :fn "read-header!" :position @*position* ::l/sync? true)
(let [input (io/data-input-stream input)
mark (read-byte! input)
mnum (read-long! input)
@@ -235,13 +235,13 @@
(defn copy-stream!
[^OutputStream output ^InputStream input ^long size]
(let [written (io/copy! input output :size size)]
(l/trace :fn "copy-stream!" :position @*position* :size size :written written ::l/async false)
(l/trace :fn "copy-stream!" :position @*position* :size size :written written ::l/sync? true)
(swap! *position* + written)
written))
(defn write-stream!
[^DataOutputStream output stream size]
(l/trace :fn "write-stream!" :position @*position* ::l/async false :size size)
(l/trace :fn "write-stream!" :position @*position* ::l/sync? true :size size)
(doto output
(write-byte! (get-mark :stream))
(write-long! size))
@@ -250,7 +250,7 @@
(defn read-stream!
[^DataInputStream input]
(l/trace :fn "read-stream!" :position @*position* ::l/async false)
(l/trace :fn "read-stream!" :position @*position* ::l/sync? true)
(let [m (read-byte! input)
s (read-long! input)
p (tmp/tempfile :prefix "penpot.binfile.")]
@@ -264,7 +264,7 @@
(if (> s temp-file-threshold)
(with-open [^OutputStream output (io/output-stream p)]
(let [readed (io/copy! input output :offset 0 :size s)]
(l/trace :fn "read-stream*!" :expected s :readed readed :position @*position* ::l/async false)
(l/trace :fn "read-stream*!" :expected s :readed readed :position @*position* ::l/sync? true)
(swap! *position* + readed)
[s p]))
[s (io/read-as-bytes input :size s)])))
@@ -452,6 +452,7 @@
`::embed-assets?`: instead of including the libraries, embed 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?))
@@ -465,7 +466,7 @@
(with-open [output (io/data-output-stream output)]
(binding [*state* (volatile! {})]
(run! (fn [section]
(l/debug :hint "write section" :section section ::l/async false)
(l/debug :hint "write section" :section section ::l/sync? true)
(write-label! output section)
(let [options (-> options
(assoc ::output output)
@@ -499,7 +500,7 @@
(l/debug :hint "write penpot file"
:id file-id
:media (count media)
::l/async false)
::l/sync? true)
(doto output
(write-obj! file)
@@ -511,23 +512,23 @@
[{:keys [::db/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)
(l/debug :hint "found rels" :total (count rels) ::l/sync? true)
(write-obj! output rels)))
(defmethod write-section :v1/sobjects
[{:keys [storage ::output]}]
[{:keys [::sto/storage ::output]}]
(let [sids (-> *state* deref :sids)
storage (media/configure-assets-storage storage)]
(l/debug :hint "found sobjects"
:items (count sids)
::l/async false)
::l/sync? true)
;; 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)
(l/debug :hint "write sobject" :id id ::l/sync? true)
(doto output
(write-uuid! id)
(write-obj! (meta obj)))
@@ -587,7 +588,7 @@
(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)
(l/debug :hint "reading section" :section section ::l/sync? true)
(assert-read-label! input section)
(let [options (-> options
(assoc ::section section)
@@ -605,7 +606,7 @@
(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)
(l/debug :hint "metadata readed" :version (:full version) :files files ::l/sync? true)
(vswap! *state* update :index update-index files)
(vswap! *state* assoc :version version :files files)))
@@ -633,14 +634,14 @@
: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)
(l/debug :hint "update index with media" ::l/sync? true)
(vswap! *state* update :index update-index (map :id media'))
;; Store file media for later insertion
(l/debug :hint "update media references" ::l/async false)
(l/debug :hint "update media references" ::l/sync? true)
(vswap! *state* update :media into (map #(update % :id lookup-index)) media')
(l/debug :hint "processing file" :file-id file-id ::features features ::l/async false)
(l/debug :hint "processing file" :file-id file-id ::features features ::l/sync? true)
(binding [ffeat/*current* features
ffeat/*wrap-with-objects-map-fn* (if (features "storage/objects-map") omap/wrap identity)
@@ -666,7 +667,7 @@
:created-at timestamp
:modified-at timestamp}]
(l/debug :hint "create file" :id file-id' ::l/async false)
(l/debug :hint "create file" :id file-id' ::l/sync? true)
(if overwrite?
(create-or-update-file conn params)
@@ -689,7 +690,7 @@
(l/debug :hint "create file library link"
:file-id (:file-id rel)
:lib-id (:library-file-id rel)
::l/async false)
::l/sync? true)
(db/insert! conn :file-library-rel rel)))))
(defmethod read-section :v1/sobjects
@@ -706,7 +707,7 @@
: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)
(l/debug :hint "readed storage object" :id id ::l/sync? true)
(let [[size resource] (read-stream! input)
hash (sto/calculate-hash resource)
@@ -720,18 +721,18 @@
sobject @(sto/put-object! storage params)]
(l/debug :hint "persisted storage object" :id id :new-id (:id sobject) ::l/async false)
(l/debug :hint "persisted storage object" :id id :new-id (:id sobject) ::l/sync? true)
(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)
::l/sync? true)
(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)
(l/warn :hint "ignoring file media object" :file-id (:file-id item) ::l/sync? true)
(db/insert! conn :file-media-object
(-> item
(assoc :file-id file-id)
@@ -742,7 +743,7 @@
(defn- lookup-index
[id]
(let [val (get-in @*state* [:index id])]
(l/trace :fn "lookup-index" :id id :val val ::l/async false)
(l/trace :fn "lookup-index" :id id :val val ::l/sync? true)
(when (and (not (::ignore-index-errors? *options*)) (not val))
(ex/raise :type :validation
:code :incomplete-index
@@ -755,7 +756,7 @@
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)
(l/trace :fn "update-index" :id id :new-id new-id ::l/sync? true)
(recur (rest items)
(assoc index id new-id)))
index)))
@@ -803,7 +804,7 @@
(try
(process-map-form form)
(catch Throwable cause
(l/warn :hint "failed form" :form (pr-str form) ::l/async false)
(l/warn :hint "failed form" :form (pr-str form) ::l/sync? true)
(throw cause)))
form))
data)))

View File

@@ -11,7 +11,7 @@
[app.common.spec :as us]
[app.config :as cf]
[app.db :as db]
[app.emails :as eml]
[app.email :as eml]
[app.rpc :as-alias rpc]
[app.rpc.commands.profile :as profile]
[app.rpc.doc :as-alias doc]

View File

@@ -493,6 +493,7 @@
(library-summary [{:keys [id data] :as file}]
(binding [pmap/*load-fn* (partial load-pointer conn id)]
{:components (assets-sample (:components data) 4)
:media (assets-sample (:media data) 3)
:colors (assets-sample (:colors data) 3)
:typographies (assets-sample (:typographies data) 3)}))]
@@ -996,7 +997,8 @@
:opt-un [::data]))
(sv/defmethod ::upsert-file-object-thumbnail
{::doc/added "1.17"}
{::doc/added "1.17"
::audit/skip true}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id file-id)
@@ -1005,13 +1007,13 @@
;; --- MUTATION COMMAND: upsert-file-thumbnail
(def sql:upsert-file-thumbnail
(def ^:private sql:upsert-file-thumbnail
"insert into file_thumbnail (file_id, revn, data, props)
values (?, ?, ?, ?::jsonb)
on conflict(file_id, revn) do
update set data = ?, props=?, updated_at=now();")
(defn upsert-file-thumbnail
(defn- upsert-file-thumbnail!
[conn {:keys [file-id revn data props]}]
(let [props (db/tjson (or props {}))]
(db/exec-one! conn [sql:upsert-file-thumbnail
@@ -1026,9 +1028,11 @@
(sv/defmethod ::upsert-file-thumbnail
"Creates or updates the file thumbnail. Mainly used for paint the
grid thumbnails."
{::doc/added "1.17"}
{::doc/added "1.17"
::audit/skip true}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id file-id)
(upsert-file-thumbnail conn params)
(when-not (db/read-only? conn)
(upsert-file-thumbnail! conn params))
nil))

View File

@@ -23,6 +23,7 @@
[app.util.objects-map :as omap]
[app.util.pointer-map :as pmap]
[app.util.services :as sv]
[app.util.time :as dt]
[clojure.spec.alpha :as s]))
(defn create-file-role!
@@ -67,6 +68,10 @@
(->> (assoc params :file-id id :role :owner)
(create-file-role! conn))
(db/update! conn :project
{:modified-at (dt/now)}
{:id project-id})
(files/decode-row file)))
(s/def ::create-file

View File

@@ -15,7 +15,7 @@
[app.loggers.webhooks :as-alias webhooks]
[app.media :as media]
[app.rpc :as-alias rpc]
[app.rpc.climit :as-alias climit]
[app.rpc.climit :as climit]
[app.rpc.commands.files :as files]
[app.rpc.commands.projects :as projects]
[app.rpc.commands.teams :as teams]
@@ -41,7 +41,6 @@
(s/def ::project-id ::us/uuid)
(s/def ::style valid-style)
(s/def ::team-id ::us/uuid)
(s/def ::team-id ::us/uuid)
(s/def ::weight valid-weight)
;; --- QUERY: Get font variants

View File

@@ -231,12 +231,13 @@
;; Defer all constraints
(db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"])
(let [project (db/get-by-id conn :project project-id)
(let [project (-> (db/get-by-id conn :project project-id)
(assoc :is-pinned false))
files (db/query conn :file
{:project-id (:id project)
:deleted-at nil}
{:columns [:id]})
{:project-id (:id project)
:deleted-at nil}
{:columns [:id]})
project (cond-> project
(string? name)

View File

@@ -13,7 +13,7 @@
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.emails :as eml]
[app.email :as eml]
[app.http.session :as session]
[app.loggers.audit :as audit]
[app.main :as-alias main]
@@ -113,7 +113,7 @@
(declare invalidate-profile-session!)
(s/def ::password ::us/not-empty-string)
(s/def ::old-password ::us/not-empty-string)
(s/def ::old-password (s/nilable ::us/string))
(s/def ::update-profile-password
(s/keys :req [::rpc/profile-id]
@@ -145,16 +145,18 @@
(defn- validate-password!
[conn {:keys [profile-id old-password] :as params}]
(let [profile (db/get-by-id conn :profile profile-id ::db/for-update? true)]
(when-not (:valid (auth/verify-password old-password (:password profile)))
(when (and (not= (:password profile) "!")
(not (:valid (auth/verify-password old-password (:password profile)))))
(ex/raise :type :validation
:code :old-password-not-match))
profile))
(defn update-profile-password!
[conn {:keys [id password] :as profile}]
(db/update! conn :profile
{:password (auth/derive-password password)}
{:id id}))
(when-not (db/read-only? conn)
(db/update! conn :profile
{:password (auth/derive-password password)}
{:id id})))
;; --- MUTATION: Update Photo

View File

@@ -45,6 +45,7 @@
from file as f
inner join projects as pr on (f.project_id = pr.id)
where f.name ilike ('%' || ? || '%')
and (f.deleted_at is null or f.deleted_at > now())
order by f.created_at asc")
(defn search-files

View File

@@ -13,7 +13,7 @@
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.emails :as eml]
[app.email :as eml]
[app.loggers.audit :as audit]
[app.main :as-alias main]
[app.media :as media]
@@ -62,12 +62,18 @@
:can-edit (or is-owner is-admin can-edit)
:can-read true})))
(def has-admin-permissions?
(perms/make-admin-predicate-fn get-permissions))
(def has-edit-permissions?
(perms/make-edition-predicate-fn get-permissions))
(def has-read-permissions?
(perms/make-read-predicate-fn get-permissions))
(def check-admin-permissions!
(perms/make-check-fn has-admin-permissions?))
(def check-edition-permissions!
(perms/make-check-fn has-edit-permissions?))
@@ -474,7 +480,7 @@
(s/def ::team-id ::us/uuid)
(s/def ::member-id ::us/uuid)
;; Temporarily disabled viewer role
;; https://tree.taiga.io/project/uxboxproject/issue/1083
;; https://tree.taiga.io/project/penpot/issue/1083
;; (s/def ::role #{:owner :admin :editor :viewer})
(s/def ::role #{:owner :admin :editor})
@@ -593,26 +599,28 @@
(retrieve-team pool profile-id team-id))
photo (profile/upload-photo cfg params)]
;; Mark object as touched for make it ellegible for tentative
;; garbage collection.
(when-let [id (:photo-id team)]
(sto/touch-object! storage id))
(db/with-atomic [conn pool]
(check-admin-permissions! conn profile-id team-id)
;; Mark object as touched for make it ellegible for tentative
;; garbage collection.
(when-let [id (:photo-id team)]
(sto/touch-object! storage id))
;; Save new photo
(db/update! pool :team
{:photo-id (:id photo)}
{:id team-id})
(assoc team :photo-id (:id photo))))
;; Save new photo
(db/update! pool :team
{:photo-id (:id photo)}
{:id team-id})
(assoc team :photo-id (:id photo)))))
;; --- Mutation: Create Team Invitation
(def sql:upsert-team-invitation
"insert into team_invitation(team_id, email_to, role, valid_until)
values (?, ?, ?, ?)
"insert into team_invitation(id, team_id, email_to, role, valid_until)
values (?, ?, ?, ?, ?)
on conflict(team_id, email_to) do
update set role = ?, updated_at = now();")
update set role = ?, valid_until = ?, updated_at = now()
returning *")
(defn- create-invitation-token
[cfg {:keys [profile-id valid-until team-id member-id member-email role]}]
@@ -633,16 +641,8 @@
:exp (dt/in-future {:days 30})}))
(defn- create-invitation
[{:keys [::conn] :as cfg} {:keys [team profile role email] :as params}]
(let [member (profile/get-profile-by-email conn email)
expire (dt/in-future "168h") ;; 7 days
itoken (create-invitation-token cfg {:profile-id (:id profile)
:valid-until expire
:team-id (:id team)
:member-email (or (:email member) email)
:member-id (:id member)
:role role})
ptoken (create-profile-identity-token cfg profile)]
[{:keys [::db/conn] :as cfg} {:keys [team profile role email] :as params}]
(let [member (profile/get-profile-by-email conn email)]
(when (and member (not (eml/allow-send-emails? conn member)))
(ex/raise :type :validation
@@ -657,9 +657,6 @@
:email email
:hint "the email you invite has been repeatedly reported as spam or bounce"))
(when (contains? cf/flags :log-invitation-tokens)
(l/trace :hint "invitation token" :token itoken))
;; When we have email verification disabled and invitation user is
;; already present in the database, we proceed to add it to the
;; team as-is, without email roundtrip.
@@ -680,10 +677,38 @@
(when-not (:is-active member)
(db/update! conn :profile
{:is-active true}
{:id (:id member)})))
(do
(db/exec-one! conn [sql:upsert-team-invitation
(:id team) (str/lower email) (name role) expire (name role)])
{:id (:id member)}))
nil)
(let [id (uuid/next)
expire (dt/in-future "168h") ;; 7 days
invitation (db/exec-one! conn [sql:upsert-team-invitation id
(:id team) (str/lower email)
(name role) expire
(name role) expire])
updated? (not= id (:id invitation))
tprops {:profile-id (:id profile)
:invitation-id (:id invitation)
:valid-until expire
:team-id (:id team)
:member-email (:email-to invitation)
:member-id (:id member)
:role role}
itoken (create-invitation-token cfg tprops)
ptoken (create-profile-identity-token cfg profile)]
(when (contains? cf/flags :log-invitation-tokens)
(l/info :hint "invitation token" :token itoken))
(audit/submit! cfg
{:type "action"
:name (if updated?
"update-team-invitation"
"create-team-invitation")
:profile-id (:id profile)
:props (-> (dissoc tprops :profile-id)
(d/without-nils))})
(eml/send! {::eml/conn conn
::eml/factory eml/invite-to-team
:public-uri (cf/get :public-uri)
@@ -691,9 +716,9 @@
:invited-by (:fullname profile)
:team (:name team)
:token itoken
:extra-data ptoken})))
:extra-data ptoken})
itoken))
itoken))))
(s/def ::email ::us/email)
(s/def ::emails ::us/set-of-valid-emails)
@@ -711,8 +736,13 @@
(let [perms (get-permissions conn profile-id team-id)
profile (db/get-by-id conn :profile profile-id)
team (db/get-by-id conn :team team-id)
emails (cond-> (or emails #{}) (string? email) (conj email))]
;; Members emails. We don't re-send inviation to already existing members
member? (into #{}
(map :email)
(db/exec! conn [sql:team-members team-id]))
emails (cond-> (or emails #{}) (string? email) (conj email))]
(run! (partial quotes/check-quote! conn)
(list {::quotes/id ::quotes/invitations-per-team
@@ -734,14 +764,15 @@
:code :profile-is-muted
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces"))
(let [cfg (assoc cfg ::conn conn)
(let [cfg (assoc cfg ::db/conn conn)
invitations (->> emails
(remove member?)
(map (fn [email]
{:email (str/lower email)
:team team
:profile profile
:role role}))
(map (partial create-invitation cfg)))]
(keep (partial create-invitation cfg)))]
(with-meta (vec invitations)
{::audit/props {:invitations (count invitations)}})))))
@@ -757,9 +788,10 @@
{::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id emails role] :as params}]
(db/with-atomic [conn pool]
(let [team (create-team conn params)
(let [params (assoc params :profile-id profile-id)
team (create-team conn params)
profile (db/get-by-id conn :profile profile-id)
cfg (assoc cfg ::conn conn)]
cfg (assoc cfg ::db/conn conn)]
;; Create invitations for all provided emails.
(->> emails
@@ -782,18 +814,16 @@
::quotes/team-id (:id team)
::quotes/incr (count emails)}))
(-> team
(vary-meta assoc ::audit/props {:invitations (count emails)})
(rph/with-defer
#(when-let [collector (::audit/collector cfg)]
(audit/submit! collector
{:type "command"
:name "create-team-invitations"
:profile-id profile-id
:props {:emails emails
:role role
:profile-id profile-id
:invitations (count emails)}})))))))
(audit/submit! cfg
{:type "command"
:name "create-team-invitations"
:profile-id profile-id
:props {:emails emails
:role role
:profile-id profile-id
:invitations (count emails)}})
(vary-meta team assoc ::audit/props {:invitations (count emails)}))))
;; --- Query: get-team-invitation-token
@@ -809,7 +839,7 @@
{:team-id team-id
:email-to (str/lower email)})
(update :role keyword))
member (profile/get-profile-by-email pool (:email invit))
member (profile/get-profile-by-email pool (:email-to invit))
token (create-invitation-token cfg {:team-id (:team-id invit)
:profile-id profile-id
:valid-until (:valid-until invit)
@@ -855,6 +885,7 @@
(ex/raise :type :validation
:code :insufficient-permissions))
(db/delete! conn :team-invitation
{:team-id team-id :email-to (str/lower email)})
nil)))
(let [invitation (db/delete! conn :team-invitation
{:team-id team-id
:email-to (str/lower email)})]
(rph/wrap nil {::audit/props {:invitation-id (:id invitation)}})))))

View File

@@ -153,45 +153,30 @@
(if (some? profile)
(if (or (= member-id profile-id)
(= member-email (:email profile)))
;; if we have logged-in user and it matches the invitation we
;; proceed with accepting the invitation and joining the
;; current profile to the invited team.
;; if we have logged-in user and it matches the invitation we proceed
;; with accepting the invitation and joining the current profile to the
;; invited team.
(let [profile (accept-invitation cfg claims invitation profile)]
(-> (assoc claims :state :created)
(rph/with-meta {::audit/name "accept-team-invitation"
::audit/props (merge
(audit/profile->props profile)
{:team-id (:team-id claims)
:role (:role claims)})
::audit/profile-id profile-id})))
::audit/profile-id (:id profile)
::audit/props {:team-id (:team-id claims)
:role (:role claims)
:invitation-id (:id invitation)}})))
(ex/raise :type :validation
:code :invalid-token
:hint "logged-in user does not matches the invitation"))
;; If we have not logged-in user, we try find the invited
;; profile by member-id or member-email props of the invitation
;; token; If profile is found, we accept the invitation and
;; leave the user logged-in.
(if-let [member (db/get* conn :profile
(if member-id
{:id member-id}
{:email member-email})
{:columns [:id :email]})]
(let [profile (accept-invitation cfg claims invitation member)]
(-> (assoc claims :state :created)
(rph/with-transform (session/create-fn cfg (:id profile)))
(rph/with-meta {::audit/name "accept-team-invitation"
::audit/props (merge
(audit/profile->props profile)
{:team-id (:team-id claims)
:role (:role claims)})
::audit/profile-id member-id})))
;; If we have not logged-in user, and invitation comes with member-id we
;; redirect user to login, if no memeber-id is present in the invitation
;; token, we redirect user the the register page.
{:invitation-token token
:iss :team-invitation
:redirect-to :auth-register
:state :pending}))))
{:invitation-token token
:iss :team-invitation
:redirect-to (if member-id :auth-login :auth-register)
:state :pending})))
;; --- Default

View File

@@ -31,7 +31,14 @@
links (->> (db/query conn :share-link {:file-id file-id})
(mapv (fn [row]
(update row :pages db/decode-pgarray #{}))))
(-> row
(update :pages db/decode-pgarray #{})
;; NOTE: the flags are deprecated but are still present
;; on the table on old rows. The flags are pgarray and
;; for avoid decoding it (because they are no longer used
;; on frontend) we just dissoc the column attribute from
;; row.
(dissoc :flags)))))
fonts (db/query conn :team-font-variant
{:team-id (:team-id project)

View File

@@ -8,6 +8,7 @@
(:require
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.common.uri :as u]
[app.common.uuid :as uuid]
[app.db :as db]
[app.http.client :as http]
@@ -22,10 +23,15 @@
[cuerdas.core :as str]
[promesa.core :as p]))
(defn decode-row
[{:keys [uri] :as row}]
(cond-> row
(string? uri) (assoc :uri (u/uri uri))))
;; --- Mutation: Create Webhook
(s/def ::team-id ::us/uuid)
(s/def ::uri ::us/not-empty-string)
(s/def ::uri ::us/uri)
(s/def ::is-active ::us/boolean)
(s/def ::mtype
#{"application/json"
@@ -59,7 +65,7 @@
(if (not= (:uri whook) (:uri params))
(->> (http/req! cfg {:method :head
:uri (:uri params)
:uri (str (:uri params))
:timeout (dt/duration "3s")})
(p/hmap (fn [response exception]
(if exception
@@ -79,22 +85,24 @@
(defn- insert-webhook!
[{:keys [::db/pool]} {:keys [team-id uri mtype is-active] :as params}]
(db/insert! pool :webhook
{:id (uuid/next)
:team-id team-id
:uri uri
:is-active is-active
:mtype mtype}))
(-> (db/insert! pool :webhook
{:id (uuid/next)
:team-id team-id
:uri (str uri)
:is-active is-active
:mtype mtype})
(decode-row)))
(defn- update-webhook!
[{:keys [::db/pool] :as cfg} {:keys [id] :as wook} {:keys [uri mtype is-active] :as params}]
(db/update! pool :webhook
{:uri uri
:is-active is-active
:mtype mtype
:error-code nil
:error-count 0}
{:id id}))
(-> (db/update! pool :webhook
{:uri (str uri)
:is-active is-active
:mtype mtype
:error-code nil
:error-count 0}
{:id id})
(decode-row)))
(sv/defmethod ::create-webhook
{::doc/added "1.17"}
@@ -110,7 +118,7 @@
(sv/defmethod ::update-webhook
{::doc/added "1.17"}
[{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [::rpc/profile-id id] :as params}]
(let [whook (db/get pool :webhook {:id id})]
(let [whook (-> (db/get pool :webhook {:id id}) (decode-row))]
(check-edition-permissions! pool profile-id (:team-id whook))
(->> (validate-webhook! cfg whook params)
(p/fmap executor (fn [_] (update-webhook! cfg whook params))))))
@@ -123,7 +131,7 @@
{::doc/added "1.17"}
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id]}]
(db/with-atomic [conn pool]
(let [whook (db/get conn :webhook {:id id})]
(let [whook (-> (db/get conn :webhook {:id id}) decode-row)]
(check-edition-permissions! conn profile-id (:team-id whook))
(db/delete! conn :webhook {:id id})
nil)))
@@ -143,4 +151,5 @@
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id]}]
(with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id team-id)
(db/exec! conn [sql:get-webhooks team-id])))
(->> (db/exec! conn [sql:get-webhooks team-id])
(mapv decode-row))))

View File

@@ -32,7 +32,7 @@
(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 ::old-password (s/nilable ::us/string))
(s/def ::theme ::us/string)
;; --- MUTATION: Update Profile (own)

View File

@@ -37,6 +37,14 @@
:is-admin false
:can-edit false)))
(defn make-admin-predicate-fn
"A simple factory for admin permission predicate functions."
[qfn]
(us/assert fn? qfn)
(fn check
([perms] (:is-admin perms))
([conn & args] (check (apply qfn conn args)))))
(defn make-edition-predicate-fn
"A simple factory for edition permission predicate functions."
[qfn]

View File

@@ -23,7 +23,7 @@
;; PUBLIC API
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(s/def ::conn ::db/conn-or-pool)
(s/def ::conn ::db/pool-or-conn)
(s/def ::file-id ::us/uuid)
(s/def ::team-id ::us/uuid)
(s/def ::project-id ::us/uuid)
@@ -53,7 +53,7 @@
(defn check-quote!
[conn quote]
(us/assert! ::db/conn-or-pool conn)
(us/assert! ::db/pool-or-conn conn)
(us/assert! ::quote quote)
(when (contains? cf/flags :quotes)
(when @enabled

View File

@@ -363,7 +363,7 @@
(let [state (read-config path)]
(l/info :hint "config refreshed"
:loaded-limits (count (::limits state))
::l/async false)
::l/sync? true)
state)))))
(schedule-next [state]
@@ -380,10 +380,10 @@
(when-not (instance? java.util.concurrent.RejectedExecutionException cause)
(if-let [explain (-> cause ex-data ex/explain)]
(l/warn ::l/raw (str "unable to refresh config, invalid format:\n" explain)
::l/async false)
::l/sync? true)
(l/warn :hint "unexpected exception on loading config"
:cause cause
::l/async false))))
::l/sync? true))))
(defn- get-config-path
[]

View File

@@ -50,14 +50,16 @@
:cause cause))))
instance-id)))
(s/def ::main/key ::us/string)
(s/def ::main/props
(s/map-of ::us/keyword some?))
(defmethod ig/pre-init-spec ::props [_]
(s/keys :req-un [::db/pool]))
(s/keys :req [::db/pool]
:opt [::main/key]))
(defmethod ig/init-key ::props
[_ {:keys [pool key] :as cfg}]
[_ {:keys [::db/pool ::main/key] :as cfg}]
(db/with-atomic [conn pool]
(db/xact-lock! conn 0)
(when-not key

View File

@@ -50,15 +50,14 @@
(defmethod ig/pre-init-spec ::server
[_]
(s/keys :req [::flag]
:req-un [::port ::host]))
(s/keys :req [::flag ::host ::port]))
(defmethod ig/prep-key ::server
[[type _] cfg]
(assoc cfg ::flag (keyword (str (name type) "-server"))))
(defmethod ig/init-key ::server
[[type _] {:keys [::flag port host] :as cfg}]
[[type _] {:keys [::flag ::port ::host] :as cfg}]
(when (contains? cf/flags flag)
(let [accept (case type
::prepl 'app.srepl/json-repl

View File

@@ -41,3 +41,35 @@
([file state]
(repair-orphaned-shapes (:data file))
(update state :total (fnil inc 0))))
(defn rename-layout-attrs
([file]
(let [found? (volatile! false)]
(letfn [(update-shape
[shape]
(when (or (= (:layout-flex-dir shape) :reverse-row)
(= (:layout-flex-dir shape) :reverse-column)
(= (:layout-wrap-type shape) :no-wrap))
(vreset! found? true))
(cond-> shape
(= (:layout-flex-dir shape) :reverse-row)
(assoc :layout-flex-dir :row-reverse)
(= (:layout-flex-dir shape) :reverse-column)
(assoc :layout-flex-dir :column-reverse)
(= (:layout-wrap-type shape) :no-wrap)
(assoc :layout-wrap-type :nowrap)))
(update-page
[page]
(h/update-shapes page update-shape))]
(let [new-file (update file :data h/update-pages update-page)]
(when @found?
(l/info :hint "Found attrs to rename in file"
:id (:id file)
:name (:name file)))
new-file))))
([file state]
(rename-layout-attrs file)
(update state :total (fnil inc 0))))

View File

@@ -58,7 +58,7 @@
:expr (string? destination)
:hint "destination should be provided")
(let [handler (:app.emails/sendmail system)]
(let [handler (:app.email/sendmail system)]
(handler {:body "test email"
:subject "test email"
:to [destination]})))
@@ -144,3 +144,23 @@
[system & {:as params}]
(enable-objects-map-feature-on-file! system params)
(enable-pointer-map-feature-on-file! system params))
(defn instrument-var
[var]
(alter-var-root var (fn [f]
(let [mf (meta f)]
(if (::original mf)
f
(with-meta
(fn [& params]
(tap> params)
(let [result (apply f params)]
(tap> result)
result))
{::original f}))))))
(defn uninstrument-var
[var]
(alter-var-root var (fn [f]
(or (::original (meta f)) f))))

View File

@@ -29,8 +29,10 @@
;; Storage Module State
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(s/def ::id #{:assets-fs :assets-s3})
(s/def ::s3 ::ss3/backend)
(s/def ::fs ::sfs/backend)
(s/def ::type #{:fs :s3})
(s/def ::backends
(s/map-of ::us/keyword
@@ -39,34 +41,26 @@
:fs ::sfs/backend))))
(defmethod ig/pre-init-spec ::storage [_]
(s/keys :req-un [::db/pool ::wrk/executor ::backends]))
(defmethod ig/prep-key ::storage
[_ {:keys [backends] :as cfg}]
(-> (d/without-nils cfg)
(assoc :backends (d/without-nils backends))))
(s/keys :req [::db/pool ::wrk/executor ::backends]))
(defmethod ig/init-key ::storage
[_ {:keys [backends] :as cfg}]
[_ {:keys [::backends ::db/pool] :as cfg}]
(-> (d/without-nils cfg)
(assoc :backends (d/without-nils backends))))
(assoc ::backends (d/without-nils backends))
(assoc ::db/pool-or-conn pool)))
(s/def ::backend keyword?)
(s/def ::storage
(s/keys :req-un [::backends ::db/pool]))
(s/keys :req [::backends ::db/pool ::db/pool-or-conn]
:opt [::backend]))
(s/def ::storage-with-backend
(s/and ::storage #(contains? % ::backend)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Database Objects
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defrecord StorageObject [id size created-at expired-at touched-at backend])
(defn storage-object?
[v]
(instance? StorageObject v))
(s/def ::storage-object storage-object?)
(s/def ::storage-content impl/content?)
(defn get-metadata
[params]
(into {}
@@ -74,19 +68,18 @@
params))
(defn- get-database-object-by-hash
[conn backend bucket hash]
[pool-or-conn backend bucket hash]
(let [sql (str "select * from storage_object "
" where (metadata->>'~:hash') = ? "
" and (metadata->>'~:bucket') = ? "
" and backend = ?"
" and deleted_at is null"
" limit 1")]
(some-> (db/exec-one! conn [sql hash bucket (name backend)])
(some-> (db/exec-one! pool-or-conn [sql hash bucket (name backend)])
(update :metadata db/decode-transit-pgobject))))
(defn- create-database-object
[{:keys [conn backend executor]} {:keys [::content ::expired-at ::touched-at] :as params}]
(us/assert ::storage-content content)
[{:keys [::backend ::wrk/executor ::db/pool-or-conn]} {:keys [::content ::expired-at ::touched-at] :as params}]
(px/with-dispatch executor
(let [id (uuid/random)
@@ -101,10 +94,10 @@
result (when (and (::deduplicate? params)
(:hash mdata)
(:bucket mdata))
(get-database-object-by-hash conn backend (:bucket mdata) (:hash mdata)))
(get-database-object-by-hash pool-or-conn backend (:bucket mdata) (:hash mdata)))
result (or result
(-> (db/insert! conn :storage-object
(-> (db/insert! pool-or-conn :storage-object
{:id id
:size (impl/get-size content)
:backend (name backend)
@@ -114,33 +107,33 @@
(update :metadata db/decode-transit-pgobject)
(update :metadata assoc ::created? true)))]
(StorageObject. (:id result)
(:size result)
(:created-at result)
(:deleted-at result)
(:touched-at result)
backend
(:metadata result)
nil))))
(impl/storage-object
(:id result)
(:size result)
(:created-at result)
(:deleted-at result)
(:touched-at result)
backend
(:metadata result)))))
(def ^:private sql:retrieve-storage-object
"select * from storage_object where id = ? and (deleted_at is null or deleted_at > now())")
(defn row->storage-object [res]
(let [mdata (or (some-> (:metadata res) (db/decode-transit-pgobject)) {})]
(StorageObject. (:id res)
(:size res)
(:created-at res)
(:deleted-at res)
(:touched-at res)
(keyword (:backend res))
mdata
nil)))
(impl/storage-object
(:id res)
(:size res)
(:created-at res)
(:deleted-at res)
(:touched-at res)
(keyword (:backend res))
mdata)))
(defn- retrieve-database-object
[{:keys [conn] :as storage} id]
(when-let [res (db/exec-one! conn [sql:retrieve-storage-object id])]
(row->storage-object res)))
[conn id]
(some-> (db/exec-one! conn [sql:retrieve-storage-object id])
(row->storage-object)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; API
@@ -152,103 +145,99 @@
(defn file-url->path
[url]
(fs/path (java.net.URI. (str url))))
(when url
(fs/path (java.net.URI. (str url)))))
(dm/export impl/content)
(dm/export impl/wrap-with-hash)
(dm/export impl/object?)
(defn get-object
[{:keys [conn pool] :as storage} id]
(us/assert ::storage storage)
(p/do
(-> (assoc storage :conn (or conn pool))
(retrieve-database-object id))))
[{:keys [::db/pool-or-conn ::wrk/executor] :as storage} id]
(us/assert! ::storage storage)
(px/with-dispatch executor
(retrieve-database-object pool-or-conn id)))
(defn put-object!
"Creates a new object with the provided content."
[{:keys [pool conn backend] :as storage} {:keys [::content] :as params}]
(us/assert ::storage storage)
(us/assert ::storage-content content)
(us/assert ::us/keyword backend)
(p/let [storage (assoc storage :conn (or conn pool))
object (create-database-object storage params)]
(when (::created? (meta object))
;; Store the data finally on the underlying storage subsystem.
(-> (impl/resolve-backend storage backend)
(impl/put-object object content)))
object))
[{:keys [::backend] :as storage} {:keys [::content] :as params}]
(us/assert! ::storage-with-backend storage)
(us/assert! ::impl/content content)
(->> (create-database-object storage params)
(p/mcat (fn [object]
(if (::created? (meta object))
;; Store the data finally on the underlying storage subsystem.
(-> (impl/resolve-backend storage backend)
(impl/put-object object content))
(p/resolved object))))))
(defn touch-object!
"Mark object as touched."
[{:keys [pool conn] :as storage} object-or-id]
(p/do
(let [id (if (storage-object? object-or-id) (:id object-or-id) object-or-id)
res (db/update! (or conn pool) :storage-object
{:touched-at (dt/now)}
{:id id}
{::db/return-keys? false})]
(pos? (:next.jdbc/update-count res)))))
[{:keys [::db/pool-or-conn ::wrk/executor] :as storage} object-or-id]
(us/assert! ::storage storage)
(px/with-dispatch executor
(let [id (if (impl/object? object-or-id) (:id object-or-id) object-or-id)
rs (db/update! pool-or-conn :storage-object
{:touched-at (dt/now)}
{:id id}
{::db/return-keys? false})]
(pos? (db/get-update-count rs)))))
(defn get-object-data
"Return an input stream instance of the object content."
[{:keys [pool conn] :as storage} object]
(us/assert ::storage storage)
(p/do
(when (or (nil? (:expired-at object))
(dt/is-after? (:expired-at object) (dt/now)))
(-> (assoc storage :conn (or conn pool))
(impl/resolve-backend (:backend object))
(impl/get-object-data object)))))
[storage object]
(us/assert! ::storage storage)
(if (or (nil? (:expired-at object))
(dt/is-after? (:expired-at object) (dt/now)))
(-> (impl/resolve-backend storage (:backend object))
(impl/get-object-data object))
(p/resolved nil)))
(defn get-object-bytes
"Returns a byte array of object content."
[{:keys [pool conn] :as storage} object]
(us/assert ::storage storage)
(p/do
(when (or (nil? (:expired-at object))
(dt/is-after? (:expired-at object) (dt/now)))
(-> (assoc storage :conn (or conn pool))
(impl/resolve-backend (:backend object))
(impl/get-object-bytes object)))))
[storage object]
(us/assert! ::storage storage)
(if (or (nil? (:expired-at object))
(dt/is-after? (:expired-at object) (dt/now)))
(-> (impl/resolve-backend storage (:backend object))
(impl/get-object-bytes object))
(p/resolved nil)))
(defn get-object-url
([storage object]
(get-object-url storage object nil))
([{:keys [conn pool] :as storage} object options]
(us/assert ::storage storage)
(p/do
(when (or (nil? (:expired-at object))
(dt/is-after? (:expired-at object) (dt/now)))
(-> (assoc storage :conn (or conn pool))
(impl/resolve-backend (:backend object))
(impl/get-object-url object options))))))
([storage object options]
(us/assert! ::storage storage)
(if (or (nil? (:expired-at object))
(dt/is-after? (:expired-at object) (dt/now)))
(-> (impl/resolve-backend storage (:backend object))
(impl/get-object-url object options))
(p/resolved nil))))
(defn get-object-path
"Get the Path to the object. Only works with `:fs` type of
storages."
[storage object]
(p/do
(let [backend (impl/resolve-backend storage (:backend object))]
(when (not= :fs (:type backend))
(ex/raise :type :internal
:code :operation-not-allowed
:hint "get-object-path only works with fs type backends"))
(when (or (nil? (:expired-at object))
(dt/is-after? (:expired-at object) (dt/now)))
(p/-> (impl/get-object-url backend object nil) file-url->path)))))
(us/assert! ::storage storage)
(let [backend (impl/resolve-backend storage (:backend object))]
(if (not= :fs (::type backend))
(p/resolved nil)
(if (or (nil? (:expired-at object))
(dt/is-after? (:expired-at object) (dt/now)))
(->> (impl/get-object-url backend object nil)
(p/fmap file-url->path))
(p/resolved nil)))))
(defn del-object!
[{:keys [conn pool] :as storage} object-or-id]
(us/assert ::storage storage)
(p/do
(let [id (if (storage-object? object-or-id) (:id object-or-id) object-or-id)
res (db/update! (or conn pool) :storage-object
[{:keys [::db/pool-or-conn ::wrk/executor] :as storage} object-or-id]
(us/assert! ::storage storage)
(px/with-dispatch executor
(let [id (if (impl/object? object-or-id) (:id object-or-id) object-or-id)
res (db/update! pool-or-conn :storage-object
{:deleted-at (dt/now)}
{:id id}
{::db/return-keys? false})]
(pos? (:next.jdbc/update-count res)))))
(pos? (db/get-update-count res)))))
(dm/export impl/resolve-backend)
(dm/export impl/calculate-hash)
@@ -265,18 +254,15 @@
(declare sql:retrieve-deleted-objects-chunk)
(s/def ::min-age ::dt/duration)
(defmethod ig/pre-init-spec ::gc-deleted-task [_]
(s/keys :req-un [::storage ::db/pool ::min-age ::wrk/executor]))
(s/keys :req [::storage ::db/pool]))
(defmethod ig/prep-key ::gc-deleted-task
[_ cfg]
(merge {:min-age (dt/duration {:hours 2})}
(d/without-nils cfg)))
(assoc cfg ::min-age (dt/duration {:hours 2})))
(defmethod ig/init-key ::gc-deleted-task
[_ {:keys [pool storage] :as cfg}]
[_ {:keys [::db/pool ::storage ::min-age]}]
(letfn [(retrieve-deleted-objects-chunk [conn min-age cursor]
(let [min-age (db/interval min-age)
rows (db/exec! conn [sql:retrieve-deleted-objects-chunk min-age cursor])]
@@ -289,27 +275,26 @@
:vf second
:kf first))
(delete-in-bulk [conn backend-name ids]
(let [backend (impl/resolve-backend storage backend-name)
backend (assoc backend :conn conn)]
(delete-in-bulk [backend-id ids]
(let [backend (impl/resolve-backend storage backend-id)]
(doseq [id ids]
(l/debug :hint "permanently delete storage object" :task "gc-deleted" :backend backend-name :id id))
(l/debug :hint "gc-deleted: permanently delete storage object" :backend backend-id :id id))
@(impl/del-objects-in-bulk backend ids)))]
(fn [params]
(let [min-age (or (:min-age params) (:min-age cfg))]
(let [min-age (or (:min-age params) min-age)]
(db/with-atomic [conn pool]
(loop [total 0
groups (retrieve-deleted-objects conn min-age)]
(if-let [[backend ids] (first groups)]
(if-let [[backend-id ids] (first groups)]
(do
(delete-in-bulk conn backend ids)
(delete-in-bulk backend-id ids)
(recur (+ total (count ids))
(rest groups)))
(do
(l/info :hint "task finished" :min-age (dt/format-duration min-age) :task "gc-deleted" :total total)
(l/info :hint "gc-deleted: task finished" :min-age (dt/format-duration min-age) :total total)
{:deleted total}))))))))
(def sql:retrieve-deleted-objects-chunk
@@ -349,10 +334,10 @@
(declare sql:retrieve-profile-nrefs)
(defmethod ig/pre-init-spec ::gc-touched-task [_]
(s/keys :req-un [::db/pool]))
(s/keys :req [::db/pool]))
(defmethod ig/init-key ::gc-touched-task
[_ {:keys [pool] :as cfg}]
[_ {:keys [::db/pool]}]
(letfn [(get-team-font-variant-nrefs [conn id]
(-> (db/exec-one! conn [sql:retrieve-team-font-variant-nrefs id id id id]) :nrefs))
@@ -409,13 +394,13 @@
(let [nrefs (get-fn conn id)]
(if (pos? nrefs)
(do
(l/debug :hint "processing storage object"
:task "gc-touched" :id id :status "freeze"
(l/debug :hint "gc-touched: processing storage object"
:id id :status "freeze"
:bucket bucket :refs nrefs)
(recur (conj to-freeze id) to-delete (rest ids)))
(do
(l/debug :hint "processing storage object"
:task "gc-touched" :id id :status "delete"
(l/debug :hint "gc-touched: processing storage object"
:id id :status "delete"
:bucket bucket :refs nrefs)
(recur to-freeze (conj to-delete id) (rest ids)))))
(do
@@ -441,7 +426,7 @@
(+ to-delete d)
(rest groups)))
(do
(l/info :hint "task finished" :task "gc-touched" :to-freeze to-freeze :to-delete to-delete)
(l/info :hint "gc-touched: task finished" :to-freeze to-freeze :to-delete to-delete)
{:freeze to-freeze :delete to-delete})))))))
(def sql:retrieve-touched-objects-chunk

View File

@@ -9,7 +9,9 @@
[app.common.exceptions :as ex]
[app.common.spec :as us]
[app.common.uri :as u]
[app.storage :as-alias sto]
[app.storage.impl :as impl]
[app.worker :as-alias wrk]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[datoteka.fs :as fs]
@@ -28,42 +30,49 @@
(s/def ::directory ::us/string)
(defmethod ig/pre-init-spec ::backend [_]
(s/keys :opt-un [::directory]))
(s/keys :opt [::directory]))
(defmethod ig/init-key ::backend
[_ cfg]
;; Return a valid backend data structure only if all optional
;; parameters are provided.
(when (string? (:directory cfg))
(let [dir (fs/normalize (:directory cfg))]
(when (string? (::directory cfg))
(let [dir (fs/normalize (::directory cfg))]
(assoc cfg
:type :fs
:directory (str dir)
:uri (u/uri (str "file://" dir))))))
::sto/type :fs
::directory (str dir)
::uri (u/uri (str "file://" dir))))))
(s/def ::type ::us/keyword)
(s/def ::uri u/uri?)
(s/def ::backend
(s/keys :req-un [::type ::directory ::uri]))
(s/keys :req [::directory
::uri]
:opt [::sto/type
::sto/id
::wrk/executor]))
;; --- API IMPL
(defmethod impl/put-object :fs
[{:keys [executor] :as backend} {:keys [id] :as object} content]
[{:keys [::wrk/executor] :as backend} {:keys [id] :as object} content]
(us/assert! ::backend backend)
(px/with-dispatch executor
(let [base (fs/path (:directory backend))
(let [base (fs/path (::directory backend))
path (fs/path (impl/id->path id))
full (fs/normalize (fs/join base path))]
(when-not (fs/exists? (fs/parent full))
(fs/create-dir (fs/parent full)))
(with-open [^InputStream src (io/input-stream content)
^OutputStream dst (io/output-stream full)]
(io/copy! src dst)))))
(io/copy! src dst))
object)))
(defmethod impl/get-object-data :fs
[{:keys [executor] :as backend} {:keys [id] :as object}]
[{:keys [::wrk/executor] :as backend} {:keys [id] :as object}]
(us/assert! ::backend backend)
(px/with-dispatch executor
(let [^Path base (fs/path (:directory backend))
(let [^Path base (fs/path (::directory backend))
^Path path (fs/path (impl/id->path id))
^Path full (fs/normalize (fs/join base path))]
(when-not (fs/exists? full)
@@ -74,33 +83,37 @@
(defmethod impl/get-object-bytes :fs
[backend object]
(p/let [input (impl/get-object-data backend object)]
(try
(io/read-as-bytes input)
(finally
(io/close! input)))))
(->> (impl/get-object-data backend object)
(p/fmap (fn [input]
(try
(io/read-as-bytes input)
(finally
(io/close! input)))))))
(defmethod impl/get-object-url :fs
[{:keys [uri executor] :as backend} {:keys [id] :as object} _]
(px/with-dispatch executor
(update uri :path
(fn [existing]
(if (str/ends-with? existing "/")
(str existing (impl/id->path id))
(str existing "/" (impl/id->path id)))))))
[{:keys [::uri] :as backend} {:keys [id] :as object} _]
(us/assert! ::backend backend)
(p/resolved
(update uri :path
(fn [existing]
(if (str/ends-with? existing "/")
(str existing (impl/id->path id))
(str existing "/" (impl/id->path id)))))))
(defmethod impl/del-object :fs
[{:keys [executor] :as backend} {:keys [id] :as object}]
[{:keys [::wrk/executor] :as backend} {:keys [id] :as object}]
(us/assert! ::backend backend)
(px/with-dispatch executor
(let [base (fs/path (:directory backend))
(let [base (fs/path (::directory backend))
path (fs/path (impl/id->path id))
path (fs/join base path)]
(Files/deleteIfExists ^Path path))))
(defmethod impl/del-objects-in-bulk :fs
[{:keys [executor] :as backend} ids]
[{:keys [::wrk/executor] :as backend} ids]
(us/assert! ::backend backend)
(px/with-dispatch executor
(let [base (fs/path (:directory backend))]
(let [base (fs/path (::directory backend))]
(doseq [id ids]
(let [path (fs/path (impl/id->path id))
path (fs/join base path)]

View File

@@ -9,9 +9,13 @@
(:require
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.db :as-alias db]
[app.storage :as-alias sto]
[app.worker :as-alias wrk]
[buddy.core.codecs :as bc]
[buddy.core.hash :as bh]
[clojure.java.io :as jio]
[clojure.spec.alpha :as s]
[datoteka.io :as io])
(:import
java.nio.ByteBuffer
@@ -21,7 +25,7 @@
;; --- API Definition
(defmulti put-object (fn [cfg _ _] (:type cfg)))
(defmulti put-object (fn [cfg _ _] (::sto/type cfg)))
(defmethod put-object :default
[cfg _ _]
@@ -29,7 +33,7 @@
:code :invalid-storage-backend
:context cfg))
(defmulti get-object-data (fn [cfg _] (:type cfg)))
(defmulti get-object-data (fn [cfg _] (::sto/type cfg)))
(defmethod get-object-data :default
[cfg _]
@@ -37,7 +41,7 @@
:code :invalid-storage-backend
:context cfg))
(defmulti get-object-bytes (fn [cfg _] (:type cfg)))
(defmulti get-object-bytes (fn [cfg _] (::sto/type cfg)))
(defmethod get-object-bytes :default
[cfg _]
@@ -45,7 +49,7 @@
:code :invalid-storage-backend
:context cfg))
(defmulti get-object-url (fn [cfg _ _] (:type cfg)))
(defmulti get-object-url (fn [cfg _ _] (::sto/type cfg)))
(defmethod get-object-url :default
[cfg _ _]
@@ -54,7 +58,7 @@
:context cfg))
(defmulti del-object (fn [cfg _] (:type cfg)))
(defmulti del-object (fn [cfg _] (::sto/type cfg)))
(defmethod del-object :default
[cfg _]
@@ -62,7 +66,7 @@
:code :invalid-storage-backend
:context cfg))
(defmulti del-objects-in-bulk (fn [cfg _] (:type cfg)))
(defmulti del-objects-in-bulk (fn [cfg _] (::sto/type cfg)))
(defmethod del-objects-in-bulk :default
[cfg _]
@@ -189,10 +193,6 @@
(make-output-stream [_ opts]
(jio/make-output-stream content opts))))
(defn content?
[v]
(satisfies? IContentObject v))
(defn calculate-hash
[resource]
(let [result (with-open [input (io/input-stream resource)]
@@ -201,13 +201,37 @@
(str "blake2b:" result)))
(defn resolve-backend
[{:keys [conn pool executor] :as storage} backend-id]
(let [backend (get-in storage [:backends backend-id])]
[{:keys [::db/pool ::wrk/executor] :as storage} backend-id]
(let [backend (get-in storage [::sto/backends backend-id])]
(when-not backend
(ex/raise :type :internal
:code :backend-not-configured
:hint (dm/fmt "backend '%' not configured" backend-id)))
(assoc backend
:executor executor
:conn (or conn pool)
:id backend-id)))
(-> backend
(assoc ::sto/id backend-id)
(assoc ::wrk/executor executor)
(assoc ::db/pool pool))))
(defrecord StorageObject [id size created-at expired-at touched-at backend])
(ns-unmap *ns* '->StorageObject)
(ns-unmap *ns* 'map->StorageObject)
(defn storage-object
([id size created-at expired-at touched-at backend]
(StorageObject. id size created-at expired-at touched-at backend))
([id size created-at expired-at touched-at backend mdata]
(StorageObject. id size created-at expired-at touched-at backend mdata nil)))
(defn object?
[v]
(instance? StorageObject v))
(defn content?
[v]
(satisfies? IContentObject v))
(s/def ::object object?)
(s/def ::content content?)

View File

@@ -8,9 +8,12 @@
"S3 Storage backend 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.storage :as-alias sto]
[app.storage.impl :as impl]
[app.storage.tmp :as tmp]
[app.util.time :as dt]
@@ -64,6 +67,9 @@
(declare build-s3-client)
(declare build-s3-presigner)
;; (set! *warn-on-reflection* true)
;; (set! *unchecked-math* :warn-on-boxed)
;; --- BACKEND INIT
(s/def ::region ::us/keyword)
@@ -72,26 +78,26 @@
(s/def ::endpoint ::us/string)
(defmethod ig/pre-init-spec ::backend [_]
(s/keys :opt-un [::region ::bucket ::prefix ::endpoint ::wrk/executor]))
(s/keys :opt [::region ::bucket ::prefix ::endpoint ::wrk/executor]))
(defmethod ig/prep-key ::backend
[_ {:keys [prefix region] :as cfg}]
[_ {:keys [::prefix ::region] :as cfg}]
(cond-> (d/without-nils cfg)
(some? prefix) (assoc :prefix prefix)
(nil? region) (assoc :region :eu-central-1)))
(some? prefix) (assoc ::prefix prefix)
(nil? region) (assoc ::region :eu-central-1)))
(defmethod ig/init-key ::backend
[_ cfg]
;; Return a valid backend data structure only if all optional
;; parameters are provided.
(when (and (contains? cfg :region)
(string? (:bucket cfg)))
(when (and (contains? cfg ::region)
(string? (::bucket cfg)))
(let [client (build-s3-client cfg)
presigner (build-s3-presigner cfg)]
(assoc cfg
:client @client
:presigner presigner
:type :s3
::sto/type :s3
::client @client
::presigner presigner
::close-fn #(.close ^java.lang.AutoCloseable client)))))
(defmethod ig/halt-key! ::backend
@@ -99,21 +105,27 @@
(when (fn? close-fn)
(px/run! close-fn)))
(s/def ::type ::us/keyword)
(s/def ::client #(instance? S3AsyncClient %))
(s/def ::presigner #(instance? S3Presigner %))
(s/def ::backend
(s/keys :req-un [::region ::bucket ::client ::type ::presigner]
:opt-un [::prefix]))
(s/keys :req [::region
::bucket
::client
::presigner]
:opt [::prefix
::sto/id
::wrk/executor]))
;; --- API IMPL
(defmethod impl/put-object :s3
[backend object content]
(us/assert! ::backend backend)
(put-object backend object content))
(defmethod impl/get-object-data :s3
[backend object]
(us/assert! ::backend backend)
(letfn [(no-such-key? [cause]
(instance? software.amazon.awssdk.services.s3.model.NoSuchKeyException cause))
(handle-not-found [cause]
@@ -127,18 +139,22 @@
(defmethod impl/get-object-bytes :s3
[backend object]
(us/assert! ::backend backend)
(get-object-bytes backend object))
(defmethod impl/get-object-url :s3
[backend object options]
(us/assert! ::backend backend)
(get-object-url backend object options))
(defmethod impl/del-object :s3
[backend object]
(us/assert! ::backend backend)
(del-object backend object))
(defmethod impl/del-objects-in-bulk :s3
[backend ids]
(us/assert! ::backend backend)
(del-object-in-bulk backend ids))
;; --- HELPERS
@@ -152,8 +168,8 @@
[region]
(Region/of (name region)))
(defn build-s3-client
[{:keys [region endpoint executor]}]
(defn- build-s3-client
[{:keys [::region ::endpoint ::wrk/executor]}]
(let [aconfig (-> (ClientAsyncConfiguration/builder)
(.advancedOption SdkAdvancedAsyncClientOption/FUTURE_COMPLETION_EXECUTOR executor)
(.build))
@@ -188,8 +204,8 @@
(.close ^NettyNioAsyncHttpClient hclient)
(.close ^S3AsyncClient client)))))
(defn build-s3-presigner
[{:keys [region endpoint]}]
(defn- build-s3-presigner
[{:keys [::region ::endpoint]}]
(let [config (-> (S3Configuration/builder)
(cond-> (some? endpoint) (.pathStyleAccessEnabled true))
(.build))]
@@ -200,65 +216,87 @@
(.serviceConfiguration ^S3Configuration config)
(.build))))
(defn- upload-thread
[id subscriber sem content]
(px/thread
{:name "penpot/s3/uploader"
:daemon true}
(l/trace :hint "start upload thread"
:object-id (str id)
:size (impl/get-size content)
::l/sync? true)
(let [stream (io/input-stream content)
bsize (* 1024 64)
tpoint (dt/tpoint)]
(try
(loop []
(.acquire ^Semaphore sem 1)
(let [buffer (byte-array bsize)
readed (.read ^InputStream stream buffer)]
(when (pos? readed)
(let [data (ByteBuffer/wrap ^bytes buffer 0 readed)]
(.onNext ^Subscriber subscriber ^ByteBuffer data)
(when (= readed bsize)
(recur))))))
(.onComplete ^Subscriber subscriber)
(catch InterruptedException _
(l/trace :hint "interrupted upload thread"
:object-:id (str id)
::l/sync? true)
nil)
(catch Throwable cause
(.onError ^Subscriber subscriber cause))
(finally
(l/trace :hint "end upload thread"
:object-id (str id)
:elapsed (dt/format-duration (tpoint))
::l/sync? true)
(.close ^InputStream stream))))))
(defn- make-request-body
[content]
(let [is (io/input-stream content)
buff-size (* 1024 64)
sem (Semaphore. 0)
[id content]
(reify
AsyncRequestBody
(contentLength [_]
(Optional/of (long (impl/get-size content))))
writer-fn (fn [^Subscriber s]
(try
(loop []
(.acquire sem 1)
(let [buffer (byte-array buff-size)
readed (.read is buffer)]
(when (pos? readed)
(.onNext ^Subscriber s (ByteBuffer/wrap buffer 0 readed))
(when (= readed buff-size)
(recur)))))
(.onComplete s)
(catch Throwable cause
(.onError s cause))
(finally
(.close ^InputStream is))))]
(reify
AsyncRequestBody
(contentLength [_]
(Optional/of (long (impl/get-size content))))
(^void subscribe [_ ^Subscriber s]
(let [thread (Thread. #(writer-fn s))]
(.setDaemon thread true)
(.setName thread "penpot/storage:s3")
(.start thread)
(.onSubscribe s (reify Subscription
(cancel [_]
(.interrupt thread)
(.release sem 1))
(request [_ n]
(.release sem (int n))))))))))
(^void subscribe [_ ^Subscriber subscriber]
(let [sem (Semaphore. 0)
thr (upload-thread id subscriber sem content)]
(.onSubscribe subscriber
(reify Subscription
(cancel [_]
(px/interrupt! thr)
(.release sem 1))
(request [_ n]
(.release sem (int n)))))))))
(defn put-object
[{:keys [client bucket prefix]} {:keys [id] :as object} content]
(p/let [path (str prefix (impl/id->path id))
mdata (meta object)
mtype (:content-type mdata "application/octet-stream")
request (.. (PutObjectRequest/builder)
(bucket bucket)
(contentType mtype)
(key path)
(build))]
(defn- put-object
[{:keys [::client ::bucket ::prefix]} {:keys [id] :as object} content]
(let [path (dm/str prefix (impl/id->path id))
mdata (meta object)
mtype (:content-type mdata "application/octet-stream")
rbody (make-request-body id content)
request (.. (PutObjectRequest/builder)
(bucket bucket)
(contentType mtype)
(key path)
(build))]
(->> (.putObject ^S3AsyncClient client
^PutObjectRequest request
^AsyncRequestBody rbody)
(p/fmap (constantly object)))))
(let [content (make-request-body content)]
(.putObject ^S3AsyncClient client
^PutObjectRequest request
^AsyncRequestBody content))))
(defn- path->stream
[path]
(proxy [FilterInputStream] [(io/input-stream path)]
(close []
(fs/delete path)
(proxy-super close))))
(defn get-object-data
[{:keys [client bucket prefix]} {:keys [id size]}]
(defn- get-object-data
[{:keys [::client ::bucket ::prefix]} {:keys [id size]}]
(let [gor (.. (GetObjectRequest/builder)
(bucket bucket)
(key (str prefix (impl/id->path id)))
@@ -267,83 +305,83 @@
;; If the file size is greater than 2MiB then stream the content
;; to the filesystem and then read with buffered inputstream; if
;; not, read the contento into memory using bytearrays.
(if (> size (* 1024 1024 2))
(p/let [path (tmp/tempfile :prefix "penpot.storage.s3.")
rxf (AsyncResponseTransformer/toFile ^Path path)
_ (.getObject ^S3AsyncClient client
^GetObjectRequest gor
^AsyncResponseTransformer rxf)]
(proxy [FilterInputStream] [(io/input-stream path)]
(close []
(fs/delete path)
(proxy-super close))))
(if (> ^long size (* 1024 1024 2))
(let [path (tmp/tempfile :prefix "penpot.storage.s3.")
rxf (AsyncResponseTransformer/toFile ^Path path)]
(->> (.getObject ^S3AsyncClient client
^GetObjectRequest gor
^AsyncResponseTransformer rxf)
(p/fmap (constantly path))
(p/fmap path->stream)))
(p/let [rxf (AsyncResponseTransformer/toBytes)
obj (.getObject ^S3AsyncClient client
^GetObjectRequest gor
^AsyncResponseTransformer rxf)]
(.asInputStream ^ResponseBytes obj)))))
(let [rxf (AsyncResponseTransformer/toBytes)]
(->> (.getObject ^S3AsyncClient client
^GetObjectRequest gor
^AsyncResponseTransformer rxf)
(p/fmap #(.asInputStream ^ResponseBytes %)))))))
(defn get-object-bytes
[{:keys [client bucket prefix]} {:keys [id]}]
(p/let [gor (.. (GetObjectRequest/builder)
(bucket bucket)
(key (str prefix (impl/id->path id)))
(build))
rxf (AsyncResponseTransformer/toBytes)
obj (.getObject ^S3AsyncClient client
^GetObjectRequest gor
^AsyncResponseTransformer rxf)]
(.asByteArray ^ResponseBytes obj)))
(defn- get-object-bytes
[{:keys [::client ::bucket ::prefix]} {:keys [id]}]
(let [gor (.. (GetObjectRequest/builder)
(bucket bucket)
(key (str prefix (impl/id->path id)))
(build))
rxf (AsyncResponseTransformer/toBytes)]
(->> (.getObject ^S3AsyncClient client
^GetObjectRequest gor
^AsyncResponseTransformer rxf)
(p/fmap #(.asByteArray ^ResponseBytes %)))))
(def default-max-age
(dt/duration {:minutes 10}))
(defn get-object-url
[{:keys [presigner bucket prefix]} {:keys [id]} {:keys [max-age] :or {max-age default-max-age}}]
(defn- get-object-url
[{:keys [::presigner ::bucket ::prefix]} {:keys [id]} {:keys [max-age] :or {max-age default-max-age}}]
(us/assert dt/duration? max-age)
(p/do
(let [gor (.. (GetObjectRequest/builder)
(bucket bucket)
(key (str prefix (impl/id->path id)))
(build))
gopr (.. (GetObjectPresignRequest/builder)
(signatureDuration ^Duration max-age)
(getObjectRequest ^GetObjectRequest gor)
(build))
pgor (.presignGetObject ^S3Presigner presigner ^GetObjectPresignRequest gopr)]
(u/uri (str (.url ^PresignedGetObjectRequest pgor))))))
(let [gor (.. (GetObjectRequest/builder)
(bucket bucket)
(key (dm/str prefix (impl/id->path id)))
(build))
gopr (.. (GetObjectPresignRequest/builder)
(signatureDuration ^Duration max-age)
(getObjectRequest ^GetObjectRequest gor)
(build))
pgor (.presignGetObject ^S3Presigner presigner ^GetObjectPresignRequest gopr)]
(p/resolved
(u/uri (str (.url ^PresignedGetObjectRequest pgor))))))
(defn del-object
[{:keys [bucket client prefix]} {:keys [id] :as obj}]
(p/let [dor (.. (DeleteObjectRequest/builder)
(bucket bucket)
(key (str prefix (impl/id->path id)))
(build))]
(.deleteObject ^S3AsyncClient client
^DeleteObjectRequest dor)))
(defn- del-object
[{:keys [::bucket ::client ::prefix]} {:keys [id] :as obj}]
(let [dor (.. (DeleteObjectRequest/builder)
(bucket bucket)
(key (dm/str prefix (impl/id->path id)))
(build))]
(->> (.deleteObject ^S3AsyncClient client ^DeleteObjectRequest dor)
(p/fmap (constantly nil)))))
(defn del-object-in-bulk
[{:keys [bucket client prefix]} ids]
(p/let [oids (map (fn [id]
(.. (ObjectIdentifier/builder)
(key (str prefix (impl/id->path id)))
(build)))
ids)
delc (.. (Delete/builder)
(objects ^Collection oids)
(build))
dor (.. (DeleteObjectsRequest/builder)
(bucket bucket)
(delete ^Delete delc)
(build))
dres (.deleteObjects ^S3AsyncClient client
^DeleteObjectsRequest dor)]
(when (.hasErrors ^DeleteObjectsResponse dres)
(let [errors (seq (.errors ^DeleteObjectsResponse dres))]
(ex/raise :type :internal
:code :error-on-s3-bulk-delete
:s3-errors (mapv (fn [^S3Error error]
{:key (.key error)
:msg (.message error)})
errors))))))
(defn- del-object-in-bulk
[{:keys [::bucket ::client ::prefix]} ids]
(let [oids (map (fn [id]
(.. (ObjectIdentifier/builder)
(key (str prefix (impl/id->path id)))
(build)))
ids)
delc (.. (Delete/builder)
(objects ^Collection oids)
(build))
dor (.. (DeleteObjectsRequest/builder)
(bucket bucket)
(delete ^Delete delc)
(build))]
(->> (.deleteObjects ^S3AsyncClient client ^DeleteObjectsRequest dor)
(p/fmap (fn [dres]
(when (.hasErrors ^DeleteObjectsResponse dres)
(let [errors (seq (.errors ^DeleteObjectsResponse dres))]
(ex/raise :type :internal
:code :error-on-s3-bulk-delete
:s3-errors (mapv (fn [^S3Error error]
{:key (.key error)
:msg (.message error)})
errors)))))))))

View File

@@ -32,27 +32,24 @@
;; HANDLER
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(s/def ::min-age ::dt/duration)
(defmethod ig/pre-init-spec ::handler [_]
(s/keys :req-un [::db/pool ::min-age]))
(s/keys :req [::db/pool]))
(defmethod ig/prep-key ::handler
[_ cfg]
(merge {:min-age cf/deletion-delay}
(d/without-nils cfg)))
(assoc cfg ::min-age cf/deletion-delay))
(defmethod ig/init-key ::handler
[_ {:keys [pool] :as cfg}]
[_ {:keys [::db/pool] :as cfg}]
(fn [{:keys [file-id] :as params}]
(db/with-atomic [conn pool]
(let [min-age (or (:min-age params) (:min-age cfg))
cfg (assoc cfg :min-age min-age :conn conn :file-id file-id)]
(let [min-age (or (:min-age params) (::min-age cfg))
cfg (assoc cfg ::min-age min-age ::conn conn ::file-id file-id)]
(loop [total 0
files (retrieve-candidates cfg)]
(if-let [file (first files)]
(do
(process-file cfg file)
(process-file conn file)
(recur (inc total)
(rest files)))
(do
@@ -84,7 +81,7 @@
for update skip locked")
(defn- retrieve-candidates
[{:keys [conn min-age file-id] :as cfg}]
[{:keys [::conn ::min-age ::file-id]}]
(if (uuid? file-id)
(do
(l/warn :hint "explicit file id passed on params" :file-id file-id)
@@ -256,7 +253,7 @@
(db/delete! conn :file-data-fragment {:id fragment-id :file-id file-id}))))
(defn- process-file
[{:keys [conn] :as cfg} {:keys [id data revn modified-at features] :as file}]
[conn {:keys [id data revn modified-at features] :as file}]
(l/debug :hint "processing file" :id id :modified-at modified-at)
(binding [pmap/*load-fn* (partial files/load-pointer conn id)]

View File

@@ -8,42 +8,36 @@
"A maintenance task that performs a garbage collection of the file
change (transaction) log."
(:require
[app.common.data :as d]
[app.common.logging :as l]
[app.db :as db]
[app.util.time :as dt]
[clojure.spec.alpha :as s]
[integrant.core :as ig]))
(declare sql:delete-files-xlog)
(s/def ::min-age ::dt/duration)
(def ^:private
sql:delete-files-xlog
"delete from file_change
where created_at < now() - ?::interval")
(defmethod ig/pre-init-spec ::handler [_]
(s/keys :req-un [::db/pool]
:opt-un [::min-age]))
(s/keys :req [::db/pool]))
(defmethod ig/prep-key ::handler
[_ cfg]
(merge {:min-age (dt/duration {:hours 72})}
(d/without-nils cfg)))
(assoc cfg ::min-age (dt/duration {:hours 72})))
(defmethod ig/init-key ::handler
[_ {:keys [pool] :as cfg}]
[_ {:keys [::db/pool] :as cfg}]
(fn [params]
(let [min-age (or (:min-age params) (:min-age cfg))]
(let [min-age (or (:min-age params) (::min-age cfg))]
(db/with-atomic [conn pool]
(let [interval (db/interval min-age)
result (db/exec-one! conn [sql:delete-files-xlog interval])
result (:next.jdbc/update-count result)]
result (db/get-update-count result)]
(l/info :hint "task finished" :min-age (dt/format-duration min-age) :total result)
(when (:rollback? params)
(db/rollback! conn))
result)))))
(def ^:private
sql:delete-files-xlog
"delete from file_change
where created_at < now() - ?::interval")

View File

@@ -25,16 +25,12 @@
(declare ^:private delete-files!)
(declare ^:private delete-orphan-teams!)
(s/def ::min-age ::dt/duration)
(defmethod ig/pre-init-spec ::handler [_]
(s/keys :req [::db/pool ::sto/storage]
:opt [::min-age]))
(s/keys :req [::db/pool ::sto/storage]))
(defmethod ig/prep-key ::handler
[_ cfg]
(merge {::min-age cf/deletion-delay}
(d/without-nils cfg)))
(assoc cfg ::min-age cf/deletion-delay))
(defmethod ig/init-key ::handler
[_ {:keys [::db/pool ::sto/storage] :as cfg}]
@@ -133,7 +129,6 @@
:kf first
:initk (dt/now)))))
(def ^:private sql:get-orphan-teams-chunk
"select t.id, t.created_at
from team as t
@@ -154,14 +149,15 @@
[(some->> rows peek :created-at) rows]))]
(reduce
(fn [total {:keys [id]}]
(l/debug :hint "mark team for deletion" :id (str id))
(let [result (db/update! conn :team
{:deleted-at (dt/now)}
{:id id :deleted-at nil}
{::db/return-keys? false})
count (db/get-update-count result)]
(when (pos? count)
(l/debug :hint "mark team for deletion" :id (str id) ))
;; And finally, permanently delete the team.
(db/update! conn :team
{:deleted-at (dt/now)}
{:id id})
(inc total))
(+ total count)))
0
(d/iteration get-chunk
:vf second

View File

@@ -8,35 +8,33 @@
"A maintenance task that performs a cleanup of already executed tasks
from the database table."
(:require
[app.common.data :as d]
[app.common.logging :as l]
[app.config :as cf]
[app.db :as db]
[app.util.time :as dt]
[clojure.spec.alpha :as s]
[integrant.core :as ig]))
(declare sql:delete-completed-tasks)
(s/def ::min-age ::dt/duration)
(def ^:private
sql:delete-completed-tasks
"delete from task_completed
where scheduled_at < now() - ?::interval")
(defmethod ig/pre-init-spec ::handler [_]
(s/keys :req-un [::db/pool]
:opt-un [::min-age]))
(s/keys :req [::db/pool]))
(defmethod ig/prep-key ::handler
[_ cfg]
(merge {:min-age cf/deletion-delay}
(d/without-nils cfg)))
(assoc cfg ::min-age cf/deletion-delay))
(defmethod ig/init-key ::handler
[_ {:keys [pool] :as cfg}]
[_ {:keys [::db/pool ::min-age] :as cfg}]
(fn [params]
(let [min-age (or (:min-age params) (:min-age cfg))]
(let [min-age (or (:min-age params) min-age)]
(db/with-atomic [conn pool]
(let [interval (db/interval min-age)
result (db/exec-one! conn [sql:delete-completed-tasks interval])
result (:next.jdbc/update-count result)]
result (db/get-update-count result)]
(l/debug :hint "task finished" :total result)
(when (:rollback? params)
@@ -44,7 +42,3 @@
result)))))
(def ^:private
sql:delete-completed-tasks
"delete from task_completed
where scheduled_at < now() - ?::interval")

View File

@@ -13,7 +13,7 @@
(s/def ::name string?)
(s/def ::step (s/keys :req-un [::name ::fn]))
(s/def ::steps (s/every ::step :kind vector?))
(s/def ::steps (s/every ::step))
(s/def ::migrations
(s/keys :req-un [::name ::steps]))

View File

@@ -242,7 +242,7 @@
(let [result (a/<! (handler wsp v))]
;; (l/trace :hint "message received" :message v)
(cond
(ex/ex-info? result)
(ex/error? result)
(a/>! output-ch {:type :error :error (ex-data result)})
(ex/exception? result)

View File

@@ -45,7 +45,7 @@
(defmethod ig/init-key ::executor
[skey {:keys [::parallelism]}]
(let [prefix (if (vector? skey) (-> skey first name keyword) "default")
(let [prefix (if (vector? skey) (-> skey first name) "default")
tname (str "penpot/" prefix "/%s")
factory (px/forkjoin-thread-factory :name tname)]
(px/forkjoin-executor
@@ -90,10 +90,10 @@
(s/def ::registry (s/map-of ::us/string fn?))
(defmethod ig/pre-init-spec ::registry [_]
(s/keys :req-un [::mtx/metrics ::tasks]))
(s/keys :req [::mtx/metrics ::tasks]))
(defmethod ig/init-key ::registry
[_ {:keys [metrics tasks]}]
[_ {:keys [::mtx/metrics ::tasks]}]
(l/info :hint "registry initialized" :tasks (count tasks))
(reduce-kv (fn [registry k v]
(let [tname (name k)]

View File

@@ -7,7 +7,7 @@
(ns backend-tests.bounce-handling-test
(:require
[app.db :as db]
[app.emails :as emails]
[app.email :as email]
[app.http.awsns :as awsns]
[app.tokens :as tokens]
[app.util.time :as dt]
@@ -261,11 +261,11 @@
(th/create-complaint-for pool {:type :bounce :id (:id profile)})
(th/create-complaint-for pool {:type :bounce :id (:id profile)})
(t/is (true? (emails/allow-send-emails? pool profile)))
(t/is (true? (email/allow-send-emails? pool profile)))
(t/is (= 4 (:call-count @mock)))
(th/create-complaint-for pool {:type :bounce :id (:id profile)})
(t/is (false? (emails/allow-send-emails? pool profile))))))
(t/is (false? (email/allow-send-emails? pool profile))))))
(t/deftest test-allow-send-messages-predicate-with-complaints
@@ -281,32 +281,32 @@
(th/create-complaint-for pool {:type :bounce :id (:id profile)})
(th/create-complaint-for pool {:type :complaint :id (:id profile)})
(t/is (true? (emails/allow-send-emails? pool profile)))
(t/is (true? (email/allow-send-emails? pool profile)))
(t/is (= 4 (:call-count @mock)))
(th/create-complaint-for pool {:type :complaint :id (:id profile)})
(t/is (false? (emails/allow-send-emails? pool profile))))))
(t/is (false? (email/allow-send-emails? pool profile))))))
(t/deftest test-has-complaint-reports-predicate
(let [profile (th/create-profile* 1)
pool (:app.db/pool th/*system*)]
(t/is (false? (emails/has-complaint-reports? pool (:email profile))))
(t/is (false? (email/has-complaint-reports? pool (:email profile))))
(th/create-global-complaint-for pool {:type :bounce :email (:email profile)})
(t/is (false? (emails/has-complaint-reports? pool (:email profile))))
(t/is (false? (email/has-complaint-reports? pool (:email profile))))
(th/create-global-complaint-for pool {:type :complaint :email (:email profile)})
(t/is (true? (emails/has-complaint-reports? pool (:email profile))))))
(t/is (true? (email/has-complaint-reports? pool (:email profile))))))
(t/deftest test-has-bounce-reports-predicate
(let [profile (th/create-profile* 1)
pool (:app.db/pool th/*system*)]
(t/is (false? (emails/has-bounce-reports? pool (:email profile))))
(t/is (false? (email/has-bounce-reports? pool (:email profile))))
(th/create-global-complaint-for pool {:type :complaint :email (:email profile)})
(t/is (false? (emails/has-bounce-reports? pool (:email profile))))
(t/is (false? (email/has-bounce-reports? pool (:email profile))))
(th/create-global-complaint-for pool {:type :bounce :email (:email profile)})
(t/is (true? (emails/has-bounce-reports? pool (:email profile))))))
(t/is (true? (email/has-bounce-reports? pool (:email profile))))))

View File

@@ -8,7 +8,7 @@
(:require
[backend-tests.helpers :as th]
[app.db :as db]
[app.emails :as emails]
[app.email :as emails]
[clojure.test :as t]
[promesa.core :as p]))

View File

@@ -80,9 +80,9 @@
:path (-> "backend_tests/test_files/template.penpot" io/resource fs/path)}]
system (-> (merge main/system-config main/worker-config)
(assoc-in [:app.redis/redis :app.redis/uri] (:redis-uri config))
(assoc-in [:app.db/pool :uri] (:database-uri config))
(assoc-in [:app.db/pool :username] (:database-username config))
(assoc-in [:app.db/pool :password] (:database-password config))
(assoc-in [::db/pool ::db/uri] (:database-uri config))
(assoc-in [::db/pool ::db/username] (:database-username config))
(assoc-in [::db/pool ::db/password] (:database-password config))
(assoc-in [:app.rpc/methods :templates] templates)
(dissoc :app.srepl/server
:app.http/server
@@ -390,7 +390,7 @@
(defn ex-info?
[v]
(instance? clojure.lang.ExceptionInfo v))
(ex/error? v))
(defn ex-type
[e]

View File

@@ -154,7 +154,7 @@
(t/is (th/success? out))
(let [[thread :as result] (:result out)]
(t/is (= 1 (count result)))
(t/is (= "Page-1" (:page-name thread)))
(t/is (= "Page 1" (:page-name thread)))
(t/is (= "hello world" (:content thread)))
(t/is (= 2 (:count-comments thread)))
(t/is (true? (:is-resolved thread))))))

View File

@@ -44,8 +44,8 @@
(let [storage (:app.storage/storage th/*system*)
mobj1 @(sto/get-object storage media-id)
mobj2 @(sto/get-object storage thumbnail-id)]
(t/is (sto/storage-object? mobj1))
(t/is (sto/storage-object? mobj2))
(t/is (sto/object? mobj1))
(t/is (sto/object? mobj2))
(t/is (= 122785 (:size mobj1)))
;; This is because in ubuntu 21.04 generates different
;; thumbnail that in ubuntu 22.04. This hack should be removed
@@ -85,8 +85,8 @@
(let [storage (:app.storage/storage th/*system*)
mobj1 @(sto/get-object storage media-id)
mobj2 @(sto/get-object storage thumbnail-id)]
(t/is (sto/storage-object? mobj1))
(t/is (sto/storage-object? mobj2))
(t/is (sto/object? mobj1))
(t/is (sto/object? mobj2))
(t/is (= 312043 (:size mobj1)))
(t/is (= 3887 (:size mobj2)))))
))
@@ -164,8 +164,8 @@
(let [storage (:app.storage/storage th/*system*)
mobj1 @(sto/get-object storage media-id)
mobj2 @(sto/get-object storage thumbnail-id)]
(t/is (sto/storage-object? mobj1))
(t/is (sto/storage-object? mobj2))
(t/is (sto/object? mobj1))
(t/is (sto/object? mobj2))
(t/is (= 122785 (:size mobj1)))
;; This is because in ubuntu 21.04 generates different
;; thumbnail that in ubuntu 22.04. This hack should be removed
@@ -205,8 +205,8 @@
(let [storage (:app.storage/storage th/*system*)
mobj1 @(sto/get-object storage media-id)
mobj2 @(sto/get-object storage thumbnail-id)]
(t/is (sto/storage-object? mobj1))
(t/is (sto/storage-object? mobj2))
(t/is (sto/object? mobj1))
(t/is (sto/object? mobj2))
(t/is (= 312043 (:size mobj1)))
(t/is (= 3887 (:size mobj2)))))
))

View File

@@ -231,7 +231,7 @@
(t/deftest prepare-register-and-register-profile-2
(with-redefs [app.rpc.commands.auth/register-retry-threshold (dt/duration 500)]
(with-mocks [mock {:target 'app.emails/send! :return nil}]
(with-mocks [mock {:target 'app.email/send! :return nil}]
(let [current-token (atom nil)]
;; PREPARE REGISTER
@@ -409,7 +409,7 @@
(t/is (= :email-as-password (:code edata))))))
(t/deftest email-change-request
(with-mocks [mock {:target 'app.emails/send! :return nil}]
(with-mocks [mock {:target 'app.email/send! :return nil}]
(let [profile (th/create-profile* 1)
pool (:app.db/pool th/*system*)
data {::th/type :request-email-change
@@ -443,7 +443,7 @@
(t/deftest email-change-request-without-smtp
(with-mocks [mock {:target 'app.emails/send! :return nil}]
(with-mocks [mock {:target 'app.email/send! :return nil}]
(with-redefs [app.config/flags #{}]
(let [profile (th/create-profile* 1)
pool (:app.db/pool th/*system*)
@@ -459,7 +459,7 @@
(t/deftest request-profile-recovery
(with-mocks [mock {:target 'app.emails/send! :return nil}]
(with-mocks [mock {:target 'app.email/send! :return nil}]
(let [profile1 (th/create-profile* 1)
profile2 (th/create-profile* 2 {:is-active true})
pool (:app.db/pool th/*system*)

View File

@@ -22,7 +22,7 @@
(t/use-fixtures :each th/database-reset)
(t/deftest create-team-invitations
(with-mocks [mock {:target 'app.emails/send! :return nil}]
(with-mocks [mock {:target 'app.email/send! :return nil}]
(let [profile1 (th/create-profile* 1 {:is-active true})
profile2 (th/create-profile* 2 {:is-active true})
profile3 (th/create-profile* 3 {:is-active true :is-muted true})
@@ -105,7 +105,7 @@
(t/deftest invitation-tokens
(with-mocks [mock {:target 'app.emails/send! :return nil}]
(with-mocks [mock {:target 'app.email/send! :return nil}]
(let [profile1 (th/create-profile* 1 {:is-active true})
profile2 (th/create-profile* 2 {:is-active true})
@@ -166,6 +166,7 @@
(t/deftest accept-invitation-tokens
(let [profile1 (th/create-profile* 1 {:is-active true})
profile2 (th/create-profile* 2 {:is-active true})
profile3 (th/create-profile* 3 {:is-active true})
team (th/create-team* 1 {:profile-id (:id profile1)})
@@ -181,25 +182,29 @@
:member-email (:email profile2)
:member-id (:id profile2)})]
;; --- Verify token as anonymous user
(t/testing "Verify token as anonymous user"
(db/insert! pool :team-invitation
{:team-id (:id team)
:email-to (:email profile2)
:role "editor"
:valid-until (dt/in-future "48h")})
(db/insert! pool :team-invitation
{:team-id (:id team)
:email-to (:email profile2)
:role "editor"
:valid-until (dt/in-future "48h")})
(let [data {::th/type :verify-token :token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (th/success? out))
(let [data {::th/type :verify-token :token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (th/success? out))
(let [result (:result out)]
(t/is (= :created (:state result)))
(t/is (= (:email profile2) (:member-email result)))
(t/is (= (:id profile2) (:member-id result))))
(let [result (:result out)]
(t/is (contains? result :invitation-token))
(t/is (contains? result :iss))
(t/is (contains? result :redirect-to))
(t/is (contains? result :state))
(let [rows (db/query pool :team-profile-rel {:team-id (:id team)})]
(t/is (= 2 (count rows)))))
(t/is (= :pending (:state result)))
(t/is (= :auth-login (:redirect-to result))))
(let [rows (db/query pool :team-profile-rel {:team-id (:id team)})]
(t/is (= 1 (count rows))))))
;; Clean members
(db/delete! pool :team-profile-rel
@@ -207,51 +212,42 @@
:profile-id (:id profile2)})
;; --- Verify token as logged-in user
(t/testing "Verify token as logged-in user"
(let [data {::th/type :verify-token
::rpc/profile-id (:id profile2)
:token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (th/success? out))
(let [result (:result out)]
(t/is (= :created (:state result)))
(t/is (= (:email profile2) (:member-email result)))
(t/is (= (:id profile2) (:member-id result))))
(db/insert! pool :team-invitation
{:team-id (:id team)
:email-to (:email profile2)
:role "editor"
:valid-until (dt/in-future "48h")})
(let [rows (db/query pool :team-profile-rel {:team-id (:id team)})]
(t/is (= 2 (count rows))))))
(let [data {::th/type :verify-token
::rpc/profile-id (:id profile2)
:token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (th/success? out))
(let [result (:result out)]
(t/is (= :created (:state result)))
(t/is (= (:email profile2) (:member-email result)))
(t/is (= (:id profile2) (:member-id result))))
(t/testing "Verify token as logged-in wrong user"
(db/insert! pool :team-invitation
{:team-id (:id team)
:email-to (:email profile3)
:role "editor"
:valid-until (dt/in-future "48h")})
(let [rows (db/query pool :team-profile-rel {:team-id (:id team)})]
(t/is (= 2 (count rows)))))
;; --- Verify token as logged-in wrong user
(db/insert! pool :team-invitation
{:team-id (:id team)
:email-to (:email profile2)
:role "editor"
:valid-until (dt/in-future "48h")})
(let [data {::th/type :verify-token
::rpc/profile-id (:id profile1)
:token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (not (th/success? out)))
(let [edata (-> out :error ex-data)]
(t/is (= :validation (:type edata)))
(t/is (= :invalid-token (:code edata)))))
(let [data {::th/type :verify-token
::rpc/profile-id (:id profile1)
:token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (not (th/success? out)))
(let [edata (-> out :error ex-data)]
(t/is (= :validation (:type edata)))
(t/is (= :invalid-token (:code edata))))))
)))
(t/deftest create-team-invitations-with-email-verification-disabled
(with-mocks [mock {:target 'app.emails/send! :return nil}]
(with-mocks [mock {:target 'app.email/send! :return nil}]
(let [profile1 (th/create-profile* 1 {:is-active true})
profile2 (th/create-profile* 2 {:is-active true})
profile3 (th/create-profile* 3 {:is-active true :is-muted true})

View File

@@ -6,11 +6,12 @@
(ns backend-tests.rpc-webhooks-test
(:require
[app.common.uri :as u]
[app.common.uuid :as uuid]
[app.db :as db]
[app.http :as http]
[app.storage :as sto]
[app.rpc :as-alias rpc]
[app.storage :as sto]
[backend-tests.helpers :as th]
[clojure.test :as t]
[mockery.core :refer [with-mocks]]))
@@ -31,7 +32,7 @@
(let [params {::th/type :create-webhook
::rpc/profile-id (:id prof)
:team-id team-id
:uri "http://example.com"
:uri (u/uri "http://example.com")
:mtype "application/json"}
out (th/command! params)]

View File

@@ -27,11 +27,11 @@
"Given storage map, returns a storage configured with the appropriate
backend for assets."
([storage]
(assoc storage :backend :assets-fs))
(assoc storage ::sto/backend :assets-fs))
([storage conn]
(-> storage
(assoc :conn conn)
(assoc :backend :assets-fs))))
(assoc ::db/pool-or-conn conn)
(assoc ::sto/backend :assets-fs))))
(t/deftest put-and-retrieve-object
(let [storage (-> (:app.storage/storage th/*system*)
@@ -40,8 +40,10 @@
object @(sto/put-object! storage {::sto/content content
:content-type "text/plain"
:other "data"})]
(t/is (sto/storage-object? object))
(t/is (sto/object? object))
(t/is (fs/path? @(sto/get-object-path storage object)))
(t/is (nil? (:expired-at object)))
(t/is (= :assets-fs (:backend object)))
(t/is (= "data" (:other (meta object))))
@@ -58,7 +60,8 @@
::sto/expired-at (dt/in-future {:seconds 1})
:content-type "text/plain"
})]
(t/is (sto/storage-object? object))
(t/is (sto/object? object))
(t/is (dt/instant? (:expired-at object)))
(t/is (dt/is-after? (:expired-at object) (dt/now)))
(t/is (= object @(sto/get-object storage (:id object))))
@@ -77,7 +80,7 @@
object @(sto/put-object! storage {::sto/content content
:content-type "text/plain"
:expired-at (dt/in-future {:seconds 1})})]
(t/is (sto/storage-object? object))
(t/is (sto/object? object))
(t/is (true? @(sto/del-object! storage object)))
;; retrieving the same object should be not nil because the

View File

@@ -8,7 +8,6 @@
(:require
[backend-tests.helpers :as th]
[app.db :as db]
[app.emails :as emails]
[app.util.time :as dt]
[clojure.pprint :refer [pprint]]
[clojure.test :as t]

View File

@@ -11,13 +11,13 @@
org.apache.logging.log4j/log4j-core {:mvn/version "2.19.0"}
org.apache.logging.log4j/log4j-web {:mvn/version "2.19.0"}
org.apache.logging.log4j/log4j-jul {:mvn/version "2.19.0"}
org.apache.logging.log4j/log4j-slf4j18-impl {:mvn/version "2.18.0"}
org.slf4j/slf4j-api {:mvn/version "2.0.0-alpha1"}
org.apache.logging.log4j/log4j-slf4j2-impl {:mvn/version "2.19.0"}
org.slf4j/slf4j-api {:mvn/version "2.0.6"}
pl.tkowalcz.tjahzi/log4j2-appender {:mvn/version "0.9.26"}
selmer/selmer {:mvn/version "1.12.55"}
criterium/criterium {:mvn/version "0.4.6"}
expound/expound {:mvn/version "0.9.0"}
com.cognitect/transit-clj {:mvn/version "1.0.329"}
com.cognitect/transit-cljs {:mvn/version "0.8.280"}
@@ -52,7 +52,7 @@
:build
{:extra-deps
{io.github.clojure/tools.build {:git/tag "v0.9.0" :git/sha "8c93e0c"}}
{io.github.clojure/tools.build {:git/tag "v0.9.3" :git/sha "e537cd1"}}
:ns-default build}
:test

View File

@@ -8,23 +8,26 @@
"A collection if helpers for working with data structures and other
data resources."
(:refer-clojure :exclude [read-string hash-map merge name update-vals
parse-double group-by iteration concat mapcat])
parse-double group-by iteration concat mapcat
parse-uuid])
#?(:cljs
(:require-macros [app.common.data]))
(:require
[app.common.math :as mth]
[clojure.set :as set]
[cuerdas.core :as str]
#?(:cljs [cljs.reader :as r]
:clj [clojure.edn :as r])
#?(:cljs [cljs.core :as c]
:clj [clojure.core :as c])
[app.common.exceptions :as ex]
[app.common.math :as mth]
[clojure.set :as set]
[cuerdas.core :as str]
[linked.map :as lkm]
[linked.set :as lks])
#?(:clj
(:import
linked.set.LinkedSet
linked.map.LinkedMap
java.lang.AutoCloseable)))
(def boolean-or-nil?
@@ -39,11 +42,21 @@
([a] (conj lks/empty-linked-set a))
([a & xs] (apply conj lks/empty-linked-set a xs)))
(defn ordered-map
([] lkm/empty-linked-map)
([a] (conj lkm/empty-linked-map a))
([a & xs] (apply conj lkm/empty-linked-map a xs)))
(defn ordered-set?
[o]
#?(:cljs (instance? lks/LinkedSet o)
:clj (instance? LinkedSet o)))
(defn ordered-map?
[o]
#?(:cljs (instance? lkm/LinkedMap o)
:clj (instance? LinkedMap o)))
#?(:clj
(defmethod print-method clojure.lang.PersistentQueue [q, w]
;; Overload the printer for queues so they look like fish
@@ -203,19 +216,22 @@
([coll value]
(sequence (replace-by-id value) coll)))
(defn without-nils
"Given a map, return a map removing key-value
pairs when value is `nil`."
[data]
(into {} (remove (comp nil? second)) data))
(defn vec-without-nils
[coll]
(into [] (remove nil?) coll))
(defn without-nils
"Given a map, return a map removing key-value
pairs when value is `nil`."
([] (remove (comp nil? val)))
([data]
(into {} (without-nils) data)))
(defn without-qualified
[data]
(into {} (remove (comp qualified-keyword? first)) data))
([]
(remove (comp qualified-keyword? key)))
([data]
(into {} (without-qualified) data)))
(defn without-keys
"Return a map without the keys provided
@@ -505,6 +521,10 @@
default
v))))
(defn parse-uuid
[v]
(ex/ignoring (c/parse-uuid v)))
(defn num-string? [v]
;; https://stackoverflow.com/questions/175739/built-in-way-in-javascript-to-check-if-a-string-is-a-valid-number
#?(:cljs (and (string? v)

View File

@@ -12,7 +12,12 @@
[app.common.pprint :as pp]
[clojure.spec.alpha :as s]
[cuerdas.core :as str]
[expound.alpha :as expound]))
[expound.alpha :as expound])
#?(:clj
(:import
clojure.lang.IPersistentMap)))
#?(:clj (set! *warn-on-reflection* true))
(defmacro error
[& {:keys [type hint] :as params}]
@@ -41,44 +46,22 @@
[& exprs]
`(try* (^:once fn* [] ~@exprs) identity))
(defn cause
"Retrieve chained cause if available of the exception."
[^Throwable throwable]
(.getCause throwable))
(defn ex-info?
[v]
(instance? #?(:clj clojure.lang.ExceptionInfo :cljs cljs.core.ExceptionInfo) v))
(instance? #?(:clj clojure.lang.IExceptionInfo :cljs cljs.core.ExceptionInfo) v))
(defn error?
[v]
(instance? #?(:clj clojure.lang.IExceptionInfo :cljs cljs.core.ExceptionInfo) v))
(defn exception?
[v]
(instance? #?(:clj java.lang.Throwable :cljs js/Error) v))
#?(:cljs
(deftype WrappedException [cause meta]
cljs.core/IMeta
(-meta [_] meta)
cljs.core/IDeref
(-deref [_] cause))
:clj
(deftype WrappedException [cause meta]
clojure.lang.IMeta
(meta [_] meta)
clojure.lang.IDeref
(deref [_] cause)))
#?(:clj (ns-unmap 'app.common.exceptions '->WrappedException))
#?(:clj (ns-unmap 'app.common.exceptions 'map->WrappedException))
(defn wrapped?
[o]
(instance? WrappedException o))
(defn wrap-with-context
[cause context]
(WrappedException. cause context))
#?(:clj
(defn runtime-exception?
[v]
(instance? RuntimeException v)))
(defn explain
([data] (explain data nil))
@@ -97,15 +80,17 @@
(s/explain-out (update data ::s/problems #(take max-problems %))))))))
#?(:clj
(defn print-throwable
[^Throwable cause
& {:keys [trace? data? chain? data-level data-length trace-length explain-length]
:or {trace? true
data? true
chain? true
explain-length 10
data-length 10
data-level 3}}]
(defn format-throwable
[^Throwable cause & {:keys [summary? detail? header? data? explain? chain? data-level data-length trace-length]
:or {summary? true
detail? true
header? true
data? true
explain? true
chain? true
data-length 10
data-level 3}}]
(letfn [(print-trace-element [^StackTraceElement e]
(let [class (.getClassName e)
method (.getMethodName e)]
@@ -132,28 +117,29 @@
(doseq [line lines]
(println " " line)))))
(print-trace-title [cause]
(print-trace-title [^Throwable cause]
(print " → ")
(printf "%s: %s" (.getName (class cause)) (first (str/lines (ex-message cause))))
(when-let [e (first (.getStackTrace cause))]
(when-let [^StackTraceElement e (first (.getStackTrace ^Throwable cause))]
(printf " (%s:%d)" (or (.getFileName e) "") (.getLineNumber e)))
(newline))
(print-summary [cause]
(let [causes (loop [cause (.getCause cause)
(print-summary [^Throwable cause]
(let [causes (loop [cause (ex-cause cause)
result []]
(if cause
(recur (.getCause cause)
(recur (ex-cause cause)
(conj result cause))
result))]
(println "TRACE:")
(when header?
(println "SUMMARY:"))
(print-trace-title cause)
(doseq [cause causes]
(print-trace-title cause))))
(print-trace [cause]
(print-trace [^Throwable cause]
(print-trace-title cause)
(let [st (.getStackTrace cause)]
(print " at: ")
@@ -167,35 +153,35 @@
(print-trace-element e)
(newline))))
(print-all [cause]
(print-summary cause)
(println "DETAIL:")
(when trace?
(print-trace cause))
(when data?
(when-let [data (ex-data cause)]
(print-detail [^Throwable cause]
(print-trace cause)
(when-let [data (ex-data cause)]
(when data?
(print-data (dissoc data ::s/problems ::s/spec ::s/value)))
(when explain?
(if-let [explain (explain data)]
(print-explain explain)
(print-data data))))
(print-explain explain)))))
(when chain?
(loop [cause cause]
(when-let [cause (.getCause cause)]
(newline)
(print-trace cause)
(print-all [^Throwable cause]
(when summary?
(print-summary cause))
(when data?
(when-let [data (ex-data cause)]
(if-let [explain (explain data)]
(print-explain explain)
(print-data data))))
(when detail?
(when header?
(println "DETAIL:"))
(recur cause)))))
(print-detail cause)
(when chain?
(loop [cause cause]
(when-let [cause (ex-cause cause)]
(newline)
(print-detail cause)
(recur cause))))))
]
(with-out-str
(print-all cause)))))
(println
(with-out-str
(print-all cause))))))
#?(:clj
(defn print-throwable
[cause & {:as opts}]
(println (format-throwable cause opts))))

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