mirror of
https://github.com/penpot/penpot.git
synced 2026-01-15 18:00:21 -05:00
Compare commits
9289 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d31138db72 | ||
|
|
2c5f35e192 | ||
|
|
5a8f8ba349 | ||
|
|
3fe5cd3752 | ||
|
|
da60911d81 | ||
|
|
f4f1f80050 | ||
|
|
18445ea5f4 | ||
|
|
2d28e02742 | ||
|
|
b0b963fb7c | ||
|
|
5cfee13956 | ||
|
|
7271e98df3 | ||
|
|
f0386ef7b0 | ||
|
|
185cabb2fa | ||
|
|
3a19223264 | ||
|
|
2c38f31aa9 | ||
|
|
a1dcb11261 | ||
|
|
9f8d86a80e | ||
|
|
c59fc87fc4 | ||
|
|
3421e6ef57 | ||
|
|
40349c8ece | ||
|
|
5a53376b01 | ||
|
|
d4dfdaff57 | ||
|
|
c7f87d0f26 | ||
|
|
c7954990f0 | ||
|
|
fe118819ce | ||
|
|
073ec9ea2b | ||
|
|
f85a731969 | ||
|
|
a3a88d7a0a | ||
|
|
1660dd634e | ||
|
|
6e698110d6 | ||
|
|
951c67a2d5 | ||
|
|
50b7337b8c | ||
|
|
15e62ff649 | ||
|
|
e7ddd6055f | ||
|
|
aa3438f800 | ||
|
|
a45380a91c | ||
|
|
86b68aeca4 | ||
|
|
d69d392362 | ||
|
|
506c2b8d7b | ||
|
|
b463ebc17b | ||
|
|
f90fda2c90 | ||
|
|
87c5aa71a3 | ||
|
|
4f82f6bde4 | ||
|
|
545b3860b4 | ||
|
|
d4921c8eb9 | ||
|
|
18652d0b6f | ||
|
|
2dbeda1d8f | ||
|
|
9422d1e9e2 | ||
|
|
e0441bc16a | ||
|
|
d7d6166232 | ||
|
|
6fd6205634 | ||
|
|
7cd6f5ba70 | ||
|
|
9cc3cceb06 | ||
|
|
6f6bcd2f7e | ||
|
|
f9f3b3951f | ||
|
|
22ded62000 | ||
|
|
71d104f768 | ||
|
|
5a36cbceb7 | ||
|
|
f2033c46f3 | ||
|
|
6b225a10b5 | ||
|
|
38fe6e856a | ||
|
|
1984109436 | ||
|
|
9f9d9277a6 | ||
|
|
e041f93680 | ||
|
|
2d779a4414 | ||
|
|
21fc9289a6 | ||
|
|
b40ea3fb2a | ||
|
|
444e9a3081 | ||
|
|
f93d305545 | ||
|
|
09a91c87be | ||
|
|
e71d569cda | ||
|
|
a56a9868dc | ||
|
|
a09198b46e | ||
|
|
c7e9c658cd | ||
|
|
58d7bc5c14 | ||
|
|
e939db927e | ||
|
|
efe50479de | ||
|
|
ea1b3bd058 | ||
|
|
4751d7d385 | ||
|
|
bc88e30efa | ||
|
|
9623dbfbd6 | ||
|
|
f177de6661 | ||
|
|
43043e2dc1 | ||
|
|
05d21d7d07 | ||
|
|
02aab37ee7 | ||
|
|
d3aee1afa3 | ||
|
|
ac361cdb36 | ||
|
|
7ac6f49c08 | ||
|
|
d3e11433bf | ||
|
|
771d1d9194 | ||
|
|
4a3a53182b | ||
|
|
c25cf043fa | ||
|
|
7440d38c94 | ||
|
|
a8c0d437ce | ||
|
|
8d683beae4 | ||
|
|
4007d8713c | ||
|
|
ead64a1820 | ||
|
|
88e2a5c56e | ||
|
|
9782d9077f | ||
|
|
b4c4511d9d | ||
|
|
316b3d4539 | ||
|
|
1c54e9fa4d | ||
|
|
3d064b804b | ||
|
|
088a8af345 | ||
|
|
8ee7915c1d | ||
|
|
ea8755ce24 | ||
|
|
381aae735d | ||
|
|
a4826eddcd | ||
|
|
31e2fff4d4 | ||
|
|
021c714867 | ||
|
|
231ac00934 | ||
|
|
578ff944a6 | ||
|
|
bf8a514871 | ||
|
|
8d60b3fc3e | ||
|
|
8468e7af24 | ||
|
|
50eee3f597 | ||
|
|
b9b3fcdb6a | ||
|
|
f0d74ab63e | ||
|
|
dad5d953ce | ||
|
|
f6058aa71e | ||
|
|
85d56e6057 | ||
|
|
c353d3703b | ||
|
|
9367788898 | ||
|
|
2b978777d7 | ||
|
|
2a30c23334 | ||
|
|
2f188e7fb4 | ||
|
|
0743b07667 | ||
|
|
f38197b227 | ||
|
|
bc9be7846a | ||
|
|
28114b166c | ||
|
|
be74cd2c7b | ||
|
|
b329de6487 | ||
|
|
9c66998530 | ||
|
|
8b377ac556 | ||
|
|
8c6f07ab65 | ||
|
|
dc89610d07 | ||
|
|
40195a4f52 | ||
|
|
6a257503ae | ||
|
|
a3e583d745 | ||
|
|
685a071e87 | ||
|
|
73658c47f3 | ||
|
|
d98fd76032 | ||
|
|
2fef3dc881 | ||
|
|
a1a0444cc7 | ||
|
|
792c17fe46 | ||
|
|
77d71abb5d | ||
|
|
75d6e21af8 | ||
|
|
0632111e96 | ||
|
|
fe77ef4438 | ||
|
|
e7ac7ff7fb | ||
|
|
d78ad30e23 | ||
|
|
4b5caf5fb9 | ||
|
|
4e1eb2d6e9 | ||
|
|
ab7683f1e3 | ||
|
|
89371e10d1 | ||
|
|
9fd6c65d93 | ||
|
|
1f9c89fb32 | ||
|
|
61e83d7e01 | ||
|
|
a1a3d09998 | ||
|
|
de7a1d34c0 | ||
|
|
f93d0e1c4d | ||
|
|
c5d8d77070 | ||
|
|
c18d3c66a8 | ||
|
|
0d96b5b798 | ||
|
|
24f45fafbf | ||
|
|
ca8df3a8d8 | ||
|
|
d14f4c5c4a | ||
|
|
f6ff80a3d4 | ||
|
|
b2d8f807f9 | ||
|
|
03b3b441b5 | ||
|
|
523539e403 | ||
|
|
3280a6853e | ||
|
|
fb060cb806 | ||
|
|
8892cebb6f | ||
|
|
6fb97e54a9 | ||
|
|
1c3470ca53 | ||
|
|
0ae42be851 | ||
|
|
ff6f0b2744 | ||
|
|
a3a2ab1ecd | ||
|
|
01ba68fd6f | ||
|
|
1ab669cc7b | ||
|
|
ab421ac3f9 | ||
|
|
0faa0b21a4 | ||
|
|
4ca6a89e6f | ||
|
|
ab5fd68689 | ||
|
|
275eb993ce | ||
|
|
88143cfb8b | ||
|
|
5f0f3abeae | ||
|
|
b203c87dbb | ||
|
|
7a796bc83f | ||
|
|
196e193281 | ||
|
|
d0a15cda96 | ||
|
|
c3733ed2e1 | ||
|
|
379623d629 | ||
|
|
cb2553a8ca | ||
|
|
1b7ea6ed53 | ||
|
|
57a569a07a | ||
|
|
a5006b1687 | ||
|
|
24dc40a1b0 | ||
|
|
b4fc39f73c | ||
|
|
095dc2ad11 | ||
|
|
fcbbe8e5c7 | ||
|
|
bafe3ec087 | ||
|
|
5d44d75465 | ||
|
|
44102050ee | ||
|
|
cae436f365 | ||
|
|
e6d80e34b9 | ||
|
|
fbec07bd48 | ||
|
|
a555028ee2 | ||
|
|
d91e8c349e | ||
|
|
abe26007d7 | ||
|
|
2da421bb7a | ||
|
|
7d48b86e46 | ||
|
|
28663b5ff6 | ||
|
|
651d4f794b | ||
|
|
58aa6b3666 | ||
|
|
131c2f331e | ||
|
|
8df861faaa | ||
|
|
4f81f9636a | ||
|
|
31dfdf51c9 | ||
|
|
acf51ea744 | ||
|
|
a54f5484e8 | ||
|
|
3a8486f4b0 | ||
|
|
43c3d67521 | ||
|
|
4b2d82e100 | ||
|
|
f2fd380979 | ||
|
|
984187037c | ||
|
|
173e5da98e | ||
|
|
2ab3ed9ab4 | ||
|
|
74e4273549 | ||
|
|
12392a4038 | ||
|
|
987b7f44f4 | ||
|
|
3480d6979b | ||
|
|
9ca1efc128 | ||
|
|
81a95d362c | ||
|
|
a7dfda515b | ||
|
|
b5c1199f4d | ||
|
|
4aa8baa129 | ||
|
|
553f2f5576 | ||
|
|
b132837432 | ||
|
|
36bc276d93 | ||
|
|
35aa391129 | ||
|
|
2c2755b35e | ||
|
|
bedaef961b | ||
|
|
fe7f4004f1 | ||
|
|
eef42acf79 | ||
|
|
937713311e | ||
|
|
94fc067286 | ||
|
|
ae6ea7744e | ||
|
|
f628955a15 | ||
|
|
6cdf696fc4 | ||
|
|
c42ef7c5b0 | ||
|
|
853be27780 | ||
|
|
b235d3f0f2 | ||
|
|
1fdf09a692 | ||
|
|
c2e0b18f26 | ||
|
|
672cfa4ecc | ||
|
|
c459c56f37 | ||
|
|
97a884018f | ||
|
|
1718f49a90 | ||
|
|
2c1fb1424c | ||
|
|
5e1cabc857 | ||
|
|
6f72ea0530 | ||
|
|
c2d8c1994c | ||
|
|
985d5cc20c | ||
|
|
a0364e8835 | ||
|
|
b273bd44c5 | ||
|
|
ec2fff31a0 | ||
|
|
53a8718e8d | ||
|
|
10439934d4 | ||
|
|
84e9f69213 | ||
|
|
837b52aea1 | ||
|
|
98698cf2db | ||
|
|
d5ab0eea1a | ||
|
|
333acacbbf | ||
|
|
598959cd3f | ||
|
|
f56b8be33d | ||
|
|
644854a651 | ||
|
|
e926b11fef | ||
|
|
40da1c302a | ||
|
|
b5e53b57d1 | ||
|
|
e8d561ac7f | ||
|
|
cf87c54ed4 | ||
|
|
3ce1540331 | ||
|
|
cda2dade95 | ||
|
|
baf4dfdecc | ||
|
|
ade13d3bca | ||
|
|
ff9b2090cf | ||
|
|
733b35dd53 | ||
|
|
466e018411 | ||
|
|
32d39c35e4 | ||
|
|
5f77df1996 | ||
|
|
24538add3f | ||
|
|
407831ffd1 | ||
|
|
379997f9db | ||
|
|
b1d99232a9 | ||
|
|
7e21d827c9 | ||
|
|
443d8b21c1 | ||
|
|
e372e8ba3e | ||
|
|
27451b9796 | ||
|
|
73a3e0c0ae | ||
|
|
d68be0869b | ||
|
|
7a8b0e710b | ||
|
|
3b61a7dd91 | ||
|
|
941aa6ad5d | ||
|
|
42b69df671 | ||
|
|
4442246e08 | ||
|
|
d1dbc3850d | ||
|
|
ed4a5f6c60 | ||
|
|
0144939f34 | ||
|
|
ede07e4f44 | ||
|
|
b2c55c79a4 | ||
|
|
0b2ffbe1fa | ||
|
|
ebfe651b7d | ||
|
|
dac11d1606 | ||
|
|
c8bd1e89d6 | ||
|
|
8111db1110 | ||
|
|
0a8dfde0a2 | ||
|
|
9f6a3cbc23 | ||
|
|
6592456085 | ||
|
|
3bbf632121 | ||
|
|
104059a7b1 | ||
|
|
f75af88877 | ||
|
|
d4360be96e | ||
|
|
dcf95a7502 | ||
|
|
4fc3f316e0 | ||
|
|
83c8e7f03a | ||
|
|
074864a6bf | ||
|
|
aed7f0ad43 | ||
|
|
cd2df41e87 | ||
|
|
00fbfd6e9e | ||
|
|
93726cf8fe | ||
|
|
1dc6464974 | ||
|
|
81cebb2aa8 | ||
|
|
6c8144a18a | ||
|
|
47bf758ad7 | ||
|
|
13cfe56301 | ||
|
|
33f7cec933 | ||
|
|
1f00d91dd7 | ||
|
|
c1a8437b6d | ||
|
|
5cb3aa5dbc | ||
|
|
de72dc5769 | ||
|
|
b827037f90 | ||
|
|
60fb3f3d0e | ||
|
|
84fd952471 | ||
|
|
e37fc00351 | ||
|
|
4164c8f012 | ||
|
|
c86af68349 | ||
|
|
4302ab05e4 | ||
|
|
777e2fb0a3 | ||
|
|
f7412ccbd7 | ||
|
|
fe11b37b8f | ||
|
|
c469bd5757 | ||
|
|
7d817eb080 | ||
|
|
2840cb893e | ||
|
|
7f5491f45b | ||
|
|
ef9dcf391d | ||
|
|
81ecb26f8b | ||
|
|
35fd3ce150 | ||
|
|
68d2afc75d | ||
|
|
d094eb3595 | ||
|
|
f0d4ad4b20 | ||
|
|
b929564fa7 | ||
|
|
53d9b547c3 | ||
|
|
50c17e1261 | ||
|
|
a113a64554 | ||
|
|
c13730dca7 | ||
|
|
498ec29e47 | ||
|
|
880d01368f | ||
|
|
1fe1a352c3 | ||
|
|
8ffe023d3e | ||
|
|
16f30316c0 | ||
|
|
ac7cb3c8c7 | ||
|
|
61c1b65072 | ||
|
|
ef994548c1 | ||
|
|
159085fd83 | ||
|
|
84bee9fb93 | ||
|
|
2dcb4a155e | ||
|
|
abf397fe5b | ||
|
|
0087447b01 | ||
|
|
f47c20e079 | ||
|
|
4b26b6fc02 | ||
|
|
abeec9f869 | ||
|
|
c9c070b5f4 | ||
|
|
d80a24b1e3 | ||
|
|
ae8000df26 | ||
|
|
f239c401e2 | ||
|
|
f2e2700c79 | ||
|
|
d38c495807 | ||
|
|
025cd44eae | ||
|
|
8ac96d09cd | ||
|
|
8f2a02ae72 | ||
|
|
710878a667 | ||
|
|
350e4a1d1b | ||
|
|
801d926946 | ||
|
|
e50ecd70c6 | ||
|
|
f11da06637 | ||
|
|
a6b26f0563 | ||
|
|
dbf743d58a | ||
|
|
d35e35acde | ||
|
|
36f2ca6bb2 | ||
|
|
c570557203 | ||
|
|
797ae22526 | ||
|
|
4e1e67fc3d | ||
|
|
76a83bece9 | ||
|
|
5605ac2769 | ||
|
|
e88d6d88a8 | ||
|
|
0cc6c76cdb | ||
|
|
fa7cf70cee | ||
|
|
e25cf13783 | ||
|
|
6b199bef89 | ||
|
|
74e6c01213 | ||
|
|
970dc04bc6 | ||
|
|
aefdbfa8ef | ||
|
|
1b3976da47 | ||
|
|
c52046d25b | ||
|
|
609fa87fe2 | ||
|
|
9ca2450813 | ||
|
|
408d33bdec | ||
|
|
226afe98e0 | ||
|
|
db7920435b | ||
|
|
bdd00be5e4 | ||
|
|
6eedb5315b | ||
|
|
7045496a39 | ||
|
|
02f29ed4d0 | ||
|
|
6ea0279c9e | ||
|
|
6a7a25121e | ||
|
|
a8f65ba69e | ||
|
|
096b5f096c | ||
|
|
842463ed1b | ||
|
|
7d2e3a0864 | ||
|
|
c2ced974b1 | ||
|
|
653b6bdb42 | ||
|
|
c820c49fc5 | ||
|
|
7a9172560d | ||
|
|
be5053ce22 | ||
|
|
44e87e75e6 | ||
|
|
c9ad82edc3 | ||
|
|
430752383b | ||
|
|
e9064611cf | ||
|
|
2ce36ce052 | ||
|
|
56870ad68e | ||
|
|
7507a3b74f | ||
|
|
84903ae1f2 | ||
|
|
507800ae4e | ||
|
|
d56082307b | ||
|
|
782f2ed57d | ||
|
|
d7459db292 | ||
|
|
fd7d189bb7 | ||
|
|
5aaaab4f80 | ||
|
|
03228a9801 | ||
|
|
2fbd1d8078 | ||
|
|
029efefb62 | ||
|
|
ae79ee435e | ||
|
|
240e480b2e | ||
|
|
f2b60261f8 | ||
|
|
21abd98b95 | ||
|
|
edaa62b05b | ||
|
|
5b9f0ed0b1 | ||
|
|
d768711caa | ||
|
|
d584ae5a0f | ||
|
|
9debfa3b27 | ||
|
|
c0a4b7dc76 | ||
|
|
7f589b09ca | ||
|
|
27c4cdb5f9 | ||
|
|
fb0cf6fcbc | ||
|
|
7ca74c0467 | ||
|
|
cd6aa8f691 | ||
|
|
90bc9943bc | ||
|
|
fe7b4331d1 | ||
|
|
e1de3ba5e7 | ||
|
|
5cd108c21a | ||
|
|
c53420c1f5 | ||
|
|
05e437ee06 | ||
|
|
d0d63169e2 | ||
|
|
c148326d1c | ||
|
|
76a19a82c3 | ||
|
|
4d1a22bd11 | ||
|
|
95a18fce8d | ||
|
|
8bc265a598 | ||
|
|
de6cba8c0b | ||
|
|
f2fe1dd6f8 | ||
|
|
2ec479afd4 | ||
|
|
67682fe211 | ||
|
|
79f27a849c | ||
|
|
f607540f23 | ||
|
|
8609308cb4 | ||
|
|
28f1e671cb | ||
|
|
c411ce248e | ||
|
|
d283c6418e | ||
|
|
415a3cad7b | ||
|
|
36d2f72768 | ||
|
|
a64d92b005 | ||
|
|
172f4c142b | ||
|
|
4b55c7a8e0 | ||
|
|
7dbe39b1b5 | ||
|
|
6c2d2e142b | ||
|
|
2183599c8d | ||
|
|
cdbfec4f19 | ||
|
|
cb7354a19c | ||
|
|
3157ad79a5 | ||
|
|
02d619ed48 | ||
|
|
d97afa0e6d | ||
|
|
baade567ca | ||
|
|
39b9daa3a7 | ||
|
|
d8bb62c498 | ||
|
|
b45a0a979b | ||
|
|
861328af3e | ||
|
|
8bad9d8340 | ||
|
|
7f7efc5760 | ||
|
|
e43fc0feb0 | ||
|
|
e53e715861 | ||
|
|
32350bcf87 | ||
|
|
29b1b4dbc9 | ||
|
|
2c558a6a02 | ||
|
|
95876c271c | ||
|
|
ccff27ac23 | ||
|
|
148f6cb3c2 | ||
|
|
c9dbeec689 | ||
|
|
2b7c967920 | ||
|
|
94cdd4a481 | ||
|
|
296b6c646e | ||
|
|
ad491ccc8f | ||
|
|
ca7ebdcc8f | ||
|
|
efb4b2cb7d | ||
|
|
92403f2afe | ||
|
|
0e949679d9 | ||
|
|
1b8e4dfdfa | ||
|
|
afe8883e37 | ||
|
|
d5398e672f | ||
|
|
3252088494 | ||
|
|
fffacf3552 | ||
|
|
a19417417a | ||
|
|
4c1f2cfded | ||
|
|
a907041564 | ||
|
|
dff4552549 | ||
|
|
a4acdd1886 | ||
|
|
c1a1120137 | ||
|
|
32cd32649e | ||
|
|
678b6a285f | ||
|
|
de1a3de433 | ||
|
|
412564b418 | ||
|
|
c451c7bb9d | ||
|
|
be24989eab | ||
|
|
a439fb65ce | ||
|
|
c98635bca1 | ||
|
|
0d2b228eb7 | ||
|
|
c79d549f53 | ||
|
|
600f9ef071 | ||
|
|
04243be4a5 | ||
|
|
fc4e755f2b | ||
|
|
c28534555b | ||
|
|
380cba3a72 | ||
|
|
89a19dec5b | ||
|
|
f6305db2a8 | ||
|
|
197eff93e8 | ||
|
|
12cc5c6c97 | ||
|
|
cd47c0356a | ||
|
|
1c2a462124 | ||
|
|
329b1eb6f3 | ||
|
|
bcfb4e0f81 | ||
|
|
69011007ac | ||
|
|
0600b2abe4 | ||
|
|
13a092b192 | ||
|
|
10bf6c5e56 | ||
|
|
427e43585c | ||
|
|
667fabbdc5 | ||
|
|
8413a8eb3e | ||
|
|
f579bb0c8d | ||
|
|
a2b70f227c | ||
|
|
706714d557 | ||
|
|
399d57ace0 | ||
|
|
f2525f8159 | ||
|
|
0fece05cc9 | ||
|
|
13c7d06353 | ||
|
|
9593ded808 | ||
|
|
99adbbe91d | ||
|
|
6f1c2f474b | ||
|
|
0061b37c13 | ||
|
|
69bb4654c9 | ||
|
|
694d90d485 | ||
|
|
32746a5960 | ||
|
|
7c3f87d7b0 | ||
|
|
b4e4a5cab4 | ||
|
|
c12c9a4419 | ||
|
|
cc60cfc86d | ||
|
|
879c477ada | ||
|
|
0a72859424 | ||
|
|
6b7adec617 | ||
|
|
e7865b8643 | ||
|
|
461e5cb376 | ||
|
|
77a397de0c | ||
|
|
c656dd146c | ||
|
|
441e142349 | ||
|
|
54fd836dd4 | ||
|
|
7ffdf21657 | ||
|
|
8a6f1d82e5 | ||
|
|
87ebb2e24c | ||
|
|
9334138510 | ||
|
|
1b9dea01e2 | ||
|
|
ccb7c466bf | ||
|
|
c72be4ae2a | ||
|
|
fbd042d4ee | ||
|
|
bbf95434d8 | ||
|
|
2a46989ec9 | ||
|
|
baf9124304 | ||
|
|
c69d4820cb | ||
|
|
7d48714aa2 | ||
|
|
6565655ac3 | ||
|
|
d886889334 | ||
|
|
a95a7b9f90 | ||
|
|
3d381b92d9 | ||
|
|
08399ebac1 | ||
|
|
6a296a3e52 | ||
|
|
af03f720b0 | ||
|
|
5400fdb293 | ||
|
|
831839080f | ||
|
|
8b7310032b | ||
|
|
848f5125d8 | ||
|
|
9fd778f9c1 | ||
|
|
ce7852329a | ||
|
|
527e4643da | ||
|
|
e3616ea2b5 | ||
|
|
2a2b5c7dba | ||
|
|
93bbe1b2f8 | ||
|
|
dbd8c12ac0 | ||
|
|
ff0b031c8b | ||
|
|
9981bc7c9a | ||
|
|
3de217a52e | ||
|
|
d5bb486de1 | ||
|
|
afa6a97693 | ||
|
|
32756db1c1 | ||
|
|
efc1b87ab0 | ||
|
|
7b2f0303e8 | ||
|
|
4c5e8f42ce | ||
|
|
6e35b5c6b6 | ||
|
|
39041bb63b | ||
|
|
56efb571be | ||
|
|
c1affe75e1 | ||
|
|
cdaba395c4 | ||
|
|
e61e76a074 | ||
|
|
a2e26210d1 | ||
|
|
b5df7bbfc5 | ||
|
|
7375eed18f | ||
|
|
861eb283e8 | ||
|
|
c86d88834e | ||
|
|
7caf4b9136 | ||
|
|
4ecc166055 | ||
|
|
7f0054959f | ||
|
|
0274567d83 | ||
|
|
cebda20dd4 | ||
|
|
94602feab1 | ||
|
|
503a1dabac | ||
|
|
81d2f9dd9d | ||
|
|
4b61e3228f | ||
|
|
b8c90fdcf3 | ||
|
|
58fd20094a | ||
|
|
af098bb64d | ||
|
|
11f347941e | ||
|
|
c3ed46d3ab | ||
|
|
025cac0228 | ||
|
|
8bcb9e1976 | ||
|
|
bc890a0b33 | ||
|
|
8d9ed4f8af | ||
|
|
c01c46041d | ||
|
|
5050c35257 | ||
|
|
3c424786a7 | ||
|
|
1affb53a26 | ||
|
|
58dbe21544 | ||
|
|
6b1ecfd89c | ||
|
|
20738545b8 | ||
|
|
2cd8b65a5c | ||
|
|
8852ed815f | ||
|
|
fde03e21b0 | ||
|
|
5192b36669 | ||
|
|
b20d2badfe | ||
|
|
dfb73192b8 | ||
|
|
59ba87d9cd | ||
|
|
38ed3b076a | ||
|
|
f3472fcd79 | ||
|
|
3ef99c287e | ||
|
|
12e2d3ad96 | ||
|
|
0dc3dba428 | ||
|
|
efb0ec46bf | ||
|
|
aa9e125e31 | ||
|
|
16afa90b9c | ||
|
|
fa93e5a1a7 | ||
|
|
1298956d92 | ||
|
|
67b4d5a1c7 | ||
|
|
bfccae2373 | ||
|
|
5d9606f4d0 | ||
|
|
76333cec26 | ||
|
|
a42d7164ad | ||
|
|
c027de2592 | ||
|
|
ce99ca0aa8 | ||
|
|
751b99bf47 | ||
|
|
67fc499001 | ||
|
|
6713d8eb3f | ||
|
|
e36d611f19 | ||
|
|
111cf54ff6 | ||
|
|
1f73558f1b | ||
|
|
37ad04d2a6 | ||
|
|
6ad9a5aadb | ||
|
|
9c33dc529d | ||
|
|
82d72fd388 | ||
|
|
43ab19f690 | ||
|
|
dbe516f725 | ||
|
|
2af28fef80 | ||
|
|
0548fdb43d | ||
|
|
358d25680b | ||
|
|
57e7691e66 | ||
|
|
ee4f063889 | ||
|
|
38d74b93b3 | ||
|
|
99aea77355 | ||
|
|
8b988e0f1f | ||
|
|
4be9d58181 | ||
|
|
a85a65a554 | ||
|
|
2f423a9add | ||
|
|
abbdd13b5d | ||
|
|
b681e40af0 | ||
|
|
b669047f83 | ||
|
|
29ec7ca0c6 | ||
|
|
f276910ce3 | ||
|
|
508f3161b0 | ||
|
|
d663d2bebf | ||
|
|
c3fe8c8ebd | ||
|
|
21c9c205cb | ||
|
|
afe4250ea9 | ||
|
|
ed3d24bdb4 | ||
|
|
6eb85b2c8c | ||
|
|
2375f9ab83 | ||
|
|
8f325e4303 | ||
|
|
cc577a21db | ||
|
|
76b235e608 | ||
|
|
b98cf29134 | ||
|
|
cc06bb7755 | ||
|
|
bd1003e383 | ||
|
|
190d67a0cb | ||
|
|
627f497e7f | ||
|
|
f4a0b304a9 | ||
|
|
72f6905077 | ||
|
|
b191df0351 | ||
|
|
6ac08df63f | ||
|
|
821981e579 | ||
|
|
02382b95f6 | ||
|
|
a1c9503fea | ||
|
|
ab86d5238a | ||
|
|
76675e1949 | ||
|
|
66055a0b14 | ||
|
|
2ee15c3147 | ||
|
|
318563858b | ||
|
|
b8a83a3479 | ||
|
|
52239a9670 | ||
|
|
9fddc4611a | ||
|
|
a8b2f8868d | ||
|
|
e6b2c40441 | ||
|
|
490d295f3a | ||
|
|
e16da8bd2d | ||
|
|
f51e35aa9c | ||
|
|
6323c3ac92 | ||
|
|
59e6ef5609 | ||
|
|
eafb723415 | ||
|
|
5463671db1 | ||
|
|
c24596b7f9 | ||
|
|
47be9a21f4 | ||
|
|
4c133ec880 | ||
|
|
39c601a51f | ||
|
|
6c2c843f0a | ||
|
|
e227e49ea6 | ||
|
|
d53741b8fd | ||
|
|
666631a4bd | ||
|
|
0a529943a2 | ||
|
|
08a5550547 | ||
|
|
6894d90137 | ||
|
|
3f5ac58c73 | ||
|
|
cdb4524c45 | ||
|
|
3e21b0d8cc | ||
|
|
89a27e298d | ||
|
|
9df8935d48 | ||
|
|
fb3d6b04af | ||
|
|
66c086d4d3 | ||
|
|
5e55dddd87 | ||
|
|
bc0f0064ed | ||
|
|
ca8919dff0 | ||
|
|
5aeac28f36 | ||
|
|
a6113df552 | ||
|
|
f28b62cd3d | ||
|
|
8de1ae0478 | ||
|
|
4fe767c169 | ||
|
|
e50137d186 | ||
|
|
8e6b93e2a7 | ||
|
|
2befad433f | ||
|
|
96af4e26b0 | ||
|
|
3dc2c52f64 | ||
|
|
b2cbb1e60f | ||
|
|
c0eab96253 | ||
|
|
951b3eb4fe | ||
|
|
69f084e1df | ||
|
|
c4104c816b | ||
|
|
4ece0cdeda | ||
|
|
b1296ef765 | ||
|
|
5fe3842d1e | ||
|
|
d71c5e4105 | ||
|
|
8ad4dfe454 | ||
|
|
c23167a455 | ||
|
|
5a6b7800d7 | ||
|
|
3e118177d0 | ||
|
|
aaf645bad4 | ||
|
|
00e724ce09 | ||
|
|
8451444861 | ||
|
|
ef5bc687ab | ||
|
|
8463d501cd | ||
|
|
a59ca5b781 | ||
|
|
369dc8ffb5 | ||
|
|
04f8bbb1f2 | ||
|
|
10e0cf121b | ||
|
|
b23ece88c2 | ||
|
|
0765587373 | ||
|
|
5c8710b8cb | ||
|
|
88cd19d21a | ||
|
|
ac3251b29e | ||
|
|
a8150e1b05 | ||
|
|
e2f6274ff2 | ||
|
|
c670d81a20 | ||
|
|
a8e6516059 | ||
|
|
87d323bb4c | ||
|
|
4b52612682 | ||
|
|
948bda7cc8 | ||
|
|
8baaae1770 | ||
|
|
ea10ec22c2 | ||
|
|
160e0d218b | ||
|
|
7e70f0ce30 | ||
|
|
0618aa32a0 | ||
|
|
e1403d74bd | ||
|
|
41f5fb9621 | ||
|
|
563a6da83c | ||
|
|
3395fcb697 | ||
|
|
b7d5960ec3 | ||
|
|
c690a71b3e | ||
|
|
b5ab9af5c9 | ||
|
|
1e07c16633 | ||
|
|
9e6f12cb82 | ||
|
|
00180f4fba | ||
|
|
ea15735372 | ||
|
|
0f5ba91f44 | ||
|
|
13cb186c70 | ||
|
|
8b1e8408f2 | ||
|
|
796211c655 | ||
|
|
0afef0fa44 | ||
|
|
1dbaaf12fa | ||
|
|
adb19d0c83 | ||
|
|
d5d1cff420 | ||
|
|
5ef390f07e | ||
|
|
fca26f4022 | ||
|
|
5caaa2d593 | ||
|
|
c4c419b971 | ||
|
|
0f7295dd7c | ||
|
|
556c0d0c2a | ||
|
|
582a20d369 | ||
|
|
bcd9aa7ba7 | ||
|
|
cad2201c54 | ||
|
|
274e034033 | ||
|
|
dec9c339cd | ||
|
|
5423999913 | ||
|
|
670365acb7 | ||
|
|
9915990e10 | ||
|
|
748ab5f75e | ||
|
|
b995830693 | ||
|
|
d47d4c2d58 | ||
|
|
4b2b7278a7 | ||
|
|
85bd44e37b | ||
|
|
374909e05e | ||
|
|
ec19ec9280 | ||
|
|
fe371c088b | ||
|
|
e3f0c2eaeb | ||
|
|
919fb96b34 | ||
|
|
ac5412301e | ||
|
|
c0b778b67a | ||
|
|
1309b51320 | ||
|
|
9391cc9a41 | ||
|
|
cc1dff4d3d | ||
|
|
6e28bb9df8 | ||
|
|
c5ff785ff5 | ||
|
|
02c0c867d6 | ||
|
|
796fcee1d8 | ||
|
|
66cd60e02c | ||
|
|
da33d539bf | ||
|
|
80463536a8 | ||
|
|
5a06749664 | ||
|
|
5ad385cf93 | ||
|
|
c534a40923 | ||
|
|
348bc48db4 | ||
|
|
7fa44aa256 | ||
|
|
f1c3c41455 | ||
|
|
c925528212 | ||
|
|
fc44610893 | ||
|
|
ccb17e68e2 | ||
|
|
5bdc2cc25d | ||
|
|
f466d7a484 | ||
|
|
cbe51fcabd | ||
|
|
65da328b25 | ||
|
|
fadb1dfba6 | ||
|
|
eb7f93d2e6 | ||
|
|
c53152f027 | ||
|
|
953607fc4a | ||
|
|
50af997f55 | ||
|
|
fc01acffc7 | ||
|
|
687e4dce2a | ||
|
|
c5b875c925 | ||
|
|
a08b9adeee | ||
|
|
c2158b0f3c | ||
|
|
97c36ce86c | ||
|
|
b41ca75512 | ||
|
|
5bbfe376cf | ||
|
|
d468c74851 | ||
|
|
7d0f2d76e8 | ||
|
|
8b721d2024 | ||
|
|
3044d0abcc | ||
|
|
b2fd13e6bf | ||
|
|
c9ba5ff31e | ||
|
|
0c1d04919f | ||
|
|
746f492632 | ||
|
|
e30bea0b6f | ||
|
|
ac4218a3c2 | ||
|
|
0680d25fd7 | ||
|
|
4a3a181403 | ||
|
|
9ae40b392f | ||
|
|
6fc5813182 | ||
|
|
6e5ba88240 | ||
|
|
a6d9a65843 | ||
|
|
8fae7f7aa6 | ||
|
|
e9d3e8a643 | ||
|
|
8c20890c7b | ||
|
|
dc863e8b97 | ||
|
|
44241ada56 | ||
|
|
f9b7235f8b | ||
|
|
cc68eaa9f7 | ||
|
|
af640234b5 | ||
|
|
3c1ab1d58a | ||
|
|
243e29fdb4 | ||
|
|
a4bbb43555 | ||
|
|
16e8d1fcf2 | ||
|
|
0e49625ebf | ||
|
|
98f490703f | ||
|
|
fcbb95e8b6 | ||
|
|
0fc2442175 | ||
|
|
8c39c3af9f | ||
|
|
8f786407af | ||
|
|
7dcd362abd | ||
|
|
23d1087bc5 | ||
|
|
8d5a97f6e5 | ||
|
|
80f49e06cc | ||
|
|
4378d71b70 | ||
|
|
fd6d72128b | ||
|
|
b7206d734b | ||
|
|
886ab0e152 | ||
|
|
f470efc9c7 | ||
|
|
055a870c1f | ||
|
|
a59a4d9891 | ||
|
|
058727a44b | ||
|
|
2a55d2ebdb | ||
|
|
a6e14846c7 | ||
|
|
c2fec03fc7 | ||
|
|
43ceb6bb44 | ||
|
|
a5b36fd3f8 | ||
|
|
14788846a5 | ||
|
|
94a9bc844a | ||
|
|
745aa17d8a | ||
|
|
012315f207 | ||
|
|
ba37168a84 | ||
|
|
c68a6cbc10 | ||
|
|
41d5a490d4 | ||
|
|
6a329fac27 | ||
|
|
4ef876bf58 | ||
|
|
7303d311d5 | ||
|
|
35a72be4f2 | ||
|
|
53c358cfd7 | ||
|
|
c2ccdd5680 | ||
|
|
3a4563d755 | ||
|
|
ab22909b6c | ||
|
|
89e64236b0 | ||
|
|
748499a26f | ||
|
|
8fec5af55e | ||
|
|
84655c0fa3 | ||
|
|
1dc493c2d5 | ||
|
|
2753a934aa | ||
|
|
47363d96f1 | ||
|
|
b74631bf4a | ||
|
|
99a718e407 | ||
|
|
8bdfd188d8 | ||
|
|
278f6685b6 | ||
|
|
06bce92cdc | ||
|
|
757cee67fb | ||
|
|
37e2fe5c65 | ||
|
|
395a7096bf | ||
|
|
65afa2a833 | ||
|
|
041ecf67fe | ||
|
|
1a7583e6ad | ||
|
|
6ac1d47de1 | ||
|
|
f2de69e1f3 | ||
|
|
2030e845bb | ||
|
|
b1edc53a1c | ||
|
|
4d56b5f1b9 | ||
|
|
e1960b4472 | ||
|
|
dfae7d30a1 | ||
|
|
d8f1df0142 | ||
|
|
86993c0e21 | ||
|
|
a471d96b53 | ||
|
|
51307cdf8d | ||
|
|
c373b3741f | ||
|
|
bc55268a17 | ||
|
|
494b08b975 | ||
|
|
0bc24bb6eb | ||
|
|
1be1e94869 | ||
|
|
3ea3ca3bd9 | ||
|
|
6c09ecbef5 | ||
|
|
e888b06ec4 | ||
|
|
c1a4ae9d36 | ||
|
|
1a9fbee412 | ||
|
|
4909d6574e | ||
|
|
577db35777 | ||
|
|
f9187cd202 | ||
|
|
a868840132 | ||
|
|
33a8c47f6e | ||
|
|
e80ad112b8 | ||
|
|
07601975ac | ||
|
|
c709505733 | ||
|
|
c3f0657652 | ||
|
|
853b78613d | ||
|
|
0b4a1553b9 | ||
|
|
f67c4ddca0 | ||
|
|
bc693ad1bb | ||
|
|
fad2e51cbe | ||
|
|
61d1a3a77b | ||
|
|
d060ddaeae | ||
|
|
161a139194 | ||
|
|
407423b480 | ||
|
|
aadc3c25db | ||
|
|
26b32634f7 | ||
|
|
836511f5c7 | ||
|
|
043683775f | ||
|
|
f6792ce67f | ||
|
|
84760f940c | ||
|
|
4faa3db6f8 | ||
|
|
71f2e4cabe | ||
|
|
96ef9a3c52 | ||
|
|
83f734977f | ||
|
|
9d02bbcc1c | ||
|
|
f4264e47f0 | ||
|
|
0fa8f54ce4 | ||
|
|
920cb86849 | ||
|
|
d2d7803186 | ||
|
|
706bf86c95 | ||
|
|
fbaa19d405 | ||
|
|
38468d7584 | ||
|
|
a9e8f4eb67 | ||
|
|
944cfd0fc4 | ||
|
|
1ef4d42b28 | ||
|
|
441e9627b5 | ||
|
|
5d01a0e24c | ||
|
|
69ab9e9696 | ||
|
|
d5fea6100d | ||
|
|
47ba8383e8 | ||
|
|
48e6cc5a6b | ||
|
|
da5fabbc66 | ||
|
|
691a9fa877 | ||
|
|
b2d0f3cac2 | ||
|
|
2667e515f7 | ||
|
|
f1552e4091 | ||
|
|
79e35e2608 | ||
|
|
6e33d5b311 | ||
|
|
e1b62805e5 | ||
|
|
5cff6eb592 | ||
|
|
f3115f8f3a | ||
|
|
f7cfb5708f | ||
|
|
e75c9df17e | ||
|
|
dfc1b03a60 | ||
|
|
726baefa25 | ||
|
|
3063725a62 | ||
|
|
aed065eec1 | ||
|
|
4961991e18 | ||
|
|
199142045f | ||
|
|
f444d3d01d | ||
|
|
bea96cb586 | ||
|
|
cc18f84d62 | ||
|
|
ac75d0cc1b | ||
|
|
21683be07b | ||
|
|
5ac123dc4b | ||
|
|
ec53288b66 | ||
|
|
2348146f00 | ||
|
|
41134f22e9 | ||
|
|
9bfdcc6277 | ||
|
|
d3347a1be0 | ||
|
|
ef2918a115 | ||
|
|
92d3015d24 | ||
|
|
c4aba025c4 | ||
|
|
3aac620276 | ||
|
|
e2b39c0680 | ||
|
|
58d604a20a | ||
|
|
dc7e252972 | ||
|
|
4433c1136c | ||
|
|
0dbefcc401 | ||
|
|
051a65c346 | ||
|
|
449a6c9127 | ||
|
|
3c2ba92f6c | ||
|
|
58319d84ad | ||
|
|
87691499d7 | ||
|
|
e0112ac3a3 | ||
|
|
f72b94ac9b | ||
|
|
ee8b5cc1c5 | ||
|
|
c638ab459f | ||
|
|
345ae020d6 | ||
|
|
b6b800a8e2 | ||
|
|
eeb8d284cc | ||
|
|
00222499cc | ||
|
|
c6067ce336 | ||
|
|
6f42f4ec45 | ||
|
|
12b98c22bc | ||
|
|
435c627afd | ||
|
|
4de579f861 | ||
|
|
978b309b04 | ||
|
|
32b8c17dad | ||
|
|
c6e33fa9bc | ||
|
|
69c2d95768 | ||
|
|
80cfe6df9d | ||
|
|
29550add6c | ||
|
|
9a4ad38957 | ||
|
|
780b833a67 | ||
|
|
879041b0bc | ||
|
|
1019a037d8 | ||
|
|
9af04c8fbb | ||
|
|
9d63bc99bf | ||
|
|
516735cd0b | ||
|
|
4497d8842a | ||
|
|
48118a0ff4 | ||
|
|
be5a232994 | ||
|
|
843e1e91c2 | ||
|
|
ddf8aaf68f | ||
|
|
ffaf5d835d | ||
|
|
f94571b3b4 | ||
|
|
7832a80f82 | ||
|
|
16a0af802a | ||
|
|
9cb6e71258 | ||
|
|
c9c2f9e40f | ||
|
|
bdd487adc0 | ||
|
|
8e2ccfb4b0 | ||
|
|
6067498570 | ||
|
|
3cd9a3254d | ||
|
|
4af851d4c6 | ||
|
|
8fa49eada8 | ||
|
|
f921085c72 | ||
|
|
81a4c6b3f1 | ||
|
|
e58c943f41 | ||
|
|
c43e8bda3c | ||
|
|
028e0c5b70 | ||
|
|
108cdcecbb | ||
|
|
36f30c611e | ||
|
|
172a39c2e2 | ||
|
|
4027241bc0 | ||
|
|
f736ec813e | ||
|
|
7618fcade0 | ||
|
|
7599b7abc6 | ||
|
|
67cbfc631d | ||
|
|
dc6afb46bf | ||
|
|
f98512242a | ||
|
|
8b29767932 | ||
|
|
b5b042e6e4 | ||
|
|
8ac1dfce29 | ||
|
|
8e0e77fd3c | ||
|
|
5b92dca270 | ||
|
|
5454cabf98 | ||
|
|
e4e0deeb1c | ||
|
|
d47d687b43 | ||
|
|
0595d6b88d | ||
|
|
726f55bd04 | ||
|
|
f86f93deea | ||
|
|
e657c1bbfa | ||
|
|
b5e26fe615 | ||
|
|
73d2aad4db | ||
|
|
27aa20f00b | ||
|
|
643e58c61b | ||
|
|
f7aba14f76 | ||
|
|
19a9440f11 | ||
|
|
1e2d100c81 | ||
|
|
675a07bac6 | ||
|
|
21ec8bfdac | ||
|
|
de57300fe3 | ||
|
|
2da6732aba | ||
|
|
be18defcb1 | ||
|
|
52344fdb18 | ||
|
|
d632ca3114 | ||
|
|
c19237b45a | ||
|
|
c47f5ca186 | ||
|
|
fb8543c4e4 | ||
|
|
e0ac583aba | ||
|
|
5cd0079e7f | ||
|
|
00a7760c0f | ||
|
|
89732d911b | ||
|
|
c184ab58a3 | ||
|
|
182b572550 | ||
|
|
f394e8dba3 | ||
|
|
5bcf5ff4bc | ||
|
|
d41c2388c1 | ||
|
|
0155ef80b2 | ||
|
|
ad32512980 | ||
|
|
d082ff0a2b | ||
|
|
c0fc68b9f0 | ||
|
|
82032bedf5 | ||
|
|
915d4249a0 | ||
|
|
004334a7c8 | ||
|
|
97d5f48ab5 | ||
|
|
0155c6c5c4 | ||
|
|
45adc8a61d | ||
|
|
a555e13b6a | ||
|
|
f1b536034a | ||
|
|
6018df480e | ||
|
|
3a6876eeec | ||
|
|
7e4b7424a5 | ||
|
|
ce7eed5ea0 | ||
|
|
11018581ed | ||
|
|
3aa25e7a90 | ||
|
|
302c135d51 | ||
|
|
9c68432936 | ||
|
|
851092fc9e | ||
|
|
c69cb20be1 | ||
|
|
a1fccd46ff | ||
|
|
d75648e6b0 | ||
|
|
179a77eb05 | ||
|
|
352c044aad | ||
|
|
5503e371aa | ||
|
|
46053b6bbf | ||
|
|
251e7eada2 | ||
|
|
7e58e2f5eb | ||
|
|
04d6e76c6c | ||
|
|
ee1058950e | ||
|
|
be656bb4ef | ||
|
|
d6317297d7 | ||
|
|
5820f73b6e | ||
|
|
89e5607d7f | ||
|
|
2ecf33d7bb | ||
|
|
2f21560fe3 | ||
|
|
fd973d87fd | ||
|
|
ec3651d85b | ||
|
|
8f98b81829 | ||
|
|
469704def6 | ||
|
|
3cbb2defb3 | ||
|
|
47b745592b | ||
|
|
819492f453 | ||
|
|
83905c2f56 | ||
|
|
d6d9d25fce | ||
|
|
44f4d9c50c | ||
|
|
2cb8e7b986 | ||
|
|
8314e6c17b | ||
|
|
84d85edc0b | ||
|
|
1884a8abe6 | ||
|
|
c54354f143 | ||
|
|
29f1c8bb4d | ||
|
|
a301856c0d | ||
|
|
4e6a5ffa69 | ||
|
|
199541aeee | ||
|
|
0268e8594d | ||
|
|
9f1540cd00 | ||
|
|
28a721ce9c | ||
|
|
1e62b72769 | ||
|
|
ab94968648 | ||
|
|
4ba5be4450 | ||
|
|
1bb83b3019 | ||
|
|
d175c96871 | ||
|
|
24a56f029a | ||
|
|
a2d368636b | ||
|
|
ecfc20f514 | ||
|
|
c69bf9fd35 | ||
|
|
77118a3cc7 | ||
|
|
282941d284 | ||
|
|
d034b61318 | ||
|
|
02f3809b89 | ||
|
|
1c033fd9f6 | ||
|
|
40130d1ca7 | ||
|
|
5376c4aa23 | ||
|
|
3c759a46ec | ||
|
|
1675d945d9 | ||
|
|
12ba46642c | ||
|
|
acb9432f61 | ||
|
|
ff3b6fc0c8 | ||
|
|
60d8486f24 | ||
|
|
99050af903 | ||
|
|
c488efa515 | ||
|
|
396d35840e | ||
|
|
d1550ebb2a | ||
|
|
edc88458d3 | ||
|
|
517d47f016 | ||
|
|
70f6a6cecc | ||
|
|
07ce252d60 | ||
|
|
a055a31286 | ||
|
|
1b33b0dcef | ||
|
|
01d99222e0 | ||
|
|
85ec1668f3 | ||
|
|
a1654aeb0e | ||
|
|
00e9195af8 | ||
|
|
f7186fa781 | ||
|
|
2974125e8f | ||
|
|
e705a333a9 | ||
|
|
08ccd7be70 | ||
|
|
aa4344a76f | ||
|
|
581843f99b | ||
|
|
02efffceb4 | ||
|
|
44330ffb3b | ||
|
|
8a33a63f91 | ||
|
|
3c492f03d1 | ||
|
|
e8990caefb | ||
|
|
d2cd29bf76 | ||
|
|
c1942ef408 | ||
|
|
35c1008b37 | ||
|
|
b072c1d1d1 | ||
|
|
5a0ec9525b | ||
|
|
8ce8b3fdef | ||
|
|
f0e521b8d5 | ||
|
|
be1c19e718 | ||
|
|
1e9fb6e391 | ||
|
|
8dfd74547a | ||
|
|
cb064358f8 | ||
|
|
a6210be63a | ||
|
|
1fdd3b85ab | ||
|
|
8d8e4c5e22 | ||
|
|
595700f8b3 | ||
|
|
29223e8db8 | ||
|
|
4e319fd9ef | ||
|
|
c1348189d4 | ||
|
|
1b42e324a2 | ||
|
|
7af914eef0 | ||
|
|
1649ca4ff7 | ||
|
|
f9b44ccc5c | ||
|
|
b9f767a614 | ||
|
|
3e3a10b5dd | ||
|
|
082bcd2bde | ||
|
|
10bb75c1a1 | ||
|
|
a37c1f7fca | ||
|
|
50d371c14b | ||
|
|
48de242a2d | ||
|
|
9722e6ea97 | ||
|
|
f9502315ec | ||
|
|
dbd2b8527a | ||
|
|
eb797f37a7 | ||
|
|
36af303850 | ||
|
|
d16761772b | ||
|
|
b3d6b4b402 | ||
|
|
b6e17a0f09 | ||
|
|
7325322ebf | ||
|
|
a5975864fb | ||
|
|
4a655c863a | ||
|
|
9b642b6055 | ||
|
|
17c5eeb740 | ||
|
|
54f19564d4 | ||
|
|
c3219d1de5 | ||
|
|
f279e54f2f | ||
|
|
da18314e37 | ||
|
|
f689d2f84f | ||
|
|
a43f76bb3f | ||
|
|
0c717c579b | ||
|
|
bf63e9da95 | ||
|
|
7f7032aaa5 | ||
|
|
61cb43f2f0 | ||
|
|
de8d693292 | ||
|
|
5c5ec8ef56 | ||
|
|
9725dd5fff | ||
|
|
d6faf68dce | ||
|
|
9950c5dc0f | ||
|
|
756b6d4fbd | ||
|
|
8d06227d1e | ||
|
|
4ad27c3fca | ||
|
|
4cc88bf84f | ||
|
|
d8332e62d1 | ||
|
|
86389256a9 | ||
|
|
4f0cc3d0d8 | ||
|
|
95335e64b1 | ||
|
|
c219d1cc89 | ||
|
|
7fa609d5f4 | ||
|
|
95bb3f31af | ||
|
|
8d7baa75de | ||
|
|
5867e64d36 | ||
|
|
fee264007f | ||
|
|
df00760ffa | ||
|
|
ac8ef1d622 | ||
|
|
ec2a3c0de1 | ||
|
|
d533e37ae0 | ||
|
|
6ee6e5e23e | ||
|
|
7626d912b9 | ||
|
|
ada0938e27 | ||
|
|
918d2ab4a9 | ||
|
|
b3623ed14c | ||
|
|
94c3dfbfe8 | ||
|
|
f360958c66 | ||
|
|
c9885d757a | ||
|
|
4a054dec25 | ||
|
|
0b9546c541 | ||
|
|
0e513a4a25 | ||
|
|
a77f9eae7c | ||
|
|
678a163b01 | ||
|
|
3b57e7a583 | ||
|
|
dd73152afd | ||
|
|
99ed610dde | ||
|
|
b38f99b2f6 | ||
|
|
6df2089a60 | ||
|
|
b9b53258c1 | ||
|
|
5b5fe8ebbc | ||
|
|
edb46b2080 | ||
|
|
2d24529165 | ||
|
|
0a939185d2 | ||
|
|
2211fea976 | ||
|
|
b404246f8a | ||
|
|
6f415cc046 | ||
|
|
84ee6555a3 | ||
|
|
0471df36ef | ||
|
|
37f5b41486 | ||
|
|
29ff06dc6a | ||
|
|
42dd38b4ee | ||
|
|
5791ddda49 | ||
|
|
36def65c87 | ||
|
|
763877b713 | ||
|
|
b560c07243 | ||
|
|
e6dcfec90c | ||
|
|
58a06b8cf3 | ||
|
|
c30d4d313c | ||
|
|
183e0bf985 | ||
|
|
aceefc0485 | ||
|
|
0b3d25a890 | ||
|
|
173f0d68bb | ||
|
|
61f2799e49 | ||
|
|
7611aec4c6 | ||
|
|
adbadc8743 | ||
|
|
6d61f75db6 | ||
|
|
efa382c906 | ||
|
|
a54e0900d0 | ||
|
|
e1972692ab | ||
|
|
9ffd00d821 | ||
|
|
33706e0bda | ||
|
|
57ec9f8218 | ||
|
|
e863ef7dbf | ||
|
|
390ad34b13 | ||
|
|
29fa36ad2d | ||
|
|
fe7c01323a | ||
|
|
5febd35cfe | ||
|
|
1df9f0b29e | ||
|
|
74c6556ad6 | ||
|
|
d270c9670e | ||
|
|
b926409fa2 | ||
|
|
87419d63a5 | ||
|
|
53d7c4332d | ||
|
|
6981d92b11 | ||
|
|
052404b1b4 | ||
|
|
b8b60d9208 | ||
|
|
ed701fd9c5 | ||
|
|
d832482dae | ||
|
|
fd08511514 | ||
|
|
812131fdbc | ||
|
|
52cc91f4c4 | ||
|
|
f455580cf7 | ||
|
|
15d7b94940 | ||
|
|
d30b6ac5b9 | ||
|
|
39fb391128 | ||
|
|
77b1ebfcc6 | ||
|
|
f5df0eacef | ||
|
|
fdc01cfed5 | ||
|
|
0cc51db533 | ||
|
|
4266d9be83 | ||
|
|
8795e134c1 | ||
|
|
732755066e | ||
|
|
424e9faa8e | ||
|
|
8fe98b1f7a | ||
|
|
5c6212d7a2 | ||
|
|
ed5ce777b9 | ||
|
|
d477f74d13 | ||
|
|
fbfcb827ed | ||
|
|
b4d5ff3452 | ||
|
|
4c03450b88 | ||
|
|
10e0a662e4 | ||
|
|
c62427501e | ||
|
|
64217b34ca | ||
|
|
140731cf34 | ||
|
|
39ae2ed98d | ||
|
|
5336db4456 | ||
|
|
f301ec5d2f | ||
|
|
6237829445 | ||
|
|
abfca5c89a | ||
|
|
d54ebaa0d7 | ||
|
|
5e57fb4023 | ||
|
|
c1daa4a4c4 | ||
|
|
ddc7f412a4 | ||
|
|
f25222e441 | ||
|
|
ae20a06e97 | ||
|
|
18970cb233 | ||
|
|
5cd12ac710 | ||
|
|
91baae3580 | ||
|
|
a6ee1617ab | ||
|
|
01306841a9 | ||
|
|
1c446a011e | ||
|
|
0aa60b22b0 | ||
|
|
bcc7be16ad | ||
|
|
f4482eb5a7 | ||
|
|
0667089833 | ||
|
|
c108974ad2 | ||
|
|
dd5a6f7f50 | ||
|
|
a5bf1c03e7 | ||
|
|
1ef37281e6 | ||
|
|
eebd596fca | ||
|
|
dcf18b3aee | ||
|
|
43e0b5cfa5 | ||
|
|
7da159d52a | ||
|
|
54e0071c9c | ||
|
|
165cdd871f | ||
|
|
ce09ea6eb5 | ||
|
|
bdcbe46d0d | ||
|
|
5dc7bc213f | ||
|
|
758d0d8943 | ||
|
|
f8fbb7abba | ||
|
|
c6f74692ba | ||
|
|
98402ae1db | ||
|
|
902c746dbb | ||
|
|
26fd1a261c | ||
|
|
b93b8a8966 | ||
|
|
4e2dbdbebe | ||
|
|
8379cc3625 | ||
|
|
aa95114860 | ||
|
|
d084f17430 | ||
|
|
e3f878ef2f | ||
|
|
7a3f1a36e9 | ||
|
|
b3415d0d52 | ||
|
|
10f8d1365c | ||
|
|
a48db277b9 | ||
|
|
9263f70d6a | ||
|
|
d2aa985714 | ||
|
|
b5796b4cdb | ||
|
|
c3f67e6358 | ||
|
|
1477837cbf | ||
|
|
5834e29b39 | ||
|
|
1fa25060a0 | ||
|
|
c354c560d4 | ||
|
|
4b2729b041 | ||
|
|
5b658c2f8a | ||
|
|
05a86581a5 | ||
|
|
8237805cf5 | ||
|
|
8fd908a59f | ||
|
|
07eab923f0 | ||
|
|
2e077e3ea9 | ||
|
|
99dea51eea | ||
|
|
e7ae8f5c58 | ||
|
|
ee51e8c719 | ||
|
|
b4ad907c73 | ||
|
|
8285cb8f62 | ||
|
|
7f611c89e1 | ||
|
|
00b6d76164 | ||
|
|
333e1d32a2 | ||
|
|
58f93d2177 | ||
|
|
08c0070f22 | ||
|
|
6408689d4c | ||
|
|
bfe54fe5e1 | ||
|
|
14c28ccce7 | ||
|
|
dece149c9e | ||
|
|
9275f5e5ce | ||
|
|
483da5248f | ||
|
|
4bf05c8a42 | ||
|
|
1eede8442d | ||
|
|
3778bb4b1d | ||
|
|
1174502cb8 | ||
|
|
cd8578480f | ||
|
|
e5ebe0a295 | ||
|
|
d3dd2644ae | ||
|
|
d2a5344407 | ||
|
|
48615ca5b2 | ||
|
|
b49348ff86 | ||
|
|
d9cc76f8ba | ||
|
|
f89ccac567 | ||
|
|
b57ddf9dca | ||
|
|
8e9ab32a9f | ||
|
|
fdbcf977f5 | ||
|
|
cc6b3dcec6 | ||
|
|
7abbcdf226 | ||
|
|
4088e55c9f | ||
|
|
54d9b02b4d | ||
|
|
b2da41720e | ||
|
|
2b70331630 | ||
|
|
05c8ad8bf9 | ||
|
|
18ca2aca15 | ||
|
|
51023396bc | ||
|
|
3e7b9805c9 | ||
|
|
be0c810c5f | ||
|
|
dd180d93f4 | ||
|
|
3ac1760141 | ||
|
|
28abe785e8 | ||
|
|
5e5355230c | ||
|
|
6e5a23c190 | ||
|
|
84c0825893 | ||
|
|
51e8eea795 | ||
|
|
7176bb6f1a | ||
|
|
1c8aef6fa8 | ||
|
|
aeb8fa1896 | ||
|
|
3c3664535e | ||
|
|
a958aed058 | ||
|
|
e662a7090f | ||
|
|
2e2b05a7a4 | ||
|
|
4e5146c210 | ||
|
|
4bac2f15a2 | ||
|
|
1c09328d0e | ||
|
|
06905d5fa6 | ||
|
|
46c9fc1c5f | ||
|
|
b901a10aaa | ||
|
|
9022520334 | ||
|
|
c4bdb84d70 | ||
|
|
edad00ad95 | ||
|
|
8ac32fc3c2 | ||
|
|
641ebf8b8e | ||
|
|
8c84cc7fa0 | ||
|
|
40415bb0d8 | ||
|
|
f2bd6a552f | ||
|
|
62bb3d9087 | ||
|
|
374f52a819 | ||
|
|
d140f15f37 | ||
|
|
f32bb56b95 | ||
|
|
37e9adc6b6 | ||
|
|
602cead4ae | ||
|
|
aadb7cb1bf | ||
|
|
d60f849089 | ||
|
|
98190ed92d | ||
|
|
c02e8ff883 | ||
|
|
4d55ed4860 | ||
|
|
5e2c1fb4cd | ||
|
|
f9447029f3 | ||
|
|
2a9c8eb9af | ||
|
|
cdcf3facd2 | ||
|
|
5c696851bf | ||
|
|
c8051633d9 | ||
|
|
17645bb2a7 | ||
|
|
2fe770e0bb | ||
|
|
d032953121 | ||
|
|
f4f58bc163 | ||
|
|
d90b4370fb | ||
|
|
ade41f77f3 | ||
|
|
c405e9a7a3 | ||
|
|
50f30eb12f | ||
|
|
6b8ab7aa72 | ||
|
|
0dac3f7845 | ||
|
|
537fff4c80 | ||
|
|
dd130615a1 | ||
|
|
356ff4683d | ||
|
|
70028e1371 | ||
|
|
a3580a5ab9 | ||
|
|
6bb5fb0361 | ||
|
|
f2140a1421 | ||
|
|
f7f9ba99f7 | ||
|
|
14d1cb90bd | ||
|
|
11f7efb850 | ||
|
|
a16606c8e3 | ||
|
|
7fe7b234bf | ||
|
|
ba4f558f62 | ||
|
|
8446df2056 | ||
|
|
8f22c421de | ||
|
|
2c0725a9d2 | ||
|
|
288dab3fe7 | ||
|
|
672c52b369 | ||
|
|
e458e3adb7 | ||
|
|
b38ffdcf30 | ||
|
|
09a3cf4b58 | ||
|
|
7406aac0c7 | ||
|
|
e44fb2cdbf | ||
|
|
bfb0ba47f5 | ||
|
|
9c194ee3cb | ||
|
|
ebe8fdcba8 | ||
|
|
d021ac0226 | ||
|
|
7256bdbcd5 | ||
|
|
27d81ee47d | ||
|
|
be304811d5 | ||
|
|
bd4548cd25 | ||
|
|
cbc5811290 | ||
|
|
935639411c | ||
|
|
6de78cabd4 | ||
|
|
73f1418c95 | ||
|
|
cf2de3cfac | ||
|
|
481c45ee60 | ||
|
|
716b0639f2 | ||
|
|
ced3830d7a | ||
|
|
115314e97c | ||
|
|
d2250274f2 | ||
|
|
0f04398e61 | ||
|
|
72979e4535 | ||
|
|
a271a285ad | ||
|
|
b68407a6c0 | ||
|
|
5136eef4bc | ||
|
|
f132651175 | ||
|
|
6f94745aed | ||
|
|
7052f64547 | ||
|
|
29220cd0d3 | ||
|
|
ec55d64454 | ||
|
|
e4eb8004e2 | ||
|
|
b1e6a8b1e9 | ||
|
|
da2214379c | ||
|
|
4d19ceff8d | ||
|
|
b944d977bb | ||
|
|
07881eed65 | ||
|
|
f2862b6c16 | ||
|
|
ccae7cc2d4 | ||
|
|
c6de41421e | ||
|
|
fa06da36ac | ||
|
|
03c019ded0 | ||
|
|
248ab953b2 | ||
|
|
14754aae05 | ||
|
|
dc7464220d | ||
|
|
7396410267 | ||
|
|
9bd3cba58c | ||
|
|
b08b1a546a | ||
|
|
639eaa2458 | ||
|
|
ab1405b79c | ||
|
|
ce14acac2c | ||
|
|
826bd29327 | ||
|
|
5151a7bd49 | ||
|
|
0ad0a65fa9 | ||
|
|
10a33fb102 | ||
|
|
b0c0c6ed43 | ||
|
|
e31fbb5c5f | ||
|
|
e2bdf1a155 | ||
|
|
5e2ff2cf6f | ||
|
|
c211e84498 | ||
|
|
75dc9e64a7 | ||
|
|
69810750c5 | ||
|
|
4549281b6c | ||
|
|
90532b760a | ||
|
|
eb190296d7 | ||
|
|
46d075611d | ||
|
|
ebcb385593 | ||
|
|
8e60834292 | ||
|
|
6469a543ba | ||
|
|
666b9fa4d4 | ||
|
|
137c10f631 | ||
|
|
ac1167d0c9 | ||
|
|
e1d6cded62 | ||
|
|
53df0f7585 | ||
|
|
95829ff3de | ||
|
|
6d4e898f79 | ||
|
|
2bed06de64 | ||
|
|
a08c1b1278 | ||
|
|
3053e867cb | ||
|
|
3a55f07f45 | ||
|
|
408f73396f | ||
|
|
7cdbadc5b7 | ||
|
|
fb1dbd6f31 | ||
|
|
9dabe2959f | ||
|
|
2d61497159 | ||
|
|
c582ae667b | ||
|
|
529fb350fa | ||
|
|
e638475a67 | ||
|
|
1bde183c50 | ||
|
|
45b690ed05 | ||
|
|
2799c09294 | ||
|
|
a774f4d4fa | ||
|
|
2e3f443758 | ||
|
|
e0a1da6bca | ||
|
|
108291337d | ||
|
|
ca326ac231 | ||
|
|
566dde21a5 | ||
|
|
cab2b8469e | ||
|
|
a37233be1e | ||
|
|
b4e218c13a | ||
|
|
9bd382f833 | ||
|
|
a4cc57886b | ||
|
|
0bb0063be4 | ||
|
|
79a46efa35 | ||
|
|
c8ad379bf8 | ||
|
|
8c5cc446b0 | ||
|
|
688ec2589a | ||
|
|
aa584e6d35 | ||
|
|
a9303c37c4 | ||
|
|
0bbd898173 | ||
|
|
ae468ecdf2 | ||
|
|
0654741e28 | ||
|
|
c60c04f167 | ||
|
|
8f7fd21454 | ||
|
|
24d23d9e5a | ||
|
|
66cec51c44 | ||
|
|
65b6d1e07b | ||
|
|
adf2d82a52 | ||
|
|
dce479bc4b | ||
|
|
199360efa6 | ||
|
|
943fa880a7 | ||
|
|
5e2a7e76f3 | ||
|
|
e0bd3425bc | ||
|
|
963df4b44f | ||
|
|
32b2b46df7 | ||
|
|
667598a0eb | ||
|
|
58a1060ed8 | ||
|
|
b3f8d98c34 | ||
|
|
20f357d75d | ||
|
|
310c322883 | ||
|
|
9ae5528355 | ||
|
|
e7e231b719 | ||
|
|
69cb626cab | ||
|
|
7f9d070692 | ||
|
|
206ffcc6e8 | ||
|
|
6b5ee24010 | ||
|
|
2132bad898 | ||
|
|
189d33221e | ||
|
|
5870d25bec | ||
|
|
6190ce9b35 | ||
|
|
65753cdc17 | ||
|
|
1174590af4 | ||
|
|
e5cb5860a8 | ||
|
|
65e99cabbf | ||
|
|
97bf20dd4c | ||
|
|
a3fd5d6516 | ||
|
|
c26273c9b3 | ||
|
|
7e1a771e24 | ||
|
|
fa7b0d3b35 | ||
|
|
85f2804af8 | ||
|
|
d9420081c4 | ||
|
|
424dd5c41a | ||
|
|
97f5f54d1c | ||
|
|
e6b80bf73e | ||
|
|
bc3914e7e0 | ||
|
|
e6b1c578d4 | ||
|
|
801cdd940a | ||
|
|
28b6175943 | ||
|
|
ba85dcf1a3 | ||
|
|
c3486c566a | ||
|
|
437e352bf4 | ||
|
|
71501d966c | ||
|
|
288e6e1ea1 | ||
|
|
8bb2f20eae | ||
|
|
a8c3ac630d | ||
|
|
0fcd414792 | ||
|
|
da6675c91e | ||
|
|
9eba666c31 | ||
|
|
1764d965c1 | ||
|
|
a120630a7f | ||
|
|
f33ad5e8fa | ||
|
|
f04859f8a6 | ||
|
|
31aed2aaa4 | ||
|
|
18109b2387 | ||
|
|
a0cc8a06b6 | ||
|
|
3b26ec6b8c | ||
|
|
71ce0b66e0 | ||
|
|
4c1903b4e8 | ||
|
|
462ec0c12a | ||
|
|
2b61b1768f | ||
|
|
424630a67f | ||
|
|
14b1970a8a | ||
|
|
541168aee4 | ||
|
|
6e9a77edcd | ||
|
|
8d1cd2f56d | ||
|
|
65cda41245 | ||
|
|
c029948cce | ||
|
|
32540f1ba5 | ||
|
|
5d20815776 | ||
|
|
0b149dd302 | ||
|
|
662fc073df | ||
|
|
46be4ca6d1 | ||
|
|
784365f45c | ||
|
|
23d3e88214 | ||
|
|
c356ae6de8 | ||
|
|
c5e872b81d | ||
|
|
0307e58fbe | ||
|
|
5c14c3fafc | ||
|
|
321c3fb34b | ||
|
|
4764674374 | ||
|
|
0416988913 | ||
|
|
72251f57b1 | ||
|
|
05aee3507a | ||
|
|
f651b7585d | ||
|
|
68e603a86c | ||
|
|
52adf7eaf1 | ||
|
|
ec884787f1 | ||
|
|
c08ad5c8c0 | ||
|
|
3d8c41cd69 | ||
|
|
2ce766c49e | ||
|
|
bb18a69394 | ||
|
|
96ed66d86e | ||
|
|
be7733a2cf | ||
|
|
57ccb18517 | ||
|
|
d5df465992 | ||
|
|
ea6c34f6b2 | ||
|
|
36390be72a | ||
|
|
3c41693787 | ||
|
|
b25806b172 | ||
|
|
0828d43f8f | ||
|
|
402212c808 | ||
|
|
8d51e32c5a | ||
|
|
11b2144274 | ||
|
|
8c51d1ac95 | ||
|
|
64e2fa9e2f | ||
|
|
216dbc8e0d | ||
|
|
fa5b0ed6ac | ||
|
|
67b81fbe67 | ||
|
|
89f485a674 | ||
|
|
fcafe66bd8 | ||
|
|
931759f468 | ||
|
|
f33360a22b | ||
|
|
910fb55b69 | ||
|
|
18849307e9 | ||
|
|
0f2b2d4590 | ||
|
|
68e38271fb | ||
|
|
066d53b81b | ||
|
|
ef37abcbbd | ||
|
|
02427285ef | ||
|
|
38bc3b061a | ||
|
|
047b3f0987 | ||
|
|
6a8f3c7283 | ||
|
|
525da266b8 | ||
|
|
97c9035cfd | ||
|
|
35681c3af8 | ||
|
|
8a6f01404c | ||
|
|
6901431f8a | ||
|
|
2261bde6f1 | ||
|
|
40e1d5a2a1 | ||
|
|
ffbc098af8 | ||
|
|
d52c4541ae | ||
|
|
e8f61df710 | ||
|
|
3604d0cfc9 | ||
|
|
b0c3b38cc5 | ||
|
|
393d959289 | ||
|
|
494e2df49f | ||
|
|
a453f1a648 | ||
|
|
dcac6d9ea4 | ||
|
|
cdd6801360 | ||
|
|
f5128d8d43 | ||
|
|
4c2182dd0b | ||
|
|
cca5ddb81a | ||
|
|
c83affe351 | ||
|
|
51a9b10d51 | ||
|
|
28e2d64ac6 | ||
|
|
0fc2c312d5 | ||
|
|
6eb24bd1b7 | ||
|
|
79467b7b72 | ||
|
|
e14c6e5a6f | ||
|
|
2be432e1d4 | ||
|
|
eb07350cac | ||
|
|
ba139d7d2c | ||
|
|
235d3dbf3d | ||
|
|
426758d9b2 | ||
|
|
542fb9c754 | ||
|
|
13492f5ac7 | ||
|
|
43d3b06c30 | ||
|
|
d8a7402046 | ||
|
|
93b582c385 | ||
|
|
d45bb0ace1 | ||
|
|
25ff15c62e | ||
|
|
30bcdda90e | ||
|
|
e22ef536ed | ||
|
|
b5e696c6b4 | ||
|
|
2b1e126ff8 | ||
|
|
1690f1ee23 | ||
|
|
6a74f29f96 | ||
|
|
d666755159 | ||
|
|
fa00d674eb | ||
|
|
7c23b7ea79 | ||
|
|
919ca68a77 | ||
|
|
684805067a | ||
|
|
db7761b742 | ||
|
|
29010453e6 | ||
|
|
a8cc9ace72 | ||
|
|
9ab922a0fa | ||
|
|
c9dadce12a | ||
|
|
eabfa7a541 | ||
|
|
95a2da5ebc | ||
|
|
180c355340 | ||
|
|
01664a04fc | ||
|
|
edce45095e | ||
|
|
5a07599fc7 | ||
|
|
d684970bfb | ||
|
|
216b510900 | ||
|
|
5c2b5f7cda | ||
|
|
712c68fc77 | ||
|
|
f290465edd | ||
|
|
141bcdd25e | ||
|
|
f68a4eb84a | ||
|
|
a240fbdf5b | ||
|
|
799bb87398 | ||
|
|
2b5282025c | ||
|
|
08beb57ff1 | ||
|
|
a2de5f8fb4 | ||
|
|
080139cd56 | ||
|
|
570f038062 | ||
|
|
ae84f3cbe8 | ||
|
|
abdc9b2cbd | ||
|
|
92d7521ec7 | ||
|
|
4730273ad3 | ||
|
|
a3935953f7 | ||
|
|
ea50622bf7 | ||
|
|
4b0b7463c7 | ||
|
|
95d4018074 | ||
|
|
3f413e4920 | ||
|
|
db8e829339 | ||
|
|
448e0dd415 | ||
|
|
15418a252e | ||
|
|
21d845d254 | ||
|
|
c84017eb72 | ||
|
|
431e42c80a | ||
|
|
ca2eb1ac12 | ||
|
|
d2983c1110 | ||
|
|
74612178d7 | ||
|
|
af519b3f89 | ||
|
|
d8d4ce7a46 | ||
|
|
3930be5d9e | ||
|
|
d85a4d6539 | ||
|
|
7446fe77b3 | ||
|
|
8b1f8d1418 | ||
|
|
d387ca81d8 | ||
|
|
b7b5f3b4c2 | ||
|
|
698dd872e4 | ||
|
|
767f0fe16b | ||
|
|
a19c56c0ce | ||
|
|
b9e984300c | ||
|
|
0727757eb1 | ||
|
|
50037a6a88 | ||
|
|
5bdea086e9 | ||
|
|
fef69cb707 | ||
|
|
20211101b7 | ||
|
|
ce41a38098 | ||
|
|
c14ece9f8d | ||
|
|
f2bb59fd77 | ||
|
|
af6a687187 | ||
|
|
40de8781ef | ||
|
|
33e776fefe | ||
|
|
efcabe7ffb | ||
|
|
77e9b8aa70 | ||
|
|
238cd14eb8 | ||
|
|
22193635d6 | ||
|
|
8432e970cb | ||
|
|
55df28d5dc | ||
|
|
33882f44ef | ||
|
|
accba56b89 | ||
|
|
c06042c91b | ||
|
|
2976c5c572 | ||
|
|
8df93c2707 | ||
|
|
0c26dad3b2 | ||
|
|
8d399cb562 | ||
|
|
82d744b94a | ||
|
|
94d3f66ef1 | ||
|
|
40a38cbd38 | ||
|
|
644c796772 | ||
|
|
81dac233a7 | ||
|
|
6bbd76f350 | ||
|
|
3a6072bc8f | ||
|
|
0bcf3d99a0 | ||
|
|
8cd7f61150 | ||
|
|
96aa756eb6 | ||
|
|
c5ba399bcd | ||
|
|
fb879660d0 | ||
|
|
4cdf8cec4e | ||
|
|
d9a9eb3729 | ||
|
|
8298d460e6 | ||
|
|
462eabd8a1 | ||
|
|
afa1af6dc2 | ||
|
|
37fdf51eaf | ||
|
|
1102bc9cba | ||
|
|
18afb701fb | ||
|
|
15a26d10f0 | ||
|
|
9b8b6134c5 | ||
|
|
7e05b7e6d9 | ||
|
|
b86ea5b5e2 | ||
|
|
1729fe7312 | ||
|
|
66f7d35510 | ||
|
|
8fb22b8eee | ||
|
|
5b37c11221 | ||
|
|
1723ff1da5 | ||
|
|
9099403421 | ||
|
|
baf3f7ea15 | ||
|
|
1d39bbaa3c | ||
|
|
0db2f87e3e | ||
|
|
430ccda02c | ||
|
|
fe6e62482a | ||
|
|
82185794a8 | ||
|
|
053975ef82 | ||
|
|
7185199d05 | ||
|
|
bd7ea210f5 | ||
|
|
9cacca4802 | ||
|
|
9fab2fc24a | ||
|
|
9dcad7ebef | ||
|
|
4363e32aae | ||
|
|
39e4651374 | ||
|
|
fe1ae7dbb4 | ||
|
|
28fc7178f1 | ||
|
|
39b0de1ced | ||
|
|
2f0e85f619 | ||
|
|
151de33586 | ||
|
|
4d106d9e15 | ||
|
|
e5ccf36c07 | ||
|
|
d92df31b3e | ||
|
|
8b3062be0b | ||
|
|
c7e23c1b58 | ||
|
|
9923268589 | ||
|
|
a8103cbc3e | ||
|
|
26a074768f | ||
|
|
1c87195fa6 | ||
|
|
2a1ca07554 | ||
|
|
c3be87ed30 | ||
|
|
0afbf02443 | ||
|
|
eb143c8399 | ||
|
|
85f1cb47a7 | ||
|
|
609ce1c106 | ||
|
|
f7dbb4f944 | ||
|
|
5b2d1b310a | ||
|
|
a7ded66eab | ||
|
|
74d195c745 | ||
|
|
1705954b07 | ||
|
|
71bb34efc5 | ||
|
|
32d61eaf70 | ||
|
|
20badb7676 | ||
|
|
b90a308d66 | ||
|
|
dbfa0e7a4b | ||
|
|
95c73585d2 | ||
|
|
c4939c152d | ||
|
|
7560e32911 | ||
|
|
d50299bdbb | ||
|
|
c34c1c4375 | ||
|
|
b62f387ff4 | ||
|
|
b3847cafa8 | ||
|
|
d28b4092d9 | ||
|
|
658e3b7aee | ||
|
|
d18c96360f | ||
|
|
c83bb70074 | ||
|
|
02157cbeb9 | ||
|
|
7581230b6e | ||
|
|
049f4ce784 | ||
|
|
c01e4e52f8 | ||
|
|
3ab3ea68b4 | ||
|
|
41948ff86b | ||
|
|
01ca538c72 | ||
|
|
2b9badfd4e | ||
|
|
6ad591eb23 | ||
|
|
581c50b5ff | ||
|
|
a18e067d7a | ||
|
|
036fe44471 | ||
|
|
b008835d43 | ||
|
|
fc95443cc4 | ||
|
|
9492dd7856 | ||
|
|
b239a9b09e | ||
|
|
e0aeb3b5ac | ||
|
|
58cfd61997 | ||
|
|
a82bcd0ab2 | ||
|
|
dfc9d0709d | ||
|
|
b7d33041e8 | ||
|
|
f945a6e649 | ||
|
|
6a3a460203 | ||
|
|
b576ef02af | ||
|
|
814042909a | ||
|
|
9856da4a1f | ||
|
|
2061018742 | ||
|
|
202e7eb3f2 | ||
|
|
38deacdf31 | ||
|
|
c809890cfd | ||
|
|
224d466122 | ||
|
|
08c6e9b702 | ||
|
|
9e940dc042 | ||
|
|
6fda156164 | ||
|
|
5eb53da374 | ||
|
|
68e0b3e756 | ||
|
|
cfe374b08c | ||
|
|
cc046555a3 | ||
|
|
31ec4092ed | ||
|
|
d9d47b2c65 | ||
|
|
506f63317a | ||
|
|
d658145450 | ||
|
|
b2d13f277a | ||
|
|
59310cdd71 | ||
|
|
121b5af5d0 | ||
|
|
1d69cb2580 | ||
|
|
e68689aa4f | ||
|
|
989ff8db7a | ||
|
|
b68fdee946 | ||
|
|
c8d3975680 | ||
|
|
b6f2800aa3 | ||
|
|
a579ea3c25 | ||
|
|
7b3ab2287a | ||
|
|
81df2ca355 | ||
|
|
b78d9dcc52 | ||
|
|
caa81b4fe2 | ||
|
|
b9ab00c549 | ||
|
|
2707903f8a | ||
|
|
28031a247a | ||
|
|
56cdd1ffeb | ||
|
|
175f4b57f5 | ||
|
|
2ae2877f45 | ||
|
|
5e7a609b3d | ||
|
|
9ffe406d0d | ||
|
|
adfc0902a2 | ||
|
|
620efcb5cb | ||
|
|
0ed23f94c7 | ||
|
|
1cac7d55d0 | ||
|
|
c9937f6b91 | ||
|
|
875fd78f73 | ||
|
|
7e37aca5ee | ||
|
|
070886bbf6 | ||
|
|
c00168b61d | ||
|
|
0e9119d603 | ||
|
|
df39e9baf4 | ||
|
|
668aca725c | ||
|
|
c865082a6a | ||
|
|
82ae4e60f8 | ||
|
|
5fc27a7594 | ||
|
|
6ad06d9665 | ||
|
|
c766e08027 | ||
|
|
62f55a47c5 | ||
|
|
b1edcba0c2 | ||
|
|
f7d2f6ec51 | ||
|
|
3a95a1cea1 | ||
|
|
4143573868 | ||
|
|
26daf507b3 | ||
|
|
f2c0683803 | ||
|
|
395f23dec8 | ||
|
|
58905f0b99 | ||
|
|
aa2bb75f95 | ||
|
|
004fddfcf4 | ||
|
|
a61301c698 | ||
|
|
b2607b28ff | ||
|
|
c2c01831fb | ||
|
|
bf70719899 | ||
|
|
ea38d12a73 | ||
|
|
76abd6796e | ||
|
|
0bb20197f1 | ||
|
|
2af057a79f | ||
|
|
fd9b442075 | ||
|
|
5edbebcfec | ||
|
|
e62f0603b5 | ||
|
|
654e12a2c3 | ||
|
|
5299465864 | ||
|
|
18855ef2ef | ||
|
|
39fa939f58 | ||
|
|
4adc5d25a7 | ||
|
|
7a38b08506 | ||
|
|
df4b92fb6b | ||
|
|
ca02999ae9 | ||
|
|
701a98fab6 | ||
|
|
c026d05bc3 | ||
|
|
602b736163 | ||
|
|
c5b1b67c50 | ||
|
|
8eae892983 | ||
|
|
7d32d03156 | ||
|
|
f9e83f2cc7 | ||
|
|
20d3251a93 | ||
|
|
147f56749e | ||
|
|
9140fc71b9 | ||
|
|
d6abd2202c | ||
|
|
911d4edb9f | ||
|
|
e9e5b07bdb | ||
|
|
cef1c0d1d1 | ||
|
|
0fb54a5edd | ||
|
|
abd7a88ba0 | ||
|
|
d37457dc10 | ||
|
|
fc7707ad3e | ||
|
|
f43c6ab3c5 | ||
|
|
8ae05ff7b6 | ||
|
|
11c3b6cfe2 | ||
|
|
b4a997cde9 | ||
|
|
9e4650cbb6 | ||
|
|
7105255212 | ||
|
|
1338491616 | ||
|
|
0afb47ade0 | ||
|
|
88292f2f3b | ||
|
|
d389dab8d2 | ||
|
|
1205bdcaae | ||
|
|
5e7e055539 | ||
|
|
3822be76a8 | ||
|
|
b904237c5a | ||
|
|
df930cb879 | ||
|
|
327331475e | ||
|
|
91a8386ba4 | ||
|
|
b7e0619e9a | ||
|
|
3b75d9b362 | ||
|
|
0b984a44d7 | ||
|
|
c9ddc83eef | ||
|
|
b2b221516c | ||
|
|
5170634b90 | ||
|
|
01c92c04cf | ||
|
|
1bcb0128f0 | ||
|
|
5633291ab0 | ||
|
|
785ae01a51 | ||
|
|
34fd9d0d88 | ||
|
|
9f19676dc2 | ||
|
|
4a3fb55b30 | ||
|
|
eaa6327663 | ||
|
|
13ca506015 | ||
|
|
59d0bafdc9 | ||
|
|
cee85942e6 | ||
|
|
f303d3c45d | ||
|
|
6f7f74f7c6 | ||
|
|
ba398569c1 | ||
|
|
a8a47dca8f | ||
|
|
f782a7027a | ||
|
|
a434318535 | ||
|
|
134265094c | ||
|
|
4909e7861f | ||
|
|
ad9a7fdce8 | ||
|
|
97e97d0984 | ||
|
|
4c6433b0f1 | ||
|
|
f0d956f71c | ||
|
|
3a9d348cab | ||
|
|
586bd13cc2 | ||
|
|
e601e2acca | ||
|
|
2a3c0e11da | ||
|
|
bee40ae35c | ||
|
|
0392a1649f | ||
|
|
3cb15df08d | ||
|
|
d4b52ad4f1 | ||
|
|
91249bc892 | ||
|
|
87f5efeadb | ||
|
|
369eab3b5f | ||
|
|
6780d17d2e | ||
|
|
af22fee0c1 | ||
|
|
61c111d5ae | ||
|
|
6897c0c3fe | ||
|
|
4010fb7d1e | ||
|
|
3301148da6 | ||
|
|
09c57bdb86 | ||
|
|
9ce0497f00 | ||
|
|
36027583cd | ||
|
|
9abf4b126c | ||
|
|
ec5a4d09b8 | ||
|
|
2832736826 | ||
|
|
b87e3c22b3 | ||
|
|
9582cc0211 | ||
|
|
1943877b21 | ||
|
|
c876534c85 | ||
|
|
b91c42e186 | ||
|
|
27c8f883ff | ||
|
|
d28bbdaaf7 | ||
|
|
5817b5fe19 | ||
|
|
1db9b04bfd | ||
|
|
00d851998b | ||
|
|
927dbbfe82 | ||
|
|
d73ed95719 | ||
|
|
01194d5e25 | ||
|
|
32d31da0da | ||
|
|
655afa088d | ||
|
|
0355e1bfc7 | ||
|
|
a44f1df0d4 | ||
|
|
3e745ff45d | ||
|
|
bc87e3d6d0 | ||
|
|
5aa68c7052 | ||
|
|
6e36f66dde | ||
|
|
32e4569495 | ||
|
|
5a591d2acd | ||
|
|
e8980fbbfe | ||
|
|
8e68781a1b | ||
|
|
ad19d64ce8 | ||
|
|
c1a67c0097 | ||
|
|
5ed84e3ae5 | ||
|
|
5264863863 | ||
|
|
9c5c2ac8bf | ||
|
|
1bbcf67396 | ||
|
|
8b44b4d8f1 | ||
|
|
4ef9d4d5f6 | ||
|
|
ea7266dc3b | ||
|
|
effb76c8db | ||
|
|
2d52c4f4f5 | ||
|
|
4ed093f28f | ||
|
|
a753037178 | ||
|
|
0d449f1292 | ||
|
|
2e3addc6da | ||
|
|
a0762aca45 | ||
|
|
80549bda9b | ||
|
|
88ad68069c | ||
|
|
80ef69c710 | ||
|
|
1d5d597103 | ||
|
|
6b164e10f2 | ||
|
|
b3d70f2556 | ||
|
|
8fa708d573 | ||
|
|
a68612ca2b | ||
|
|
7d483b36d0 | ||
|
|
61e409a09e | ||
|
|
5564d93d59 | ||
|
|
6674135c74 | ||
|
|
a4fbc050cc | ||
|
|
205b6d9881 | ||
|
|
f2d1a4190a | ||
|
|
6008dc12d3 | ||
|
|
118b4367e7 | ||
|
|
e6f8269c0b | ||
|
|
928128ba2d | ||
|
|
444567faac | ||
|
|
eaa6ea80e6 | ||
|
|
a4d362d43d | ||
|
|
89e2f4a481 | ||
|
|
8acc9af1f5 | ||
|
|
0ebc1a766e | ||
|
|
bf6211903c | ||
|
|
ad262f6fb3 | ||
|
|
0a7d1831d2 | ||
|
|
ca56e08459 | ||
|
|
31bfe3930d | ||
|
|
48624b1db6 | ||
|
|
5a33a002e4 | ||
|
|
43d3cc36e9 | ||
|
|
ee813abdc1 | ||
|
|
411acc0a2f | ||
|
|
28cd649db3 | ||
|
|
94f2269ff2 | ||
|
|
c106b74239 | ||
|
|
3ae7c42afa | ||
|
|
0d4de50f13 | ||
|
|
d4c1e2fc36 | ||
|
|
903a9356a9 | ||
|
|
2f6018c35c | ||
|
|
0e0fb68c38 | ||
|
|
f60d8c6c96 | ||
|
|
4a9e38a221 | ||
|
|
f0a9889f33 | ||
|
|
aa386e12bc | ||
|
|
ba46ab7361 | ||
|
|
5ce3ce06c6 | ||
|
|
e95d940b5d | ||
|
|
14ed83fb31 | ||
|
|
497d42b822 | ||
|
|
3bae4839bd | ||
|
|
81adcd03fb | ||
|
|
7f3c67724e | ||
|
|
741ad29d82 | ||
|
|
374de57e15 | ||
|
|
ff30d505af | ||
|
|
d4dc32a5e5 | ||
|
|
c073a66e7e | ||
|
|
4d2de63374 | ||
|
|
fa33c5852c | ||
|
|
510d9ab4d8 | ||
|
|
4f07613154 | ||
|
|
d2b5283489 | ||
|
|
aec68c52ab | ||
|
|
b5e965cf1a | ||
|
|
640723a4e7 | ||
|
|
ccca3a38f0 | ||
|
|
9b862b672f | ||
|
|
ad4c1aae45 | ||
|
|
099d1259b2 | ||
|
|
e5206e65e7 | ||
|
|
9332d6f36c | ||
|
|
f4be3aa9de | ||
|
|
0f54e85b36 | ||
|
|
ed9400912c | ||
|
|
999af63118 | ||
|
|
b0e2200166 | ||
|
|
43d4acc94b | ||
|
|
7a253dc9e4 | ||
|
|
b587f88968 | ||
|
|
491748af9f | ||
|
|
10e981d034 | ||
|
|
e188ae732a | ||
|
|
7e8d8eef5a | ||
|
|
e6d6b60b63 | ||
|
|
70beb6c60c | ||
|
|
1990722f18 | ||
|
|
aa416a782d | ||
|
|
7f2d5f4d69 | ||
|
|
4fa6d37d6f | ||
|
|
b061844530 | ||
|
|
5add196d88 | ||
|
|
1e580638d2 | ||
|
|
f33d6610e7 | ||
|
|
a592f37593 | ||
|
|
51dd869874 | ||
|
|
5347409804 | ||
|
|
aa6f82c31f | ||
|
|
d9bd63d34f | ||
|
|
a8f5604718 | ||
|
|
cf4f999b6a | ||
|
|
52029f83ef | ||
|
|
0c9a06789a | ||
|
|
5709d2e757 | ||
|
|
11a0e01f08 | ||
|
|
553c0e6d6a | ||
|
|
7b81bb3fc2 | ||
|
|
e609670a41 | ||
|
|
a7b455fb9a | ||
|
|
8ed857b4b9 | ||
|
|
2bb8c535bd | ||
|
|
e09884af60 | ||
|
|
57399aeab2 | ||
|
|
33c3e86e66 | ||
|
|
a7e77c3ea6 | ||
|
|
2d76364b09 | ||
|
|
36eaa18749 | ||
|
|
f7bb08382c | ||
|
|
9841a39d04 | ||
|
|
edf53840de | ||
|
|
6bd2dcff2a | ||
|
|
73117f6f27 | ||
|
|
3d588a88e2 | ||
|
|
636dbd4e57 | ||
|
|
0a04a856da | ||
|
|
e139284a98 | ||
|
|
a04980b251 | ||
|
|
8120a0cb9c | ||
|
|
c84f8808cb | ||
|
|
1b444a42f2 | ||
|
|
a7e79b13f9 | ||
|
|
3e6be7e04c | ||
|
|
aa1e3f59ed | ||
|
|
a13fb1f94f | ||
|
|
19f4faa03f | ||
|
|
965148f3a6 | ||
|
|
a0c0ab1871 | ||
|
|
43cbe2dd39 | ||
|
|
9c00de047a | ||
|
|
49649a8814 | ||
|
|
18a67a80bc | ||
|
|
867669cc98 | ||
|
|
0158a93391 | ||
|
|
fdb6533149 | ||
|
|
6f32d721c2 | ||
|
|
5f49656e30 | ||
|
|
8114b165d9 | ||
|
|
dd39cb5a1c | ||
|
|
7f8c217e7c | ||
|
|
d731a095c6 | ||
|
|
6630899d6e | ||
|
|
0cfd5095a7 | ||
|
|
a588267fc2 | ||
|
|
4f379821b5 | ||
|
|
9eea7dabc2 | ||
|
|
ca85a9a2a5 | ||
|
|
e34885de9b | ||
|
|
192b9213ac | ||
|
|
7e26e2bc21 | ||
|
|
f9c0482949 | ||
|
|
7e0d7ef727 | ||
|
|
d6820a69d4 | ||
|
|
cf09ff8dc3 | ||
|
|
bda941746b | ||
|
|
f638a2ff49 | ||
|
|
b348a882f4 | ||
|
|
9e4a50fb15 | ||
|
|
cfe657d853 | ||
|
|
a1c3789ec2 | ||
|
|
1cf9ad55c6 | ||
|
|
087d896569 | ||
|
|
17fc15138a | ||
|
|
d4af28c52b | ||
|
|
767a162077 | ||
|
|
78d7fe3e10 | ||
|
|
dc18a6c3bc | ||
|
|
03cb738e55 | ||
|
|
d1c834e647 | ||
|
|
03a082fe40 | ||
|
|
7691377c1b | ||
|
|
0534570784 | ||
|
|
f2e389593a | ||
|
|
2037c3b202 | ||
|
|
1dc7db4456 | ||
|
|
fae79d67e6 | ||
|
|
271f69d59d | ||
|
|
6563cd9c8b | ||
|
|
8d700491da | ||
|
|
7962c104b6 | ||
|
|
505d0f4768 | ||
|
|
e60b8a7aef | ||
|
|
cb65eca062 | ||
|
|
d6a5913086 | ||
|
|
a644599b16 | ||
|
|
52def43f5a | ||
|
|
5d2715dd32 | ||
|
|
13af98e5ad | ||
|
|
d14e907954 | ||
|
|
3f804339b9 | ||
|
|
a73a393e26 | ||
|
|
1bad233e2f | ||
|
|
f64b1d3651 | ||
|
|
eb57c2f980 | ||
|
|
ecd491cd09 | ||
|
|
dead3138b3 | ||
|
|
0416082d4d | ||
|
|
98d1fd85fb | ||
|
|
719aacd6f8 | ||
|
|
4ee2ca2a33 | ||
|
|
45f9d5bb81 | ||
|
|
9f2d87d7d7 | ||
|
|
d5b163f04d | ||
|
|
05c77d0248 | ||
|
|
2fc4c30bed | ||
|
|
d2590c7651 | ||
|
|
237af505f9 | ||
|
|
7b4f522a33 | ||
|
|
0e7ce55f9a | ||
|
|
fe43b3494c | ||
|
|
4c00c8f3ec | ||
|
|
f05518e357 | ||
|
|
6e667e078c | ||
|
|
84a36624a6 | ||
|
|
165c551e39 | ||
|
|
fe6ed2ceae | ||
|
|
92bcd549ef | ||
|
|
5216471226 | ||
|
|
6497ee02fb | ||
|
|
859e26cf8f | ||
|
|
9964360656 | ||
|
|
73f5e7c2ef | ||
|
|
64ffa9bb3f | ||
|
|
ec63d23666 | ||
|
|
a3063eb46d | ||
|
|
40b7cafacc | ||
|
|
82c6b8daae | ||
|
|
3228582cbe | ||
|
|
d0e008665f | ||
|
|
96eacb6efe | ||
|
|
e183d67e2a | ||
|
|
bbf91a8957 | ||
|
|
618d22d214 | ||
|
|
d83459f674 | ||
|
|
6cb6adc134 | ||
|
|
18dded1a00 | ||
|
|
1c2785f34e | ||
|
|
a411cbc640 | ||
|
|
b4c87ad0b9 | ||
|
|
37a35b1827 | ||
|
|
ddae26b48b | ||
|
|
c3f57cf900 | ||
|
|
56b74c6ff2 | ||
|
|
8682c07148 | ||
|
|
96870c3fee | ||
|
|
24a0b4445e | ||
|
|
e139cba621 | ||
|
|
07e8d110a2 | ||
|
|
87c1bc4bdb | ||
|
|
31b13f3551 | ||
|
|
e15f5bb432 | ||
|
|
340ee859f9 | ||
|
|
496ba433e9 | ||
|
|
b183dc3e62 | ||
|
|
0b0ae756a3 | ||
|
|
0ade0405f5 | ||
|
|
fcf8ad0611 | ||
|
|
e0cb6d32ea | ||
|
|
aeed535f1b | ||
|
|
974084a9ca | ||
|
|
88706534c2 | ||
|
|
70def21153 | ||
|
|
941174a9fa | ||
|
|
46bfb2aacd | ||
|
|
a4ef3f770c | ||
|
|
7cf27ac86d | ||
|
|
823e5ca058 | ||
|
|
b7a182129d | ||
|
|
10b147a25d | ||
|
|
6550631003 | ||
|
|
9d04dc7d9a | ||
|
|
486d89c5d0 | ||
|
|
d24f16563f | ||
|
|
bb68838fa4 | ||
|
|
aed6a8a5ff | ||
|
|
e13bceeb59 | ||
|
|
1dab89f7ae | ||
|
|
96facc5100 | ||
|
|
43d94d208f | ||
|
|
741ee99e6b | ||
|
|
6f2cff2f33 | ||
|
|
0035827209 | ||
|
|
c626b1d106 | ||
|
|
9c895cb8bb | ||
|
|
23a9c74297 | ||
|
|
aecb8a1464 | ||
|
|
b9e3426532 | ||
|
|
809d7ab7f4 | ||
|
|
6486b24c8b | ||
|
|
e11d78d37a | ||
|
|
3a34b3ae5f | ||
|
|
75a8f85ebb | ||
|
|
3d8f757712 | ||
|
|
b37d6ec500 | ||
|
|
4efd8b7d3f | ||
|
|
5d17933593 | ||
|
|
277d8f8b93 | ||
|
|
f2c5add752 | ||
|
|
60d37b6de0 | ||
|
|
206778021f | ||
|
|
4a262de550 | ||
|
|
350663b7ce | ||
|
|
f1db0fea03 | ||
|
|
1990232adc | ||
|
|
256ed7410f | ||
|
|
09a4cb30ec | ||
|
|
aa3826c389 | ||
|
|
b91042c1e5 | ||
|
|
7eed8c5ee5 | ||
|
|
3207860374 | ||
|
|
b3bb8b6692 | ||
|
|
5b8b13c94c | ||
|
|
e8426006e3 | ||
|
|
116fafd0e1 | ||
|
|
e9fe1800e0 | ||
|
|
82796822d1 | ||
|
|
ce61b783fb | ||
|
|
9b78b2a432 | ||
|
|
321b2c7c23 | ||
|
|
dee397615c | ||
|
|
ef9339f6f1 | ||
|
|
f7f32408fc | ||
|
|
d4e6992442 | ||
|
|
420ece7005 | ||
|
|
741d2b3f3c | ||
|
|
c8bf319b39 | ||
|
|
34df52be5f | ||
|
|
fc2399a885 | ||
|
|
699ec93ca4 | ||
|
|
10598063d1 | ||
|
|
db1e9574cd | ||
|
|
af74a1575b | ||
|
|
03242e1a9c | ||
|
|
dcbd89ff7c | ||
|
|
2312561041 | ||
|
|
b591fbecf0 | ||
|
|
3fbb440436 | ||
|
|
d358185a04 | ||
|
|
8babb59f75 | ||
|
|
3461ec2281 | ||
|
|
3dd94bd362 | ||
|
|
827c2140b7 | ||
|
|
5a5222a97a | ||
|
|
bea3699451 | ||
|
|
93174f54a3 | ||
|
|
e1348725c1 | ||
|
|
528839cde2 | ||
|
|
c5c331ee30 | ||
|
|
69effa37a3 | ||
|
|
4c7a781228 | ||
|
|
62a67bdb94 | ||
|
|
c5c0b36f28 | ||
|
|
0d48c758df | ||
|
|
4856413b24 | ||
|
|
a1586280a9 | ||
|
|
00950b2c97 | ||
|
|
79666bd51a | ||
|
|
ca284a86a3 | ||
|
|
ee5b341d0e | ||
|
|
85cab5031d | ||
|
|
2f7029516b | ||
|
|
a1da4d4233 | ||
|
|
24724e3340 | ||
|
|
048ab9a0fc | ||
|
|
40b005f46e | ||
|
|
ae2a99acb0 | ||
|
|
a81b6db093 | ||
|
|
39b05f5f9f | ||
|
|
979f61df99 | ||
|
|
e665f4e285 | ||
|
|
2c25dfcf1b | ||
|
|
0632028579 | ||
|
|
95b9085258 | ||
|
|
cdc91feb28 | ||
|
|
4caf278da5 | ||
|
|
809a3420c1 | ||
|
|
af8e9058a3 | ||
|
|
2b1c8cafe9 | ||
|
|
1abcd5819b | ||
|
|
76b34bb600 | ||
|
|
67c6a042a0 | ||
|
|
72c2a213b4 | ||
|
|
ec1cc8ec64 | ||
|
|
fbbb079599 | ||
|
|
b8f2f3e34d | ||
|
|
39b29ee3f0 | ||
|
|
5f6cb1e0d7 | ||
|
|
46250e6fab | ||
|
|
fc2a26f249 | ||
|
|
341caa3489 | ||
|
|
38b7474f0b | ||
|
|
c91e2d13c0 | ||
|
|
7134bbf484 | ||
|
|
6b18b258a4 | ||
|
|
86e4826e48 | ||
|
|
6461ebe2b8 | ||
|
|
bfb23ad60b | ||
|
|
637d6a0076 | ||
|
|
cbb8d13570 | ||
|
|
2a6ba79e9a | ||
|
|
1e0dacfe9b | ||
|
|
b194c0c5d8 | ||
|
|
9789b7081a | ||
|
|
03052ddd28 | ||
|
|
779f685f72 | ||
|
|
1dee767762 | ||
|
|
5cac5eb26b | ||
|
|
b26cbeccca | ||
|
|
8d4612c683 | ||
|
|
e352c70013 | ||
|
|
8c3c9a8ca4 | ||
|
|
ada837f7e4 | ||
|
|
1599b2644a | ||
|
|
acc3d00fd5 | ||
|
|
0f459ede50 | ||
|
|
105cb6fa13 | ||
|
|
1797c702a7 | ||
|
|
5f580f10ca | ||
|
|
bd359f42f5 | ||
|
|
34bf73210e | ||
|
|
f1db4aae35 | ||
|
|
7710ffcbf1 | ||
|
|
e9f45a0d0a | ||
|
|
743c2c3385 | ||
|
|
6f714facf9 | ||
|
|
5f81c7bc2d | ||
|
|
72b00fa9af | ||
|
|
449756a0e4 | ||
|
|
75930a0ce9 | ||
|
|
a2c3b0926b | ||
|
|
57666e9173 | ||
|
|
37f4b83d96 | ||
|
|
5576b7568c | ||
|
|
99e067b863 | ||
|
|
5103624fe0 | ||
|
|
26e5d57ced | ||
|
|
b586f2552c | ||
|
|
0fbcec667c | ||
|
|
f40c58c64a | ||
|
|
d66619fe6d | ||
|
|
5c1b007c1b | ||
|
|
86c394f4ce | ||
|
|
90d130a3bc | ||
|
|
f185836fd4 | ||
|
|
4c851856ff | ||
|
|
bc2a0432b9 | ||
|
|
f72e140327 | ||
|
|
a633ed3c9a | ||
|
|
a8a6882708 | ||
|
|
1b76ed97e1 | ||
|
|
04f7169aef | ||
|
|
d83b362c9f | ||
|
|
b1d55348dc | ||
|
|
f8a46c56e9 | ||
|
|
420525cdf0 | ||
|
|
686cacd5ae | ||
|
|
0092806dda | ||
|
|
2f8c63505f | ||
|
|
d892be4971 | ||
|
|
59ed833abc | ||
|
|
110fb2e8db | ||
|
|
9f7a04e330 | ||
|
|
ccbc519c04 | ||
|
|
036860b91b | ||
|
|
7ac2a55315 | ||
|
|
f6cf8d2b1b | ||
|
|
16788d7ab7 | ||
|
|
3142d48f3c | ||
|
|
e1a88ae899 | ||
|
|
a2e80cee47 | ||
|
|
5f14769abc | ||
|
|
406c4063de | ||
|
|
b4bc30e56f | ||
|
|
3482d6c303 | ||
|
|
9dfd5c0bcc | ||
|
|
b2b3de2782 | ||
|
|
50c20e2290 | ||
|
|
a10dcbd918 | ||
|
|
6e0433a34b | ||
|
|
8833e19c7f | ||
|
|
663358bdae | ||
|
|
d9b1c0e2e6 | ||
|
|
39334b81ac | ||
|
|
62f7323acf | ||
|
|
3f89baa1fe | ||
|
|
f0fd1bb40c | ||
|
|
f303d7b33e | ||
|
|
d356a3fa56 | ||
|
|
64e7cad292 | ||
|
|
0766938f98 | ||
|
|
918829ad0a | ||
|
|
540e1fc492 | ||
|
|
ac30754a96 | ||
|
|
b470a0ebbf | ||
|
|
69daee4137 | ||
|
|
3d6c903273 | ||
|
|
bc04a0b9f0 | ||
|
|
bfef94dbfb | ||
|
|
9e06275945 | ||
|
|
6410bcf3c8 | ||
|
|
20baf02726 | ||
|
|
8f6fdf361b | ||
|
|
ffa134f824 | ||
|
|
b4bf6b9235 | ||
|
|
c3e37b0e04 | ||
|
|
374bba763b | ||
|
|
2d00e68b78 | ||
|
|
9a965dc693 | ||
|
|
b96ad5b37f | ||
|
|
07a0f67b32 | ||
|
|
c754a757eb | ||
|
|
dcd53183a8 | ||
|
|
5641132eb9 | ||
|
|
b4c23f3554 | ||
|
|
7385445aa8 | ||
|
|
5409f83167 | ||
|
|
43951aad69 | ||
|
|
9681d8c805 | ||
|
|
ff4d3cfeac | ||
|
|
8e4338c1c9 | ||
|
|
c27d709b6b | ||
|
|
8caa289586 | ||
|
|
f7568f6348 | ||
|
|
6a6f079a84 | ||
|
|
0f04b86316 | ||
|
|
1dae8a0771 | ||
|
|
9bc816fc1c | ||
|
|
11ea4c7aec | ||
|
|
0c53aa158b | ||
|
|
072e4a4f98 | ||
|
|
1b3b3b0ee6 | ||
|
|
d1e4f0de3e | ||
|
|
fd3f304e07 | ||
|
|
9e7551551f | ||
|
|
36bb5cbe01 | ||
|
|
f754c12e8c | ||
|
|
6f5916e334 | ||
|
|
13dd1cb6b6 | ||
|
|
eb4e7e0f0c | ||
|
|
7afb3e2c6d | ||
|
|
9cf5258053 | ||
|
|
56dfdaecb7 | ||
|
|
1d174a4379 | ||
|
|
2aeded1940 | ||
|
|
c23691284c | ||
|
|
f7f6515561 | ||
|
|
438c14d29d | ||
|
|
87351000ae | ||
|
|
0895a69bac | ||
|
|
4285972e41 | ||
|
|
d33542c4dc | ||
|
|
bda97adf4f | ||
|
|
b6f460940f | ||
|
|
aa0e8ed8d6 | ||
|
|
b99fa16b96 | ||
|
|
630d7a3220 | ||
|
|
03c91664cb | ||
|
|
13773d829a | ||
|
|
d9e6e9b017 | ||
|
|
5d8982c734 | ||
|
|
f13c82da2a | ||
|
|
363b0ba997 | ||
|
|
a4c45942c9 | ||
|
|
a86e3a8636 | ||
|
|
db61d579e6 | ||
|
|
e6e3f2cbd5 | ||
|
|
ffdd539233 | ||
|
|
ef17af38a1 | ||
|
|
6dedfaea2f | ||
|
|
cbb3783d84 | ||
|
|
327c095d79 | ||
|
|
88e222420c | ||
|
|
045eec072b | ||
|
|
5f3c381f88 | ||
|
|
6090cf6c68 | ||
|
|
9ac4239c11 | ||
|
|
da2a3b6883 | ||
|
|
b4accaad07 | ||
|
|
edaef0096a | ||
|
|
afba5ff083 | ||
|
|
8b8d614150 | ||
|
|
090dbfda10 | ||
|
|
04f5a6a9f9 | ||
|
|
d8311ac3fa | ||
|
|
9c7f4dfd98 | ||
|
|
8da66e1599 | ||
|
|
2927b0cfc6 | ||
|
|
4663c296cd | ||
|
|
9403f8fd6e | ||
|
|
badb5c6a9b | ||
|
|
e5430259e9 | ||
|
|
50fd44d3f2 | ||
|
|
a8249b73b6 | ||
|
|
a15f867059 | ||
|
|
4216e2e92b | ||
|
|
8ef20be9bd | ||
|
|
6413c9dddd | ||
|
|
eb10f075b9 | ||
|
|
cd55ed7c8d | ||
|
|
2fb96a1b7d | ||
|
|
c48da3d316 | ||
|
|
9488a9a1ad | ||
|
|
2feb22d3bd | ||
|
|
f74569506e | ||
|
|
6633d0b4fb | ||
|
|
6fb35b40d7 | ||
|
|
614d699098 | ||
|
|
8f4fbff40f | ||
|
|
aaf8d2a233 | ||
|
|
0eb2336bc6 | ||
|
|
f9cc9164b3 | ||
|
|
238ec60f89 | ||
|
|
363a82d068 | ||
|
|
4360c1fe4b | ||
|
|
1d575ece06 | ||
|
|
d246788a35 | ||
|
|
e9fa04dd1b | ||
|
|
8e57932966 | ||
|
|
51ea354bcb | ||
|
|
6334520c66 | ||
|
|
6354883a6f | ||
|
|
477f553675 | ||
|
|
1ded4b2b28 | ||
|
|
16c4116c15 | ||
|
|
f5cfbce1c2 | ||
|
|
7bbf98dfb1 | ||
|
|
533cac7881 | ||
|
|
6afc734e91 | ||
|
|
c4fb826d89 | ||
|
|
1321bdeac5 | ||
|
|
e0b7001a09 | ||
|
|
88120b83bd | ||
|
|
a952f7369c | ||
|
|
d4fab3b46c | ||
|
|
06b3499e7d | ||
|
|
fdd66bd513 | ||
|
|
3b5aaf21fa | ||
|
|
59c46833ed | ||
|
|
aee35cb456 | ||
|
|
4a55ee2965 | ||
|
|
4b490e3ca4 | ||
|
|
6727717d1a | ||
|
|
d08891cffa | ||
|
|
799a83ba73 | ||
|
|
261724e555 | ||
|
|
10e7d660ef | ||
|
|
bdfea7cda5 | ||
|
|
fdb1c5e1f9 | ||
|
|
71734df489 | ||
|
|
071b81eadd | ||
|
|
2abe3fde71 | ||
|
|
27e64ccaa8 | ||
|
|
c9185f265c | ||
|
|
79e5716f36 | ||
|
|
9f0e156916 | ||
|
|
d24d45f4cb | ||
|
|
bf55250ae9 | ||
|
|
36016ad9ef | ||
|
|
bf66b81702 | ||
|
|
758ffbf217 | ||
|
|
f24563503a | ||
|
|
a2dbc40571 | ||
|
|
a096b0777f | ||
|
|
87690a534c | ||
|
|
a70e416b0b | ||
|
|
cd1170c543 | ||
|
|
2962dc1faa | ||
|
|
535c1fd007 | ||
|
|
2bd94aff0e | ||
|
|
9ea90c3400 | ||
|
|
0ac5d85117 | ||
|
|
d3a83142ae | ||
|
|
d5886123d8 | ||
|
|
dea090e7d3 | ||
|
|
ba5e345677 | ||
|
|
13ae7b0976 | ||
|
|
39c7bfb49f | ||
|
|
8479a6581d | ||
|
|
e5885e83eb | ||
|
|
914b41fcd4 | ||
|
|
224aa5b89a | ||
|
|
01c89f6554 | ||
|
|
f0e1bc1d59 | ||
|
|
7b487e1bc3 | ||
|
|
c394495a26 | ||
|
|
6dae420254 | ||
|
|
c69d7f50a3 | ||
|
|
e9c654f30d | ||
|
|
ae9b95f81b | ||
|
|
c240b69b5a | ||
|
|
493a7680e0 | ||
|
|
c28a2acfc7 | ||
|
|
60af960f42 | ||
|
|
4c86d5cfe3 | ||
|
|
99a6142134 | ||
|
|
b2211aec59 | ||
|
|
fa09fff2b5 | ||
|
|
0204cdab83 | ||
|
|
445195e9eb | ||
|
|
7f5b0f359c | ||
|
|
d8f4176487 | ||
|
|
220ab22115 | ||
|
|
67776c46d6 | ||
|
|
2d118ecc65 | ||
|
|
4bc2d7444d | ||
|
|
5c6d72b353 | ||
|
|
1839397ebc | ||
|
|
0ee34637f5 | ||
|
|
c6054f7ab2 | ||
|
|
9554dfbc5e | ||
|
|
98d5789b1b | ||
|
|
0cad1a1e7e | ||
|
|
31c07274cd | ||
|
|
37a736339e | ||
|
|
869abcc835 | ||
|
|
a6f05ea8c2 | ||
|
|
6812099900 | ||
|
|
53e6d7ef2a | ||
|
|
c2f604cd01 | ||
|
|
888ffa1bcd | ||
|
|
d06cfed50e | ||
|
|
e06d063946 | ||
|
|
634ec1b113 | ||
|
|
0bf883d5b2 | ||
|
|
c6d0e0124f | ||
|
|
ce115c53e2 | ||
|
|
7014bc7a3c | ||
|
|
219f9c478d | ||
|
|
a9904c6ada | ||
|
|
81cbc33dbb | ||
|
|
24062beebe | ||
|
|
f3548aff8c | ||
|
|
771bb20976 | ||
|
|
8072caeff1 | ||
|
|
d5568fcc25 | ||
|
|
eb1bcfba83 | ||
|
|
a2d3616171 | ||
|
|
a83e37493a | ||
|
|
0feccc9d1c | ||
|
|
e18ecb8c49 | ||
|
|
f5b87a9865 | ||
|
|
3b93434dd3 | ||
|
|
d522096caf | ||
|
|
6c67110dde | ||
|
|
963efc369b | ||
|
|
384f0a05c6 | ||
|
|
a3016b8400 | ||
|
|
0df219c3ad | ||
|
|
a0d527f795 | ||
|
|
e44ea47497 | ||
|
|
9ee5a3159c | ||
|
|
06d41c552b | ||
|
|
7874971550 | ||
|
|
9925716134 | ||
|
|
64c456678b | ||
|
|
16ed09a303 | ||
|
|
1359a1aa7a | ||
|
|
6ae36982b6 | ||
|
|
136d269605 | ||
|
|
932c0ed4ad | ||
|
|
371875440f | ||
|
|
b01a9f2f95 | ||
|
|
0d2def102f | ||
|
|
beff3fe843 | ||
|
|
7a97c94f2b | ||
|
|
ce81908f02 | ||
|
|
f8cecfd61f | ||
|
|
8a2a1d6d70 | ||
|
|
76dafea8a6 | ||
|
|
86bbfde19e | ||
|
|
71d6f7b1a2 | ||
|
|
0c0ab612c0 | ||
|
|
73042115e0 | ||
|
|
0f7166d34a | ||
|
|
f35f2c95f0 | ||
|
|
4d280bdb6d | ||
|
|
47acab766d | ||
|
|
1cc3819e65 | ||
|
|
16fa6259ea | ||
|
|
95717c4c32 | ||
|
|
7564f27f95 | ||
|
|
565046aaa6 | ||
|
|
fb9b023fae | ||
|
|
b05908a760 | ||
|
|
3bbcd235e1 | ||
|
|
9d66984c62 | ||
|
|
9024408ed2 | ||
|
|
2b32e864fd | ||
|
|
626d0cba46 | ||
|
|
2a11e9962d | ||
|
|
7dffddd437 | ||
|
|
6a7600fd52 | ||
|
|
b897f202dd | ||
|
|
eb396f2367 | ||
|
|
95bf3e3af4 | ||
|
|
19944202fb | ||
|
|
2596ad27c3 | ||
|
|
ece914303a | ||
|
|
7a0c12e073 | ||
|
|
14b23b491f | ||
|
|
039b03249b | ||
|
|
3919cf4f86 | ||
|
|
319a9fd2de | ||
|
|
cf1f9f93aa | ||
|
|
0dd805da7f | ||
|
|
e7a1833c44 | ||
|
|
54f8487b46 | ||
|
|
b68d721b39 | ||
|
|
b9ccb4e52c | ||
|
|
c4a11f73a0 | ||
|
|
f96d4198c3 | ||
|
|
fe6a0ec5b8 | ||
|
|
e7b4010eba | ||
|
|
c4947d3737 | ||
|
|
8a8d677f85 | ||
|
|
baf4393310 | ||
|
|
723916d930 | ||
|
|
591d66564d | ||
|
|
79a2d522bf | ||
|
|
4ad34ab5c8 | ||
|
|
33c7847dfc | ||
|
|
7a04f15710 | ||
|
|
7e5b10eb3e | ||
|
|
896a07fa9a | ||
|
|
07e8bb00fb | ||
|
|
5d2742dd37 | ||
|
|
9ae3f1eb68 | ||
|
|
8c6e0cf43a | ||
|
|
1e220fd506 | ||
|
|
4ff7855fd4 | ||
|
|
eb57354109 | ||
|
|
a82a33cecf | ||
|
|
c90fc2a9bf | ||
|
|
c1a40e4aeb | ||
|
|
9999b8bfab | ||
|
|
cf62008acf | ||
|
|
1c959a6653 | ||
|
|
b8043a2432 | ||
|
|
ed5de525aa | ||
|
|
8105d9388b | ||
|
|
8151dcc05f | ||
|
|
25b1c5fe90 | ||
|
|
f566d2a0da | ||
|
|
ea218839e4 | ||
|
|
4c18a1881b | ||
|
|
0be2b2791f | ||
|
|
bf51e3db60 | ||
|
|
abca69f408 | ||
|
|
6eac9102c9 | ||
|
|
0a7da1b7f2 | ||
|
|
b4361cb202 | ||
|
|
d2d4090e27 | ||
|
|
583eb53c9d | ||
|
|
39246f2beb | ||
|
|
cd2d3d5fa3 | ||
|
|
589e646023 | ||
|
|
b7ba3098ae | ||
|
|
631c5ecae3 | ||
|
|
4962e45bd9 | ||
|
|
c57219a356 | ||
|
|
03e6a187c5 | ||
|
|
0bdbbd35e3 | ||
|
|
401afe7c1a | ||
|
|
66b0039566 | ||
|
|
17da51440c | ||
|
|
c5adeecd90 | ||
|
|
da6c62414b | ||
|
|
6650fe863f | ||
|
|
76c00c42b5 | ||
|
|
f8609419a1 | ||
|
|
250e79eda1 | ||
|
|
f7401daeae | ||
|
|
7390e372e0 | ||
|
|
239c521ad9 | ||
|
|
77b4f09cfb | ||
|
|
bb178af278 | ||
|
|
3c39661174 | ||
|
|
1fffc1e828 | ||
|
|
ed50cd1fa8 | ||
|
|
ef6a02e8ef | ||
|
|
e7003dde83 | ||
|
|
bf2a393fd3 | ||
|
|
bb2cfd52f4 | ||
|
|
6a6f88c6ef | ||
|
|
0a2b1a4fbe | ||
|
|
5fd48c9e98 | ||
|
|
022d32cd44 | ||
|
|
af10cf71db | ||
|
|
1bf1de8ce8 | ||
|
|
b80ddfa580 | ||
|
|
aa276ab308 | ||
|
|
f50943d470 | ||
|
|
959c998664 | ||
|
|
b6b6b6043c | ||
|
|
8e0807d502 | ||
|
|
78d027b25e | ||
|
|
503f0bee69 | ||
|
|
50d756b189 | ||
|
|
7c3d71e572 | ||
|
|
bf895d26b0 | ||
|
|
5530e8581a | ||
|
|
f913816d87 | ||
|
|
3d59d31b0a | ||
|
|
9a66f26bd9 | ||
|
|
d5b6605ce8 | ||
|
|
38e5184be4 | ||
|
|
369ec9f814 | ||
|
|
620b454c49 | ||
|
|
2e5040e65d | ||
|
|
71fe7ef125 | ||
|
|
e0e8fd7ddc | ||
|
|
01b4b4933e | ||
|
|
fced22bc60 | ||
|
|
898ae64a57 | ||
|
|
8d50852cbe | ||
|
|
a11c7b10ac | ||
|
|
fe9033b8be | ||
|
|
e26f9e4a71 | ||
|
|
c477328da4 | ||
|
|
214c64c49e | ||
|
|
bce0e9194c | ||
|
|
40326177fd | ||
|
|
4ab0272fa6 | ||
|
|
fb33366c91 | ||
|
|
75352c9afe | ||
|
|
a0f98e3823 | ||
|
|
bff6768adf | ||
|
|
8ce2eb448c | ||
|
|
7c5d00f8a4 | ||
|
|
30cd499014 | ||
|
|
99d173789e | ||
|
|
ae72db8129 | ||
|
|
9437cc1806 | ||
|
|
0e76aa0265 | ||
|
|
756e654d32 | ||
|
|
78d1c57b7c | ||
|
|
bb27405e8f | ||
|
|
0cfc46b417 | ||
|
|
bfb30fe68d | ||
|
|
63959b4b22 | ||
|
|
66d086892f | ||
|
|
b878570b14 | ||
|
|
b059610d16 | ||
|
|
972aa7f4e3 | ||
|
|
797c1421da | ||
|
|
e65cbcba65 | ||
|
|
6d96dd3818 | ||
|
|
16db31c53c | ||
|
|
c72138d15a | ||
|
|
6d28a9ad58 | ||
|
|
75c8d97a6e | ||
|
|
c35f53af89 | ||
|
|
55784f64b8 | ||
|
|
a7241d4128 | ||
|
|
1573d794b9 | ||
|
|
bc725800ed | ||
|
|
007728819b | ||
|
|
f32f13069f | ||
|
|
5ec73da17f | ||
|
|
5fd3689333 | ||
|
|
cca1431012 | ||
|
|
7ba9558a7a | ||
|
|
c65e8b4a5e | ||
|
|
eed75bcbda | ||
|
|
1af4325e8f | ||
|
|
5e6719e22e | ||
|
|
a4bbfe3c79 | ||
|
|
63f42fc8bb | ||
|
|
5b9bcf8b1d | ||
|
|
f02bc82525 | ||
|
|
92f89c6cc1 | ||
|
|
a1908be982 | ||
|
|
f08894629d | ||
|
|
e46f11e6f8 | ||
|
|
df6234ea28 | ||
|
|
21cdf8b0ae | ||
|
|
192fb07ef1 | ||
|
|
226216d111 | ||
|
|
bed2deb683 | ||
|
|
6e327b69f5 | ||
|
|
1d3c8e867e | ||
|
|
d0f761172a | ||
|
|
fd6a8aec71 | ||
|
|
e00e501605 | ||
|
|
81a42ef1df | ||
|
|
ee5eb2abc5 | ||
|
|
0ed14f0288 | ||
|
|
c55f740978 | ||
|
|
38952b6734 | ||
|
|
925058467f | ||
|
|
e5afeccadf | ||
|
|
ad18604552 | ||
|
|
d2d506dbf0 | ||
|
|
2833d3126f | ||
|
|
950367b055 | ||
|
|
703859ac75 | ||
|
|
4bf5434e8f | ||
|
|
350c44f56f | ||
|
|
679c630a4d | ||
|
|
dbbb0a4a3d | ||
|
|
0ca7d074ac | ||
|
|
65894bf582 | ||
|
|
8eacf738c2 | ||
|
|
1b69eda43e | ||
|
|
09d1c958ce | ||
|
|
589d16bc37 | ||
|
|
a6dfa6bbbd | ||
|
|
b2721305c5 | ||
|
|
24b3404876 | ||
|
|
92ca1e4873 | ||
|
|
59d44c41e4 | ||
|
|
08a503f160 | ||
|
|
e0e68835ef | ||
|
|
734287b66d | ||
|
|
ddc9d30a3e | ||
|
|
890bf9eced | ||
|
|
b8677b2b9a | ||
|
|
1f5e974cfc | ||
|
|
54e7e44df1 | ||
|
|
9736810f87 | ||
|
|
5547383434 | ||
|
|
85f8e77928 | ||
|
|
6918216b86 | ||
|
|
efd2ad8f8b | ||
|
|
5a8ce52105 | ||
|
|
cbee65671c | ||
|
|
75a7ce24bf | ||
|
|
013f56347d | ||
|
|
a052bfd2fa | ||
|
|
4b1fa2589e | ||
|
|
1a61c855ca | ||
|
|
0159eea526 | ||
|
|
f3bb5c55f5 | ||
|
|
9ecbddc18c | ||
|
|
d36bf188ae | ||
|
|
b8cddbca88 | ||
|
|
ee9b7166a6 | ||
|
|
9c1c755836 | ||
|
|
6722ca41bf | ||
|
|
9586d478ad | ||
|
|
77cf4a5332 | ||
|
|
7199ab7cbe | ||
|
|
5de2ff40d8 | ||
|
|
790d532cee | ||
|
|
9f03e353c7 | ||
|
|
68e3d53cb7 | ||
|
|
f9082e18e2 | ||
|
|
02d31a7947 | ||
|
|
3e3faf6576 | ||
|
|
bee1db135f | ||
|
|
09d39ca425 | ||
|
|
d58b6e5117 | ||
|
|
f0cf3e6411 | ||
|
|
b64d5ef357 | ||
|
|
2eccf77986 | ||
|
|
0b8b766b62 | ||
|
|
8d634a79c8 | ||
|
|
4b9e7fdb15 | ||
|
|
165a84534a | ||
|
|
fe4cab3a9e | ||
|
|
9e5166d991 | ||
|
|
48e78125e8 | ||
|
|
3fb3a92a8f | ||
|
|
8dba55d5cb | ||
|
|
045a5156d1 | ||
|
|
8a162e39d5 | ||
|
|
695788df0e | ||
|
|
4df96b03eb | ||
|
|
49c2cb985c | ||
|
|
a189dc8243 | ||
|
|
ff8db0cd77 | ||
|
|
eff3e4015b | ||
|
|
9ad43e13da | ||
|
|
1bd3a792da | ||
|
|
75f8e473a5 | ||
|
|
8c25ee7796 | ||
|
|
c3520cf606 | ||
|
|
75d2d97d8e | ||
|
|
778a542e1c | ||
|
|
74f3d551f2 | ||
|
|
fcc7b6791e | ||
|
|
56e2db22eb | ||
|
|
c56f024a86 | ||
|
|
6fd35ae5d9 | ||
|
|
1db2895606 | ||
|
|
df60ee06a1 | ||
|
|
0b4b2d3814 | ||
|
|
9f08153a85 | ||
|
|
5031700af6 | ||
|
|
57245dd77e | ||
|
|
4697a1904a | ||
|
|
ed380c86eb | ||
|
|
02deecf54b | ||
|
|
133c0312be | ||
|
|
45e501ce02 | ||
|
|
87dfa8c7fc | ||
|
|
edefb588b6 | ||
|
|
8ce8b85089 | ||
|
|
54c409a71c | ||
|
|
2f8960d34f | ||
|
|
20036bd72b | ||
|
|
38a84d4598 | ||
|
|
bc1372c2f9 | ||
|
|
c241100886 | ||
|
|
fea2d91a63 | ||
|
|
f2c4aa852d | ||
|
|
f8d09917a5 | ||
|
|
bbdf1152c1 | ||
|
|
f208731746 | ||
|
|
0516cfa296 | ||
|
|
157e8413fb | ||
|
|
4708af3b91 | ||
|
|
bee47d7fda | ||
|
|
d246db7be8 | ||
|
|
02025bc70a | ||
|
|
4275298f19 | ||
|
|
f0a02e4734 | ||
|
|
59464469c2 | ||
|
|
4d880a0d77 | ||
|
|
26b28e2364 | ||
|
|
835b597af5 | ||
|
|
c44d22ccf5 | ||
|
|
a11cda91de | ||
|
|
cfbbb85254 | ||
|
|
8a0bba3c7a | ||
|
|
da1135c80f | ||
|
|
7fcf481243 | ||
|
|
06e54a17c0 | ||
|
|
1fe23ff732 | ||
|
|
39278b47dd | ||
|
|
bff0030f2b | ||
|
|
b4b2f91363 | ||
|
|
c7252a950b | ||
|
|
e48b01fd18 | ||
|
|
ef2337f6d8 | ||
|
|
13d83cb0d1 | ||
|
|
033355395f | ||
|
|
6c332b949b | ||
|
|
0711438433 | ||
|
|
ee6350189f | ||
|
|
46189c0ff1 | ||
|
|
45d55e87eb | ||
|
|
8a158146cd | ||
|
|
1a859fc639 | ||
|
|
43518c6cfe | ||
|
|
7bfb7b6da0 | ||
|
|
c0474b206e | ||
|
|
fe6623b342 | ||
|
|
de8220245c | ||
|
|
562f0d9872 | ||
|
|
ed89f858e1 | ||
|
|
9527b2c456 | ||
|
|
5da2e5e7b7 | ||
|
|
e55e5aa168 | ||
|
|
22b45266bf | ||
|
|
b280b5a517 | ||
|
|
60cb358cce | ||
|
|
f03a74abc7 | ||
|
|
34885b64bd | ||
|
|
f3bfa4e587 | ||
|
|
3136ce7dc2 | ||
|
|
15a050517b | ||
|
|
85a1c61880 | ||
|
|
15991d0226 | ||
|
|
413bc41695 | ||
|
|
36137808f0 | ||
|
|
12c1852297 | ||
|
|
95e3c3eafc | ||
|
|
c458fa6441 | ||
|
|
66c1e386ce | ||
|
|
59e203fd52 | ||
|
|
7e0c097f23 | ||
|
|
926fa483b9 | ||
|
|
2ebc92a167 | ||
|
|
eb511757db | ||
|
|
b5b97f7626 | ||
|
|
ba0f7416bb | ||
|
|
f6e18de6af | ||
|
|
320a4552bc | ||
|
|
203473c965 | ||
|
|
255177d12b | ||
|
|
290bf00b2d | ||
|
|
8464e6a822 | ||
|
|
8af46ac7fc | ||
|
|
daeaf14032 | ||
|
|
bd52a7c926 | ||
|
|
c8c43de510 | ||
|
|
bb49071088 | ||
|
|
7a523a9d89 | ||
|
|
885d7de11b | ||
|
|
f44675a1e4 | ||
|
|
ce912c7430 | ||
|
|
e9fdd74a99 | ||
|
|
df8269bc7f | ||
|
|
23e4fa82c8 | ||
|
|
9bea604a46 | ||
|
|
119fbd114d | ||
|
|
1b6e6ec2e4 | ||
|
|
2dfa4f9ec9 | ||
|
|
3cd3e89679 | ||
|
|
c3be1c870d | ||
|
|
6b571fd2bb | ||
|
|
92df7abcf0 | ||
|
|
498d1570ce | ||
|
|
e587179359 | ||
|
|
c9985121c4 | ||
|
|
e768600df3 | ||
|
|
3dffb9c8a0 | ||
|
|
eb40297a35 | ||
|
|
837985ccc5 | ||
|
|
1def4b0f0c | ||
|
|
4c430cedf5 | ||
|
|
18d9212253 | ||
|
|
36314691f1 | ||
|
|
24da25f0f7 | ||
|
|
84ba8e6dde | ||
|
|
c6fe035939 | ||
|
|
be9073f0b7 | ||
|
|
ac6c07b771 | ||
|
|
c8102f4bff | ||
|
|
df1fcd5e22 | ||
|
|
de87da9c91 | ||
|
|
3532263af4 | ||
|
|
a9cf4dad82 | ||
|
|
1de1eb6b9b | ||
|
|
f6742d1bbf | ||
|
|
a377c602cc | ||
|
|
58f0ad999c | ||
|
|
f612d35daf | ||
|
|
7d202cb492 | ||
|
|
39bb7f209d | ||
|
|
bbd38a7e47 | ||
|
|
d8b2cc7e1b | ||
|
|
09b328167c | ||
|
|
4439ef07b6 | ||
|
|
f8491e9631 | ||
|
|
63259b3f92 | ||
|
|
10db35eab4 | ||
|
|
0fa79c7a46 | ||
|
|
e20f557bd6 | ||
|
|
25d8d76524 | ||
|
|
cc0f99333f | ||
|
|
982aa874f2 | ||
|
|
2a70964dce | ||
|
|
3051a185e5 | ||
|
|
5e788fff99 | ||
|
|
326c52604b | ||
|
|
e7d1647769 | ||
|
|
1e35116d8f | ||
|
|
35ca3ec895 | ||
|
|
3435684c87 | ||
|
|
7c30cccc97 | ||
|
|
4194abe4f2 | ||
|
|
0b698576da | ||
|
|
3fbd73129e | ||
|
|
bbd6d171be | ||
|
|
f7929bbf93 | ||
|
|
29cd8530a3 | ||
|
|
574387acac | ||
|
|
6a1ab4d73c | ||
|
|
29e0c32679 | ||
|
|
db7fe023c6 | ||
|
|
bed702d8de | ||
|
|
ccf3d7a285 | ||
|
|
e4f755416d | ||
|
|
4d5b0731be | ||
|
|
fde6ea1c83 | ||
|
|
7a94a2f087 | ||
|
|
97b8f742dd | ||
|
|
06733ea7cd | ||
|
|
efa5120fac | ||
|
|
80ab6bbda2 | ||
|
|
53620b9f1b | ||
|
|
259b405526 | ||
|
|
c6fe19c321 | ||
|
|
9d545004cb | ||
|
|
7fe419ecb0 | ||
|
|
55ddf9cc38 | ||
|
|
38292bcda7 | ||
|
|
08062e8ce8 | ||
|
|
bff35de39f | ||
|
|
394e6b08ad | ||
|
|
d61a86cad1 | ||
|
|
43198eb263 | ||
|
|
8493e51070 | ||
|
|
07eeb76a5f | ||
|
|
6ee6a03e4a | ||
|
|
8e3eb98789 | ||
|
|
c5b23816e9 | ||
|
|
0a3cd4f8e4 | ||
|
|
7882dead81 | ||
|
|
44f96dd6a3 | ||
|
|
a442afd8d2 | ||
|
|
bdbc57b926 | ||
|
|
9ed53ba064 | ||
|
|
9d372301ed | ||
|
|
b483513fa8 | ||
|
|
578c561473 | ||
|
|
f6134a6bd3 | ||
|
|
fb59d5d268 | ||
|
|
2758b6ffd9 | ||
|
|
fa99dea8fe | ||
|
|
6ced56301c | ||
|
|
008134fde8 | ||
|
|
3ed593e4b6 | ||
|
|
1fc5182979 | ||
|
|
9ebafddac2 | ||
|
|
26467187c4 | ||
|
|
69e256ab86 | ||
|
|
b4b12e68bf | ||
|
|
768216d9bc | ||
|
|
f29d54ad0d | ||
|
|
946309a485 | ||
|
|
7c98336148 | ||
|
|
455b0efa71 | ||
|
|
05cf14846c | ||
|
|
9ddcb036cf | ||
|
|
185e06ed79 | ||
|
|
17ae6bf89d | ||
|
|
7efc1a0366 | ||
|
|
899dc5b680 | ||
|
|
5126c85623 | ||
|
|
9ec23ceed6 | ||
|
|
a6d156438f | ||
|
|
23e4915d60 | ||
|
|
5ecfe05f3b | ||
|
|
d35192d50f | ||
|
|
e2f9ce0fc5 | ||
|
|
8f55741c3e | ||
|
|
b7dc6d6cce | ||
|
|
8fb8a5d89a | ||
|
|
dc22c2763e | ||
|
|
a77863d3c5 | ||
|
|
0c8e0ed3dd | ||
|
|
fb7751eaae | ||
|
|
56795f8d26 | ||
|
|
741d3050ad | ||
|
|
0ff0fd7ced | ||
|
|
b9b287d3b2 | ||
|
|
dc089ba84a | ||
|
|
55d2acdf13 | ||
|
|
3a64efd136 | ||
|
|
4e439792ec | ||
|
|
895889d27a | ||
|
|
d2777f5915 | ||
|
|
9b878bd1cc | ||
|
|
73a08fd119 | ||
|
|
7b9b3dabbe | ||
|
|
163215d5c9 | ||
|
|
7cc9fa6d30 | ||
|
|
2d38d7af82 | ||
|
|
26e9f652b6 | ||
|
|
19afc2274a | ||
|
|
16fcc60a59 | ||
|
|
1b44fe8fec | ||
|
|
028e1d63a3 | ||
|
|
e1e825f350 | ||
|
|
65a4aff5fc | ||
|
|
8f95f2ba12 | ||
|
|
991e0d5e5b | ||
|
|
84cf63d1ba | ||
|
|
60009476d6 | ||
|
|
1894fc7cfa | ||
|
|
c9c24c3464 | ||
|
|
cb731176eb | ||
|
|
1ee14a76f4 | ||
|
|
e9945235ed | ||
|
|
60b29a3bf5 | ||
|
|
3eb209b602 | ||
|
|
d1cce44616 | ||
|
|
c02638e10e | ||
|
|
ddbdc2a27f | ||
|
|
f312c122ca | ||
|
|
1d6a421388 | ||
|
|
6e40e4e994 | ||
|
|
2149576289 | ||
|
|
96891a5e5c | ||
|
|
2771cab71a | ||
|
|
d0ab813520 | ||
|
|
1b1c0ff9e4 | ||
|
|
083696a899 | ||
|
|
1376c26def | ||
|
|
e13cfad9da | ||
|
|
723cb3b546 | ||
|
|
dac7a6497f | ||
|
|
ea8bc687c0 | ||
|
|
c98958053c | ||
|
|
5f1ed511ea | ||
|
|
61b7c279d6 | ||
|
|
4c84b18bb6 | ||
|
|
484eb3a7c4 | ||
|
|
f73880e565 | ||
|
|
36cca0d871 | ||
|
|
08d2dbc9bb | ||
|
|
ce13902680 | ||
|
|
e818170eec | ||
|
|
91b6a0bf69 | ||
|
|
85a6edb1fd | ||
|
|
7d14122746 | ||
|
|
aa14d9626f | ||
|
|
98f072619f | ||
|
|
150427cd39 | ||
|
|
3295685938 | ||
|
|
ca4ce569e7 | ||
|
|
ca9edf2bc9 | ||
|
|
be387ad892 | ||
|
|
9b9959da9a | ||
|
|
234a698538 | ||
|
|
fbf1c10077 | ||
|
|
4d0dcc5876 | ||
|
|
4e909dc369 | ||
|
|
ac1d0a5502 | ||
|
|
d89a4a1218 | ||
|
|
71759386c5 | ||
|
|
fdbf94f415 | ||
|
|
ad4115acc8 | ||
|
|
432a8f2338 | ||
|
|
b994363972 | ||
|
|
2a81321ead | ||
|
|
dd7f5fd228 | ||
|
|
047791413e | ||
|
|
358fa7b20f | ||
|
|
c937ccc92b | ||
|
|
e796c3dfba | ||
|
|
0f3e4c289c | ||
|
|
e0846ce00e | ||
|
|
30e77556db | ||
|
|
3e4e54870b | ||
|
|
e90185b553 | ||
|
|
4a82c14808 | ||
|
|
80371233c9 | ||
|
|
09314c8926 | ||
|
|
0e67e0d87e | ||
|
|
c21ad48370 | ||
|
|
9e3ba85b72 | ||
|
|
c82d936e96 | ||
|
|
7b4603e33e | ||
|
|
84a7ab8568 | ||
|
|
beaea73276 | ||
|
|
ef1c1d8ced | ||
|
|
91425050e4 | ||
|
|
41d05d6de0 | ||
|
|
376d0663c2 | ||
|
|
231a133f23 | ||
|
|
eacc945254 | ||
|
|
16b5bb595c | ||
|
|
a1ad6ca289 | ||
|
|
a8523f41b3 | ||
|
|
1d6905cb25 | ||
|
|
a548bd7ffd | ||
|
|
46e0151c28 | ||
|
|
23b315c58f | ||
|
|
ac37f903d4 | ||
|
|
5572c0798f | ||
|
|
cb5e300534 | ||
|
|
50e0284084 | ||
|
|
e08788190d | ||
|
|
44441ae928 | ||
|
|
e42e1e8751 | ||
|
|
ae4b743ea4 | ||
|
|
370b6bb2f2 | ||
|
|
796141f2b8 | ||
|
|
2711181e19 | ||
|
|
2cd7f0f74c | ||
|
|
96e7910cf9 | ||
|
|
4683d959a5 | ||
|
|
9300adf374 | ||
|
|
5c9ec92cc5 | ||
|
|
76e2309778 | ||
|
|
9fc633080a | ||
|
|
8952cb4e00 | ||
|
|
d6e009ce78 | ||
|
|
a106c728ba | ||
|
|
5cddc9836f | ||
|
|
2728fa2b8d | ||
|
|
2293253558 | ||
|
|
ee7248204f | ||
|
|
0c97a44a2a | ||
|
|
0c49ed1fec | ||
|
|
dd15bf7328 | ||
|
|
3aa5fda695 | ||
|
|
e880d94f51 | ||
|
|
0647fa832a | ||
|
|
4af83eadc4 | ||
|
|
cc2c249a07 | ||
|
|
152bcf451a | ||
|
|
83879fb931 | ||
|
|
8d703a3fb4 | ||
|
|
022d57ef42 | ||
|
|
4928f875b3 | ||
|
|
840430c189 | ||
|
|
024cc88738 | ||
|
|
eee0cf569e | ||
|
|
371c78b1d3 | ||
|
|
6988ae83c9 | ||
|
|
f95705d2d6 | ||
|
|
ff3caec36c | ||
|
|
4c4dac8e90 | ||
|
|
beaa62c9a9 | ||
|
|
69fe8bc9b5 | ||
|
|
092a973f9a | ||
|
|
55b0f6e950 | ||
|
|
b9df489962 | ||
|
|
144127224c | ||
|
|
2202f90d74 | ||
|
|
860e0227af | ||
|
|
c4b4976be0 | ||
|
|
a2b0305162 | ||
|
|
6404907699 | ||
|
|
d4b02e36a7 | ||
|
|
71c4145ea2 | ||
|
|
075f0a1bb0 | ||
|
|
d80bd3661d | ||
|
|
44f4441372 | ||
|
|
782e060448 | ||
|
|
8c223b9fb8 | ||
|
|
1232f93f1a | ||
|
|
8f3c5b5cea | ||
|
|
c4d3023fd3 | ||
|
|
a97c7cada4 | ||
|
|
5b0cd974ac | ||
|
|
bb5804cde3 | ||
|
|
7819757759 | ||
|
|
b861e261ed | ||
|
|
17b32d6518 | ||
|
|
d2359046c4 | ||
|
|
8a700170b0 | ||
|
|
8c68e29bf3 | ||
|
|
1a81631886 | ||
|
|
634fe2c458 | ||
|
|
6cc8fca506 | ||
|
|
053d46144e | ||
|
|
b2e7bb6be1 | ||
|
|
31689cd947 | ||
|
|
d855b930c5 | ||
|
|
61545ea13e | ||
|
|
21aa23e7f5 | ||
|
|
f197124ee5 | ||
|
|
b76fef1e44 | ||
|
|
9f36f4fbe7 | ||
|
|
a76bf1d0b2 | ||
|
|
6cbbfa6499 | ||
|
|
bf5f845789 | ||
|
|
d7eec3b92b | ||
|
|
bae709df5b | ||
|
|
ba33de815f | ||
|
|
1b495ebad1 | ||
|
|
4e0289b341 | ||
|
|
866d95149e | ||
|
|
e9bbe9fca0 | ||
|
|
8da0e9adb2 | ||
|
|
f0e78f693f | ||
|
|
9333ed5be4 | ||
|
|
a244fbee4d | ||
|
|
9bc2f7dce4 | ||
|
|
056fce9187 | ||
|
|
9f034c7e7e | ||
|
|
2704258dba | ||
|
|
3d5caf18e3 | ||
|
|
e45f7598db | ||
|
|
09b72588d8 | ||
|
|
a0f80e740e | ||
|
|
a6de4e3742 | ||
|
|
2d6a375afc | ||
|
|
585e5d0199 | ||
|
|
fcb4cb38a9 | ||
|
|
de5e8f8e57 | ||
|
|
11f360bdab | ||
|
|
ebc79c278b | ||
|
|
b2fef7b7a8 | ||
|
|
71524fe649 | ||
|
|
55d2768807 | ||
|
|
3c7dda02c6 | ||
|
|
6ed182002b | ||
|
|
ee1738c9d4 | ||
|
|
068c94da4e | ||
|
|
2ec769981a | ||
|
|
548664f6ce | ||
|
|
9d54f71dbb | ||
|
|
d102144746 | ||
|
|
3d7a3f27d5 | ||
|
|
46448bc5c7 | ||
|
|
6a2e45988f | ||
|
|
2f8f1f0b9a | ||
|
|
d572fdac9b | ||
|
|
ac41ed1af4 | ||
|
|
f47bb6bcd0 | ||
|
|
a3eb5e2928 | ||
|
|
53cb36dd8a | ||
|
|
9cda361523 | ||
|
|
1a70071405 | ||
|
|
b648fb7446 | ||
|
|
aaef0777b0 | ||
|
|
68d287ed82 | ||
|
|
641e4080bc | ||
|
|
a80120278e | ||
|
|
d4bf3ef6fd | ||
|
|
ca5c374ecd | ||
|
|
69ea8229ca | ||
|
|
4d19b87fff | ||
|
|
8847047fd1 | ||
|
|
6e8a5015c9 | ||
|
|
e8919ee340 | ||
|
|
f8f506a8be | ||
|
|
74756db7e6 | ||
|
|
96d9e101cc | ||
|
|
7eb3693804 | ||
|
|
cad2b831ed | ||
|
|
b2dc849e52 | ||
|
|
6489ad4114 | ||
|
|
0de8bfeba6 | ||
|
|
6710d99878 | ||
|
|
7a32d902ec | ||
|
|
52f699c175 | ||
|
|
ba211e3cbd | ||
|
|
897f41bc7a | ||
|
|
2834850337 | ||
|
|
67cd877281 | ||
|
|
6e18bc9e04 | ||
|
|
6d0b36e9b9 | ||
|
|
bd8aa8163d | ||
|
|
febaec1b1e | ||
|
|
2ac790693a | ||
|
|
08dce3bcdc | ||
|
|
806dc78d2b | ||
|
|
e5d4755619 | ||
|
|
c44befb957 | ||
|
|
871e849660 | ||
|
|
43b34aa279 | ||
|
|
6b1e5b4169 | ||
|
|
952bcd853e | ||
|
|
77446a71e2 | ||
|
|
d722f37468 | ||
|
|
9757836067 | ||
|
|
7d80a5a7f7 | ||
|
|
a9e8115088 | ||
|
|
f92dc6f4b4 | ||
|
|
e43ab51b7d | ||
|
|
6a68e9c118 | ||
|
|
95cb6d132b | ||
|
|
ed95b59003 | ||
|
|
5730769a19 | ||
|
|
2a67008531 | ||
|
|
651230d40f | ||
|
|
28c5fd4583 | ||
|
|
944e7c6e3d | ||
|
|
3094fe2855 | ||
|
|
deb0ee3d29 | ||
|
|
23076727c7 | ||
|
|
42072f2584 | ||
|
|
b50ffa087d | ||
|
|
03b74b582e | ||
|
|
4af5341f81 | ||
|
|
77ab0706be | ||
|
|
1d6094e893 | ||
|
|
af29ca92cc | ||
|
|
c83bfe0b16 | ||
|
|
891ce8a33d | ||
|
|
c356e64be5 | ||
|
|
245f7256e1 | ||
|
|
e0a0b82958 | ||
|
|
2b4a78ea28 | ||
|
|
33a1e29a0c | ||
|
|
8a76d8322f | ||
|
|
1ff9b24818 | ||
|
|
4613aef1c8 | ||
|
|
7ff608ff0b | ||
|
|
87aa4622b4 | ||
|
|
188126a895 | ||
|
|
f57fb5006d | ||
|
|
6c1e13b6e5 | ||
|
|
344622b1c1 | ||
|
|
20b8269766 | ||
|
|
810f868b67 | ||
|
|
9c99ec3410 | ||
|
|
2ea200be78 | ||
|
|
8831f3241c | ||
|
|
3752322c01 | ||
|
|
fa87187849 | ||
|
|
662f87080c | ||
|
|
6003591ecd | ||
|
|
c618317a76 | ||
|
|
5d689551e3 | ||
|
|
c9e7be28af | ||
|
|
346fb8fb11 | ||
|
|
3fdcea78e4 | ||
|
|
fb2d1e7953 | ||
|
|
ce19bcd364 | ||
|
|
610afc7702 | ||
|
|
6557792a98 | ||
|
|
a3e464aea3 | ||
|
|
087f2aee09 | ||
|
|
88d8431985 | ||
|
|
ea22f3f81c | ||
|
|
93d8c171be | ||
|
|
b2e01cd52b | ||
|
|
9afe499075 | ||
|
|
91fe0b0985 | ||
|
|
90aab92a59 | ||
|
|
d613d00bca | ||
|
|
c15c277b03 | ||
|
|
a86c4a8309 | ||
|
|
4b7f82a9d9 | ||
|
|
c33c3fb2fa | ||
|
|
07f3d48a9d | ||
|
|
f5a6159e1d | ||
|
|
3656ab977b | ||
|
|
891506ab52 | ||
|
|
37f9a5d9f2 | ||
|
|
958c5ebcc6 | ||
|
|
b8afdda856 | ||
|
|
2c250a2740 | ||
|
|
512b66cb04 | ||
|
|
a11cec9fdc | ||
|
|
81e5a8c925 | ||
|
|
a12f369bda | ||
|
|
ec2f88ebc0 | ||
|
|
c449492a33 | ||
|
|
5614aceaa8 | ||
|
|
d6e7dfc648 | ||
|
|
b84222e171 | ||
|
|
8e785e62e3 | ||
|
|
4977c22b08 | ||
|
|
5c0bc1cf84 | ||
|
|
ddbaee228a | ||
|
|
c858707c39 | ||
|
|
83bca7fb10 | ||
|
|
7d19518ba8 | ||
|
|
9775b79a0b | ||
|
|
e1dfd91e24 | ||
|
|
b4351208cc | ||
|
|
ae1e9a861b | ||
|
|
ab799c83ee | ||
|
|
4118e53d7d | ||
|
|
384b464f0f | ||
|
|
ecacd47523 | ||
|
|
334ac26f0d | ||
|
|
e94e202cef | ||
|
|
7cf120e2e1 | ||
|
|
0f8e2a9b1b | ||
|
|
c70bc5baff | ||
|
|
e7b3f12b71 | ||
|
|
a03882de76 | ||
|
|
d9a4a8d6de | ||
|
|
4c48f34d61 | ||
|
|
ebb6df4696 | ||
|
|
7033ae4f2e | ||
|
|
0cc600de6d | ||
|
|
c1278194ce | ||
|
|
50bdcea81b | ||
|
|
c5fa8f560c | ||
|
|
6d5276c0c6 | ||
|
|
4405bd95f9 | ||
|
|
3bb3fcfbda | ||
|
|
5e0101e424 | ||
|
|
2c96ecac87 | ||
|
|
9fcddc37f6 | ||
|
|
1fd2b3fff8 | ||
|
|
39066bfee3 | ||
|
|
2d75efbace | ||
|
|
8a8403834f | ||
|
|
e98b88f673 | ||
|
|
d2f8d4a306 | ||
|
|
2138530f3e | ||
|
|
94d94684c8 | ||
|
|
550164cf5e | ||
|
|
5352918ff8 | ||
|
|
57b6807333 | ||
|
|
e3171d9ee5 | ||
|
|
8ef49d2ec4 | ||
|
|
3ce4769e8d | ||
|
|
abb244c940 | ||
|
|
4825efb582 | ||
|
|
2195b8932e | ||
|
|
81c406bb60 | ||
|
|
9d28807796 | ||
|
|
6dbabf2935 | ||
|
|
4018e4df79 | ||
|
|
8835216ca9 | ||
|
|
04ab99c8ad | ||
|
|
1bc210c9a9 | ||
|
|
6250b457ad | ||
|
|
460c824117 | ||
|
|
77c2a98304 | ||
|
|
8ad8196d70 | ||
|
|
fa4410bea3 | ||
|
|
af23d62568 | ||
|
|
e241273a1e | ||
|
|
269efc98c3 | ||
|
|
2c3a3845ac | ||
|
|
4bf0ae0a9d | ||
|
|
a3ead3aa6d | ||
|
|
d965736751 | ||
|
|
437a6cf476 | ||
|
|
a91a57581f | ||
|
|
be0d1c19fa | ||
|
|
447e1bf435 | ||
|
|
6a62f4d3fb | ||
|
|
f507722f43 | ||
|
|
0030b9c3ac | ||
|
|
4db7a6782b | ||
|
|
bb3be3d495 | ||
|
|
fe28648a88 | ||
|
|
2b393355ad | ||
|
|
79f5c6a008 | ||
|
|
f75ee48795 | ||
|
|
81d9727d03 | ||
|
|
4ac3573ab1 | ||
|
|
92b79f1731 | ||
|
|
32b623e82b | ||
|
|
285a0d5f47 | ||
|
|
308fd8d4b0 | ||
|
|
4000855f45 | ||
|
|
ca777790d4 | ||
|
|
e15a212b14 | ||
|
|
8c6863e2ad | ||
|
|
5e329e62b3 | ||
|
|
2582e87ffa | ||
|
|
1c0822ffb3 | ||
|
|
9d0877e985 | ||
|
|
a6fb4a8271 | ||
|
|
9adf0b3611 | ||
|
|
e3896da3c4 | ||
|
|
f5ad7dc2dc | ||
|
|
d0af14c40f | ||
|
|
d8fb575d46 | ||
|
|
aaf0618d24 | ||
|
|
92d1dcb3d4 | ||
|
|
a9e93a5ace | ||
|
|
e9ae59ad00 | ||
|
|
056b80939e | ||
|
|
3a4f63848d | ||
|
|
907f39c73f | ||
|
|
91f60000b3 | ||
|
|
0b6c2df5b6 | ||
|
|
ac27d35ff5 | ||
|
|
c62905b9a8 | ||
|
|
2974fb0f4e | ||
|
|
df73df311b | ||
|
|
b0575e969f | ||
|
|
547a472016 | ||
|
|
d67dd21c56 | ||
|
|
59187f9ff4 | ||
|
|
da8a32047c | ||
|
|
4c93ef4bb3 | ||
|
|
e9b8295bf1 | ||
|
|
14475fdc67 | ||
|
|
21cf845c02 | ||
|
|
2cea7405b5 | ||
|
|
dff067c1a7 | ||
|
|
a507ab0e07 | ||
|
|
1ee1cada9e | ||
|
|
1584f3771b | ||
|
|
1ec423c11d | ||
|
|
c3611c3047 | ||
|
|
41e57bcb6b | ||
|
|
057b0e163c | ||
|
|
3840e4c214 | ||
|
|
715900d0ef | ||
|
|
e9cbfbe7f8 | ||
|
|
a14d8e2b41 | ||
|
|
d57f4cebff | ||
|
|
cbe54d0bbe | ||
|
|
2034f0a7c2 | ||
|
|
ce5429a531 | ||
|
|
df11ef4aca | ||
|
|
8ecc0b3cd9 | ||
|
|
5d2f4bac76 | ||
|
|
bb73ddc58f | ||
|
|
0f91f02508 | ||
|
|
ce072937e4 | ||
|
|
3b5b25fc86 | ||
|
|
170ab9e93b | ||
|
|
a67370bb83 | ||
|
|
42cc97f86b | ||
|
|
e3440ad773 | ||
|
|
5e73e68ef7 | ||
|
|
c16434e608 | ||
|
|
9e39e53488 | ||
|
|
1a7b098282 | ||
|
|
2184286a78 | ||
|
|
3583eb6aa9 | ||
|
|
adff40a4e7 | ||
|
|
f7064c5c0e | ||
|
|
208b6515a4 | ||
|
|
66e54f7bd2 | ||
|
|
d0baf76599 | ||
|
|
1757db3051 | ||
|
|
c7cfca8437 | ||
|
|
3efb50b103 | ||
|
|
be5c382ace | ||
|
|
d06f08e156 | ||
|
|
43f7750658 | ||
|
|
65ad46ab38 | ||
|
|
432d24dc94 | ||
|
|
6331dff484 | ||
|
|
961a7a2e03 | ||
|
|
c7683dfd80 | ||
|
|
de11e85d2b | ||
|
|
0455aaa4cd | ||
|
|
e2cf3a5a98 | ||
|
|
f7712f2b40 | ||
|
|
41bf436c3a | ||
|
|
9aee88f9f1 | ||
|
|
94a3c5853b | ||
|
|
55ea84a056 | ||
|
|
2828ccda7f | ||
|
|
465a25145d | ||
|
|
d64eaab0b9 | ||
|
|
fad9d2fd3a | ||
|
|
dd92e5d773 | ||
|
|
2b35dce037 | ||
|
|
a777e8e42a | ||
|
|
88eb6abdd6 | ||
|
|
e4d4245b6c | ||
|
|
874378869d | ||
|
|
dd6bd6bbff | ||
|
|
d946aceacb | ||
|
|
e3691cc0e3 | ||
|
|
db7518025d | ||
|
|
ac4bfc9bac | ||
|
|
63b95e71a7 | ||
|
|
9e5923004f | ||
|
|
d01eb30ef2 | ||
|
|
b585c2ac22 | ||
|
|
74a09301a7 | ||
|
|
07799d9b01 | ||
|
|
48ba80c6e2 | ||
|
|
74f99f0d48 | ||
|
|
f396ef4fa0 | ||
|
|
de8207c5a6 | ||
|
|
5f114163dc | ||
|
|
5ce2bc862c | ||
|
|
6db144e5ed | ||
|
|
fc383664c7 | ||
|
|
bc3640893c | ||
|
|
5361e42976 | ||
|
|
e81b1b8115 | ||
|
|
421b30c1d8 | ||
|
|
2e6dacf539 | ||
|
|
c22b4a1de2 | ||
|
|
a06a8c648e | ||
|
|
bb719d6211 | ||
|
|
5a49ce2028 | ||
|
|
e8da04d4ab | ||
|
|
112e656f40 | ||
|
|
77a2fd6e36 | ||
|
|
3613e6f3d3 | ||
|
|
eff333cbaf | ||
|
|
ba0f9360f9 | ||
|
|
a8565dc2c2 | ||
|
|
9f5c19244d | ||
|
|
7cc4873dd4 | ||
|
|
03a031091f | ||
|
|
14359d9acf | ||
|
|
bfbc715977 | ||
|
|
6161911ff1 | ||
|
|
162b0cfa6c | ||
|
|
94ccc013d7 | ||
|
|
239ec12529 | ||
|
|
99bcf0484a | ||
|
|
6e80a2f9fb | ||
|
|
4cca8f0600 | ||
|
|
b9ca4e7f9b | ||
|
|
464a686c04 | ||
|
|
f0439da293 | ||
|
|
f545e41d10 | ||
|
|
7d14aef393 | ||
|
|
9a0f6018a7 | ||
|
|
0a44dbd921 | ||
|
|
fa2d0f5ed7 | ||
|
|
d889d39151 | ||
|
|
8daf6e822e | ||
|
|
c40d9d9a7c | ||
|
|
e12a6e65a6 | ||
|
|
a92820e910 | ||
|
|
080dd88509 | ||
|
|
44d64e4831 | ||
|
|
6f2306439c | ||
|
|
2c6b896989 | ||
|
|
4e1d85a5f4 | ||
|
|
09aa28a943 | ||
|
|
faff32203c | ||
|
|
77280961ef | ||
|
|
5f7f88d299 | ||
|
|
50bc1b0347 | ||
|
|
166fdbd406 | ||
|
|
a6920122e6 | ||
|
|
e677692594 | ||
|
|
459c9a3bb1 | ||
|
|
9544ee2140 | ||
|
|
45cd05184b | ||
|
|
e8aa521a1e | ||
|
|
c4c0e105cf | ||
|
|
69031bb8e1 | ||
|
|
19ced21b20 | ||
|
|
46b55822dc | ||
|
|
4f20d22a4f | ||
|
|
8f51450f7e | ||
|
|
94a294e147 | ||
|
|
4acfc15705 | ||
|
|
73b555eb9b | ||
|
|
d93fa72e48 | ||
|
|
bbb26002e4 | ||
|
|
1ab1059b06 | ||
|
|
7b67e05e50 | ||
|
|
c7fcb00b81 | ||
|
|
2b66d0ea06 | ||
|
|
f3b779e50c | ||
|
|
9e348ecc99 | ||
|
|
78fe0ab7e3 | ||
|
|
fc9f2864d8 | ||
|
|
1e642bba8f | ||
|
|
a5994140e2 | ||
|
|
018b47ab6b | ||
|
|
f4f51dbf6b | ||
|
|
43e75401d7 | ||
|
|
8b45ed9c0c | ||
|
|
88a3548d7e | ||
|
|
3ddc95d4b5 | ||
|
|
08a682efc2 | ||
|
|
32afe57e18 | ||
|
|
43465f7c4b | ||
|
|
351daacca0 | ||
|
|
0926fbcbc6 | ||
|
|
59a45530a8 | ||
|
|
cf2998eeec | ||
|
|
6f1508acc1 | ||
|
|
edb88027a4 | ||
|
|
5111551c89 | ||
|
|
6891826c78 | ||
|
|
e68d63ea71 | ||
|
|
d83d241c39 | ||
|
|
9950f464ce | ||
|
|
676a2db1f5 | ||
|
|
62ed2221e9 | ||
|
|
60f6093357 | ||
|
|
ed893b995d | ||
|
|
3ebc94ab8e | ||
|
|
cd7ad03cf0 | ||
|
|
0f6ce233bd | ||
|
|
a14890f163 | ||
|
|
213a8c69fb | ||
|
|
2500486186 | ||
|
|
9cd15fd362 | ||
|
|
efdfbbaf5e | ||
|
|
87aa3fbfe8 | ||
|
|
ea3f2fbfce | ||
|
|
7d68d79fc3 | ||
|
|
6f6a750373 | ||
|
|
993530dbcb | ||
|
|
7b4ca6dcef | ||
|
|
ae1d5667cc | ||
|
|
90a51dc44a | ||
|
|
caf1ef653f | ||
|
|
8cba56b2d5 | ||
|
|
2fed88e840 | ||
|
|
1df407ca96 | ||
|
|
f4c3aa8b89 | ||
|
|
cc92e4be75 | ||
|
|
aa866bbe13 | ||
|
|
18ec8009a1 | ||
|
|
c6d7f0e352 | ||
|
|
038e820815 | ||
|
|
605143ef7e | ||
|
|
3cb9470db2 | ||
|
|
7c21624e09 | ||
|
|
47c58df2a4 | ||
|
|
e6a2cc16a4 | ||
|
|
66f9e98499 | ||
|
|
4f8d82dae7 | ||
|
|
66f88576e1 | ||
|
|
3c2ae03cea | ||
|
|
a2d8518724 | ||
|
|
20e4562c09 | ||
|
|
68eadcb24f | ||
|
|
0a3b244f44 | ||
|
|
19ea7e8b2f | ||
|
|
c447279c75 | ||
|
|
b1477d8087 | ||
|
|
7f7c803d9e | ||
|
|
41b5374027 | ||
|
|
27d28f7baf | ||
|
|
50aef6ab65 | ||
|
|
ecff4c5dce | ||
|
|
c380400578 | ||
|
|
92e07c3b54 | ||
|
|
0756de25f8 | ||
|
|
1773de88f5 | ||
|
|
b534f5b736 | ||
|
|
727d6b78ce | ||
|
|
2dbcb4c2a2 | ||
|
|
a399363b08 | ||
|
|
7ac78cb103 | ||
|
|
ec217d8201 | ||
|
|
013fc2fc9c | ||
|
|
8cf2d4f3a4 | ||
|
|
ebcb820335 | ||
|
|
43963fa09b | ||
|
|
b17067b8da | ||
|
|
66f92405e2 | ||
|
|
288c5c7fc4 | ||
|
|
6383dc0952 | ||
|
|
0008a2aa48 | ||
|
|
d7d56db1af | ||
|
|
60f9b47115 | ||
|
|
4729801fca | ||
|
|
136a48a18f | ||
|
|
5c31830edb | ||
|
|
17ab753c2b | ||
|
|
422f4ee6c2 | ||
|
|
a988292253 | ||
|
|
dcb913d9fa | ||
|
|
d2d1eed68a | ||
|
|
e7085571bf | ||
|
|
28691e2bf2 | ||
|
|
e15d93e8a4 | ||
|
|
a16f4393b9 | ||
|
|
3681c17f4b | ||
|
|
abcd92a6b1 | ||
|
|
dd4930e055 | ||
|
|
4a58a429d4 | ||
|
|
142086b2c3 | ||
|
|
1e25e543b3 | ||
|
|
a1e75c6e03 | ||
|
|
ca2612937e | ||
|
|
1e6673f6b6 | ||
|
|
6d821660c9 | ||
|
|
67138ac629 | ||
|
|
b816e0ed32 | ||
|
|
d9aa94025a | ||
|
|
b464181213 | ||
|
|
ef901dbd5e | ||
|
|
0cb816c16d | ||
|
|
4d3142e826 | ||
|
|
fc29e8fb6b | ||
|
|
fb36ab0e41 | ||
|
|
aa83f1bbd3 | ||
|
|
7bc91e7224 | ||
|
|
ca52f4f8ea | ||
|
|
ede42e42b1 | ||
|
|
f0087e11b0 | ||
|
|
5519cdfd7c | ||
|
|
68e3566b8b | ||
|
|
13131a0226 | ||
|
|
92254a175e | ||
|
|
48747d9553 | ||
|
|
fde6126ac6 | ||
|
|
7db82a6af1 | ||
|
|
7709d219a9 | ||
|
|
3bef80932d | ||
|
|
439e5ee6a1 | ||
|
|
2de1c92ee8 | ||
|
|
f7d0383919 | ||
|
|
84b7a2de0b | ||
|
|
797ba3ef9b | ||
|
|
374653d9f6 | ||
|
|
22c8a2b538 | ||
|
|
47320330ad | ||
|
|
4b2a4c8fa3 | ||
|
|
c2332331ce | ||
|
|
81a604dca2 | ||
|
|
b547f1cd7e | ||
|
|
931deec6bd | ||
|
|
aed94c8e91 | ||
|
|
277ff12822 | ||
|
|
3329216e3c | ||
|
|
c12cbbca2e | ||
|
|
172372d4c0 | ||
|
|
6d427cdc9c | ||
|
|
e1df3efd6e | ||
|
|
2a4849cf8f | ||
|
|
33a2f8d788 | ||
|
|
fee99a081b | ||
|
|
d263dd52e9 | ||
|
|
47e0c2c75b | ||
|
|
5b25a42f32 | ||
|
|
d64dd29ca9 | ||
|
|
6e1e3772b9 | ||
|
|
b0336e1f7b | ||
|
|
ed3d571793 | ||
|
|
3d043adb03 | ||
|
|
7f624b5c61 | ||
|
|
d70910fc0d | ||
|
|
69ac552881 | ||
|
|
275438673d | ||
|
|
404e2bf0b3 | ||
|
|
708ba3d7ac | ||
|
|
9e47318e09 | ||
|
|
f0eaf9aa20 | ||
|
|
cf78861396 | ||
|
|
517751c116 | ||
|
|
2fd6344c44 | ||
|
|
1be993f8b1 | ||
|
|
942c62bf1d | ||
|
|
31fa4a8c8b | ||
|
|
d39694af59 | ||
|
|
68d8a49466 | ||
|
|
99d9d77c63 | ||
|
|
4da3270d34 | ||
|
|
6f07b4ea80 | ||
|
|
29f421d867 | ||
|
|
40ddcb89fc | ||
|
|
e75284ce97 | ||
|
|
7482122964 | ||
|
|
d3345c0fa6 | ||
|
|
237ef2a205 | ||
|
|
0f7596bacf | ||
|
|
6e88d3a04c | ||
|
|
59022904fb | ||
|
|
94c5004c33 | ||
|
|
23d531a664 | ||
|
|
19febde547 | ||
|
|
741d67c30b | ||
|
|
507f3c06e7 | ||
|
|
c16a24a59a | ||
|
|
e446f47e2c | ||
|
|
146394f3ca | ||
|
|
9d7214702f | ||
|
|
861d5f0064 | ||
|
|
34c4f23e49 | ||
|
|
48a094d22d | ||
|
|
f143197f1f | ||
|
|
fdeaac7f65 | ||
|
|
cbb68acd75 | ||
|
|
31165c4ce6 | ||
|
|
77ed530de7 | ||
|
|
f509d9acd0 | ||
|
|
6c47df20af | ||
|
|
dca402eb18 | ||
|
|
fbfe792a93 | ||
|
|
868f18fd21 | ||
|
|
5ae823b25c | ||
|
|
2de16985d3 | ||
|
|
2ca8ff4db1 | ||
|
|
ee6717ef69 | ||
|
|
7c2f0ed7b9 | ||
|
|
161b8cdabb | ||
|
|
1f7ddc081a | ||
|
|
df0dcc587f | ||
|
|
e1ae80583f | ||
|
|
2adc45fc19 | ||
|
|
2e92757df6 | ||
|
|
c6765a48c5 | ||
|
|
6a345c4b8a | ||
|
|
044f1f63c0 | ||
|
|
9945243a23 | ||
|
|
d1261fc841 | ||
|
|
e87dc6d34c | ||
|
|
70cba4bbdf | ||
|
|
93311b8b98 | ||
|
|
3b9201ed0e | ||
|
|
044aef8414 | ||
|
|
3234b19790 | ||
|
|
f14325d12b | ||
|
|
db49c54681 | ||
|
|
c54d9b777d | ||
|
|
52a3cd6ae4 | ||
|
|
9b8d73ef86 | ||
|
|
f12f46981b | ||
|
|
9fb8ba2ff1 | ||
|
|
fe114d2e66 | ||
|
|
56ed474d8c | ||
|
|
a595effbe9 | ||
|
|
ee8c430d85 | ||
|
|
0683c4a963 | ||
|
|
833944bebb | ||
|
|
2a8a0afd09 | ||
|
|
61ad112451 | ||
|
|
129cc86e3b | ||
|
|
645954bc7c | ||
|
|
ecd020eec2 | ||
|
|
8fb5dbb980 | ||
|
|
cef0353642 | ||
|
|
e3727aaefe | ||
|
|
85781c5b7f | ||
|
|
62784d0708 | ||
|
|
48a62ddd2b | ||
|
|
04af15cba5 | ||
|
|
65a3126f15 | ||
|
|
82d7a0163d | ||
|
|
5b200fd6a2 | ||
|
|
b79c986fc9 | ||
|
|
8f4e13072c | ||
|
|
d517daa045 | ||
|
|
3171d9d64d | ||
|
|
0ea2951515 | ||
|
|
0612e71166 | ||
|
|
f9b24bd01c | ||
|
|
65eb8e7c43 | ||
|
|
c7795640e1 | ||
|
|
2eea63dd1a | ||
|
|
7e1ee087d3 | ||
|
|
8fd37dbad5 | ||
|
|
2e68d41dcc | ||
|
|
1eddc9de33 | ||
|
|
ca1a97a52e | ||
|
|
b14c98b76e | ||
|
|
d89bf772a6 | ||
|
|
688d649c4a | ||
|
|
002a6f1e52 | ||
|
|
4a61eba3b9 | ||
|
|
e1161037a5 | ||
|
|
6e840a439e | ||
|
|
29addbe987 | ||
|
|
19f098359b | ||
|
|
5ce450f578 | ||
|
|
fb51580740 | ||
|
|
995017df5a | ||
|
|
c79036aa65 | ||
|
|
fbe2e2a285 | ||
|
|
a63f28a2e5 | ||
|
|
5e2bb3f546 | ||
|
|
60232baffb | ||
|
|
c38117d116 | ||
|
|
d56b758490 | ||
|
|
de394a7d4e | ||
|
|
55b1417df8 | ||
|
|
471cad3ae9 | ||
|
|
299b29b66f | ||
|
|
344a7dfbaa | ||
|
|
56c204509a | ||
|
|
f7ecd4880f | ||
|
|
b2f8a843b5 | ||
|
|
1d01ac72ba | ||
|
|
1ad1f3eb33 | ||
|
|
e3bad997fd | ||
|
|
800f97c5a1 | ||
|
|
abb8d8502b | ||
|
|
dc69d0c7f4 | ||
|
|
56b10d669a | ||
|
|
4991cae5ad | ||
|
|
784a4f8ecd | ||
|
|
2e084cc2a6 | ||
|
|
0f35906930 | ||
|
|
e96d2336cf | ||
|
|
803caf6531 | ||
|
|
cfa47cc7b9 | ||
|
|
043c038dae | ||
|
|
41aede2b50 | ||
|
|
0014bb3d24 | ||
|
|
94405ab72d | ||
|
|
0f9b2923c2 | ||
|
|
60f4f863df | ||
|
|
c1476d0397 | ||
|
|
90d7efe3a9 | ||
|
|
136d00797c | ||
|
|
101027e6b8 | ||
|
|
23f95c2b2b | ||
|
|
baaeb20d6b | ||
|
|
cd313dc2fe | ||
|
|
d86dc608b0 | ||
|
|
6c2b5ff0c7 | ||
|
|
fcda3b557e | ||
|
|
d8104f0d22 | ||
|
|
964dad0d5b | ||
|
|
30819a08f4 | ||
|
|
22b8eb856e | ||
|
|
f8ccd0b120 | ||
|
|
0c0f26bb18 | ||
|
|
9c0dc54cfe | ||
|
|
fb0c1f548b | ||
|
|
7708752ad9 | ||
|
|
9d49d781cc | ||
|
|
a81d20a2d1 | ||
|
|
17229228a3 | ||
|
|
fc619f975c | ||
|
|
5858f3f180 | ||
|
|
d5ff5ea91e | ||
|
|
cf465d93f9 | ||
|
|
521ccc25cf | ||
|
|
dc0765f6b0 | ||
|
|
8cfc2ec21a | ||
|
|
10cad69fac | ||
|
|
b7d3158514 | ||
|
|
4b8334fe1c | ||
|
|
608b5cc9f9 | ||
|
|
42a55015fa | ||
|
|
0a6e0d0f2c | ||
|
|
7846682223 | ||
|
|
5336bbbe65 | ||
|
|
8e5fd5892e | ||
|
|
eaff888486 | ||
|
|
f1383f4dca | ||
|
|
d9c10cea5d | ||
|
|
d48a1ca0b0 | ||
|
|
bfcfe2fd31 | ||
|
|
648c088d02 | ||
|
|
70258e0eee | ||
|
|
5b1e9ec7da | ||
|
|
7a250a170e | ||
|
|
2e438385f3 | ||
|
|
d6f3efb358 | ||
|
|
884410c0d8 | ||
|
|
cdab9ff69c | ||
|
|
1da43bb5b5 | ||
|
|
6f3a08be0c | ||
|
|
e5cb6ebec7 | ||
|
|
f60ad9e559 | ||
|
|
69b23e4000 | ||
|
|
bedfb9a1ee | ||
|
|
e4fb802d7a | ||
|
|
068a099f37 | ||
|
|
fa573f8a24 | ||
|
|
ebb745cc11 | ||
|
|
2b33300d79 | ||
|
|
946d40e6cd | ||
|
|
36285a65d2 | ||
|
|
fc49674997 | ||
|
|
d0a8647186 | ||
|
|
9b875aba21 | ||
|
|
76e43f339a | ||
|
|
32e832eb39 | ||
|
|
60704bca17 | ||
|
|
43e4712b86 | ||
|
|
5359c3a7ed | ||
|
|
81bf68c67c | ||
|
|
4d5231598f | ||
|
|
c1a139fc51 | ||
|
|
1cb18ad7cb | ||
|
|
6f0258c8d4 | ||
|
|
124efc0d88 | ||
|
|
924ecd998f | ||
|
|
07a94de607 | ||
|
|
7bd05d63ac | ||
|
|
bb15924c95 | ||
|
|
1ebce37e17 | ||
|
|
b93dc752fe | ||
|
|
dbbe1f7df2 | ||
|
|
a709c47f6f | ||
|
|
68ed30ff35 | ||
|
|
a65a31810c | ||
|
|
8c50dc0c72 | ||
|
|
a8a036206b | ||
|
|
8313f1d96e | ||
|
|
1898ed215e | ||
|
|
83aceba913 | ||
|
|
c56fb0ea47 | ||
|
|
83a2df3ef3 | ||
|
|
4703f6d5c7 | ||
|
|
8d2797f8a1 | ||
|
|
6cdde84445 | ||
|
|
afa35379b2 | ||
|
|
1099e08b7d | ||
|
|
89cb20ada7 | ||
|
|
32b0fd7b36 | ||
|
|
04670bb5f2 | ||
|
|
8566fe4ac1 | ||
|
|
e607e8315c | ||
|
|
a9b7cf61a5 | ||
|
|
7c7bda669c | ||
|
|
0c82c6f2f5 | ||
|
|
b7cbe49cb2 | ||
|
|
7378089f4a | ||
|
|
62b6b12066 | ||
|
|
39fdff9052 | ||
|
|
32c0913f00 | ||
|
|
7eb90d62b0 | ||
|
|
ec2683417f | ||
|
|
cb23c8b093 | ||
|
|
687f7ddf64 | ||
|
|
992a8e9aef | ||
|
|
6e08c6bc35 | ||
|
|
b71d05935a | ||
|
|
c14dbc19f8 | ||
|
|
1eff1c94c4 | ||
|
|
53be7feee1 | ||
|
|
e182cc4028 | ||
|
|
80309cbff3 | ||
|
|
816db29f9c | ||
|
|
526e0afc70 | ||
|
|
77973af49f | ||
|
|
dc5cff645a | ||
|
|
0ea8e9e750 | ||
|
|
69b4968578 | ||
|
|
b7e266e350 | ||
|
|
b056cc35e4 | ||
|
|
d66452423f | ||
|
|
d85537fa7b | ||
|
|
fc11fb6e3d | ||
|
|
cbdfb4349b | ||
|
|
19ed0b70c2 | ||
|
|
3092747b5f | ||
|
|
0adfc2ddab | ||
|
|
8fd8bc4537 | ||
|
|
e7d6a54907 | ||
|
|
e3c273c84b | ||
|
|
8aedbd1418 | ||
|
|
e713c30785 | ||
|
|
74a168d87e | ||
|
|
ca63ff621a | ||
|
|
d120af2c81 | ||
|
|
95ab5b57b7 | ||
|
|
2e7f90f3cc | ||
|
|
8403352af8 | ||
|
|
526b6e1f03 | ||
|
|
f2fd976934 | ||
|
|
8b9371d7e1 | ||
|
|
948a4038c6 | ||
|
|
57c366ec9a | ||
|
|
3c65f9fe91 | ||
|
|
4f92e68172 | ||
|
|
4e9d599e64 | ||
|
|
650c8bfc9e | ||
|
|
b3f9c3d27e | ||
|
|
240de28567 | ||
|
|
5ff11fdd0a | ||
|
|
2de758a167 | ||
|
|
4ee6c278d9 | ||
|
|
9771db7133 | ||
|
|
464c19bf39 | ||
|
|
1d349ec62b | ||
|
|
334830b826 | ||
|
|
ccf1031fad | ||
|
|
5041020596 | ||
|
|
e2d842ec1a | ||
|
|
5a053d89b7 | ||
|
|
7b82d91a7c | ||
|
|
822bd91323 | ||
|
|
a397ab63f7 | ||
|
|
4afd9e75da | ||
|
|
d1f7bc6198 | ||
|
|
3dd22fd298 | ||
|
|
5ee6897ce6 | ||
|
|
b252b55c85 | ||
|
|
b80295a21c | ||
|
|
6dafc087e9 | ||
|
|
a599835e1f | ||
|
|
fac0354b2d | ||
|
|
26948fb68b | ||
|
|
586d95fb55 | ||
|
|
2456b82e65 | ||
|
|
2145130d21 | ||
|
|
233cd8c3d6 | ||
|
|
5751ac6b4e | ||
|
|
2c05a82204 | ||
|
|
43b8743569 | ||
|
|
c62bc408dc | ||
|
|
8253ef90d0 | ||
|
|
e54b443247 | ||
|
|
b57e63d7d6 | ||
|
|
60ba3eaf03 | ||
|
|
04246936d2 | ||
|
|
5b7ffac74e | ||
|
|
f4bbcdb382 | ||
|
|
c127978dd2 | ||
|
|
e3891df243 | ||
|
|
510d3cfa33 | ||
|
|
676ce9b68d | ||
|
|
0d17d34983 | ||
|
|
cd8a304690 | ||
|
|
1dcd7dc806 | ||
|
|
b2bde8d97e | ||
|
|
afedd397a7 | ||
|
|
1210924562 | ||
|
|
341bb8495a | ||
|
|
b0749b5595 | ||
|
|
393c9cd13c | ||
|
|
b44dfc2d9d | ||
|
|
fa852a1ab8 | ||
|
|
e38d78a7b4 | ||
|
|
bc3275e624 | ||
|
|
bb04181abf | ||
|
|
17d28ed9bc | ||
|
|
2374cf41f8 | ||
|
|
3faa5b4a11 | ||
|
|
41ec622e26 | ||
|
|
c84faeaa72 | ||
|
|
81480f203d | ||
|
|
fd620a858c | ||
|
|
8f1b373c3d | ||
|
|
f72a09b698 | ||
|
|
11ff1994f3 | ||
|
|
0a6db0ff9b | ||
|
|
c4e47a8169 | ||
|
|
fe67bf8fdb | ||
|
|
8d9d711ad8 | ||
|
|
1a4f3f0e18 | ||
|
|
ccafd3a293 | ||
|
|
2359abf8a5 | ||
|
|
2c89b611b5 | ||
|
|
b6f359bcb8 | ||
|
|
b966722899 | ||
|
|
4c5ef5ac8c | ||
|
|
1273336622 | ||
|
|
44eb961c27 | ||
|
|
385c7274a3 | ||
|
|
00ca9755be | ||
|
|
3348370138 | ||
|
|
4b9ac6f1e5 | ||
|
|
1c098d9b04 | ||
|
|
af478c83cd | ||
|
|
bd3921b91b | ||
|
|
849eb7714c | ||
|
|
4da1b46b05 | ||
|
|
ba12a2bc6d | ||
|
|
fac6dd81b9 | ||
|
|
03d8bcaea2 | ||
|
|
686814f537 | ||
|
|
0cfb66ae16 | ||
|
|
1ce68cb1cf | ||
|
|
36eb48c649 | ||
|
|
897b3d3f39 | ||
|
|
b1b1f1f579 | ||
|
|
b9fe8e4b33 | ||
|
|
f7a4f9906c | ||
|
|
fb05999e9e | ||
|
|
60eae40006 | ||
|
|
815d1a906f | ||
|
|
cf77ebde6a | ||
|
|
07d552c86b | ||
|
|
4513033634 | ||
|
|
6a077c967a | ||
|
|
ea03477e8e | ||
|
|
d218d70b8d | ||
|
|
bc655ed9ef | ||
|
|
1c42ace096 | ||
|
|
7ec28c9481 | ||
|
|
09c63c636f | ||
|
|
a42d87742f | ||
|
|
870eff5826 | ||
|
|
7f3ef7bb82 | ||
|
|
c0fb108e06 | ||
|
|
7759418f5d | ||
|
|
884bf57193 | ||
|
|
8236d84dfa | ||
|
|
f8b349814c | ||
|
|
9f581ed10b | ||
|
|
a3ffbeccd0 | ||
|
|
404fae9c7c | ||
|
|
b2bd4bd694 | ||
|
|
a69a35a0b6 | ||
|
|
340d1d43be | ||
|
|
d68286821b | ||
|
|
5d0ad1ada2 | ||
|
|
9d7a814180 | ||
|
|
33c25bfe6d | ||
|
|
c42949b61e | ||
|
|
3e84c9b70f | ||
|
|
592153f968 | ||
|
|
3c7fbb8fd6 | ||
|
|
0bbc006b98 | ||
|
|
5518f561f0 | ||
|
|
7cfe768dbd | ||
|
|
04b0cf6330 | ||
|
|
1b70283c3a | ||
|
|
5c1290d5b3 | ||
|
|
4ee1f9cf2c | ||
|
|
594bceff77 | ||
|
|
4e271603c2 | ||
|
|
47a77ae1e2 | ||
|
|
bea093e8da | ||
|
|
b4ba9d4375 | ||
|
|
66fe0048a5 | ||
|
|
dfc6ebfeb0 | ||
|
|
b0ea9d3096 | ||
|
|
e4eaa74b51 | ||
|
|
716490be26 | ||
|
|
86936a66e0 | ||
|
|
40e54dbbd4 | ||
|
|
f0b9837407 | ||
|
|
11418501a4 | ||
|
|
e240525a35 | ||
|
|
1467fd5dbf | ||
|
|
5d67a6f427 | ||
|
|
d7a5cddcb3 | ||
|
|
83f84e5b6a | ||
|
|
d19dc1cf56 | ||
|
|
27e83342f9 | ||
|
|
9cfefbdb86 | ||
|
|
412a3c923b | ||
|
|
4e43bf5f78 | ||
|
|
ef25f8a721 | ||
|
|
34e5e5c513 | ||
|
|
d8ee07d1e4 | ||
|
|
d494e44df3 | ||
|
|
15edabc977 | ||
|
|
4fbd2e6caa | ||
|
|
b7a90eb4e4 | ||
|
|
af310854fc | ||
|
|
9805f8b9f2 | ||
|
|
dd283381a1 | ||
|
|
c775f5aba0 | ||
|
|
6df976d1f3 | ||
|
|
43d32af540 | ||
|
|
43ac9a9a22 | ||
|
|
91db8a9247 | ||
|
|
e69d402b4f | ||
|
|
f3d5515795 | ||
|
|
bde62473a4 | ||
|
|
87cf91a044 | ||
|
|
0f7372bfb4 | ||
|
|
76b7272a72 | ||
|
|
b3abc9fd6a | ||
|
|
20731be1a4 | ||
|
|
8f57ab343c | ||
|
|
83f43af36e | ||
|
|
32de3d9f1d | ||
|
|
c04af27bf3 | ||
|
|
091ea785e5 | ||
|
|
fe7faf0d0d | ||
|
|
43b1d3ca43 | ||
|
|
6453cb9d11 | ||
|
|
bb5d0b63ef | ||
|
|
999e2f6633 | ||
|
|
fd4c61ece7 | ||
|
|
767f1c7b3d | ||
|
|
a3d8af9a96 | ||
|
|
9ee54d6267 | ||
|
|
cf4a4b2b25 | ||
|
|
3b6c9f9511 | ||
|
|
356572c21b | ||
|
|
cb7499c10a | ||
|
|
28658cae73 | ||
|
|
ba7b2fd270 | ||
|
|
a14686c9f3 | ||
|
|
a450dee7cf | ||
|
|
55a7a34a1d | ||
|
|
4e7a3c09a6 | ||
|
|
292faec46f | ||
|
|
b616efd75c | ||
|
|
ee147612a3 | ||
|
|
69ead3348f | ||
|
|
f66ddcaa2d | ||
|
|
70d464189f | ||
|
|
60e2abde1b | ||
|
|
79fc3cbf12 | ||
|
|
ad2d8c8ee0 | ||
|
|
6a32428ca1 | ||
|
|
f8f90f308e | ||
|
|
678fe3d63e | ||
|
|
f06264ea0a | ||
|
|
25824629f2 | ||
|
|
b999c05d1e | ||
|
|
5f0020a95c | ||
|
|
bb07c4b3b7 | ||
|
|
9043d2574b | ||
|
|
031123b2ca | ||
|
|
3135de3eb3 | ||
|
|
64828c918d | ||
|
|
7aa7257d29 | ||
|
|
c648add963 | ||
|
|
16469daff3 | ||
|
|
d32cacf1da | ||
|
|
77c1163591 | ||
|
|
261cb249d2 | ||
|
|
0c3184ed83 | ||
|
|
f909b316c7 | ||
|
|
4768b023a4 | ||
|
|
d188ac2df4 | ||
|
|
fdd36d48bc | ||
|
|
6f5b18de3a | ||
|
|
df4adfe717 | ||
|
|
ff7330048b | ||
|
|
afabd179fb | ||
|
|
0c30d53d95 | ||
|
|
151e36df0e | ||
|
|
2ece527f9b | ||
|
|
d12b78985e | ||
|
|
2d07df2541 | ||
|
|
27a85ce0da | ||
|
|
f75ec43b71 | ||
|
|
b9e4861f16 | ||
|
|
802f19453d | ||
|
|
5b79928590 | ||
|
|
860a97a769 | ||
|
|
25177898e1 | ||
|
|
195fb3b29d | ||
|
|
234b2c9427 | ||
|
|
f3b5b07796 | ||
|
|
63cc6aecaf | ||
|
|
8aedb0b881 | ||
|
|
8487859fc2 | ||
|
|
20ecc79cd1 | ||
|
|
f83c8d4523 | ||
|
|
33c8743215 | ||
|
|
ab944fb9ae | ||
|
|
3d88749976 | ||
|
|
7d0cf6e8cc | ||
|
|
6fd7feffee | ||
|
|
760eb926bf | ||
|
|
9146642947 | ||
|
|
6c1e2b8eab | ||
|
|
ff6482fa29 | ||
|
|
c99f571296 | ||
|
|
9688bd8408 | ||
|
|
707fa160e8 | ||
|
|
4d9418e620 | ||
|
|
9f12456456 | ||
|
|
31d7aacec1 | ||
|
|
c4720edda7 | ||
|
|
2f0fcaf5d3 | ||
|
|
66606b7309 | ||
|
|
6d328e852d | ||
|
|
3f887f20e9 | ||
|
|
9ae9da8256 | ||
|
|
33b6df01ba | ||
|
|
6af3824293 | ||
|
|
507550edad | ||
|
|
b53fceefb9 | ||
|
|
c1c01aab02 | ||
|
|
e1923468a4 | ||
|
|
84007e6ad1 | ||
|
|
5636881463 | ||
|
|
7f8f8ecd62 | ||
|
|
88c0beddc6 | ||
|
|
37bd43a19f | ||
|
|
9b02889ea5 | ||
|
|
f4cb7d1862 | ||
|
|
4dd9767590 | ||
|
|
dea5cf4b5d | ||
|
|
b4b88bde0b | ||
|
|
9c73444102 | ||
|
|
c5f4ae2242 | ||
|
|
a3c583af1d | ||
|
|
84e95ab4c2 | ||
|
|
f12ade3b67 | ||
|
|
dbb1e6a890 | ||
|
|
38a645ad49 | ||
|
|
4a5e27e641 | ||
|
|
b7353db14e | ||
|
|
0f37c8ecbd | ||
|
|
2c0a2ce750 | ||
|
|
c0bc7553a9 | ||
|
|
067aece437 | ||
|
|
a14a71c222 | ||
|
|
4f6f4eea4c | ||
|
|
f84d0f34e6 | ||
|
|
4849904b0b | ||
|
|
9ed01cc0df | ||
|
|
ea2079f36f | ||
|
|
6fc90e20e9 | ||
|
|
01edf49de0 | ||
|
|
8f37f74d29 | ||
|
|
7e020f967b | ||
|
|
99d3b80033 | ||
|
|
b80332b9b3 | ||
|
|
4ef471919c | ||
|
|
3c336cd8f6 | ||
|
|
4a2db204f1 | ||
|
|
7b458daa98 | ||
|
|
dbf67dc47b | ||
|
|
686e9b64ef | ||
|
|
c674a300c6 | ||
|
|
09bce9c285 | ||
|
|
e26ece57d1 | ||
|
|
9822c52573 | ||
|
|
6ed470ed5f | ||
|
|
4e48b78e03 | ||
|
|
baec7838b4 | ||
|
|
53b5d78cdc | ||
|
|
f4157ba0e5 | ||
|
|
4903d26038 | ||
|
|
574e8a92a8 | ||
|
|
45882ba6a1 | ||
|
|
83519e8513 | ||
|
|
6cb2f1663e | ||
|
|
05563168c3 | ||
|
|
2907dd1a50 | ||
|
|
419eecbe72 | ||
|
|
1c8de910a6 | ||
|
|
a674b3ab94 | ||
|
|
4d7a34a998 | ||
|
|
a881d86637 | ||
|
|
20a7366432 | ||
|
|
4b22615f97 | ||
|
|
07d77c1320 | ||
|
|
07e0c02d11 | ||
|
|
c7e33929db | ||
|
|
3a8a212432 | ||
|
|
34af5e4563 | ||
|
|
60e6dd7822 | ||
|
|
621545cc74 | ||
|
|
2f1b72e105 | ||
|
|
266d4c837c | ||
|
|
24e998ca2e | ||
|
|
1b9aae3bbd | ||
|
|
86dc2379a3 | ||
|
|
1b5efa6ba2 | ||
|
|
489dc521e3 | ||
|
|
4d4e7c75da | ||
|
|
2aaa82a5c4 | ||
|
|
c30aa7346b | ||
|
|
d0306ac92c | ||
|
|
62d98ad265 | ||
|
|
0da16c7219 | ||
|
|
28a2df96ff | ||
|
|
ac2310f71f | ||
|
|
ab5323d873 | ||
|
|
e1ccde6533 | ||
|
|
a71a7d6951 | ||
|
|
4a5a5b1081 | ||
|
|
e375c1e7d8 | ||
|
|
3ddf04fa31 | ||
|
|
976dab90c8 | ||
|
|
e9714c4fd0 | ||
|
|
b4246c2869 | ||
|
|
9b8ec1ac02 | ||
|
|
43bd98151a | ||
|
|
6d2e2489bb | ||
|
|
03639cb9ac | ||
|
|
2bd89c94d9 | ||
|
|
c21dc7ea7f | ||
|
|
b92045f905 | ||
|
|
6b9d91e2be | ||
|
|
718a187c1e | ||
|
|
0f62bcf25f | ||
|
|
e581cf8aeb | ||
|
|
6186d82151 | ||
|
|
264811c5ee | ||
|
|
ba529b9fd6 | ||
|
|
34d15e4192 | ||
|
|
8caa5c6a65 | ||
|
|
1f5050eb01 | ||
|
|
e165ca7c3b | ||
|
|
590c57c18d | ||
|
|
7b1f84f509 | ||
|
|
247273631c | ||
|
|
e0cc0a7f3a | ||
|
|
44a21de783 | ||
|
|
6881e3d795 | ||
|
|
ce3c055819 | ||
|
|
74077a0ead | ||
|
|
deb39c2291 | ||
|
|
1fb5ffb59b | ||
|
|
8bc37416a0 | ||
|
|
7f1a436f30 | ||
|
|
f37a85fd82 | ||
|
|
b180d9e878 | ||
|
|
001f90a540 | ||
|
|
9260c59afb | ||
|
|
7ec30d1f5e | ||
|
|
497c174d63 | ||
|
|
45da7df4b4 | ||
|
|
9ca6830267 | ||
|
|
f4124b28d8 | ||
|
|
6f64053dd5 | ||
|
|
39fb611160 | ||
|
|
e73a8a2f7e | ||
|
|
3db9907c25 | ||
|
|
8ae6176242 | ||
|
|
64a53a68c1 | ||
|
|
3835e0ea74 | ||
|
|
4a7b5618ff | ||
|
|
39db88c533 | ||
|
|
0416ce8da6 | ||
|
|
357e779013 | ||
|
|
9445e3abb8 | ||
|
|
6390af0827 | ||
|
|
df95bd7010 | ||
|
|
53297ec9d9 | ||
|
|
420d98c0f9 | ||
|
|
a976a826a5 | ||
|
|
b4341cba18 | ||
|
|
becd0d9cbf | ||
|
|
0046b60c93 | ||
|
|
2a17f0e507 | ||
|
|
b67b3243bb | ||
|
|
1f92237b19 | ||
|
|
ddf0e75a11 | ||
|
|
4c1150bf78 | ||
|
|
40240d294a | ||
|
|
56861e8d01 | ||
|
|
62c727e253 | ||
|
|
1e30d00189 | ||
|
|
cd97735c3f | ||
|
|
24ea6a63c6 | ||
|
|
55d8afd985 | ||
|
|
1e49ddab8b | ||
|
|
78a8d20e04 | ||
|
|
29669b8b0f | ||
|
|
b0fbc86ca7 | ||
|
|
1d08bac493 | ||
|
|
5a17237015 | ||
|
|
76d8ca1e72 | ||
|
|
6c07cfcd25 | ||
|
|
be24e1fb71 | ||
|
|
81e909b961 | ||
|
|
f314efdfdc | ||
|
|
f8cf7103ca | ||
|
|
89f0f24707 | ||
|
|
5c71601fcf | ||
|
|
7ceb9b4009 | ||
|
|
02b3c6ac28 | ||
|
|
6db1a907c8 | ||
|
|
830d932eec | ||
|
|
2582766a89 | ||
|
|
ebb7410e5b | ||
|
|
912be76400 | ||
|
|
3543acbac7 | ||
|
|
d2806fd9ba | ||
|
|
0fcfd30535 | ||
|
|
ef9b0062dd | ||
|
|
25fe4f1269 | ||
|
|
b37ee0fcac | ||
|
|
b66b0cb431 | ||
|
|
f339f1ee98 | ||
|
|
e2593c2dad | ||
|
|
55b71a111b | ||
|
|
b1c786077b | ||
|
|
8db7078ce8 | ||
|
|
275f6e3dc2 | ||
|
|
05366eac6f | ||
|
|
d8ab3473bf | ||
|
|
f3cce1904c | ||
|
|
af68c26aea | ||
|
|
2c50bb16dc | ||
|
|
10a24d68c9 | ||
|
|
67ed1d89ac | ||
|
|
d25dbfb09b | ||
|
|
f170c7546b | ||
|
|
76df21083e | ||
|
|
cc667d63cd | ||
|
|
061fdcd5bd | ||
|
|
013c866137 | ||
|
|
2dafbeddb0 | ||
|
|
45f17f78f3 | ||
|
|
686f96eb27 | ||
|
|
9f9f146444 | ||
|
|
d862406b44 | ||
|
|
17de1414ef | ||
|
|
0e43fb3f06 | ||
|
|
742af4e066 | ||
|
|
420294aef4 | ||
|
|
fa0e0af5b9 | ||
|
|
6800adfaa4 | ||
|
|
f5967c3c0a | ||
|
|
fca48e39ee | ||
|
|
b2957c5f35 | ||
|
|
b71d5d26a6 | ||
|
|
64a6ba1949 | ||
|
|
e1db6d3a37 | ||
|
|
ceed926274 | ||
|
|
a22ec43cf2 | ||
|
|
08589889fd | ||
|
|
6e57d93f74 | ||
|
|
9bd393c159 | ||
|
|
5f4d1b9e10 | ||
|
|
a90f3f2176 | ||
|
|
ac9622ac07 | ||
|
|
619e51d258 | ||
|
|
6f9cf454a2 | ||
|
|
385616f3df | ||
|
|
5dc646e44e | ||
|
|
447293bc4e | ||
|
|
e9dba06600 | ||
|
|
0f7773882a | ||
|
|
1ffdb41cfc | ||
|
|
24f0a3945d | ||
|
|
bf7cfef355 | ||
|
|
48663eacdc | ||
|
|
388d255243 | ||
|
|
51f9dfbc4f | ||
|
|
e5ea8bf302 | ||
|
|
4ad08a7449 | ||
|
|
29180e4196 | ||
|
|
3dc4893cf1 | ||
|
|
c05e377d16 | ||
|
|
58585a6521 | ||
|
|
0221cc06f8 | ||
|
|
1db1bd03f5 | ||
|
|
b22b77a39e | ||
|
|
ef01caf829 | ||
|
|
c3ca59ac93 | ||
|
|
10f146883a | ||
|
|
bdfd0d38b8 | ||
|
|
1061d96fde | ||
|
|
15545d0b2f | ||
|
|
5b9c596170 | ||
|
|
132e99ab71 | ||
|
|
3cd0d9636f | ||
|
|
96e6ba77be | ||
|
|
47b166536e | ||
|
|
f4374c5046 | ||
|
|
715b7ba13e | ||
|
|
a947a53aa2 | ||
|
|
692f84bd14 | ||
|
|
75b34c9b98 | ||
|
|
1823ecda40 | ||
|
|
fd1f42dc94 | ||
|
|
1bb4511dd3 | ||
|
|
b3fee39433 | ||
|
|
3ccb52735f | ||
|
|
9d095d2acb | ||
|
|
2588edf88c | ||
|
|
9bd042dee0 | ||
|
|
fd1d564f03 | ||
|
|
444491f577 | ||
|
|
a2ecc1bdb6 | ||
|
|
75202fdcba | ||
|
|
d847c607e3 | ||
|
|
02207dedf3 | ||
|
|
8ae52be773 | ||
|
|
833a53f131 | ||
|
|
7a80297d31 | ||
|
|
6feccd5e36 | ||
|
|
96098be5ae | ||
|
|
28f90da70e | ||
|
|
5d6b07f2a7 | ||
|
|
486f2b6ba5 | ||
|
|
8165fec50e | ||
|
|
03c8b42296 | ||
|
|
8afe037def | ||
|
|
c38d0e3211 | ||
|
|
14d10af9b8 | ||
|
|
25686eeba1 | ||
|
|
2960908109 | ||
|
|
c2aa0b97ee | ||
|
|
34a9b852ef | ||
|
|
a6f9678d63 | ||
|
|
5d2b8ba2ac | ||
|
|
1706d13448 | ||
|
|
33908413d6 | ||
|
|
f4af0b0420 | ||
|
|
48ae26d08a | ||
|
|
104ce0967c | ||
|
|
d1437997d2 | ||
|
|
4adb79e869 | ||
|
|
d01921cd61 | ||
|
|
dc6b476c57 | ||
|
|
db9364a57b | ||
|
|
198bc8f9a1 | ||
|
|
8fa0a039c4 | ||
|
|
154b58057a | ||
|
|
ffbbc52460 | ||
|
|
f1ee1b55b7 | ||
|
|
27322bcad6 | ||
|
|
b52289860f | ||
|
|
d6573c2bcc | ||
|
|
1e48221d7b | ||
|
|
04f620ec00 | ||
|
|
aaaf099a3f | ||
|
|
c5e617271c | ||
|
|
556061ff52 | ||
|
|
03fec6056f | ||
|
|
d59622cd5f | ||
|
|
3379eed38e | ||
|
|
560ad5b280 | ||
|
|
ff9345b1db | ||
|
|
a86f198406 | ||
|
|
bf8e61b9bb | ||
|
|
52532253c3 | ||
|
|
21a6de02d8 | ||
|
|
ded42707ab | ||
|
|
919a045088 | ||
|
|
c2b6e8610b | ||
|
|
7d074678bf | ||
|
|
6b240da892 | ||
|
|
ca83e13802 | ||
|
|
36abc4646a | ||
|
|
6448592929 | ||
|
|
046ee7e475 | ||
|
|
a783a77404 | ||
|
|
4705656d29 | ||
|
|
9f917603d8 | ||
|
|
5876927505 | ||
|
|
f57941fd7c | ||
|
|
823aa426ed | ||
|
|
0ddf6d30b8 | ||
|
|
b2ec9aa83a | ||
|
|
ccba95f541 | ||
|
|
4be7f054b7 | ||
|
|
f9c45849c8 | ||
|
|
ee89b2e7f4 | ||
|
|
ad66955a54 | ||
|
|
fd12724108 | ||
|
|
b8e47c87ba | ||
|
|
0e3c3ebfbd | ||
|
|
6f58899f90 | ||
|
|
65512d46c2 | ||
|
|
c94d7f712a | ||
|
|
49e678340b | ||
|
|
80c1382593 | ||
|
|
5b56bdf94a | ||
|
|
9b2b16c066 | ||
|
|
567e177699 | ||
|
|
57c93f80e2 | ||
|
|
447bb5204d | ||
|
|
5e299551b7 | ||
|
|
f992c740d5 | ||
|
|
f8abcc1b9c | ||
|
|
929d620c60 | ||
|
|
e961492192 | ||
|
|
4bb832b597 | ||
|
|
9f0a443b5c | ||
|
|
245c39b1f6 | ||
|
|
7d7008d405 | ||
|
|
69fb1426d4 | ||
|
|
a412fc113d | ||
|
|
c266f78d1e | ||
|
|
381aef77ee | ||
|
|
a7335533bb | ||
|
|
fcbcc6de95 | ||
|
|
ef0126a6ae | ||
|
|
3064000a2c | ||
|
|
3e14393c97 | ||
|
|
a6d47cca10 | ||
|
|
68ca44188c | ||
|
|
7c75b75f5b | ||
|
|
a3eb634740 | ||
|
|
42ba0d7b7a | ||
|
|
3f627cb132 | ||
|
|
dad11ff490 | ||
|
|
174952430e | ||
|
|
d914ab1390 | ||
|
|
2765883152 | ||
|
|
41cb5196d1 | ||
|
|
58db899a29 | ||
|
|
2c31b074c8 | ||
|
|
08b537a158 | ||
|
|
8466c1c54d | ||
|
|
142036891a | ||
|
|
f6830b4b85 | ||
|
|
e757c1b2d9 | ||
|
|
0a34aa6231 | ||
|
|
c85ae7dbdf | ||
|
|
03981628b8 | ||
|
|
f427c6f8b6 | ||
|
|
d4cb868503 | ||
|
|
8ea4ea23cc | ||
|
|
12b4951994 | ||
|
|
96fbc83a0a | ||
|
|
e01e689f69 | ||
|
|
6f56a19390 | ||
|
|
272d023f3c | ||
|
|
b97bbd10f0 | ||
|
|
59ae0b0f55 | ||
|
|
6d9155032f | ||
|
|
d8e0bab4ad | ||
|
|
e9bf3624b7 | ||
|
|
55c92da286 | ||
|
|
3bb4fcb28f | ||
|
|
f45d3f9c65 | ||
|
|
2c23b0e7f4 | ||
|
|
f45845cf85 | ||
|
|
bd92936a64 | ||
|
|
5f650832e6 | ||
|
|
c28fce58f1 | ||
|
|
7248b168fa | ||
|
|
718a676fa8 | ||
|
|
4a4cff74e8 | ||
|
|
4af113b133 | ||
|
|
caa92ea413 | ||
|
|
c4ef2eaab3 | ||
|
|
d5e70e51ba | ||
|
|
e26ba1fabd | ||
|
|
2cf15677cc | ||
|
|
483401f328 | ||
|
|
736c9e7e8c | ||
|
|
20952e30da | ||
|
|
4355d5ae80 | ||
|
|
a4d6524cb6 | ||
|
|
b3252ec2b2 | ||
|
|
47d347f357 | ||
|
|
51a4232cf2 | ||
|
|
880091a4f7 | ||
|
|
5e73da4bca | ||
|
|
b8fbe65d36 | ||
|
|
f78bd11cec | ||
|
|
f6870a2fee | ||
|
|
7950cc07c1 | ||
|
|
92db554ae7 | ||
|
|
091de20934 | ||
|
|
215c4fdb56 | ||
|
|
64c0884eb9 | ||
|
|
23b53faac7 | ||
|
|
e55f33d48d | ||
|
|
747b4e9600 | ||
|
|
e1ff33f84e | ||
|
|
94470dd1fe | ||
|
|
d5545fadaf | ||
|
|
380f069822 | ||
|
|
9873ac9104 | ||
|
|
ae61ce05c9 | ||
|
|
3b516aa139 | ||
|
|
494cd1e96c | ||
|
|
cd151db5ee | ||
|
|
d17c6d8fce | ||
|
|
0545e7cb28 | ||
|
|
a227a0fe21 | ||
|
|
bd31e5742f | ||
|
|
df70cd5c50 | ||
|
|
62a2713c03 | ||
|
|
abdd4d68d5 | ||
|
|
9c61c52dc5 | ||
|
|
55cfe10fd8 | ||
|
|
3e0b0ee069 | ||
|
|
7d9fdc34be | ||
|
|
dda6a96407 | ||
|
|
1b598e2f6d | ||
|
|
9755516178 | ||
|
|
3a1618451c | ||
|
|
51697c63dd | ||
|
|
609eb4f342 | ||
|
|
558bcc07b2 | ||
|
|
e860f9d62b | ||
|
|
f3477fefa5 | ||
|
|
432a139f35 | ||
|
|
d0f403e7df | ||
|
|
50ef7a8c73 | ||
|
|
0f5ce3b836 | ||
|
|
ff00550372 | ||
|
|
09c8920293 | ||
|
|
e2dec81699 | ||
|
|
63c9e80ed4 | ||
|
|
47a8da43dc | ||
|
|
1ad9a7f82f | ||
|
|
f837bad894 | ||
|
|
917643489f | ||
|
|
5e585201d3 | ||
|
|
8396357f36 | ||
|
|
da1179f628 | ||
|
|
2976bc4e5c | ||
|
|
9bb3e68d05 | ||
|
|
2660e914fc | ||
|
|
8732407a7f | ||
|
|
43fccd17b4 | ||
|
|
0b5ae3632e | ||
|
|
a70e4eeb01 | ||
|
|
3a135c626d | ||
|
|
bdefe0d04e | ||
|
|
d80c1f2bfb | ||
|
|
b879860833 | ||
|
|
7c068621cf | ||
|
|
28da2406d3 | ||
|
|
50321895e5 | ||
|
|
a33689cbae | ||
|
|
51541303a6 | ||
|
|
65b579c6b6 | ||
|
|
1bde48730c | ||
|
|
a1b709a9fd | ||
|
|
8dc3165e54 | ||
|
|
0b4887ed96 | ||
|
|
3a4ef73f06 | ||
|
|
88763d236a | ||
|
|
a3419312d7 | ||
|
|
4f7603a0a4 | ||
|
|
c036543487 | ||
|
|
065fc157bf | ||
|
|
60b241e867 | ||
|
|
0dbcfcbc0b | ||
|
|
7c1b3d1b85 | ||
|
|
01f38d5cea | ||
|
|
1bb056cbdb | ||
|
|
5abc6e3252 | ||
|
|
aa2a09e073 | ||
|
|
e0b6e74e5d | ||
|
|
549370e5f5 | ||
|
|
a4bb162090 | ||
|
|
147ccd176a | ||
|
|
b5a875c03b | ||
|
|
d6fc98c70b | ||
|
|
e737ec0311 | ||
|
|
726cdb9a27 | ||
|
|
f8b3baef3f | ||
|
|
8aad43883f | ||
|
|
91aa8862ff | ||
|
|
ab35716ba2 | ||
|
|
a0627efffe | ||
|
|
568f7f5b3d | ||
|
|
73dbad9c58 | ||
|
|
6c74f74bb5 | ||
|
|
ab7fee50ea | ||
|
|
12a2b35b28 | ||
|
|
4e694ff194 | ||
|
|
182afedc54 | ||
|
|
e926dc2918 | ||
|
|
7b4abe4b67 | ||
|
|
da0a63a449 | ||
|
|
3a430a749e | ||
|
|
cc1353300e | ||
|
|
dcd7e0b3cc | ||
|
|
b57c20ebb6 | ||
|
|
51e7ffb959 | ||
|
|
5f338921cf | ||
|
|
5440778957 | ||
|
|
27b4e483f0 | ||
|
|
271ba51951 | ||
|
|
7ab7a87a88 | ||
|
|
e3faddeb6b | ||
|
|
5ae1b72943 | ||
|
|
fc01690315 | ||
|
|
56237671ed | ||
|
|
5a2ba515b8 | ||
|
|
3789adde80 | ||
|
|
6f90abbf52 | ||
|
|
9159f18982 | ||
|
|
c2fe4b0ccc | ||
|
|
6c67c3c71b | ||
|
|
aaf8b71837 | ||
|
|
9d8282e1be | ||
|
|
7dd9e8763b | ||
|
|
1118a995e2 | ||
|
|
fb910a24e1 | ||
|
|
2746d598b0 | ||
|
|
c0cd0d4a23 | ||
|
|
93aaa52e73 | ||
|
|
8fbc54e03c | ||
|
|
9d416cae4c | ||
|
|
752038039c | ||
|
|
5ff0a723d5 | ||
|
|
91899be423 | ||
|
|
62e2c1cc14 | ||
|
|
fee5b001e8 | ||
|
|
5289268156 | ||
|
|
b29aac5d5f | ||
|
|
54be06ff18 | ||
|
|
93bde62581 | ||
|
|
d349e46cd8 | ||
|
|
b1b3ad61a5 | ||
|
|
bda9cad3c2 | ||
|
|
b0fa8c3bfc | ||
|
|
89f4fbfbb1 | ||
|
|
712563a984 | ||
|
|
b475853922 | ||
|
|
055504ba9a | ||
|
|
2d60ec9dee | ||
|
|
d1c409ce90 | ||
|
|
f2c4ff7518 | ||
|
|
272c27061d | ||
|
|
6fd7a23690 | ||
|
|
005f70ed19 | ||
|
|
764bc2074c | ||
|
|
05d54e4bea | ||
|
|
bd7114182f | ||
|
|
8b1ba8c020 | ||
|
|
eb02f420ec | ||
|
|
313723cfe1 | ||
|
|
9b4cc30d3f | ||
|
|
1b6753932f | ||
|
|
1dfab78686 | ||
|
|
9fb821d6e0 | ||
|
|
69ae893bd0 | ||
|
|
a38430fb80 | ||
|
|
d40f27e18c | ||
|
|
af2c49dd16 | ||
|
|
49c57be84a | ||
|
|
8c8b5887d6 | ||
|
|
8f8dc80cad | ||
|
|
17813e5090 | ||
|
|
17bea924b2 | ||
|
|
d3c81822e8 | ||
|
|
840c4af4be | ||
|
|
3e4432df8e | ||
|
|
d170ff8456 | ||
|
|
3040bcd6d4 | ||
|
|
4375c3eeff | ||
|
|
86b5ed48fd | ||
|
|
36f4948ad1 | ||
|
|
2b461d5b08 | ||
|
|
89394bba10 | ||
|
|
3e92425779 | ||
|
|
02257c08bd | ||
|
|
c2a4dc6859 | ||
|
|
835c958972 | ||
|
|
37351744cd | ||
|
|
f33713912a | ||
|
|
06712bf1a5 | ||
|
|
e4afda08d7 | ||
|
|
87fc2db78a | ||
|
|
c6c5cc4e14 | ||
|
|
bd34fc40b4 | ||
|
|
21b074c547 | ||
|
|
92388df7f8 | ||
|
|
bf124fbe62 | ||
|
|
af1e79cc9d | ||
|
|
ff36a716b5 | ||
|
|
c0b7c0ccaa | ||
|
|
e3e148c569 | ||
|
|
8de55ce054 | ||
|
|
cf108e110f | ||
|
|
6020cc797d | ||
|
|
0951ba95f0 | ||
|
|
63eec77044 | ||
|
|
87fa5547be | ||
|
|
87161ae114 | ||
|
|
67923fca20 | ||
|
|
2ce4314bc3 | ||
|
|
c4525030da | ||
|
|
d1f6e9635c | ||
|
|
29e6cdd8a4 | ||
|
|
1d30ea4923 | ||
|
|
e99ec58054 | ||
|
|
edbe049171 | ||
|
|
2fb4e72240 | ||
|
|
a8d5cdc29f | ||
|
|
a2abe6d882 | ||
|
|
ceaea9d679 | ||
|
|
b640bc06ab | ||
|
|
76178b4e71 | ||
|
|
be9780be55 | ||
|
|
ef0330502b | ||
|
|
f8881b3c72 | ||
|
|
b8526c6e3b | ||
|
|
bcb2609b18 | ||
|
|
4679373e0e | ||
|
|
516b8d9c64 | ||
|
|
34f690d057 | ||
|
|
5aad9ca5e0 | ||
|
|
73adfadcce | ||
|
|
a1f0625bec | ||
|
|
20c4b46930 | ||
|
|
b97dc09e26 | ||
|
|
c2db6d4f35 | ||
|
|
d521416329 | ||
|
|
45a67c57af | ||
|
|
db7f17abdf | ||
|
|
bc672932ad | ||
|
|
0f99b1ab90 | ||
|
|
85c8d544ed | ||
|
|
72c36660b9 | ||
|
|
85bae6cf26 | ||
|
|
c0cbb5877b | ||
|
|
6b49479ef7 | ||
|
|
41c6917260 | ||
|
|
cfaf09a5c1 | ||
|
|
84bf0e26b2 | ||
|
|
06c301c7d6 | ||
|
|
8330a001ae | ||
|
|
33f3431dfe | ||
|
|
f489accf57 | ||
|
|
bbd06972ad | ||
|
|
632d534594 | ||
|
|
763f1ee13d | ||
|
|
1119ffffcf | ||
|
|
96d4c1e2fd | ||
|
|
dff038f4d7 | ||
|
|
b7bf7c8baf | ||
|
|
8c49d11026 | ||
|
|
4fb8a3dc8d | ||
|
|
5cfad9ca9f | ||
|
|
249a9558f8 | ||
|
|
d3951f7f8f | ||
|
|
1dfc604cf0 | ||
|
|
65c9c46a22 | ||
|
|
b058b5d56a | ||
|
|
dd9d83cdab | ||
|
|
828455ac27 | ||
|
|
df7092e2d4 | ||
|
|
1dd1b9d987 | ||
|
|
778bfbab59 | ||
|
|
beb6048bf9 | ||
|
|
39364853b8 | ||
|
|
5266a33bc2 | ||
|
|
c41b98e914 | ||
|
|
7b0061c038 | ||
|
|
8874f0b0c8 | ||
|
|
91c07d5573 | ||
|
|
861150ff46 | ||
|
|
9ed6d23041 | ||
|
|
a1ff567b30 | ||
|
|
b68a076e57 | ||
|
|
93a967ed8f | ||
|
|
3e00d67504 | ||
|
|
ef42ffee65 | ||
|
|
ff5ec11bac | ||
|
|
200983fb7c | ||
|
|
5791a98c1c | ||
|
|
7b44f34b32 | ||
|
|
0b2074c254 | ||
|
|
f63657509f | ||
|
|
51acd2b529 | ||
|
|
e25da8c6d3 | ||
|
|
1e709ee166 | ||
|
|
82f05f108f | ||
|
|
bab73cf4eb | ||
|
|
7fb897a7ad | ||
|
|
2d4e76619f | ||
|
|
45476eff0b | ||
|
|
b8dab7253e | ||
|
|
4bdaab637c | ||
|
|
fd91a7b1f4 | ||
|
|
3f554df687 | ||
|
|
60ec32f7cc | ||
|
|
b915ba51aa | ||
|
|
3e5f9a01ad | ||
|
|
5f373df569 | ||
|
|
006bacfef0 | ||
|
|
a4c7215fa8 | ||
|
|
68798522af | ||
|
|
04f829eba3 | ||
|
|
2ac8a32986 | ||
|
|
69f0f16085 | ||
|
|
5f9d3606f6 | ||
|
|
49beffa2c8 | ||
|
|
a6a57ec0bb | ||
|
|
aed3113a7a | ||
|
|
8cc97d181b | ||
|
|
c19b289ac8 | ||
|
|
7efee0f0a9 | ||
|
|
a8e8c9990d | ||
|
|
e0a567551f | ||
|
|
c6581a91e4 | ||
|
|
8d789a4d7d | ||
|
|
6e696f2fc2 | ||
|
|
9fb6744ee8 | ||
|
|
d10863d928 | ||
|
|
2bc1523496 | ||
|
|
1c6bdb5060 | ||
|
|
d33e3de8f1 | ||
|
|
6e8cdd764a | ||
|
|
e17ab24e66 | ||
|
|
75f8b04fa8 | ||
|
|
9acf6adf0c | ||
|
|
353e86c49d | ||
|
|
3b0ea9b7fd | ||
|
|
dfc938b804 | ||
|
|
07ba79a64c | ||
|
|
636e66943b | ||
|
|
f7e30aed8b | ||
|
|
2447375925 | ||
|
|
b441ac64d0 | ||
|
|
8989591b04 | ||
|
|
67baeff5af | ||
|
|
e5619672d3 | ||
|
|
13133badf3 | ||
|
|
1ecb7c9178 | ||
|
|
bd2ee2fd11 | ||
|
|
a3490ea36d | ||
|
|
700e99a25d | ||
|
|
a83c2ff669 | ||
|
|
7bf06353ad | ||
|
|
94959ffff8 | ||
|
|
f5f7607ce3 | ||
|
|
9901f0a52b | ||
|
|
e1ae3da41e | ||
|
|
1f82c0f1e1 | ||
|
|
ccbe50b042 | ||
|
|
8077f2704e | ||
|
|
7861c81538 | ||
|
|
2f79e79498 | ||
|
|
396a2b0e44 | ||
|
|
8c18e9bfbf | ||
|
|
935b835853 | ||
|
|
174b9db1d2 | ||
|
|
d6c97f9d19 | ||
|
|
bedd244f44 | ||
|
|
b3237524bd | ||
|
|
c4664ab7f9 | ||
|
|
56763e9aa8 | ||
|
|
bac35853d3 | ||
|
|
e3e8180b7a | ||
|
|
bbf3bc7909 | ||
|
|
bee4e5177c | ||
|
|
fbd6e395a4 | ||
|
|
721879aaa8 | ||
|
|
738a791a27 | ||
|
|
21613e6614 | ||
|
|
751bd5e3d6 | ||
|
|
b5317782e9 | ||
|
|
b7f6c72981 | ||
|
|
117744f3f7 | ||
|
|
8fb70e9f8d | ||
|
|
2588260f6c | ||
|
|
ff701ecf13 | ||
|
|
dc135aa890 | ||
|
|
5a41d1b4da | ||
|
|
0536b64ae5 | ||
|
|
a27828ed79 | ||
|
|
b331489741 | ||
|
|
288e8e061c | ||
|
|
17bdf4c6bf | ||
|
|
550ca7e4b2 | ||
|
|
5983155680 | ||
|
|
19cd84597d | ||
|
|
5268a7663f | ||
|
|
7bda554889 | ||
|
|
7d5f9c1078 | ||
|
|
d0defe5d93 | ||
|
|
47901870bd | ||
|
|
da77aa558e | ||
|
|
25518a4ac0 | ||
|
|
a050a45424 | ||
|
|
593d1cdd39 | ||
|
|
e06d8e754f | ||
|
|
b5a6d9981d | ||
|
|
524089e051 | ||
|
|
7d464c14c0 | ||
|
|
d21a3e7ca2 | ||
|
|
f199317f47 | ||
|
|
ca5091d8b0 | ||
|
|
b205e4ab50 | ||
|
|
0611ce98a3 | ||
|
|
cbfa60f2e7 | ||
|
|
283a13acc1 | ||
|
|
e3f7e352d4 | ||
|
|
9b4ffbaf0c | ||
|
|
6cb4822842 | ||
|
|
0f7b33837c | ||
|
|
6ebce57abc | ||
|
|
bcd755c9e4 | ||
|
|
fd18a6a6d0 | ||
|
|
fe08810340 | ||
|
|
9681a4b32e | ||
|
|
2a4573842d | ||
|
|
4c36d83e38 | ||
|
|
23ca77fe3a | ||
|
|
235e196094 | ||
|
|
ad99e6df9d | ||
|
|
1d2ae6d5eb | ||
|
|
8d9e772dca | ||
|
|
d2229f43c7 | ||
|
|
3308d762f1 | ||
|
|
0b4996b31a | ||
|
|
8c77ea463d | ||
|
|
ff2c437531 | ||
|
|
d9c1454893 | ||
|
|
de21b3df88 | ||
|
|
d103488b07 | ||
|
|
c34a6b10d5 | ||
|
|
bbcff7f7c8 | ||
|
|
d54b5509bd | ||
|
|
06cebbc527 | ||
|
|
82baaae593 | ||
|
|
a68cdde584 | ||
|
|
adde1d3d63 | ||
|
|
7917370136 | ||
|
|
c8e1925065 | ||
|
|
7a3616d542 | ||
|
|
1fb6a6c8a5 | ||
|
|
1fd9a09e46 | ||
|
|
b532e74310 | ||
|
|
ccb79f7188 | ||
|
|
f64d1fb8f6 | ||
|
|
6df0d34e2a | ||
|
|
b8cc4314ce | ||
|
|
4bfa70853c | ||
|
|
27181bf863 | ||
|
|
05595541fa | ||
|
|
b87b12adfe | ||
|
|
fe9913dd3f | ||
|
|
6356755f06 | ||
|
|
817c22dc3c | ||
|
|
4b31a147a9 | ||
|
|
5a03c13731 | ||
|
|
1639e15975 | ||
|
|
c6403637ea | ||
|
|
1487809c6d | ||
|
|
4be9606341 | ||
|
|
cd17eccd43 | ||
|
|
0e7809a8f1 | ||
|
|
d067ff644d | ||
|
|
cf5a2dcaae | ||
|
|
9a5387fa1c | ||
|
|
481a43096e | ||
|
|
c4c7fc71a0 | ||
|
|
0c12d7b0d2 | ||
|
|
e9c97ef5d0 | ||
|
|
27810c9df8 | ||
|
|
fa3445edfb | ||
|
|
0befd25ab7 | ||
|
|
bd8123d475 | ||
|
|
d67dbcf2aa | ||
|
|
c3a5156a6c | ||
|
|
f3e1d35f3d | ||
|
|
994a1fa144 | ||
|
|
225b6a8f1c | ||
|
|
e602a8cc36 | ||
|
|
b2b84de10e | ||
|
|
23ba635aab | ||
|
|
5dddd8788a | ||
|
|
adac603465 | ||
|
|
9309c76162 | ||
|
|
3230c5c2ed | ||
|
|
35c172a06b | ||
|
|
590f9956ad | ||
|
|
a10dcdcff7 | ||
|
|
69bd4cf4bb | ||
|
|
16c3520587 | ||
|
|
d9c459e877 | ||
|
|
1e6ed35c77 | ||
|
|
6a6ead0d63 | ||
|
|
22975f4f7d | ||
|
|
1af87b9140 | ||
|
|
ba618eb51d | ||
|
|
22f8285e0b | ||
|
|
80a86b14b8 | ||
|
|
c73a2014c3 | ||
|
|
51356c10f5 | ||
|
|
581b1912ae | ||
|
|
b7815f137d | ||
|
|
b8d30466bb | ||
|
|
dc97056fcf | ||
|
|
e596555932 | ||
|
|
a7bdb02dbf | ||
|
|
02e438eb28 | ||
|
|
c4a21907bd | ||
|
|
02efe42a29 | ||
|
|
8a7a02532c | ||
|
|
525b3f640f | ||
|
|
adfa117dc0 | ||
|
|
f2274d8617 | ||
|
|
a2fbf99f3b | ||
|
|
9d827d4b30 | ||
|
|
0cf0413ac4 | ||
|
|
323832da63 | ||
|
|
e6aaf65d03 | ||
|
|
9951ec691e | ||
|
|
54b7d04639 | ||
|
|
0aeb966b11 | ||
|
|
1e898f94f3 | ||
|
|
49023117c3 | ||
|
|
85d7617143 | ||
|
|
fdb6d05826 | ||
|
|
acd646bcdf | ||
|
|
c97b119998 | ||
|
|
598a235bb8 | ||
|
|
894cf3f0ed | ||
|
|
f5e277269c | ||
|
|
6b9ed0c5a3 | ||
|
|
cf05980dae | ||
|
|
76cf9e1bb0 | ||
|
|
bee543da74 | ||
|
|
e92688f37e | ||
|
|
c5dce559cd | ||
|
|
8cbc12ef94 | ||
|
|
ffd0c95760 | ||
|
|
37d0f20b7e | ||
|
|
0a79372305 | ||
|
|
af43eca399 | ||
|
|
80dcf587bf | ||
|
|
df8451c4e3 | ||
|
|
2ac8d68e01 | ||
|
|
7e643abc15 | ||
|
|
c2f91097bb | ||
|
|
2ac42d15b6 | ||
|
|
00168d392b | ||
|
|
a5592dfe04 | ||
|
|
8446ad13cb | ||
|
|
7fe7c3da6c | ||
|
|
d856b9aae3 | ||
|
|
e84a462dde | ||
|
|
6d6e0ecd21 | ||
|
|
6cffaafe24 | ||
|
|
d3fe0a8abd | ||
|
|
d37a2766db | ||
|
|
fac14be90a | ||
|
|
bf4b5eb56a | ||
|
|
e0453643d8 | ||
|
|
40d6cd50fb | ||
|
|
5d24e76b62 | ||
|
|
ee0cda67c1 | ||
|
|
24f2f3b677 | ||
|
|
c2847bec4d | ||
|
|
e9d5eccd16 | ||
|
|
51c39d169f | ||
|
|
07981487bf | ||
|
|
b9210d45f3 | ||
|
|
6f7f7c4032 | ||
|
|
fc1e28a3d8 | ||
|
|
68164102d1 | ||
|
|
71e30a1607 | ||
|
|
7ab3d86bc6 | ||
|
|
db4e0fc314 | ||
|
|
cc5d0b8fbf | ||
|
|
0512a8dc5f | ||
|
|
85b17442de | ||
|
|
511335ba33 | ||
|
|
9b3879b3cf | ||
|
|
8b6f72a09b | ||
|
|
0ee25b6f42 | ||
|
|
f2e4417d87 | ||
|
|
f4f79008a4 | ||
|
|
285735e35f | ||
|
|
1c3664921d | ||
|
|
2e54989357 | ||
|
|
7a93a06770 | ||
|
|
c8c4bec316 | ||
|
|
9e21d6ab9d | ||
|
|
de826f606d | ||
|
|
0435954f32 | ||
|
|
3d4cbe28f2 | ||
|
|
009720696c | ||
|
|
c0173c499a | ||
|
|
fe397f89de | ||
|
|
38bbe89f60 | ||
|
|
9441b87d36 | ||
|
|
5ce741ba6d | ||
|
|
aea7b9b6bf | ||
|
|
aca1959dd7 | ||
|
|
beed68166e | ||
|
|
91eebecb61 | ||
|
|
a7cb90919f | ||
|
|
822b13ef75 | ||
|
|
285be7f5ff | ||
|
|
9f351ef155 | ||
|
|
6d1a676f2f | ||
|
|
de476383bd | ||
|
|
b0318d3144 | ||
|
|
e6855fd9b3 | ||
|
|
8886db7453 | ||
|
|
b73958efd0 | ||
|
|
d050103f58 | ||
|
|
338b5f3778 | ||
|
|
428bd42f15 | ||
|
|
7fba483bf1 | ||
|
|
6ba3a28143 | ||
|
|
96472b6cd2 | ||
|
|
04d364225c | ||
|
|
11f54f51ea | ||
|
|
e056da04c3 | ||
|
|
d6d38283e7 | ||
|
|
45bf06733f | ||
|
|
77d555dbf2 | ||
|
|
7f7f28f871 | ||
|
|
4030e43ac1 | ||
|
|
f7dce00c1f | ||
|
|
5d6d855562 | ||
|
|
6adb168ca4 | ||
|
|
484702527e | ||
|
|
517b50238e | ||
|
|
f19a8e67e1 | ||
|
|
490949ebee | ||
|
|
284ee4b68f | ||
|
|
7b5f780ac7 | ||
|
|
5ea72630e8 | ||
|
|
e3d3751468 | ||
|
|
f5e16eb469 | ||
|
|
4a4a0d0fd4 | ||
|
|
e11d2033d2 | ||
|
|
331f698ca3 | ||
|
|
9cd25d4fa0 | ||
|
|
953b3caf3d | ||
|
|
b3770963b0 | ||
|
|
9eed0eedeb | ||
|
|
668aa3ea93 | ||
|
|
a591e4539c | ||
|
|
3eb7cb0bb1 | ||
|
|
cd72860ea0 | ||
|
|
63f43d917b | ||
|
|
1d678acdd9 | ||
|
|
5fb0c5c312 | ||
|
|
527acac92e | ||
|
|
a9253f5f51 | ||
|
|
065ab2aade | ||
|
|
45422722c7 | ||
|
|
43e91883d8 | ||
|
|
ce37b46de0 | ||
|
|
07fb644e07 | ||
|
|
55c85c0d5c | ||
|
|
e240f14a37 | ||
|
|
c2ed39a36d | ||
|
|
f0478ffa01 | ||
|
|
b24307cf35 | ||
|
|
3fd1513128 | ||
|
|
41fc23f99b | ||
|
|
043cf27ace | ||
|
|
a739343df5 | ||
|
|
a4619376d1 | ||
|
|
a97230f5cb | ||
|
|
d9d3adb3e2 | ||
|
|
fcbcb55e53 | ||
|
|
ae7a7a3c6c | ||
|
|
5ee25f827c | ||
|
|
cf56614d87 | ||
|
|
bb29bea207 | ||
|
|
2abe95cfc2 | ||
|
|
0dd8c66ce4 | ||
|
|
facd650e4c | ||
|
|
dd34907f5c | ||
|
|
040dd73599 | ||
|
|
26b2b89b69 | ||
|
|
9734bc9325 | ||
|
|
317a39e76b | ||
|
|
345b25f636 | ||
|
|
6731fa1e5e | ||
|
|
344a7cbebe | ||
|
|
5c3499057e | ||
|
|
c1a2b3c868 | ||
|
|
5aa97513de | ||
|
|
ea3e17f7fe | ||
|
|
cd61269cd5 | ||
|
|
9649d67883 | ||
|
|
a91a8401d6 | ||
|
|
032252469b | ||
|
|
162fe5219c | ||
|
|
a14073d170 | ||
|
|
39807186df | ||
|
|
0b4365b3c3 | ||
|
|
5a6ccedf41 | ||
|
|
8a4a9d7df8 | ||
|
|
4a2b67c9d5 | ||
|
|
86e31e7e86 | ||
|
|
51e4bf82f2 | ||
|
|
d007bdf813 | ||
|
|
7db2db96e1 | ||
|
|
274a85186e | ||
|
|
c85fd20a58 | ||
|
|
340faa3a27 | ||
|
|
c1c661c2a9 | ||
|
|
815320cd24 | ||
|
|
43550af175 | ||
|
|
e9d60913d0 | ||
|
|
a0c5f32a42 | ||
|
|
2ab803cebc | ||
|
|
1978c6860b | ||
|
|
d86b5237c1 | ||
|
|
e59b20fb6e | ||
|
|
ac6aa427a3 | ||
|
|
4cc8f73aa8 | ||
|
|
34ce86aba0 | ||
|
|
ebc9d7aa63 | ||
|
|
8e573abf9e | ||
|
|
b798f7a988 | ||
|
|
a4ed1f30a4 | ||
|
|
b241e26285 | ||
|
|
4073b3a981 | ||
|
|
bbbd7b704e | ||
|
|
9db6be2047 | ||
|
|
92bda9675f | ||
|
|
14ff64d3d7 | ||
|
|
3f925690ee | ||
|
|
15026c900b | ||
|
|
7068a49e00 | ||
|
|
6f9b304a40 | ||
|
|
43de705308 | ||
|
|
02170a156e | ||
|
|
a0b70b7bbd | ||
|
|
7e7c0dad7f | ||
|
|
e6200aae4c | ||
|
|
20c6ae867b | ||
|
|
185b1f9ee1 | ||
|
|
f99b3bfdb8 | ||
|
|
9d0b71a36c | ||
|
|
57b2141166 | ||
|
|
d737069ef9 | ||
|
|
a9b2951d8b | ||
|
|
946f978e10 | ||
|
|
427273a09f | ||
|
|
a038cb197b | ||
|
|
d2e0913170 | ||
|
|
ed5a7fb44b | ||
|
|
ce28464251 | ||
|
|
fe203fe70c | ||
|
|
115ba72572 | ||
|
|
4465db130d | ||
|
|
c90c38d612 | ||
|
|
58b14cda3d | ||
|
|
6df2f20127 | ||
|
|
ccce224377 | ||
|
|
54f6e7cc9e | ||
|
|
8b7d613b99 | ||
|
|
fd1b89b950 | ||
|
|
d2863c91db | ||
|
|
92e5fc9980 | ||
|
|
4105692dee | ||
|
|
9492fe188d | ||
|
|
25eba9de43 | ||
|
|
49eb050423 | ||
|
|
daed3bcac7 | ||
|
|
5c13b03b3d | ||
|
|
ecd3906c51 | ||
|
|
78d26c0617 | ||
|
|
a34fb729ea | ||
|
|
fc18b39b89 | ||
|
|
bd2ea48969 | ||
|
|
861199f812 | ||
|
|
242eef8427 | ||
|
|
d589f19be4 | ||
|
|
0a1d6f1bdb | ||
|
|
b563ab445c | ||
|
|
c907126b60 | ||
|
|
6ba4531d18 | ||
|
|
648ccf4cce | ||
|
|
8597b87cad | ||
|
|
1a3a48e4de | ||
|
|
b3e6566bd8 | ||
|
|
aad3e092f7 | ||
|
|
759530ea58 | ||
|
|
ec04bb4160 | ||
|
|
9759cb9fd9 | ||
|
|
d974ed750a | ||
|
|
884cac4b3b | ||
|
|
c10c6cf149 | ||
|
|
a3a889339b | ||
|
|
f79e902762 | ||
|
|
67a481356b | ||
|
|
e443ae0962 | ||
|
|
5809a67e4c | ||
|
|
9f664f5239 | ||
|
|
1d8f2dd735 | ||
|
|
27527c5540 | ||
|
|
f9d29d723c | ||
|
|
3e706abc54 | ||
|
|
d553b0c77c | ||
|
|
e73350e2ba | ||
|
|
9d0450a4b5 | ||
|
|
69c27ca24d | ||
|
|
317895d39d | ||
|
|
ec3be76782 | ||
|
|
637f0934f9 | ||
|
|
a4d8675a15 | ||
|
|
82c264b75f | ||
|
|
83a02ad6e6 | ||
|
|
b1c029e16a | ||
|
|
14c671ebf3 | ||
|
|
dc62e4e0e6 | ||
|
|
bb9d1ce5a6 | ||
|
|
1beda52770 | ||
|
|
a8f81fbdf3 | ||
|
|
5f938f4f38 | ||
|
|
97141bfb7a | ||
|
|
461e003805 | ||
|
|
002f736b97 | ||
|
|
89a27b933a | ||
|
|
f5bced5692 | ||
|
|
eaed5a580c | ||
|
|
c4caba9016 | ||
|
|
79d0d56daf | ||
|
|
25a201bc44 | ||
|
|
71afd6719f | ||
|
|
f7bfc4d135 | ||
|
|
7d0037bfaa | ||
|
|
8b2612559d | ||
|
|
c4c9fcf9e9 | ||
|
|
1b8aab654f | ||
|
|
1869e2b160 | ||
|
|
1e82573675 | ||
|
|
15c5c41a74 | ||
|
|
e4ad75d5d1 | ||
|
|
5cd8e85034 | ||
|
|
decd3e3443 | ||
|
|
57d633b1d2 | ||
|
|
4102dca55c | ||
|
|
c09fe46790 | ||
|
|
2c1919117d | ||
|
|
4d9b05f83c | ||
|
|
cd2302c8d5 | ||
|
|
4da4bdd577 | ||
|
|
b5b2a374d8 | ||
|
|
340b1bc269 | ||
|
|
b39d747e8f | ||
|
|
8d6d839878 | ||
|
|
1ed1c95e4d | ||
|
|
fc6948e627 | ||
|
|
03e09aff17 | ||
|
|
1404057a60 | ||
|
|
48d7ea4be4 | ||
|
|
979c7dc2a0 | ||
|
|
2009545d6a | ||
|
|
2796f9d9c1 | ||
|
|
ce094b663c | ||
|
|
4c85550d58 | ||
|
|
722e0b730f | ||
|
|
62d90caf51 | ||
|
|
0c4c50270c | ||
|
|
5c871c863f | ||
|
|
7b23a2a2b4 | ||
|
|
1195777036 | ||
|
|
c66b483fba | ||
|
|
112140c006 | ||
|
|
3f8031cd0e | ||
|
|
43b263e025 | ||
|
|
9454302a83 | ||
|
|
d2f23fd95e | ||
|
|
0868b6dcf4 | ||
|
|
da1df60746 | ||
|
|
b81fb55d2c | ||
|
|
f2fa7d4e5a | ||
|
|
c846521c92 | ||
|
|
aebea8ed5f | ||
|
|
b210e84dd3 | ||
|
|
8b0eab5e90 | ||
|
|
6838b9e769 | ||
|
|
74c8107a92 | ||
|
|
0f501ed85d | ||
|
|
fdb3b8aacb | ||
|
|
a0a031dbc2 | ||
|
|
df75099000 | ||
|
|
28ebbe14ca | ||
|
|
c7791fd68d | ||
|
|
74ece8fde0 | ||
|
|
06ec1e00ee | ||
|
|
cae5b5e778 | ||
|
|
1434cb62f5 | ||
|
|
9d8dffa80b | ||
|
|
a0643c7388 | ||
|
|
74a6bc8e06 | ||
|
|
16dab34864 | ||
|
|
c663d54eb8 | ||
|
|
394d238f97 | ||
|
|
5b7182fac6 | ||
|
|
b2474fc3fb | ||
|
|
e700f937db | ||
|
|
e79b190975 | ||
|
|
9e5b7459f0 | ||
|
|
8cb84d2911 | ||
|
|
4c36bce5bd | ||
|
|
a7ffbd8a27 | ||
|
|
b2843ef1ce | ||
|
|
d5f2a1a634 | ||
|
|
376679571c | ||
|
|
18db775954 | ||
|
|
0e6f713685 | ||
|
|
118157e43a | ||
|
|
25437fafc2 | ||
|
|
371ff5e019 | ||
|
|
9e51f6baad | ||
|
|
1382e21360 | ||
|
|
5499eb6018 | ||
|
|
fc734328cb | ||
|
|
38f675d7f9 | ||
|
|
0f9d796460 | ||
|
|
0231e687b7 | ||
|
|
a6adae4f28 | ||
|
|
cba690ead8 | ||
|
|
ce1cbfa9d9 | ||
|
|
dca8971f06 | ||
|
|
383431ec6d | ||
|
|
65678ac674 | ||
|
|
8aaffeeb17 | ||
|
|
be9c9f8ce8 | ||
|
|
d1f9e63a13 | ||
|
|
561560ae04 | ||
|
|
1ffca33be9 | ||
|
|
7430a9b332 | ||
|
|
d506c9b562 | ||
|
|
b3747ee0e7 | ||
|
|
8fac45e60a | ||
|
|
b26480ba6e | ||
|
|
aa57083afc | ||
|
|
70108a0aa7 | ||
|
|
d17368e9d3 | ||
|
|
45290441de | ||
|
|
648dfdab80 | ||
|
|
53700e3cb6 | ||
|
|
4ee138f71e | ||
|
|
c09f281f58 | ||
|
|
3a24d94c2b | ||
|
|
39836fc821 | ||
|
|
eeb2f39ada | ||
|
|
f4a15e6db7 | ||
|
|
13215d7f5d | ||
|
|
47de756080 | ||
|
|
7d7cd1d3d6 | ||
|
|
1ce46a0ef8 | ||
|
|
3cb55532c8 | ||
|
|
b3e7d6ea29 | ||
|
|
203a165502 | ||
|
|
6f0d45dfcd | ||
|
|
e7519f3058 | ||
|
|
8f2e31cafa | ||
|
|
5e91f05a7b | ||
|
|
0a30c8efdf | ||
|
|
9c4ee4182f | ||
|
|
11b6261611 | ||
|
|
0f5f2a1715 | ||
|
|
8e00ba7457 | ||
|
|
ba373573e0 | ||
|
|
cbad98b783 | ||
|
|
2728e03e76 | ||
|
|
c217f65677 | ||
|
|
9ec90c8bd9 | ||
|
|
9319520043 | ||
|
|
4c99280c4a | ||
|
|
a84080ddfa | ||
|
|
1d0eb1d26f | ||
|
|
4cce1d5b23 | ||
|
|
ecde4493e0 | ||
|
|
83e33ed00b | ||
|
|
61cfebb7ba | ||
|
|
555210175e | ||
|
|
87d355f705 | ||
|
|
3ce9c8820f | ||
|
|
0ad2b13d76 | ||
|
|
90c0bce9aa | ||
|
|
a0a76f6b65 | ||
|
|
6988f0a35e | ||
|
|
8f77a59a97 | ||
|
|
35e50dc47c | ||
|
|
5d1b358179 | ||
|
|
ff43df6822 | ||
|
|
86a55fd292 | ||
|
|
3ec037cdf4 | ||
|
|
86ba4fd083 | ||
|
|
63a339dd31 | ||
|
|
7a5145fa37 | ||
|
|
6379c62e37 | ||
|
|
cb0ff7b82f | ||
|
|
fd6362e463 | ||
|
|
3507c4acb6 | ||
|
|
a63aff560f | ||
|
|
0cc5c7f7bb | ||
|
|
044f8487e2 | ||
|
|
e805515767 | ||
|
|
f37c9a5adb | ||
|
|
da29f8db86 | ||
|
|
4f8e427db9 | ||
|
|
e2d4d121d8 | ||
|
|
9373296fd4 | ||
|
|
358136b840 | ||
|
|
1ac6e466ce | ||
|
|
cd8a907a86 | ||
|
|
72c010374f | ||
|
|
c0b3618331 | ||
|
|
1a82fc28d3 | ||
|
|
ddf4550d22 | ||
|
|
3d5e4370e0 | ||
|
|
b4790c89ce | ||
|
|
460019e01b | ||
|
|
c5149ec42c | ||
|
|
d4758bc5b1 | ||
|
|
146faf74a9 | ||
|
|
841ace3aa8 | ||
|
|
49f27066b8 | ||
|
|
2a5fa4df0e | ||
|
|
2cebbbc2f8 | ||
|
|
b98d8519d4 | ||
|
|
c1dee0dbf7 | ||
|
|
62c6d85ed0 | ||
|
|
065ed23c79 | ||
|
|
89f65a15de | ||
|
|
cbffee1486 | ||
|
|
566fa9cb91 | ||
|
|
f245656c0c | ||
|
|
8057fb54a6 | ||
|
|
391b926397 | ||
|
|
dcc81c9275 | ||
|
|
8d99bd723b | ||
|
|
85f4d4824f | ||
|
|
cdcae36b58 | ||
|
|
a0df97a2e5 | ||
|
|
2f589a49e9 | ||
|
|
590b68aa29 | ||
|
|
75b64b71bf | ||
|
|
bfe6b98ee1 | ||
|
|
aaa8bfe67d | ||
|
|
121d300cf7 | ||
|
|
47ab946f81 | ||
|
|
bbd7e33059 | ||
|
|
03eb756551 | ||
|
|
de09217e30 | ||
|
|
866da08cab | ||
|
|
88d287bac0 | ||
|
|
f491cf25fc | ||
|
|
8061ee7ca7 | ||
|
|
f2cb2c3791 | ||
|
|
de233d6778 | ||
|
|
b005c3905f | ||
|
|
9bcb91ceae | ||
|
|
e4c9d29b69 | ||
|
|
f2eaa1b5d1 | ||
|
|
22e558478a | ||
|
|
a87d83e10e | ||
|
|
f099e0f90d | ||
|
|
c86b6b7b8f | ||
|
|
d4799ea1df | ||
|
|
bd5fd97fb7 | ||
|
|
7355b91796 | ||
|
|
dcd797888c | ||
|
|
170d815620 | ||
|
|
3433aa0c5b | ||
|
|
6ba46673fa | ||
|
|
c6f7ded125 | ||
|
|
70b4a2b4d4 | ||
|
|
82500ca079 | ||
|
|
3e8b570c6b | ||
|
|
282b170147 | ||
|
|
de6fa83e96 | ||
|
|
685dade614 | ||
|
|
864081c31e | ||
|
|
d034c6144c | ||
|
|
b6f6ab490f | ||
|
|
1724f55cb2 | ||
|
|
24677a3266 | ||
|
|
abb19572b5 | ||
|
|
1d726564df | ||
|
|
8dd6c8457f | ||
|
|
20cd5b1d9c | ||
|
|
2390735164 | ||
|
|
5ac5142b67 | ||
|
|
245b5e3b79 | ||
|
|
c4c0daddd3 | ||
|
|
f57ce57cb3 | ||
|
|
655c7ae023 | ||
|
|
55c2ad9644 | ||
|
|
fbefb3543e | ||
|
|
a94cb09677 | ||
|
|
0e35e33607 | ||
|
|
d16d506a78 | ||
|
|
595cdd66c7 | ||
|
|
76e19a4b41 | ||
|
|
3ab5e11d5f | ||
|
|
e642639662 | ||
|
|
ac7e5f723d | ||
|
|
72d92c419f | ||
|
|
79a91605d3 | ||
|
|
338a0b97ac | ||
|
|
747213dea3 | ||
|
|
84df0c431b | ||
|
|
e61cdc500c | ||
|
|
422536d4a1 | ||
|
|
b441ffc20f | ||
|
|
43d60e238a | ||
|
|
d578a7c96a | ||
|
|
625ad7f4b7 | ||
|
|
e42ccf932e | ||
|
|
b0ca6493e3 | ||
|
|
6165a49c10 | ||
|
|
9e68041326 | ||
|
|
bd5f25eabf | ||
|
|
8663f5a136 | ||
|
|
2420f7de9e | ||
|
|
a8f84aa93d | ||
|
|
c3186ac32e | ||
|
|
6a99345475 | ||
|
|
19529408f4 | ||
|
|
fe2c3aa8ad | ||
|
|
193c2026ba | ||
|
|
1cc64ae4d2 | ||
|
|
74f1f93732 | ||
|
|
c88602c7ab | ||
|
|
32131c5aa7 | ||
|
|
6957a49752 | ||
|
|
9a184d1c7a | ||
|
|
8dc13aa0d9 | ||
|
|
41b465f444 | ||
|
|
f7838601e6 | ||
|
|
f2a926d22d | ||
|
|
f2d475d3d3 | ||
|
|
b13488404e | ||
|
|
4f9b3ad8cb | ||
|
|
0d3ff63545 | ||
|
|
2a7d516306 | ||
|
|
9af76ad86c | ||
|
|
733bf7f4fb | ||
|
|
83894f9954 | ||
|
|
c8298c72ea | ||
|
|
9c68877328 | ||
|
|
904664319a | ||
|
|
1e57de2d6e | ||
|
|
9f5151e87f | ||
|
|
863c26a056 | ||
|
|
b0da06bad4 | ||
|
|
020625392a | ||
|
|
97d49e53b7 | ||
|
|
ab4171b8ec | ||
|
|
ba2ffb9c0a | ||
|
|
5fea05bc5f | ||
|
|
5d9e1657ba | ||
|
|
a2dc0a5f56 | ||
|
|
8e228be5a0 | ||
|
|
142bd1d049 | ||
|
|
152a5e8b94 | ||
|
|
fe5f91ce15 | ||
|
|
ebc76849b7 | ||
|
|
dc9fc5e5a9 | ||
|
|
188872d712 | ||
|
|
3ae92717da | ||
|
|
6d3232e70a | ||
|
|
4a4b0a0c3b | ||
|
|
1e058463b2 | ||
|
|
9f8936ea40 | ||
|
|
0cb6ecadb6 | ||
|
|
fee2eea87c | ||
|
|
dabbbc2e09 | ||
|
|
3789ec47c6 | ||
|
|
150eed3426 | ||
|
|
90dbe70b9f | ||
|
|
f906945101 | ||
|
|
78509f3a06 | ||
|
|
8b0f18df5a | ||
|
|
e05fe318e3 | ||
|
|
83456d46d9 | ||
|
|
4d1e4ef4c9 | ||
|
|
799ce01f14 | ||
|
|
3984da6c0f | ||
|
|
007ee0de5e | ||
|
|
b1d13b3d42 | ||
|
|
758c70f7c3 | ||
|
|
34291fc4b4 | ||
|
|
e0bd1098eb | ||
|
|
f9f36445ec | ||
|
|
34d33a2d83 | ||
|
|
51058dd83a | ||
|
|
d88bb1cfcb | ||
|
|
b7d4908e90 | ||
|
|
5bad64a497 | ||
|
|
c487f77b89 | ||
|
|
9d93b0d3fb | ||
|
|
d886c5615a | ||
|
|
54a8a89dd0 | ||
|
|
9c1c613c90 | ||
|
|
014b23647a | ||
|
|
df03b03818 | ||
|
|
9a65ef358b | ||
|
|
651a7d9a5b | ||
|
|
009a626419 | ||
|
|
58ab6f5467 | ||
|
|
b0019aa017 | ||
|
|
05ec4560b9 | ||
|
|
0a9ef0d345 | ||
|
|
9ba9e8dbae | ||
|
|
719af5f475 | ||
|
|
9dcd64c186 | ||
|
|
5c17c9b98d | ||
|
|
954b07f924 | ||
|
|
07e2fb977c | ||
|
|
861d1041b6 | ||
|
|
b03d10a824 | ||
|
|
3eff27b5c5 | ||
|
|
1a33c266e3 | ||
|
|
2436c09295 | ||
|
|
5d7fba1955 | ||
|
|
db768f356b | ||
|
|
5b96e1e9fd | ||
|
|
ca3a42f680 | ||
|
|
51041b5347 | ||
|
|
cd66d4a519 | ||
|
|
17aa744b52 | ||
|
|
b8050068d7 | ||
|
|
17b91b9368 | ||
|
|
4c5b41b6b2 | ||
|
|
87d41700fc | ||
|
|
6a74f88e6c | ||
|
|
9cc9aa015d | ||
|
|
6f894dc757 | ||
|
|
e3cd0d132b | ||
|
|
44e120d382 | ||
|
|
4dad6bef40 | ||
|
|
449ca770c1 | ||
|
|
433d8cee9a | ||
|
|
9bb5be306f | ||
|
|
932d5bb004 | ||
|
|
94fe18b4ae | ||
|
|
b5c8afe69f | ||
|
|
183f0a5400 | ||
|
|
af62d949d8 | ||
|
|
8c4bdc3f31 | ||
|
|
b4336f7ceb | ||
|
|
b66bc02098 | ||
|
|
eeb5482d36 | ||
|
|
f21ac8afbe | ||
|
|
04464da1c8 | ||
|
|
efe94f8b44 | ||
|
|
50b54683c4 | ||
|
|
cd43a6c90e | ||
|
|
45cc4e0d27 | ||
|
|
761a3c102f | ||
|
|
426677935e | ||
|
|
59bab376cd | ||
|
|
d79161aee0 | ||
|
|
69e9247fb6 | ||
|
|
3fcbb204cf | ||
|
|
8e81d796f0 | ||
|
|
ba96edc620 | ||
|
|
d1b000dcc6 | ||
|
|
792303a833 | ||
|
|
1357fed067 | ||
|
|
44a893b46e | ||
|
|
ffcba0bf22 | ||
|
|
4a395ec761 | ||
|
|
d546d1fdca | ||
|
|
e7db88a647 | ||
|
|
6e062afc67 | ||
|
|
e2c664300c | ||
|
|
f6db00a444 | ||
|
|
8809c5238f | ||
|
|
3d4808e024 | ||
|
|
14f634f9ea | ||
|
|
b2d13a2493 | ||
|
|
e9b00339a5 | ||
|
|
73753ce071 | ||
|
|
57365a6664 | ||
|
|
b6a92bade0 | ||
|
|
232d871773 | ||
|
|
7598637efc | ||
|
|
006fcaa511 | ||
|
|
fdc3a93a75 | ||
|
|
0c7fd793be | ||
|
|
dcc7b09dd8 | ||
|
|
097a6c924a | ||
|
|
6f2c952b4b | ||
|
|
bb6ec9f89f | ||
|
|
ddaf6bfc92 | ||
|
|
163e102bcf | ||
|
|
abc1406f4d | ||
|
|
c9d7de4022 | ||
|
|
74224694c3 | ||
|
|
d8edf07367 | ||
|
|
0be3a181e6 | ||
|
|
9ceb6c2644 | ||
|
|
4425b1a54c | ||
|
|
e45ad2197a | ||
|
|
affb1480ce | ||
|
|
4cf7a48567 | ||
|
|
31ffa73bda | ||
|
|
3d8b3f3040 | ||
|
|
4db8de8d9d | ||
|
|
08860e0f25 | ||
|
|
7b3ce38cc1 | ||
|
|
ff64332958 | ||
|
|
e3eb6020a8 | ||
|
|
14d9a208a7 | ||
|
|
5afbfe1ce9 | ||
|
|
97220d707b | ||
|
|
72172a3b97 | ||
|
|
245d2a5ee4 | ||
|
|
7e90a0e724 | ||
|
|
b6232b73cf | ||
|
|
22f7c0b020 | ||
|
|
3c6f7b30eb | ||
|
|
87a37ca8c1 | ||
|
|
8da93f90b6 | ||
|
|
5002353854 | ||
|
|
1e44747acf | ||
|
|
294734ce93 | ||
|
|
c013b79ec9 | ||
|
|
78f9e33ed4 | ||
|
|
7536241be0 | ||
|
|
d8afb97c7a | ||
|
|
0e8da8e39b | ||
|
|
b1459f85cd | ||
|
|
faf7877d00 | ||
|
|
a009961a58 | ||
|
|
2477b289e2 | ||
|
|
689cc5f3e7 | ||
|
|
04a5038ff4 | ||
|
|
ae2d8330ca | ||
|
|
08bd135d55 | ||
|
|
88cf5483c8 | ||
|
|
ea3e5d14cd | ||
|
|
d987e08461 | ||
|
|
b7c6204b6c | ||
|
|
263da4cc35 | ||
|
|
d147099e92 | ||
|
|
33b8d98812 | ||
|
|
9ce650377b | ||
|
|
89d0b632a4 | ||
|
|
786aefe7d8 | ||
|
|
daac4486e5 | ||
|
|
902f7c4181 | ||
|
|
f99134c70b | ||
|
|
afae00f660 | ||
|
|
5af263c70e | ||
|
|
eebd56d738 | ||
|
|
4df8a6b3b7 | ||
|
|
8f7d22921e | ||
|
|
844c866642 | ||
|
|
20364f47b4 | ||
|
|
363b2db695 | ||
|
|
12637bbfe4 | ||
|
|
f0230c346c | ||
|
|
5a820b4f9e | ||
|
|
6483800e49 | ||
|
|
1fa9faa314 | ||
|
|
4954bfdf76 | ||
|
|
d9abe2f475 | ||
|
|
807555d478 | ||
|
|
176ca590e1 | ||
|
|
247be4a8a4 | ||
|
|
41a3f4483f | ||
|
|
cf7664d446 | ||
|
|
ccc6eaf4b0 | ||
|
|
42337dcd55 | ||
|
|
5b2705e158 | ||
|
|
882102245a | ||
|
|
f45d687d2a | ||
|
|
0f4316a5ae | ||
|
|
62856e005f | ||
|
|
205d09a5cd | ||
|
|
9aac5e91ab | ||
|
|
5d5a7a21d2 | ||
|
|
8d03ff40ab | ||
|
|
f8ff79e23c | ||
|
|
4cacb9f92c | ||
|
|
93097ac4b7 | ||
|
|
1cd9a8e71c | ||
|
|
f134bd196c | ||
|
|
2707e557a7 | ||
|
|
8e81eab729 | ||
|
|
dcb6a66d8a | ||
|
|
b799d26a10 | ||
|
|
5d62c70cc5 | ||
|
|
b3caa93e8b | ||
|
|
5c12bd988d | ||
|
|
1d5bfd33d4 | ||
|
|
bebe220aa0 | ||
|
|
2c321cbdb8 | ||
|
|
775166f5f2 | ||
|
|
97d74f6cf1 | ||
|
|
69bb359615 | ||
|
|
f38806eeb2 | ||
|
|
b22f86b62c | ||
|
|
6fe9d0bfa4 | ||
|
|
005a2fe263 | ||
|
|
03c9d9c8f1 | ||
|
|
dbf754880e | ||
|
|
fcc7351552 | ||
|
|
fada526f5d | ||
|
|
204f180ec9 | ||
|
|
fd1796557d | ||
|
|
d2fe689fc5 | ||
|
|
c9e88cfdb7 | ||
|
|
c948082e0a | ||
|
|
7a88558706 | ||
|
|
212ae89c50 | ||
|
|
4e382d456f | ||
|
|
140135317c | ||
|
|
980d23c370 | ||
|
|
2a8c1d5f03 | ||
|
|
e010bbb631 | ||
|
|
406157c06c | ||
|
|
08dc1870bf | ||
|
|
2b35d85094 | ||
|
|
d34fc2512f | ||
|
|
5c4f88221c | ||
|
|
54809d05e2 | ||
|
|
2b81832b67 | ||
|
|
125e7375c0 | ||
|
|
14d97511e6 | ||
|
|
76726b6cd2 | ||
|
|
9c61389830 | ||
|
|
d17db88088 | ||
|
|
6fff307570 | ||
|
|
f341b5b3fe | ||
|
|
56b8922dbb | ||
|
|
bb64655804 | ||
|
|
3f62d7cb65 | ||
|
|
53e74ec5cd | ||
|
|
c2815d15ed | ||
|
|
321c8d14e1 | ||
|
|
9ddd9f317d | ||
|
|
c4d7d545ae | ||
|
|
891361b80c | ||
|
|
698eb14869 | ||
|
|
8c21aac16e | ||
|
|
4bee4b8960 | ||
|
|
0115a86b24 | ||
|
|
1bf12a7338 | ||
|
|
3c066ffce2 | ||
|
|
1873b94fa7 | ||
|
|
1fce679402 | ||
|
|
e786a85bbd | ||
|
|
820edba113 | ||
|
|
b6b6fdf115 | ||
|
|
8a7f981f6e | ||
|
|
3c31144a58 | ||
|
|
5ecd86ba02 | ||
|
|
6a75eb893c | ||
|
|
8cd5d10197 | ||
|
|
32af3adf39 | ||
|
|
d908cf566a | ||
|
|
d8e205ec25 | ||
|
|
945e52b4f8 | ||
|
|
5dad9ff301 | ||
|
|
7cedc9228e | ||
|
|
adfd40cde7 | ||
|
|
9a313752c5 | ||
|
|
c2bdede29a | ||
|
|
20aacd94f3 | ||
|
|
fd53f07efe | ||
|
|
5640200bb4 | ||
|
|
33e8dbd5b3 | ||
|
|
1fbd353001 | ||
|
|
93b4258f02 | ||
|
|
ef47563055 | ||
|
|
7c1168e797 | ||
|
|
5fdcae4196 | ||
|
|
3fc9c891ac | ||
|
|
095989e6ba | ||
|
|
dd6015b329 | ||
|
|
be7e8e63ee | ||
|
|
932875a317 | ||
|
|
2b45690858 | ||
|
|
b5acb5db85 | ||
|
|
26cdebece4 | ||
|
|
076c29e004 | ||
|
|
48d5188aaa | ||
|
|
31766fde32 | ||
|
|
4da6ae0a74 | ||
|
|
7103b35686 | ||
|
|
a26f480d02 | ||
|
|
c234b01dee | ||
|
|
b1b81c152f | ||
|
|
ba62ae07b5 | ||
|
|
1df06e210d | ||
|
|
15b9d8d54f | ||
|
|
7dc5ee3d4e | ||
|
|
c98e89278f | ||
|
|
bd11b5864b | ||
|
|
17e5880b2d | ||
|
|
d967095b86 | ||
|
|
f3204114cd | ||
|
|
105771a00d | ||
|
|
b322da5936 | ||
|
|
db4cdc27a3 | ||
|
|
ea1bfd5eaa | ||
|
|
b428d1dc23 | ||
|
|
2dc30cddc3 | ||
|
|
88f45fb618 | ||
|
|
ff00bdb3eb | ||
|
|
9d58d0fac5 | ||
|
|
3ff0ecee5f | ||
|
|
f64bb74a3e | ||
|
|
910e076e73 | ||
|
|
15a5d64621 | ||
|
|
0b1cd62449 | ||
|
|
f06fce06c3 | ||
|
|
f357180564 | ||
|
|
4291c80259 | ||
|
|
af8d19ab60 | ||
|
|
04a054474f | ||
|
|
57568f9614 | ||
|
|
7400f0f946 | ||
|
|
e0600a3058 | ||
|
|
210fbb9535 | ||
|
|
2e9fb97a98 | ||
|
|
9d5d4c9826 | ||
|
|
f2411368ba | ||
|
|
712269aa35 | ||
|
|
495eec6c41 | ||
|
|
0720ea0e97 | ||
|
|
5688196ea7 | ||
|
|
d06a0bb501 | ||
|
|
c6b540868f | ||
|
|
0109c3ff1d | ||
|
|
2183c37373 | ||
|
|
95fa00e4fc | ||
|
|
59b65fffe3 | ||
|
|
78c2e66aa8 | ||
|
|
acf2e53825 | ||
|
|
e9e4f5d18a | ||
|
|
9664fe4a61 | ||
|
|
06c830dd3c | ||
|
|
15ea62c8d7 | ||
|
|
a7ed60ee12 | ||
|
|
00b56eaf6c | ||
|
|
c4356bf9ce | ||
|
|
fb7853d148 | ||
|
|
d5f7ac152f | ||
|
|
a43fc529e7 | ||
|
|
cff438eccc | ||
|
|
fd516fee56 | ||
|
|
f155f48888 | ||
|
|
66fc0e68a4 | ||
|
|
7adc4e3d01 | ||
|
|
a2c41cf9a7 | ||
|
|
3b648f5aa5 | ||
|
|
8a5c1498e9 | ||
|
|
3c23975a03 | ||
|
|
4b14e02692 | ||
|
|
ade7abbd57 | ||
|
|
e72a32bf2b | ||
|
|
bc6fdbbff5 | ||
|
|
d229c16cad | ||
|
|
9aa8b9bc3e | ||
|
|
d6b3a4dd65 | ||
|
|
d393683be0 | ||
|
|
26260c7056 | ||
|
|
0a0e0e33ab | ||
|
|
2089ecf6eb | ||
|
|
2237eb0f54 | ||
|
|
58360c5db1 | ||
|
|
fd9adca1a9 | ||
|
|
e6e9f5160e | ||
|
|
b5af98d357 | ||
|
|
7cb5333c5a | ||
|
|
fa95d5cd24 | ||
|
|
3da36c82a0 | ||
|
|
39161b61c5 | ||
|
|
d445e891bf | ||
|
|
1432446c9a | ||
|
|
bbd62778f3 | ||
|
|
80942f9b02 | ||
|
|
e17c9b9be1 | ||
|
|
9e288e0c32 | ||
|
|
2137c5bd51 | ||
|
|
1bc9adf51d | ||
|
|
91d4417859 | ||
|
|
2d91c93194 | ||
|
|
1c701602bb | ||
|
|
4c97c26f6d | ||
|
|
df376758c7 | ||
|
|
ff14d05ba2 | ||
|
|
6004ea31e4 | ||
|
|
84f1b87739 | ||
|
|
6fc98885dd | ||
|
|
b5cbb90f4f | ||
|
|
2e57d2e328 | ||
|
|
0d94941001 | ||
|
|
917ab4acc9 | ||
|
|
9369f5e428 | ||
|
|
32d08414f3 | ||
|
|
061479b2f5 | ||
|
|
77eac9e1b7 | ||
|
|
5c0ec6f7c1 | ||
|
|
d8838d3fc9 | ||
|
|
6439d64b72 | ||
|
|
3f224ce47b | ||
|
|
e7e67c6ad8 | ||
|
|
2bcc229334 | ||
|
|
b54c7e1b49 | ||
|
|
e9627cf7f7 | ||
|
|
b1a611e41a | ||
|
|
2db5b0f60f | ||
|
|
a0ee33b63d | ||
|
|
4b1bd3d132 | ||
|
|
f44b9a0fc8 | ||
|
|
e06bc2cc95 | ||
|
|
edbab7f8d5 | ||
|
|
fed29ec929 | ||
|
|
36d029b597 | ||
|
|
bc1a8dfca4 | ||
|
|
2f41011160 | ||
|
|
4e636ba51f | ||
|
|
5ed4ef907b | ||
|
|
2662f9f72c | ||
|
|
cc2d0247ee | ||
|
|
bcb01813ef | ||
|
|
243a9e3b6f | ||
|
|
9fe2c557fe | ||
|
|
9bd9011eca | ||
|
|
d16aab44f2 | ||
|
|
f42563f3dd | ||
|
|
0330b2b454 | ||
|
|
26189a93af | ||
|
|
e8bd7436aa | ||
|
|
79f7d29df9 | ||
|
|
47a20b1784 | ||
|
|
1de7f24047 | ||
|
|
6498c0fe44 | ||
|
|
f8ef533f6a | ||
|
|
6394905022 | ||
|
|
8a31f67b42 | ||
|
|
122816efaf | ||
|
|
eb6f222d82 | ||
|
|
655a7ad4fe | ||
|
|
376de340d6 | ||
|
|
957688b47d | ||
|
|
60e228ec14 | ||
|
|
d58f500dee | ||
|
|
8a80aeef1c | ||
|
|
353bdd77aa | ||
|
|
5aaaaef441 | ||
|
|
d8d1f1418c | ||
|
|
554157ade0 | ||
|
|
60935ce342 | ||
|
|
e0c408dffc | ||
|
|
3b3bd2fb45 | ||
|
|
6df7610e9c | ||
|
|
9684e1031f | ||
|
|
2344850243 | ||
|
|
270066239e | ||
|
|
0d9a56ba17 | ||
|
|
127ec7536a | ||
|
|
f365fd7f20 | ||
|
|
c4155c4c21 | ||
|
|
49a6bb11a8 | ||
|
|
1ef8789d21 | ||
|
|
e72f864ee9 | ||
|
|
06cb9d9808 | ||
|
|
4cf2fa59ac | ||
|
|
8b796f12da | ||
|
|
4334b58c54 | ||
|
|
1e8fc410fd | ||
|
|
0e4f676047 | ||
|
|
86105ee7a2 | ||
|
|
3f8c206104 | ||
|
|
6692b19d7e | ||
|
|
3cb111f4a2 | ||
|
|
c8d645f920 | ||
|
|
4b9195e8e5 | ||
|
|
f035228072 | ||
|
|
1f14cb83b3 | ||
|
|
b2dc669876 | ||
|
|
858993d8e2 | ||
|
|
9dad8b75d9 | ||
|
|
c745b9dec1 | ||
|
|
3a05d58753 | ||
|
|
d9355c3db4 | ||
|
|
88fff9e48b | ||
|
|
e464fc4714 | ||
|
|
8b1867befc | ||
|
|
530185782d | ||
|
|
d8f5c256b7 | ||
|
|
b3ea590166 | ||
|
|
7324592f1f | ||
|
|
c9cf540d8e | ||
|
|
a984a2795d | ||
|
|
44f9538819 | ||
|
|
5ed897ef07 | ||
|
|
0a78505f22 | ||
|
|
e96ff679ec | ||
|
|
14168728cc | ||
|
|
fb08655a0a | ||
|
|
b50667cecc | ||
|
|
7ccc3f30e5 | ||
|
|
c44a1e0c0b | ||
|
|
8e97638e15 | ||
|
|
c8385c8149 | ||
|
|
8984319b4f | ||
|
|
e4ded8b520 | ||
|
|
42a2a72e00 | ||
|
|
397cbbf01c | ||
|
|
3447be86fb | ||
|
|
8dcf575af2 | ||
|
|
e42fd1d0ce | ||
|
|
3a5368e1bd | ||
|
|
95035cfca3 | ||
|
|
49d99d09e2 | ||
|
|
bc5fe6c6b8 | ||
|
|
aa735e52bf | ||
|
|
010f855829 | ||
|
|
1f13b70c0c | ||
|
|
4ec303520b | ||
|
|
426904000a | ||
|
|
fc8bd3049a | ||
|
|
fd9be4f940 | ||
|
|
793630b84d | ||
|
|
ba1d5ad719 | ||
|
|
b621bab433 | ||
|
|
609f09a312 | ||
|
|
026150ff7a | ||
|
|
bddf3e47c1 | ||
|
|
6b3661edee | ||
|
|
4d19729758 | ||
|
|
8a6d263145 | ||
|
|
fa79de4a4f | ||
|
|
da02dcd717 | ||
|
|
ae26d8536b | ||
|
|
f334d4c119 | ||
|
|
43f3c43eab | ||
|
|
34adb68372 | ||
|
|
171f9325a3 | ||
|
|
671c2d912c | ||
|
|
491d91b1ee | ||
|
|
c65e13a31f | ||
|
|
d55cd87090 | ||
|
|
d5b7dcc663 | ||
|
|
a7236c17f2 | ||
|
|
16d2cbd381 | ||
|
|
3f4b19618c | ||
|
|
603459fd0d | ||
|
|
54d41dd372 | ||
|
|
94677f2f7e | ||
|
|
691c359985 | ||
|
|
80ff709729 | ||
|
|
0cd3442d86 | ||
|
|
d69ab9edd8 | ||
|
|
6e7a20974d | ||
|
|
7958c33e34 | ||
|
|
d808ca2a87 | ||
|
|
7c4717fcfa | ||
|
|
027b4a5d57 | ||
|
|
eb19d5f90a | ||
|
|
cc83a9b099 | ||
|
|
cb5deda0ff | ||
|
|
4f76adcb72 | ||
|
|
475de58bc2 | ||
|
|
6ee9d668cd | ||
|
|
7d2a3a6295 | ||
|
|
e702b3358c | ||
|
|
b50cccec1f | ||
|
|
5b15e02c3b | ||
|
|
57e98487e1 | ||
|
|
cc0b61f122 | ||
|
|
5693ec0142 | ||
|
|
e81f9cc6b3 | ||
|
|
b313aa47ce | ||
|
|
13e02283d8 | ||
|
|
1835b496d1 | ||
|
|
d8b21aa350 | ||
|
|
05adaf0d61 | ||
|
|
fea85a1c1e | ||
|
|
0a70ab9c8c | ||
|
|
1aa236e812 | ||
|
|
6bc6ee68b6 | ||
|
|
c5d15f01cf | ||
|
|
8e862bdc39 | ||
|
|
e21fe53ce6 | ||
|
|
81e90ddf9d | ||
|
|
9c4c267753 | ||
|
|
c4a5d26e8b | ||
|
|
fa7b0987a1 | ||
|
|
9da623e3af | ||
|
|
9caeef72ea | ||
|
|
45d716a03e | ||
|
|
d95b43f9c1 | ||
|
|
df14f7e364 | ||
|
|
993657a1cc | ||
|
|
34e476a4db | ||
|
|
d54f70f1d7 | ||
|
|
6666244061 | ||
|
|
e453841d66 | ||
|
|
96092fda7c | ||
|
|
ff8c9d0ed8 | ||
|
|
63836ffb19 | ||
|
|
89b7834b10 | ||
|
|
7e203c9851 | ||
|
|
b3a2ae3eb2 | ||
|
|
fdf3f1b6f0 | ||
|
|
4676edca8b | ||
|
|
08ee5f0ba0 | ||
|
|
d474712cd6 | ||
|
|
8245ea0bdc | ||
|
|
bfd99ebb8f | ||
|
|
99f935863a | ||
|
|
4dc728378c | ||
|
|
4aa3372e5b | ||
|
|
891dad0b11 | ||
|
|
bc305c66d9 | ||
|
|
121e181060 | ||
|
|
7964805536 | ||
|
|
4ce0b50c04 | ||
|
|
9238a76156 | ||
|
|
1782d34d4a | ||
|
|
1954aebb48 | ||
|
|
8697df75c1 | ||
|
|
51fd90c64d | ||
|
|
f82ddac72d | ||
|
|
b7253b7fd5 | ||
|
|
9cd12a7609 | ||
|
|
042a44cba6 | ||
|
|
ac49728758 | ||
|
|
4a804ae467 | ||
|
|
8e8f230504 | ||
|
|
653e0b5669 | ||
|
|
3bb991da0b | ||
|
|
db36c617bf | ||
|
|
3a75a07de5 | ||
|
|
1172c56dfa | ||
|
|
345a788cdd | ||
|
|
1bbcb0c15f | ||
|
|
e6602ac68b | ||
|
|
618ce12fd8 | ||
|
|
4efe9ac5a9 | ||
|
|
37e011f98f | ||
|
|
ba40b99c97 | ||
|
|
05b6c25aa9 | ||
|
|
bb0b616cb3 | ||
|
|
4ba4fb3acb | ||
|
|
67974a0952 | ||
|
|
6bd22fff4c | ||
|
|
86ba57af9c | ||
|
|
bdc1a031f3 | ||
|
|
d3df339375 | ||
|
|
185e9159fb | ||
|
|
fe61765435 | ||
|
|
3940995e21 | ||
|
|
4a9d0710a4 | ||
|
|
9feba4df92 | ||
|
|
daa50470f8 | ||
|
|
edb1a124d0 | ||
|
|
6f4cc4f543 | ||
|
|
47f6d531b2 | ||
|
|
227c197692 | ||
|
|
ead0fd53e3 | ||
|
|
12f40744d2 | ||
|
|
9f9bff17e5 | ||
|
|
d57a71b8d6 | ||
|
|
d28e0abe38 | ||
|
|
5ad7e06d79 | ||
|
|
8de6311c1f | ||
|
|
bedd1def21 | ||
|
|
6f1c461e2e | ||
|
|
0bb8fa7e06 | ||
|
|
8f08a23615 | ||
|
|
e5651f6d5a | ||
|
|
6143fd3779 | ||
|
|
d792f38026 | ||
|
|
2a7cfbe96f | ||
|
|
f92df1d171 | ||
|
|
835dcb15d7 | ||
|
|
a546811b40 | ||
|
|
94800c7a84 | ||
|
|
1a12184528 | ||
|
|
e8fcb38597 | ||
|
|
340a5b3da2 | ||
|
|
b15d70ff91 | ||
|
|
2a5d8f71a4 | ||
|
|
81f788278a | ||
|
|
276ad62f24 | ||
|
|
f52d886b04 | ||
|
|
f50b82d81c | ||
|
|
6c1955c0d9 | ||
|
|
3fdce853d0 | ||
|
|
422f2aed64 | ||
|
|
f33fdc58a6 | ||
|
|
aba35dee61 | ||
|
|
12b9868f45 | ||
|
|
49b783273e | ||
|
|
16a322211e | ||
|
|
7a33c561ac | ||
|
|
ae5cef3907 | ||
|
|
0d46c5bac7 | ||
|
|
31b784f046 | ||
|
|
a8b7f79194 | ||
|
|
b0de23b011 | ||
|
|
bce58df413 | ||
|
|
76625fbb22 | ||
|
|
8981727d09 | ||
|
|
6fe09bf784 | ||
|
|
1b687a0d79 | ||
|
|
0481dc7ebf | ||
|
|
f5004518aa | ||
|
|
3f58e1b453 | ||
|
|
37ce8b7398 | ||
|
|
e150afc00b | ||
|
|
1efd25a0e5 | ||
|
|
2ba3d27db6 | ||
|
|
59d152bec5 | ||
|
|
baa2f384f4 | ||
|
|
5a641e4629 | ||
|
|
3092884525 | ||
|
|
4eaece6cb5 | ||
|
|
2c06c5b905 | ||
|
|
7f38aa97fa | ||
|
|
bdb4e077a3 | ||
|
|
4d5a8ac89d | ||
|
|
2fa169e28d | ||
|
|
680fa49f27 | ||
|
|
922a2494ed | ||
|
|
e97b4b8374 | ||
|
|
5df9337034 | ||
|
|
92fc9c1ca6 | ||
|
|
e3f58f6823 | ||
|
|
7a7059e172 | ||
|
|
bc1ffc7f4f | ||
|
|
671a72d213 | ||
|
|
08b0a096eb | ||
|
|
8c28a8c0a7 | ||
|
|
a5554b51cd | ||
|
|
da9bebf95c | ||
|
|
c95a4050f4 | ||
|
|
3cd3230342 | ||
|
|
6345cfd91a | ||
|
|
f26e1eb70f | ||
|
|
0bd8847f56 | ||
|
|
f8a3823d2d | ||
|
|
390a73eeac | ||
|
|
ce5811b86b | ||
|
|
32ec7a0634 | ||
|
|
137af0e14d | ||
|
|
c906a4e46f | ||
|
|
c46a49b7a5 | ||
|
|
09b3017874 | ||
|
|
30ae7a4deb | ||
|
|
1c731d4d77 | ||
|
|
2e16d76f7c | ||
|
|
8352cc75d9 | ||
|
|
dfa8814887 | ||
|
|
3ea4de7ab5 | ||
|
|
80ebc17a26 | ||
|
|
20e7afeee5 | ||
|
|
b00992881c | ||
|
|
f7e4e18a28 | ||
|
|
fd7b0b1958 | ||
|
|
d3e81dc810 | ||
|
|
bfce5f8a00 | ||
|
|
28d18a07a0 | ||
|
|
b3d995de53 | ||
|
|
cda2737b02 | ||
|
|
25f634352d | ||
|
|
42e79b483f | ||
|
|
e6fa720ce1 | ||
|
|
447d484f7b | ||
|
|
f82c534df4 | ||
|
|
bbaf80bedf | ||
|
|
43bb3a40b3 | ||
|
|
7d9280d3da | ||
|
|
87e19875e3 | ||
|
|
cf55aeda63 | ||
|
|
2412713f70 | ||
|
|
6801cc65af | ||
|
|
c0a81d05eb | ||
|
|
30df813edf | ||
|
|
0e91ae1ff7 | ||
|
|
cd6630c99c | ||
|
|
100446cab3 | ||
|
|
23baa32879 | ||
|
|
bdf476e297 | ||
|
|
fed684c058 | ||
|
|
b733d9e06f | ||
|
|
45f48b073c | ||
|
|
2f968fe50b | ||
|
|
23dab28d7b | ||
|
|
19a98e6586 | ||
|
|
c6d60b5fbf | ||
|
|
95604f84ab | ||
|
|
6a38675602 | ||
|
|
a3fdb22342 | ||
|
|
a3586cb3aa | ||
|
|
0424315849 | ||
|
|
6783463ae7 | ||
|
|
1181c01f55 | ||
|
|
0f21d11fd3 | ||
|
|
7264cd5437 | ||
|
|
177fa4621c | ||
|
|
960f2e7e7a | ||
|
|
2e04fe60f4 | ||
|
|
6991421782 | ||
|
|
54385ecfdb | ||
|
|
cbbfab438e | ||
|
|
1aacfc22ac | ||
|
|
fdf0e6cd7d | ||
|
|
af57d8b449 | ||
|
|
23dbc77da9 | ||
|
|
a28a6978fe | ||
|
|
462f37e601 | ||
|
|
aed1b8cd26 | ||
|
|
5acb699955 | ||
|
|
f7f05c4dbb | ||
|
|
747c07af00 | ||
|
|
8ca15771d5 | ||
|
|
0a5f4e310c | ||
|
|
b5248bf520 | ||
|
|
79b551af3e | ||
|
|
9edb2a4ee0 | ||
|
|
8dd8258565 | ||
|
|
e88495f1a5 | ||
|
|
60a596c585 | ||
|
|
3279c95a08 | ||
|
|
a9285accad | ||
|
|
75eb7c9c66 | ||
|
|
99fb0ad29b | ||
|
|
1e75c67c7e | ||
|
|
0c395a4371 | ||
|
|
e8fec61afc | ||
|
|
1142885656 | ||
|
|
7860be4a50 | ||
|
|
099b31c1a7 | ||
|
|
9eb697467f | ||
|
|
4992631e16 | ||
|
|
3ba4ec97dd | ||
|
|
549d1a01c9 | ||
|
|
98b1fd6afd | ||
|
|
1f7b3b37ab | ||
|
|
768004aed8 | ||
|
|
74bcdd2507 | ||
|
|
32041b9596 | ||
|
|
187612ed2d | ||
|
|
569aec25ba | ||
|
|
63576ae4f1 | ||
|
|
c40b4be975 | ||
|
|
845726827e | ||
|
|
aac3b05a9b | ||
|
|
a3a43d4b8a | ||
|
|
0c3d1a5ded | ||
|
|
af8bc9d3b8 | ||
|
|
e8a4bbea6c | ||
|
|
438f8f5f82 | ||
|
|
bb28ab78d6 | ||
|
|
3f1ae0feaa | ||
|
|
91fb68f906 | ||
|
|
3accc4b66e | ||
|
|
46db079334 | ||
|
|
c412384b4d | ||
|
|
40b48318ff | ||
|
|
6f8f115422 | ||
|
|
c28e889bd5 | ||
|
|
1413c01d03 | ||
|
|
17f567a4da | ||
|
|
b319ee056d | ||
|
|
6a3d8be32a | ||
|
|
5fc7dd95f7 | ||
|
|
aa4c39395f | ||
|
|
c6b1263d33 | ||
|
|
417f90e677 | ||
|
|
77bdd47c86 | ||
|
|
ac14c549ef | ||
|
|
14a1746ba5 | ||
|
|
dc83e23ed1 | ||
|
|
5a167ae031 | ||
|
|
730bbba2b9 | ||
|
|
567075f9c5 | ||
|
|
b0a4cdcc17 | ||
|
|
c018a39f7b | ||
|
|
4aa888f72d | ||
|
|
e1ff8aa11e | ||
|
|
b086d12760 | ||
|
|
5477909d4d | ||
|
|
c0aa4a042f | ||
|
|
8ec32ad130 | ||
|
|
7011293352 | ||
|
|
da39d52348 | ||
|
|
cf1188ad47 | ||
|
|
6d0737e4a3 | ||
|
|
ae4e7e6139 | ||
|
|
d9c12dcc9a | ||
|
|
1502d9ed23 | ||
|
|
5165ac6e52 | ||
|
|
4eab2f81cf | ||
|
|
e73339d2f9 | ||
|
|
0192298d09 | ||
|
|
661e2d821e | ||
|
|
249db6bd4b | ||
|
|
25b47e5b92 | ||
|
|
94aee02fb9 | ||
|
|
de57630c14 | ||
|
|
e21798f1ed | ||
|
|
92b45b2d05 | ||
|
|
b7a21a1caf | ||
|
|
770007742f | ||
|
|
3e1a7fca69 | ||
|
|
98f55d53ff | ||
|
|
3a5cd39ba4 | ||
|
|
732a42924b | ||
|
|
f0b6b9363c | ||
|
|
5fbc2ff6bb | ||
|
|
87756246a7 | ||
|
|
d1a624cfd5 | ||
|
|
7035ec719a | ||
|
|
d1210304af | ||
|
|
c1591d85cb | ||
|
|
60fc30b709 | ||
|
|
c13ec03fe1 | ||
|
|
ebdf8d7935 | ||
|
|
a3f24eda83 | ||
|
|
fce36cfdd9 | ||
|
|
29e6ebdb83 | ||
|
|
6273b05c51 | ||
|
|
96d66b275c | ||
|
|
c38079e03b | ||
|
|
dcb35084a1 | ||
|
|
e80ecd7175 | ||
|
|
4359321bed | ||
|
|
dde3991702 | ||
|
|
c6626fe4c8 | ||
|
|
0fdb11e153 | ||
|
|
e7e7738567 | ||
|
|
977e233d6d | ||
|
|
477d31ce53 | ||
|
|
351db464a5 | ||
|
|
396f16fe21 | ||
|
|
de387d00b1 | ||
|
|
d40ed45a70 | ||
|
|
b6e04b8c39 | ||
|
|
152b20e7b4 | ||
|
|
bf03a87c17 | ||
|
|
870c2d9fb1 | ||
|
|
6a0a71de5a | ||
|
|
71eb45ce2c | ||
|
|
cab9e4acfc | ||
|
|
9fb13ba73d | ||
|
|
67ae149c36 | ||
|
|
7acfcbe09c | ||
|
|
c8683ea0ba | ||
|
|
5fade8bb93 | ||
|
|
cba8e6341d | ||
|
|
eae616b823 | ||
|
|
6b85b459d7 | ||
|
|
8f8bff7b76 | ||
|
|
e4c020f78f | ||
|
|
ec9495a2fc | ||
|
|
151da534c2 | ||
|
|
aec2166759 | ||
|
|
c8ebc7ea87 | ||
|
|
9d196e7c9f | ||
|
|
fed7d913c5 | ||
|
|
4bbd8f809c | ||
|
|
a1a7aa044a | ||
|
|
bdbfa36112 | ||
|
|
2a30b2737c | ||
|
|
7b7fd7680f | ||
|
|
7abfaf639e | ||
|
|
dbaeb9d5f4 | ||
|
|
d6fd75f7b9 | ||
|
|
3cba2cda12 | ||
|
|
89779bf658 | ||
|
|
7cacbd4659 | ||
|
|
1e5777f4b6 | ||
|
|
6379315e4b | ||
|
|
3002d296c8 | ||
|
|
f60d0bc81a | ||
|
|
4c0f344c72 | ||
|
|
f9366c3146 | ||
|
|
cc53ba68b5 | ||
|
|
df2ac51698 | ||
|
|
44766f817c | ||
|
|
4fff0e9c6c | ||
|
|
69e5ad7a8a | ||
|
|
56bdddc865 | ||
|
|
6ebeaf521a | ||
|
|
0a28efb838 | ||
|
|
91bb1ad77f | ||
|
|
97b30b0b68 | ||
|
|
459705e26a | ||
|
|
7df42f3bcb | ||
|
|
658636463e | ||
|
|
ea9a096fa9 | ||
|
|
d882c2dfdb | ||
|
|
997876db32 | ||
|
|
f17642f057 | ||
|
|
8e27e19e41 | ||
|
|
13c20acaa6 | ||
|
|
68fe5eff36 | ||
|
|
34e84fbd01 | ||
|
|
168c5b490c | ||
|
|
71c6adab58 | ||
|
|
075c508d63 | ||
|
|
bd38ea590a | ||
|
|
a9a1048462 | ||
|
|
81c75d953b | ||
|
|
ae51454fc4 | ||
|
|
191c4943bd | ||
|
|
c2e5e9d92c | ||
|
|
41f9e6fe77 | ||
|
|
1fa3e55e89 | ||
|
|
e4ba3ac66b | ||
|
|
7642dab826 | ||
|
|
2a2e87cb1c | ||
|
|
6e06779cc3 | ||
|
|
2764ea0d96 | ||
|
|
02c7793050 | ||
|
|
c9221d6b36 | ||
|
|
8df6ee502a | ||
|
|
df189dc39e | ||
|
|
669442ef02 | ||
|
|
050e291e0f | ||
|
|
27f1884a47 | ||
|
|
12aee6acb6 | ||
|
|
94e3842267 | ||
|
|
3df32e903f | ||
|
|
d6a2fee09a | ||
|
|
1530464432 | ||
|
|
0b6450e78a | ||
|
|
3cf4a75b93 | ||
|
|
7690a088ab | ||
|
|
19ebf65c68 | ||
|
|
a8dd40900c | ||
|
|
217e6bd70b | ||
|
|
204a3fe68a | ||
|
|
c8846f7cbf | ||
|
|
2143bb6e04 | ||
|
|
5b995f78aa | ||
|
|
a0e261403b | ||
|
|
5db6825612 | ||
|
|
7ad0b1f555 | ||
|
|
492e57d845 | ||
|
|
a0127130a6 | ||
|
|
c3702f6bbe | ||
|
|
7c92ad390a | ||
|
|
eeb141cb8e | ||
|
|
2b00395253 | ||
|
|
ba8b20f8c0 | ||
|
|
bd870e7251 | ||
|
|
9046db4cc6 | ||
|
|
ab4fb8030d | ||
|
|
61db7aeab9 | ||
|
|
f05d6924a3 | ||
|
|
4fde0469c0 | ||
|
|
b65eee57e7 | ||
|
|
720178c03f | ||
|
|
841e178c15 | ||
|
|
5762ee0bf3 | ||
|
|
934fcba179 | ||
|
|
15695c652d | ||
|
|
b1ce61c6bb | ||
|
|
e017975162 | ||
|
|
81c9794657 | ||
|
|
9c5ec0ec9c | ||
|
|
626ed4893c | ||
|
|
61c0632fdb | ||
|
|
d2af74a07e | ||
|
|
6bb9e2ad60 | ||
|
|
b16d12f67c | ||
|
|
530acfe571 | ||
|
|
42e87588b6 | ||
|
|
ed64ddaea2 | ||
|
|
f352ca050d | ||
|
|
96b1baf6cf | ||
|
|
707f2b0d87 | ||
|
|
1f91d7d390 | ||
|
|
c617fba52b | ||
|
|
819fa69ae2 | ||
|
|
1f73638018 | ||
|
|
c22ac8e302 | ||
|
|
863f0c906b | ||
|
|
bb27a39759 | ||
|
|
b1e9936aa6 | ||
|
|
95a72a3057 | ||
|
|
a11b618d24 | ||
|
|
5d0661c05f | ||
|
|
a994dba165 | ||
|
|
69f258b4f4 | ||
|
|
4f2e4a54aa | ||
|
|
e30871bd48 | ||
|
|
b3207b5746 | ||
|
|
17cdaab2cc | ||
|
|
20cfae2a17 | ||
|
|
84ef0a75e1 | ||
|
|
c9f8b5b562 | ||
|
|
1a51aaf8cf | ||
|
|
57396cf368 | ||
|
|
692f066cf4 | ||
|
|
40b71fda5e | ||
|
|
5bf3e9a06e | ||
|
|
9af2e54a2c | ||
|
|
a325e2e3ae | ||
|
|
10930fff2e | ||
|
|
d372e32986 | ||
|
|
a4e3118073 | ||
|
|
66e70394d0 | ||
|
|
d871dfceac | ||
|
|
6795a4d9a5 | ||
|
|
77dec1d0e6 | ||
|
|
cf7a3f8e3b | ||
|
|
3ad91671a2 | ||
|
|
9fa4d8f4d9 | ||
|
|
a45d91e663 | ||
|
|
7ccb81e8fb | ||
|
|
a3db60c2fe | ||
|
|
abead1aa9e | ||
|
|
6c43164ed1 | ||
|
|
5dfaf8e4be | ||
|
|
8d20cf9d3d | ||
|
|
1acdef47d4 | ||
|
|
a7f8443843 | ||
|
|
9728098c96 | ||
|
|
85275f9448 | ||
|
|
d6f96c246d | ||
|
|
a95bd7f02a | ||
|
|
3584c1f59a | ||
|
|
366fdbd067 | ||
|
|
f46ac8154c | ||
|
|
ec04fccf0c | ||
|
|
632fccec5d | ||
|
|
130895f2c3 | ||
|
|
0a2d774b5e | ||
|
|
f1107432b7 | ||
|
|
3fdee99aa8 | ||
|
|
335c972c20 | ||
|
|
3ab79eb247 | ||
|
|
c293427752 | ||
|
|
271a305cc0 | ||
|
|
20e89cb8a7 | ||
|
|
6f92604d87 | ||
|
|
063e82c4a9 | ||
|
|
dffab9a482 | ||
|
|
cf5f7349a6 | ||
|
|
f9d80363be | ||
|
|
224c410ec3 | ||
|
|
8bcb373d07 | ||
|
|
ce8987f8b5 | ||
|
|
5f996f3cf6 | ||
|
|
e4dc14da56 | ||
|
|
83e386e95a | ||
|
|
fec40eae54 | ||
|
|
78f010b47e | ||
|
|
333e4d0dc0 | ||
|
|
a6ec2cb38b | ||
|
|
64d2b1e4dc | ||
|
|
ea267ee981 | ||
|
|
b88e4d89ce | ||
|
|
6b5888a6c2 | ||
|
|
53621ceeeb | ||
|
|
d6d38d207d | ||
|
|
9e41946a05 | ||
|
|
823a5a8120 | ||
|
|
c17f9da243 | ||
|
|
ddae77db9d | ||
|
|
fd9503af1d | ||
|
|
6866ef67c8 | ||
|
|
8910400444 | ||
|
|
92a0ffc1ae | ||
|
|
12a74070d2 | ||
|
|
38bead08e9 | ||
|
|
4069b183a7 | ||
|
|
6d1661c773 | ||
|
|
e95c1c76c0 | ||
|
|
547faa16a9 | ||
|
|
62eb224ac0 | ||
|
|
3d18f2d95e | ||
|
|
1693d172bd | ||
|
|
df10586e3f | ||
|
|
f2ebb75199 | ||
|
|
0a547aed4c | ||
|
|
7875a6b961 | ||
|
|
c1ae4348cd | ||
|
|
13e8679713 | ||
|
|
ee9b99635b | ||
|
|
7e7d2570d7 | ||
|
|
5ca6282e68 | ||
|
|
99aa09eb86 | ||
|
|
a4f29d25e1 | ||
|
|
63f6de930d | ||
|
|
9a5fce0a20 | ||
|
|
3187a3ed2c | ||
|
|
b90e70277e | ||
|
|
cd365b77d3 | ||
|
|
8c7fc4aad6 | ||
|
|
5ec4b707e8 | ||
|
|
697a4dd1fc | ||
|
|
9e904e4247 | ||
|
|
e701793e44 | ||
|
|
49c44766ba | ||
|
|
fa59407aec | ||
|
|
9aae0f0ce3 | ||
|
|
c52755b329 | ||
|
|
6b8b6ac5b9 | ||
|
|
5e8a3f2bfc | ||
|
|
5d2c066356 | ||
|
|
5fd1e7f030 | ||
|
|
3977748e34 | ||
|
|
1e9e9ef6d1 | ||
|
|
d9a5c06106 | ||
|
|
0dff98801c | ||
|
|
8f935b4d60 | ||
|
|
398f08fac7 | ||
|
|
52b0900325 | ||
|
|
b8e5239ee3 | ||
|
|
5afc297e93 | ||
|
|
9913388116 | ||
|
|
ffa9d0e8d1 | ||
|
|
56d64cf737 | ||
|
|
675f04ff9c | ||
|
|
5715e11d8f | ||
|
|
b871217bb6 | ||
|
|
414b25242d | ||
|
|
70c5a29862 | ||
|
|
955b864da5 | ||
|
|
d6cd45c6da | ||
|
|
10cfa2439a | ||
|
|
668898903f | ||
|
|
1e4a379400 | ||
|
|
fbbffe0167 | ||
|
|
eb62b106f3 | ||
|
|
c3c6385b54 | ||
|
|
7909375eb2 | ||
|
|
3bc5150249 | ||
|
|
aff352335b | ||
|
|
b183be6920 | ||
|
|
c5b93a3a61 | ||
|
|
dc6391c1db | ||
|
|
58563a41cb | ||
|
|
bfcb96ae87 | ||
|
|
44960845e2 | ||
|
|
e021d074b1 | ||
|
|
88036c1cb0 | ||
|
|
1532dccafa | ||
|
|
b154bb8ff4 | ||
|
|
f1955c2a7c | ||
|
|
d7f21dc130 | ||
|
|
f20ce6f057 | ||
|
|
10ccdf32fa | ||
|
|
43a1ca9fc0 | ||
|
|
3a7e487f18 | ||
|
|
6d82194f9f | ||
|
|
9b078e19a8 | ||
|
|
ad5095ce5d | ||
|
|
8b32c69377 | ||
|
|
5dc4dec1e0 | ||
|
|
a3d0358bdf | ||
|
|
aae868dba8 | ||
|
|
52e1021cfb | ||
|
|
d5bab51c03 | ||
|
|
938f4f5bfd | ||
|
|
897fd2bae1 | ||
|
|
4f7e1f0a98 | ||
|
|
d8057f3c83 | ||
|
|
0c2ac8352c | ||
|
|
48819a1738 | ||
|
|
6e36718702 | ||
|
|
1d060d1a50 | ||
|
|
b16bea31f9 | ||
|
|
9c37c19647 | ||
|
|
507e2b1d38 | ||
|
|
b3e17bd2b7 | ||
|
|
2810903020 | ||
|
|
277cde7978 | ||
|
|
05f9a63c96 | ||
|
|
14ac4c80f6 | ||
|
|
3702d29d4e | ||
|
|
353f6f0efa | ||
|
|
fb913fcefc | ||
|
|
6c26ffecd5 | ||
|
|
ee3877f20c | ||
|
|
a239d445b8 | ||
|
|
4ef4185373 | ||
|
|
9e056709df | ||
|
|
5e6b3e94da | ||
|
|
bf33926ec4 | ||
|
|
28b0ee7351 | ||
|
|
d6f5d1c2a2 | ||
|
|
1dcb19ddfa | ||
|
|
9c9b0ca908 | ||
|
|
f80ac704a7 | ||
|
|
73b901954a | ||
|
|
ce53bdb867 | ||
|
|
65bce97486 | ||
|
|
d5df7eba3b | ||
|
|
bced1ebd2f | ||
|
|
77933f3949 | ||
|
|
f81542a792 | ||
|
|
4607d92c31 | ||
|
|
967b67f0b1 | ||
|
|
0326c02c05 | ||
|
|
a4562c11e2 | ||
|
|
b6bcc1795c | ||
|
|
1a8e29a0b1 | ||
|
|
149e99f466 | ||
|
|
9a98faf1f6 | ||
|
|
c0cc1d2b81 | ||
|
|
3d72c53f02 | ||
|
|
6efaf019ba | ||
|
|
54fe1a4737 | ||
|
|
61c4012dc4 | ||
|
|
4840a766e7 | ||
|
|
b16a649121 | ||
|
|
dea367a84a | ||
|
|
99479c762d | ||
|
|
9115f4923f | ||
|
|
9dc94caffd | ||
|
|
929a0a84da | ||
|
|
ab36a12385 | ||
|
|
c13e86c735 | ||
|
|
0a17a44560 | ||
|
|
a065f8a946 | ||
|
|
1091ffa27a | ||
|
|
cf2a838007 | ||
|
|
d1a41c78ab | ||
|
|
d8dac5169f | ||
|
|
718e7dc803 | ||
|
|
b4bd723685 | ||
|
|
3367e9befe | ||
|
|
b84545c133 | ||
|
|
c3cbd10d69 | ||
|
|
48af73792e | ||
|
|
8e84751181 | ||
|
|
4ab20eaef0 | ||
|
|
5ce66f3c69 | ||
|
|
4124f83a22 | ||
|
|
94b38daee0 | ||
|
|
461bdca375 | ||
|
|
a01cbfa350 | ||
|
|
447da07789 | ||
|
|
53a9b4e8ba | ||
|
|
1691f265e9 | ||
|
|
2fbd3f6007 | ||
|
|
ed6417f6db | ||
|
|
a8c79d192a | ||
|
|
a9a7b830be | ||
|
|
9c7fb455a6 | ||
|
|
9d5ba9d731 | ||
|
|
a4013c283c | ||
|
|
8f55391167 | ||
|
|
f2916e11dc | ||
|
|
87152c6b46 | ||
|
|
0fc6efa7b0 | ||
|
|
380aaab806 | ||
|
|
e741678f2a | ||
|
|
5a59640a36 | ||
|
|
8c1dab4ebf | ||
|
|
fb898e6fda | ||
|
|
0c9b525fc3 | ||
|
|
d131b78a82 | ||
|
|
cdbd94615f | ||
|
|
68d75aacc9 | ||
|
|
283c3277fa | ||
|
|
c117476681 | ||
|
|
3b675af74a | ||
|
|
cf7b8d2224 | ||
|
|
32f4720111 | ||
|
|
4693722f40 | ||
|
|
17790256ec | ||
|
|
1beac78e02 | ||
|
|
931042018e | ||
|
|
fa98c544cc | ||
|
|
2df37e3ba2 | ||
|
|
4b0c8cec07 | ||
|
|
139ee821b7 | ||
|
|
96ca21277e | ||
|
|
38eb985c48 | ||
|
|
80941da252 | ||
|
|
3a7e97bc60 | ||
|
|
4237f3a04e | ||
|
|
bf5c68a58b | ||
|
|
a60fbca002 | ||
|
|
57c53ba6a1 | ||
|
|
82f1da06f8 | ||
|
|
defe616b22 | ||
|
|
2733debadc | ||
|
|
52b0b83259 | ||
|
|
0550e73034 | ||
|
|
a6d15e11ef | ||
|
|
0c0d227788 | ||
|
|
b3e9e7d1aa | ||
|
|
963d22d930 | ||
|
|
7f123c1952 | ||
|
|
e8fe5508d2 | ||
|
|
1da54f0c73 | ||
|
|
249414c051 | ||
|
|
fce1e6b362 | ||
|
|
5da652a794 | ||
|
|
75995c8cf7 | ||
|
|
8fa46d5257 | ||
|
|
e39f9767be | ||
|
|
25eff6a0a7 | ||
|
|
b3f50928d4 | ||
|
|
615586ec89 | ||
|
|
ef43f31b91 | ||
|
|
958782ea95 | ||
|
|
9422987133 | ||
|
|
3531d5054a | ||
|
|
5a50bc80e3 | ||
|
|
a779369185 | ||
|
|
e523d2d0dc | ||
|
|
eabfa50142 | ||
|
|
94e23400c3 | ||
|
|
efad3ff73c | ||
|
|
191fae23d3 | ||
|
|
4675590aac | ||
|
|
144dc59a7a | ||
|
|
3ac6df7c26 | ||
|
|
3b56210bde | ||
|
|
05df84e89c | ||
|
|
ef47a0aaad | ||
|
|
c8d48d4667 | ||
|
|
6eb65bfe39 | ||
|
|
e0ba0fd41f | ||
|
|
c57e3f2a68 | ||
|
|
5f7fea7201 | ||
|
|
c2b8f7e50c | ||
|
|
af2deea11d | ||
|
|
3f4ed6faa5 | ||
|
|
4c193d6026 | ||
|
|
87f9e8b38d | ||
|
|
d57f98e06d | ||
|
|
cb003407b2 | ||
|
|
15d5748922 | ||
|
|
c88e5fc266 | ||
|
|
cb3a9c28f0 | ||
|
|
ccb77e3ade | ||
|
|
1aab333eba | ||
|
|
da5ac3bda3 | ||
|
|
dad430de6c | ||
|
|
e9c4576015 | ||
|
|
597342d6ad | ||
|
|
4567facc8d | ||
|
|
14d403ca2d | ||
|
|
7f865db89c | ||
|
|
25bcf8f139 | ||
|
|
de2fe77270 | ||
|
|
f5d7f0abf4 | ||
|
|
c7acd82bda | ||
|
|
5fe16abd38 | ||
|
|
1391c9e671 | ||
|
|
a9d38db076 | ||
|
|
3fdd0a6bcf | ||
|
|
4f55b3995f | ||
|
|
dfe5765d50 | ||
|
|
0969d62a50 | ||
|
|
468f6a27e1 | ||
|
|
483043202a | ||
|
|
c7801a793c | ||
|
|
9e96bbce99 | ||
|
|
75905627b6 | ||
|
|
ac3d44601a | ||
|
|
f0ed85e53f | ||
|
|
f102be51cb | ||
|
|
6e4d243897 | ||
|
|
5f86df81ec | ||
|
|
15f6672b0e | ||
|
|
5695d07e7e | ||
|
|
53c657e8a4 | ||
|
|
a63c9f7d33 | ||
|
|
f054233a9e | ||
|
|
c2e63d5603 | ||
|
|
9e25f7ab9d | ||
|
|
7d17926060 | ||
|
|
8c99683eaf | ||
|
|
8db39822ff | ||
|
|
b931484e49 | ||
|
|
825eb4f8a2 | ||
|
|
9bda376bf4 | ||
|
|
eb82c8dffa | ||
|
|
00679d9c68 | ||
|
|
59e327ae0e | ||
|
|
f400a242d9 | ||
|
|
18190edcc8 | ||
|
|
5597a87c72 | ||
|
|
8b5b1697b2 | ||
|
|
1c13b62f25 | ||
|
|
33e13362fd | ||
|
|
719ab0c7cf | ||
|
|
9e06ef1729 | ||
|
|
ad73f9c514 | ||
|
|
d80ac1227e | ||
|
|
ef0e0ab74c | ||
|
|
70282daaf7 | ||
|
|
39df77e6ed | ||
|
|
8d2767999b | ||
|
|
7a8e4ef414 | ||
|
|
563d3e6dd3 | ||
|
|
1282fd9641 | ||
|
|
97daf44f5c | ||
|
|
27e14c0548 | ||
|
|
d8c214a402 | ||
|
|
0ace777e00 | ||
|
|
55025d12fa | ||
|
|
2887b5c0e1 | ||
|
|
b148de7fc0 | ||
|
|
034c26fed4 | ||
|
|
fda381c1b6 | ||
|
|
ed0717f57d | ||
|
|
6aa5dee80b | ||
|
|
632595235e | ||
|
|
070b8a8378 | ||
|
|
697020c385 | ||
|
|
0d9c899367 | ||
|
|
f24a80468e | ||
|
|
d496add1d6 | ||
|
|
b95c573fc2 | ||
|
|
ba0627c3ad | ||
|
|
aaecf3e174 | ||
|
|
8c996f3a57 | ||
|
|
3ab64e5a68 | ||
|
|
2649f4bed2 | ||
|
|
8353f92ea7 | ||
|
|
6044e8a4c2 | ||
|
|
eff6a854a0 | ||
|
|
5ebe483f8a | ||
|
|
433d4fe7ee | ||
|
|
dba3c8764a | ||
|
|
95c4f2fbc1 | ||
|
|
6571c29c1d | ||
|
|
987736f9fb | ||
|
|
b9e3e9f7d4 | ||
|
|
debc68661a | ||
|
|
48171bbecc | ||
|
|
73d2a6cfd5 | ||
|
|
51f1ac54d9 | ||
|
|
e103335cc1 | ||
|
|
8b23cd6f3c | ||
|
|
58a86872f5 | ||
|
|
7c892cf012 | ||
|
|
57bba26815 | ||
|
|
0cb28a2bba | ||
|
|
3515a0c602 | ||
|
|
82ce2d166e | ||
|
|
e77764f693 | ||
|
|
75a22381d2 | ||
|
|
10d38fd574 | ||
|
|
be446c6d79 | ||
|
|
49d2c54488 | ||
|
|
c743c94450 | ||
|
|
41073a7b57 | ||
|
|
44027c9d63 | ||
|
|
5624882ce2 | ||
|
|
e40632cca6 | ||
|
|
6d2cd7da59 | ||
|
|
47a70ad5c9 | ||
|
|
e8108813ca | ||
|
|
c314a4cde9 | ||
|
|
63e5e68fc6 | ||
|
|
a264cea1d6 | ||
|
|
58444fd363 | ||
|
|
5624adcedf | ||
|
|
e42b2ed7bf | ||
|
|
e509daaeef | ||
|
|
b37196621c | ||
|
|
f81975aa6c | ||
|
|
6eba3d4764 | ||
|
|
3d79367078 | ||
|
|
cc320215ac | ||
|
|
cb3a099fef | ||
|
|
994d67e032 | ||
|
|
706bc84d95 | ||
|
|
20ae512143 | ||
|
|
b734f303fb | ||
|
|
2ef31f3e08 | ||
|
|
280c272c2a | ||
|
|
93a84d91e2 | ||
|
|
4f0a74b1a3 | ||
|
|
8916a9b7ac | ||
|
|
94fc4d2b88 | ||
|
|
c02e144012 | ||
|
|
66c56a367e | ||
|
|
5aa629d4d4 | ||
|
|
e43ca70245 | ||
|
|
55dfbd73e5 | ||
|
|
574d78871a | ||
|
|
935bafd512 | ||
|
|
051163c3ae | ||
|
|
b920342188 | ||
|
|
d69be9a378 | ||
|
|
e791f49921 | ||
|
|
9bd358a119 | ||
|
|
e9d3e7578e | ||
|
|
aa06d824ee | ||
|
|
bcc82bc5cf | ||
|
|
551c95b8fe | ||
|
|
69329f7f64 | ||
|
|
50e8de4552 | ||
|
|
bfc0fc0f98 | ||
|
|
b66be25a8e | ||
|
|
6c64279c94 | ||
|
|
3dd741129c | ||
|
|
89a21d7abb | ||
|
|
d976afda30 | ||
|
|
650bbabe94 | ||
|
|
78987419d2 | ||
|
|
cb524ee814 | ||
|
|
57df8821f1 | ||
|
|
52d3b2c7fc | ||
|
|
18ddbaa039 | ||
|
|
3a448e33e4 | ||
|
|
808c964d3d | ||
|
|
d64253dd1b | ||
|
|
78e7f57655 | ||
|
|
ac4e090b96 | ||
|
|
3b77286c77 | ||
|
|
1f24712fa5 | ||
|
|
599d4f34c8 | ||
|
|
c419e9406b | ||
|
|
5ae1c6a78c | ||
|
|
09f75ae3fd | ||
|
|
262f305bf9 | ||
|
|
3424c768ab | ||
|
|
8f3c049536 | ||
|
|
89ac323799 | ||
|
|
01437ea6f8 | ||
|
|
aa3ce7daf6 | ||
|
|
5b01e3ed2a | ||
|
|
d771a9f513 | ||
|
|
54b8db6cfb | ||
|
|
a0726d3a36 | ||
|
|
45255ab932 | ||
|
|
4185849102 | ||
|
|
8df6dc6c43 | ||
|
|
33d69d51cc | ||
|
|
a21c6f41c4 | ||
|
|
c2126b6fe4 | ||
|
|
38509eea99 | ||
|
|
9ce6cd0f92 | ||
|
|
46d7bd2f23 | ||
|
|
d558b3e6f4 | ||
|
|
444018d105 | ||
|
|
18ada6f49f | ||
|
|
2c356f9892 | ||
|
|
f9b78cd49c | ||
|
|
4905ac913b | ||
|
|
4c5e847b11 | ||
|
|
25d76d5b88 | ||
|
|
4aaa5c3e7a | ||
|
|
bc2e7c46a9 | ||
|
|
27e9afa6a8 | ||
|
|
cc7aef895e | ||
|
|
c0bf28682c | ||
|
|
b5ba75097f | ||
|
|
8c51048f76 | ||
|
|
6ba28830f3 | ||
|
|
0a736994db | ||
|
|
d1fee179d5 | ||
|
|
ffc6c79ed7 | ||
|
|
71c9adb126 | ||
|
|
4422974e4a | ||
|
|
69a334e206 | ||
|
|
17cda7c60d | ||
|
|
711a8c9c69 | ||
|
|
2f6c632476 | ||
|
|
71fb8f0a42 | ||
|
|
e32188f593 | ||
|
|
aeff49b3e6 | ||
|
|
7e8730c26e | ||
|
|
50eb744c3b | ||
|
|
f21a377d84 | ||
|
|
86e7ffc04a | ||
|
|
b5155eebcf | ||
|
|
4519d6e508 | ||
|
|
cad95c67a5 | ||
|
|
299c2bf06c | ||
|
|
db95342759 | ||
|
|
ebd7242048 | ||
|
|
3d17eca0d2 | ||
|
|
f7013d6e8b | ||
|
|
105f995877 | ||
|
|
f32b384f5d | ||
|
|
38a64c356b | ||
|
|
cd61c65d55 | ||
|
|
41aa1eb8f5 | ||
|
|
b96a554c44 | ||
|
|
f837835be4 | ||
|
|
3875780440 | ||
|
|
84f90613ce | ||
|
|
37454ca305 | ||
|
|
6245ccc599 | ||
|
|
f46ded791c | ||
|
|
f52b8796d1 | ||
|
|
33fd84623f | ||
|
|
6f90f343e6 | ||
|
|
90957190db | ||
|
|
b6a89ccfc8 | ||
|
|
7caba13173 | ||
|
|
f43faa30b6 | ||
|
|
e2a22266bd | ||
|
|
f91203d5e2 | ||
|
|
4c4978d948 | ||
|
|
6ef76673e8 | ||
|
|
95263402b7 | ||
|
|
e7697258e4 | ||
|
|
7e7fa96f16 | ||
|
|
0912d6ec4f | ||
|
|
d8cb4669e5 | ||
|
|
ea531ae620 | ||
|
|
0746385555 | ||
|
|
8c64c7c3eb | ||
|
|
b00420d457 | ||
|
|
e2ad20b376 | ||
|
|
b8dc464e88 | ||
|
|
06142bc285 | ||
|
|
3f1a14c94e | ||
|
|
3c6b553f2f | ||
|
|
de242bdc58 | ||
|
|
ebbe3e89c1 | ||
|
|
e97e4cbb5a | ||
|
|
6b495c1656 | ||
|
|
b37d3a21b7 | ||
|
|
e050c16f9a | ||
|
|
2201339887 | ||
|
|
289ac74553 | ||
|
|
d988300a4c | ||
|
|
63f2f3f0a7 | ||
|
|
a17b79da77 | ||
|
|
a90ef042d5 | ||
|
|
687fda33b6 | ||
|
|
6fdc0c0fd6 | ||
|
|
124366cbce | ||
|
|
a70ebcab44 | ||
|
|
32dfaebbb9 | ||
|
|
91a06f4d7e | ||
|
|
71d8c5a12b | ||
|
|
3e4678a085 | ||
|
|
6492faefd4 | ||
|
|
ca4afe920c | ||
|
|
409cd05739 | ||
|
|
e71502f182 | ||
|
|
f5f4e0f0fe | ||
|
|
e6161a0f62 | ||
|
|
54ee339a48 | ||
|
|
c4e8a881c1 | ||
|
|
63bcae9dc1 | ||
|
|
87adadd445 | ||
|
|
decd5a905e | ||
|
|
8f6a63ae7c | ||
|
|
f3511809fe | ||
|
|
f8937b2dd2 | ||
|
|
f80c48e679 | ||
|
|
e5fda5aac3 | ||
|
|
92060878d7 | ||
|
|
098d38e079 | ||
|
|
a522682ee4 | ||
|
|
0c4e634c44 | ||
|
|
3353c69a51 | ||
|
|
944ebaa8f7 | ||
|
|
dac425b414 | ||
|
|
2742a7a4ec | ||
|
|
3b2078ac56 | ||
|
|
492b822568 | ||
|
|
5a49b88c60 | ||
|
|
7494d4936d | ||
|
|
6a3e0b7743 | ||
|
|
1e0892f0ee | ||
|
|
7ab1474f41 | ||
|
|
3e42e95151 | ||
|
|
fd2c27cdd6 | ||
|
|
0ce8059266 | ||
|
|
e631d2a3ef | ||
|
|
7a6bcb5d47 | ||
|
|
de233cdbf5 | ||
|
|
735ad645f7 | ||
|
|
09c5e4e3c0 | ||
|
|
e9a51c14b8 | ||
|
|
abd68ad2e9 | ||
|
|
5f5fa2a2d7 | ||
|
|
804816cafb | ||
|
|
1c0c0f509c | ||
|
|
69d903f5f8 | ||
|
|
8404eafbae | ||
|
|
bbdebfb846 | ||
|
|
8ec429f0cf | ||
|
|
dc66bbba9f | ||
|
|
755b56e3f9 | ||
|
|
290e7fdac8 | ||
|
|
b9d43cce24 | ||
|
|
7a732de656 | ||
|
|
c22b011755 | ||
|
|
0d9d2325cc | ||
|
|
724d5e00d3 | ||
|
|
a192076e0c | ||
|
|
b33ca03af7 | ||
|
|
cbad13fc20 | ||
|
|
23ee74977b | ||
|
|
53fd03f2a8 | ||
|
|
c7fd8570e3 | ||
|
|
8e5d193518 | ||
|
|
a0fbb1ac7c | ||
|
|
a637e33fce | ||
|
|
a0eea5dfe6 | ||
|
|
de1b72d935 | ||
|
|
b6e3a70267 | ||
|
|
dce8beab32 | ||
|
|
5d66a7e967 | ||
|
|
4499dfc787 | ||
|
|
22060dbe76 | ||
|
|
ea8b0f21f9 | ||
|
|
3f67c5b582 | ||
|
|
45e3ecdf83 | ||
|
|
30a6ee2c92 | ||
|
|
62588620a6 | ||
|
|
5545739c81 | ||
|
|
7b05b54307 | ||
|
|
e8d56bdd5a | ||
|
|
f50457db64 | ||
|
|
c787b33c76 | ||
|
|
cf817d4c8b | ||
|
|
ffaca0f459 | ||
|
|
5c7188eeba | ||
|
|
99da1c21d1 | ||
|
|
4dd97ca8ab | ||
|
|
5d5a0c9ea0 | ||
|
|
70d126acb5 | ||
|
|
8752f9864d | ||
|
|
0cf5c4ab9b | ||
|
|
c97204bd13 | ||
|
|
79e464d140 | ||
|
|
296da1df0b | ||
|
|
e26de8ce3b | ||
|
|
590b3b3b5c | ||
|
|
f00c322689 | ||
|
|
a9186bfbaa | ||
|
|
11e399ae21 | ||
|
|
c2821ea617 | ||
|
|
abed5dd35d | ||
|
|
016b43e1a5 | ||
|
|
3d8fb7f116 | ||
|
|
08b99e9a4b | ||
|
|
d7a568a685 | ||
|
|
9d498dfaf9 | ||
|
|
91551044d0 | ||
|
|
4cb0aa00fb | ||
|
|
e30cfc3b76 | ||
|
|
39850689fa | ||
|
|
2a0918a567 | ||
|
|
94e22902f0 | ||
|
|
b3ec87d0f4 | ||
|
|
0111b5d3e7 | ||
|
|
80a8e0a7bd | ||
|
|
e2af1a451b | ||
|
|
1a44b75790 | ||
|
|
1572c4cdd3 | ||
|
|
2fed551cf1 | ||
|
|
dae971a96c | ||
|
|
5cc367f7f2 | ||
|
|
aad8902173 | ||
|
|
05a0dc95dd | ||
|
|
bedec4f68e | ||
|
|
1b2a75ab25 | ||
|
|
4215429944 | ||
|
|
c4ddb9e529 | ||
|
|
660885a0c0 | ||
|
|
892b474e8b | ||
|
|
d6000b0f4a | ||
|
|
7aaf894a98 | ||
|
|
41dac4bc7d | ||
|
|
7318205b88 | ||
|
|
fc6d25767d | ||
|
|
3ed3219c7f | ||
|
|
2fd5760aeb | ||
|
|
cf905d77b1 | ||
|
|
e59605ec11 | ||
|
|
b3f3d51dae | ||
|
|
94bdf67c17 | ||
|
|
94203d907e | ||
|
|
e964ef9bef | ||
|
|
1ba4419eb0 | ||
|
|
5894f1d268 | ||
|
|
ec02d5868c | ||
|
|
bbd2fccf11 | ||
|
|
93028e9161 | ||
|
|
43ef168d0b | ||
|
|
bd4b8d3354 | ||
|
|
ffbaf87cf5 | ||
|
|
ce2ac6fe47 | ||
|
|
0b1f831efc | ||
|
|
a2af628a14 | ||
|
|
6f23272c63 | ||
|
|
43d7a5cec2 | ||
|
|
e73ffddb50 | ||
|
|
d75e1a5a85 | ||
|
|
411e6e4cff | ||
|
|
fd6395fcd4 | ||
|
|
9d19acfeb3 | ||
|
|
190aced819 | ||
|
|
15bd7b0939 | ||
|
|
85d7adb8c5 | ||
|
|
2635f30eea | ||
|
|
64199f911b | ||
|
|
c7e09dc13d | ||
|
|
6113e2e842 | ||
|
|
2aa95a3467 | ||
|
|
e2be16a421 | ||
|
|
9147f62c0a | ||
|
|
ebd835a8a9 | ||
|
|
7bcd2631bb | ||
|
|
b16f532180 | ||
|
|
f9c6209562 | ||
|
|
26247f86da | ||
|
|
482bf4e5e1 | ||
|
|
f3d268a764 | ||
|
|
8f17ee1fc5 | ||
|
|
1c168234b8 | ||
|
|
00df437c5d | ||
|
|
a47ef45905 | ||
|
|
0f10ce3a70 | ||
|
|
8d4e14710b | ||
|
|
6226c7f93b | ||
|
|
e104386398 | ||
|
|
52f52a5d40 | ||
|
|
6d980893b9 | ||
|
|
dee53bb1c4 | ||
|
|
e57d04f607 | ||
|
|
747c9b45aa | ||
|
|
e7ddd65ee2 | ||
|
|
ab2b4f7caa | ||
|
|
1342dd3f3a | ||
|
|
34acbfa23e | ||
|
|
b3c1cedcc8 | ||
|
|
4ce19bd5f1 | ||
|
|
0040fea032 | ||
|
|
a5c94bd34c | ||
|
|
9c72cd8a3d | ||
|
|
2d344f4701 | ||
|
|
bf44c61020 | ||
|
|
a312559b26 | ||
|
|
f8aaba6f31 | ||
|
|
36dd831a69 | ||
|
|
bb1222116d | ||
|
|
ca26cb2eeb | ||
|
|
80385e21a7 | ||
|
|
3827ecb5b7 | ||
|
|
5ce3812e8b | ||
|
|
3cd089bad8 | ||
|
|
89baabbb62 | ||
|
|
b2da09f0c1 | ||
|
|
02db77dea7 | ||
|
|
36051f40dd | ||
|
|
5d0e57acb7 | ||
|
|
b131dea579 | ||
|
|
9ede688b6e | ||
|
|
1258d954d0 | ||
|
|
be1a55f999 | ||
|
|
5653a15ad5 | ||
|
|
33cc0fe925 | ||
|
|
0551e1729a | ||
|
|
7830c11a16 | ||
|
|
3a31abac5a | ||
|
|
773f67b09b | ||
|
|
77fafa85ad | ||
|
|
61e0f6fa10 | ||
|
|
94c7f0df98 | ||
|
|
674136bcab | ||
|
|
a55d3655cf | ||
|
|
8f078876f0 | ||
|
|
5a73a4f198 | ||
|
|
c7a38113a1 | ||
|
|
2982f347a4 | ||
|
|
a49e0c2f84 | ||
|
|
6235e9c86e | ||
|
|
6d8ae401f3 | ||
|
|
c2fb18a2df | ||
|
|
f36a61dcc6 | ||
|
|
f28a6e4138 | ||
|
|
9a4b6ab417 | ||
|
|
5e732a4d20 | ||
|
|
547c2335ef | ||
|
|
0f6c8fbb44 | ||
|
|
1b4ac74118 | ||
|
|
54d1d750e9 | ||
|
|
fefd863957 | ||
|
|
8ec8e652cb | ||
|
|
b5176fe7a1 | ||
|
|
5e8f741c2f | ||
|
|
e6e7115e14 | ||
|
|
b15dfe953d | ||
|
|
5b04ef5d98 | ||
|
|
aed8560adf | ||
|
|
25a4e7b68c | ||
|
|
371eff5047 | ||
|
|
3cae356f93 | ||
|
|
35a1e9ceb3 | ||
|
|
eedbb3cbb6 | ||
|
|
f14e82291f | ||
|
|
bae17d11d5 | ||
|
|
11d08d6a24 | ||
|
|
a3b97e8998 | ||
|
|
8d82c59453 | ||
|
|
6d89c573a6 | ||
|
|
6f2a22062b | ||
|
|
9bfe24e477 | ||
|
|
5df6e6397d | ||
|
|
5d5b84a0a1 | ||
|
|
b102c19ea7 | ||
|
|
6a1dad581e | ||
|
|
670446e209 | ||
|
|
f406ec2621 | ||
|
|
1abc0e4239 | ||
|
|
0a64ff9ef3 | ||
|
|
a42f814352 | ||
|
|
2e9a5f0a36 | ||
|
|
b484dff945 | ||
|
|
996770a625 | ||
|
|
0f16e1d1cb | ||
|
|
1ba2b26cc9 | ||
|
|
0bb8b0c3bc | ||
|
|
12a2da7c56 | ||
|
|
fb2fa605d2 | ||
|
|
ae31dc5aba | ||
|
|
c85a25b75e | ||
|
|
c6ee3e46f6 | ||
|
|
7aa3bb01a9 | ||
|
|
4bec35869c | ||
|
|
aca9700baf | ||
|
|
9a1ed2bb9b | ||
|
|
615aeea576 | ||
|
|
aaf27ed6a8 | ||
|
|
25d4297df5 | ||
|
|
48af590bd8 | ||
|
|
b2e86675b3 | ||
|
|
cb3b5d45f6 | ||
|
|
bae3d7d9ef | ||
|
|
787a31ac4d | ||
|
|
e8b46229ee | ||
|
|
544cb6a226 | ||
|
|
a0bba85ca9 | ||
|
|
d238166405 | ||
|
|
2b74c34c00 | ||
|
|
6024674182 | ||
|
|
c9e71539cb | ||
|
|
811f47f929 | ||
|
|
45caf5cdda | ||
|
|
774c395a7f | ||
|
|
e727c124c7 | ||
|
|
bafc486401 | ||
|
|
e84246aecd | ||
|
|
4cf702fb0b | ||
|
|
36f1690120 | ||
|
|
c3ddf2ad32 | ||
|
|
0b5de2d2b8 | ||
|
|
3dd0d840d9 | ||
|
|
24ae745da0 | ||
|
|
73260b53f1 | ||
|
|
11249245ae | ||
|
|
5ce1728e5e | ||
|
|
1f22b424f5 | ||
|
|
17597ced08 | ||
|
|
e1a15aed9a | ||
|
|
43b6056c1e | ||
|
|
3cf5a4a4cf | ||
|
|
3b4224586f | ||
|
|
949b522ba0 | ||
|
|
deb38435fd | ||
|
|
07359752ff | ||
|
|
18b07d509c | ||
|
|
9adc366a09 | ||
|
|
80faecb9f4 | ||
|
|
e51adc2b5f | ||
|
|
879cb09be1 | ||
|
|
009d27b23d | ||
|
|
bbca48a7a2 | ||
|
|
00cc51c6d3 | ||
|
|
b4b98428d4 | ||
|
|
c4b7eb0368 | ||
|
|
e077ffc553 | ||
|
|
df08ed6dd2 | ||
|
|
55fd2ae629 | ||
|
|
bcda09ed41 | ||
|
|
af8873470c | ||
|
|
c2724bca5d | ||
|
|
e595d6b85e | ||
|
|
f207136937 | ||
|
|
e96eb76d84 | ||
|
|
32b7bfe383 | ||
|
|
28047aa64d | ||
|
|
462b9681e1 | ||
|
|
bc756337cd | ||
|
|
3ae0306632 | ||
|
|
235373b1db | ||
|
|
e1780977af | ||
|
|
5dfc0cb340 | ||
|
|
78e05f2d40 | ||
|
|
76cd1a48b8 | ||
|
|
de5036bc9b | ||
|
|
77ec0e86d2 | ||
|
|
6aef94418b | ||
|
|
1d205384fc | ||
|
|
943556186f | ||
|
|
08d9dc556b | ||
|
|
bdb0738a1b | ||
|
|
3917feb664 | ||
|
|
3c5ac2d94c | ||
|
|
a57c8d982c | ||
|
|
0e30df5e46 | ||
|
|
d4e6471d11 | ||
|
|
f17da9cb46 | ||
|
|
46d6ef2a68 | ||
|
|
80cbddcc4a | ||
|
|
832d457481 | ||
|
|
780f445f4a | ||
|
|
f55d0f9f93 | ||
|
|
399eeabda5 | ||
|
|
82b658e7fe | ||
|
|
388947d485 | ||
|
|
9e72dbb0a0 | ||
|
|
3fbe465b99 | ||
|
|
708305c66d | ||
|
|
746a8196ba | ||
|
|
ee4e8d30b5 | ||
|
|
daa09627c8 | ||
|
|
795b32c4b4 | ||
|
|
48b38281a6 | ||
|
|
847075cdc8 | ||
|
|
59af707143 | ||
|
|
42d818138a | ||
|
|
b6f459372b | ||
|
|
a1ee857caa | ||
|
|
7301a774a4 | ||
|
|
16a4d4c8b4 | ||
|
|
d74d2d7295 | ||
|
|
7ac2ae8af6 | ||
|
|
42073077ee | ||
|
|
84d87dcc4f | ||
|
|
6ee5d08086 | ||
|
|
46bc9930f7 | ||
|
|
d73781f51c | ||
|
|
f4ba1f4fdb | ||
|
|
4b6ad74f6b | ||
|
|
c3105fd698 | ||
|
|
c6051f226d | ||
|
|
8e0e725d80 | ||
|
|
a2da98f7ab | ||
|
|
8284f0141c | ||
|
|
073281f2fa | ||
|
|
970074d7d6 | ||
|
|
c2bc392178 | ||
|
|
af7ec3319c | ||
|
|
0dc30e2bdd | ||
|
|
f786b8acab | ||
|
|
e3ebdb68b2 | ||
|
|
d9bec06b2f | ||
|
|
fbefa65644 | ||
|
|
d5e636f696 | ||
|
|
b7afe65272 | ||
|
|
3bda4f6de3 | ||
|
|
eabee221b6 | ||
|
|
e353d67b02 | ||
|
|
b65e0d754a | ||
|
|
aa5cbcce0d | ||
|
|
2124aba21f | ||
|
|
a4d3136707 | ||
|
|
d89dad3827 | ||
|
|
12980b63f6 | ||
|
|
6183e5ef00 | ||
|
|
0de4f9074d | ||
|
|
293aef9085 | ||
|
|
49cff5429e | ||
|
|
a832bf89b4 | ||
|
|
75656b442c | ||
|
|
73a8829e1c | ||
|
|
7327bfba76 | ||
|
|
2f39cc08dc | ||
|
|
196b4dd89b | ||
|
|
fd1d6e1420 | ||
|
|
9b3e41432c | ||
|
|
1f642548e9 | ||
|
|
6a3372f22f | ||
|
|
b00e2118bc | ||
|
|
8b26da4e36 | ||
|
|
0eec54fc30 | ||
|
|
9f512abaeb | ||
|
|
48cb96221e | ||
|
|
d5bc6bb16a | ||
|
|
89cc964f54 | ||
|
|
5ab9cb4c53 | ||
|
|
7fb990495a | ||
|
|
304a8ab74f | ||
|
|
0d0a94e594 | ||
|
|
7933955a69 | ||
|
|
db73b0787b | ||
|
|
ce5675639f | ||
|
|
ff56734801 | ||
|
|
7fa7213e77 | ||
|
|
68c153ac5d | ||
|
|
3b687be9b4 | ||
|
|
0f9742a83b | ||
|
|
669cdd1395 | ||
|
|
d402442785 | ||
|
|
4f057756ee | ||
|
|
c324755f24 | ||
|
|
ee838a8a2e | ||
|
|
8c47d216b1 | ||
|
|
1dc85c00c6 | ||
|
|
d62a6922be | ||
|
|
1725f51818 | ||
|
|
4c84fd46da | ||
|
|
ee3ed22e2c | ||
|
|
6c0d61278a | ||
|
|
ec90f42833 | ||
|
|
aaaafb46f5 | ||
|
|
b4dd70286d | ||
|
|
b14aa3629b | ||
|
|
ccb6784ecf | ||
|
|
f817acc082 | ||
|
|
1a0af91a5e | ||
|
|
6d4f297d24 | ||
|
|
bd7f215591 | ||
|
|
d18365204d | ||
|
|
4aaf4042f0 | ||
|
|
f39d2d57a8 | ||
|
|
29b0036248 | ||
|
|
c397100cbc | ||
|
|
fb3457c6fd | ||
|
|
809ac8cbc7 | ||
|
|
8151faf15e | ||
|
|
32cbe2c1a5 | ||
|
|
92a83c2506 | ||
|
|
537dfa241d | ||
|
|
851eff897e | ||
|
|
e9dce3ff2f | ||
|
|
cc4ddd214f | ||
|
|
4c5036539d | ||
|
|
530f967fcc | ||
|
|
c8c2625082 | ||
|
|
0abc29451a | ||
|
|
2a580ccf8b | ||
|
|
449352338d | ||
|
|
5b8d8d5c7e | ||
|
|
f92fa08e9e | ||
|
|
a8fe18a465 | ||
|
|
18ff6303df | ||
|
|
6ecec4af0a | ||
|
|
a2401a4d7a | ||
|
|
936fd3b070 | ||
|
|
09c383a1bd | ||
|
|
2f6432bf24 | ||
|
|
6693c8bc9b | ||
|
|
34abb7eb27 | ||
|
|
894836fa55 | ||
|
|
7810295300 | ||
|
|
101e2366e1 | ||
|
|
e42bc6e84b | ||
|
|
0139a8ab58 | ||
|
|
0f41481580 | ||
|
|
241557f936 | ||
|
|
4b7d18128c | ||
|
|
80ab99e8cf | ||
|
|
c8a86b39d5 | ||
|
|
ee6399b10d | ||
|
|
b6900584f7 | ||
|
|
eb6e0544c7 | ||
|
|
abc743a08e | ||
|
|
6edc5e07ee | ||
|
|
cba11ae053 | ||
|
|
7c802f1885 | ||
|
|
7d8960a1ed | ||
|
|
d71bb9d22d | ||
|
|
a64c18eff7 | ||
|
|
1924570ea1 | ||
|
|
cab871fc95 | ||
|
|
edc8d25ab3 | ||
|
|
ca2c76325d | ||
|
|
da5d9c2422 | ||
|
|
046360bd14 | ||
|
|
be48fde490 | ||
|
|
06d0ea326b | ||
|
|
c364e35292 | ||
|
|
8795173dc5 | ||
|
|
d719f10b1c | ||
|
|
b122a23e08 | ||
|
|
beda689dfc | ||
|
|
f8a6342b98 | ||
|
|
80ec20d9cd | ||
|
|
6325f8cc86 | ||
|
|
657d64eec3 | ||
|
|
b937408bab | ||
|
|
d833543368 | ||
|
|
e4dc761107 | ||
|
|
213cf60eb0 | ||
|
|
46f35cf742 | ||
|
|
f19da5da9a | ||
|
|
fe6b93150b | ||
|
|
ed99a28749 | ||
|
|
d9c8e083a9 | ||
|
|
2a94efc4ef | ||
|
|
8bc2da0af0 | ||
|
|
d60e47dccf | ||
|
|
cfc8c51537 | ||
|
|
fb833f34c8 | ||
|
|
0b8cc5f964 | ||
|
|
95a1bb80df | ||
|
|
7a71b088f4 | ||
|
|
e07ada5ad4 | ||
|
|
3fcd21635e | ||
|
|
9072c816e8 | ||
|
|
78f619b25d | ||
|
|
91fabcb148 | ||
|
|
233a2969c6 | ||
|
|
a4fe43235b | ||
|
|
40bbc996f7 | ||
|
|
f7b902a7ef | ||
|
|
c182340be4 | ||
|
|
efeaa7a79e | ||
|
|
c9dc332ae2 | ||
|
|
a211c6323e | ||
|
|
dd94224383 | ||
|
|
311d107b36 | ||
|
|
a5dc634e35 | ||
|
|
17eafb8563 | ||
|
|
f28d54936f | ||
|
|
fc03388d70 | ||
|
|
6952d21af2 | ||
|
|
77587537f6 | ||
|
|
00c2f77746 | ||
|
|
7afe940087 | ||
|
|
43bd56de23 | ||
|
|
9db37cfd9b | ||
|
|
45e63c385f | ||
|
|
cd8b53174b | ||
|
|
16eb27b342 | ||
|
|
adbc0c7edd | ||
|
|
fa64f8bbbf | ||
|
|
e4a93da8ea | ||
|
|
3c093c7d51 | ||
|
|
88d1030e12 | ||
|
|
7aa80860b8 | ||
|
|
3aafaf4665 | ||
|
|
f393e301f0 | ||
|
|
75b3aaf2a6 | ||
|
|
517b3f6db3 | ||
|
|
50c0b9420b | ||
|
|
3dcf1d55da | ||
|
|
94a3bab48b | ||
|
|
c132ea1d67 | ||
|
|
a4b286888e | ||
|
|
9e8171a777 | ||
|
|
71d00f5970 | ||
|
|
11bb081c7c | ||
|
|
d8e7087af6 | ||
|
|
66b6040f86 | ||
|
|
a896bb60d6 | ||
|
|
f8b369784f | ||
|
|
46ec80dd04 | ||
|
|
08e02800d2 | ||
|
|
bd79eb9af1 | ||
|
|
3ca550568a | ||
|
|
5e7d353627 | ||
|
|
16249ccd63 | ||
|
|
2323e8f1c7 | ||
|
|
f82f9f8384 | ||
|
|
8c030ac73e | ||
|
|
d1fc7959a1 | ||
|
|
f37986d5c1 | ||
|
|
90dd207e88 | ||
|
|
8201df7014 | ||
|
|
67842b39ea | ||
|
|
2cd98a516c | ||
|
|
a0d1a03a16 | ||
|
|
5b6fe0c94b | ||
|
|
79e3f95fb8 | ||
|
|
3e336112ca | ||
|
|
3748d3c7b4 | ||
|
|
31bcce2668 | ||
|
|
5cea9f1a36 | ||
|
|
5fb93ad5fd | ||
|
|
5d7379ec32 | ||
|
|
9c2c7f40ba | ||
|
|
a93c6bfa45 | ||
|
|
379ccefd8b | ||
|
|
20bffcd6be | ||
|
|
93b7470b69 | ||
|
|
e8235fcf02 | ||
|
|
5fddf7ca4d | ||
|
|
2742db8dfd | ||
|
|
fd2bba35ce | ||
|
|
2a2fcb1a75 | ||
|
|
4a1c7ab2c9 | ||
|
|
8f3c734426 | ||
|
|
827b8a3aec | ||
|
|
e458ffe355 | ||
|
|
0fc77d0283 | ||
|
|
b317ee4904 | ||
|
|
a2c313dfc0 | ||
|
|
64c9360b38 | ||
|
|
61a212ada7 | ||
|
|
62c5c8d5c8 | ||
|
|
ede2d4becd | ||
|
|
9e0edbc082 | ||
|
|
17fc5edcda | ||
|
|
61b05613f3 | ||
|
|
feb00d83f8 | ||
|
|
031874d442 | ||
|
|
d9b5977d02 | ||
|
|
67081eded4 | ||
|
|
21d564b609 | ||
|
|
2036bb6fbb | ||
|
|
19b6d6b0fe | ||
|
|
b102b0d010 | ||
|
|
d41a2670fa | ||
|
|
0ef064f075 | ||
|
|
6595e95f12 | ||
|
|
9182244e88 | ||
|
|
7ceca619e0 | ||
|
|
3a387d8820 | ||
|
|
97fcf33088 | ||
|
|
b5bad3c785 | ||
|
|
5128410d82 | ||
|
|
c91788b8fd | ||
|
|
688d94c1da | ||
|
|
c62425cbc8 | ||
|
|
8f30151b31 | ||
|
|
8cf409878d | ||
|
|
1cee1f4b01 | ||
|
|
ebec1eb268 | ||
|
|
6b6aba7358 | ||
|
|
56f7613453 | ||
|
|
62b9267d4f | ||
|
|
2663acf224 | ||
|
|
7b8f241d41 | ||
|
|
68b5f9c2ea | ||
|
|
113f5fcd16 |
101
.circleci/config.yml
Normal file
101
.circleci/config.yml
Normal file
@@ -0,0 +1,101 @@
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: penpotapp/devenv:latest
|
||||
- image: cimg/postgres:14.5
|
||||
environment:
|
||||
POSTGRES_USER: penpot_test
|
||||
POSTGRES_PASSWORD: penpot_test
|
||||
POSTGRES_DB: penpot_test
|
||||
- image: cimg/redis:7.0.5
|
||||
|
||||
working_directory: ~/repo
|
||||
resource_class: large
|
||||
|
||||
environment:
|
||||
# Customize the JVM maximum heap limit
|
||||
JVM_OPTS: -Xmx4g
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
# Download and cache dependencies
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ checksum "backend/deps.edn" }}-{{ checksum "frontend/deps.edn"}}-{{ checksum "common/deps.edn"}}
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- v1-dependencies-
|
||||
|
||||
- run: cd .clj-kondo && cat config.edn
|
||||
|
||||
- run:
|
||||
name: frontend styles prettier
|
||||
working_directory: "./frontend"
|
||||
command: |
|
||||
yarn install
|
||||
yarn run lint-scss
|
||||
|
||||
- run:
|
||||
name: common lint
|
||||
working_directory: "./common"
|
||||
command: |
|
||||
clj-kondo --version
|
||||
clj-kondo --parallel --lint src/
|
||||
|
||||
- run:
|
||||
name: frontend lint
|
||||
working_directory: "./frontend"
|
||||
command: |
|
||||
clj-kondo --version
|
||||
clj-kondo --parallel --lint src/
|
||||
|
||||
- run:
|
||||
name: backend lint
|
||||
working_directory: "./backend"
|
||||
command: |
|
||||
clj-kondo --version
|
||||
clj-kondo --parallel --lint src/
|
||||
|
||||
- run:
|
||||
working_directory: "./common"
|
||||
name: common tests
|
||||
command: |
|
||||
yarn install
|
||||
yarn test
|
||||
clojure -X:dev:test :patterns '["common-tests.*-test"]'
|
||||
|
||||
environment:
|
||||
PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin
|
||||
JVM_OPTS: -Xmx4g
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
|
||||
- run:
|
||||
name: backend test
|
||||
working_directory: "./backend"
|
||||
command: |
|
||||
clojure -X:dev:test :patterns '["backend-tests.*-test"]'
|
||||
|
||||
environment:
|
||||
PENPOT_TEST_DATABASE_URI: "postgresql://localhost/penpot_test"
|
||||
PENPOT_TEST_DATABASE_USERNAME: penpot_test
|
||||
PENPOT_TEST_DATABASE_PASSWORD: penpot_test
|
||||
PENPOT_TEST_REDIS_URI: "redis://localhost/1"
|
||||
JVM_OPTS: -Xmx4g
|
||||
|
||||
- run:
|
||||
name: frontend tests
|
||||
working_directory: "./frontend"
|
||||
command: |
|
||||
yarn install
|
||||
yarn test
|
||||
|
||||
environment:
|
||||
PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
key: v1-dependencies-{{ checksum "backend/deps.edn" }}-{{ checksum "frontend/deps.edn"}}-{{ checksum "common/deps.edn"}}
|
||||
|
||||
62
.clj-kondo/config.edn
Normal file
62
.clj-kondo/config.edn
Normal file
@@ -0,0 +1,62 @@
|
||||
{:lint-as
|
||||
{promesa.core/let clojure.core/let
|
||||
promesa.core/->> clojure.core/->>
|
||||
promesa.core/-> clojure.core/->
|
||||
rumext.v2/defc clojure.core/defn
|
||||
rumext.v2/fnc clojure.core/fn
|
||||
app.common.data/export clojure.core/def
|
||||
app.db/with-atomic clojure.core/with-open
|
||||
app.common.data.macros/get-in clojure.core/get-in
|
||||
app.common.data.macros/with-open clojure.core/with-open
|
||||
app.common.data.macros/select-keys clojure.core/select-keys
|
||||
app.common.logging/with-context clojure.core/do}
|
||||
|
||||
:hooks
|
||||
{:analyze-call
|
||||
{app.common.data.macros/export hooks.export/export
|
||||
potok.core/reify hooks.export/potok-reify
|
||||
app.util.services/defmethod hooks.export/service-defmethod
|
||||
}}
|
||||
|
||||
:output
|
||||
{:exclude-files
|
||||
["data_readers.clj"
|
||||
"app/util/perf.cljs"
|
||||
"app/common/logging.cljc"
|
||||
"app/common/exceptions.cljc"]}
|
||||
|
||||
:linters
|
||||
{:unsorted-required-namespaces
|
||||
{:level :warning}
|
||||
|
||||
:potok/reify-type
|
||||
{:level :error}
|
||||
|
||||
:unresolved-namespace
|
||||
{:level :warning
|
||||
:exclude [data_readers]}
|
||||
|
||||
:single-key-in
|
||||
{:level :warning}
|
||||
|
||||
:non-arg-vec-return-type-hint
|
||||
{:level :off}
|
||||
|
||||
:redundant-do
|
||||
{:level :off}
|
||||
|
||||
:earmuffed-var-not-dynamic
|
||||
{:level :off}
|
||||
|
||||
:dynamic-var-not-earmuffed
|
||||
{:level :off}
|
||||
|
||||
:used-underscored-binding
|
||||
{:level :warning}
|
||||
|
||||
:unused-binding
|
||||
{:exclude-destructured-as true
|
||||
:exclude-destructured-keys-in-fn-args false
|
||||
}
|
||||
}}
|
||||
|
||||
89
.clj-kondo/hooks/export.clj
Normal file
89
.clj-kondo/hooks/export.clj
Normal file
@@ -0,0 +1,89 @@
|
||||
(ns hooks.export
|
||||
(:require [clj-kondo.hooks-api :as api]))
|
||||
|
||||
(defn export
|
||||
[{:keys [:node]}]
|
||||
(let [[_ sname] (:children node)
|
||||
result (api/list-node
|
||||
[(api/token-node (symbol "def"))
|
||||
(api/token-node (symbol (name (:value sname))))
|
||||
sname])]
|
||||
{:node result}))
|
||||
|
||||
(def registry (atom {}))
|
||||
|
||||
(defn potok-reify
|
||||
[{:keys [:node :filename] :as params}]
|
||||
(let [[rnode rtype & other] (:children node)
|
||||
rsym (symbol (str "event-type-" (name (:k rtype))))
|
||||
reg (get @registry filename #{})]
|
||||
(when-not (:namespaced? rtype)
|
||||
(let [{:keys [:row :col]} (meta rtype)]
|
||||
(api/reg-finding! {:message "ptk/reify type should be namespaced"
|
||||
:type :potok/reify-type
|
||||
:row row
|
||||
:col col})))
|
||||
|
||||
(if (contains? reg rsym)
|
||||
(let [{:keys [:row :col]} (meta rtype)]
|
||||
(api/reg-finding! {:message (str "duplicate type: " (name (:k rtype)))
|
||||
:type :potok/reify-type
|
||||
:row row
|
||||
:col col}))
|
||||
(swap! registry update filename (fnil conj #{}) rsym))
|
||||
|
||||
(let [result (api/list-node
|
||||
(into [(api/token-node (symbol "deftype"))
|
||||
(api/token-node rsym)
|
||||
(api/vector-node [])]
|
||||
other))]
|
||||
{:node result})))
|
||||
|
||||
(defn clojure-specify
|
||||
[{:keys [:node]}]
|
||||
(let [[rnode rtype & other] (:children node)
|
||||
result (api/list-node
|
||||
(into [(api/token-node (symbol "extend-type"))
|
||||
(api/token-node (gensym (:string-value rtype)))]
|
||||
other))]
|
||||
{:node result}))
|
||||
|
||||
|
||||
(defn service-defmethod
|
||||
[{:keys [:node]}]
|
||||
(let [[rnode rtype ?meta & other] (:children node)
|
||||
rsym (gensym (name (:k rtype)))
|
||||
|
||||
[?docs other] (if (api/string-node? ?meta)
|
||||
[?meta other]
|
||||
[nil (cons ?meta other)])
|
||||
|
||||
[?meta other] (let [?meta (first other)]
|
||||
(if (api/map-node? ?meta)
|
||||
[?meta (rest other)]
|
||||
[nil other]))
|
||||
|
||||
nodes [(api/token-node (symbol "do"))
|
||||
(api/list-node
|
||||
[(api/token-node (symbol "declare"))
|
||||
(api/token-node rsym)])
|
||||
|
||||
(when ?docs
|
||||
(api/list-node
|
||||
[(api/token-node (symbol "comment")) ?docs]))
|
||||
|
||||
(when ?meta
|
||||
(api/list-node
|
||||
[(api/token-node (symbol "reset-meta!"))
|
||||
(api/token-node rsym)
|
||||
?meta]))
|
||||
(api/list-node
|
||||
(into [(api/token-node (symbol "defmethod"))
|
||||
(api/token-node rsym)
|
||||
rtype]
|
||||
other))]
|
||||
result (api/list-node (filterv some? nodes))]
|
||||
|
||||
;; (prn "=====>" rtype)
|
||||
;; (prn (api/sexpr result))
|
||||
{:node result}))
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
frontend/vendor/* linguist-vendored
|
||||
89
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
89
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
description: Create a report to help us improve
|
||||
labels: ["bug"]
|
||||
name: Bug report
|
||||
title: "bug: "
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Before you start
|
||||
|
||||
Please search our [existing issues](https://github.com/penpot/penpot/issues) and open [pull requests](https://github.com/penpot/penpot/pulls) to lessen the change of filing duplicate issues or feature requests. Thank you.
|
||||
|
||||
---
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
label: Expected behavior
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
description: A clear and concise description of what happens instead; what the bug is.
|
||||
label: Actual behavior
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
description: If applicable, add screenshots to help explain your problem.
|
||||
label: Screenshots or video
|
||||
- type: textarea
|
||||
id: desktop
|
||||
attributes:
|
||||
label: Desktop (please complete the following information)
|
||||
placeholder: |
|
||||
- OS (e.g. iOS):
|
||||
- Browser & version (e.g. Chrome 89.0):
|
||||
- type: textarea
|
||||
id: mobile
|
||||
attributes:
|
||||
label: Smartphone (please complete the following information)
|
||||
placeholder: |
|
||||
- Device & model (e.g. iPhone 6):
|
||||
- OS & version (e.g. iOS 8.1):
|
||||
- Browser & version (e.g. stock browser 22):
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Environment (please complete the following information)
|
||||
placeholder: |
|
||||
- Host (e.g. https://design.penpot.app, local instance):
|
||||
|
||||
*If self-hosted:*
|
||||
- OS Version (e.g. Ubuntu 16.04):
|
||||
- Docker / Docker-compose version (e.g. Docker version 18.03.0-ce, build 0520e24):
|
||||
- Image version (e.g. Alpine):
|
||||
|
||||
Docker commands or docker-compose file (if possible and if proceed.x):
|
||||
```
|
||||
|
||||
```
|
||||
- type: textarea
|
||||
id: frontend-trace
|
||||
attributes:
|
||||
label: Frontend Stack Trace
|
||||
render: console
|
||||
- type: textarea
|
||||
id: backend-trace
|
||||
attributes:
|
||||
label: Backend Stack Trace
|
||||
render: console
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Any other context about the problem.
|
||||
37
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
37
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
description: Suggest an idea for this project.
|
||||
labels: ["needs triage", "enhancement"]
|
||||
name: "Feature request"
|
||||
title: "feature: "
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Before you start
|
||||
|
||||
Please search our [existing issues](https://github.com/penpot/penpot/issues) and open [pull requests](https://github.com/penpot/penpot/pulls) to lessen the change of filing duplicate issues or feature requests. Thank you.
|
||||
|
||||
---
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when (...)
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
description: A clear and concise description of what you want to happen.
|
||||
label: Describe the solution you'd like.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Describe alternatives you've considered.
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the feature request here.
|
||||
29
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
29
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
<!--
|
||||
|
||||
Some key notes before you open a PR:
|
||||
|
||||
1. Select which branch should this PR be merged in? By default, you should always merge to the develop branch.
|
||||
2. PR name follows [convention](http://karma-runner.github.io/4.0/dev/git-commit-msg.html)
|
||||
3. All tests pass locally, UI and Unit tests
|
||||
4. All business logic and validations must be on the server-side
|
||||
5. Update necessary Documentation
|
||||
6. Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes
|
||||
|
||||
|
||||
Also, if you're new here
|
||||
|
||||
- Contribution Guide => https://github.com/uxbox/uxbox/blob/develop/CONTRIBUTING.md
|
||||
|
||||
-->
|
||||
|
||||
> Please provide enough information so that others can review your pull request:
|
||||
|
||||
<!-- You can skip this if you're fixing a typo or updating existing documentation -->
|
||||
|
||||
> Explain the **details** for making this change. What existing problem does the pull request solve?
|
||||
|
||||
<!-- Example: When "Adding a function to do X", explain why it is necessary to have a way to do X. -->
|
||||
|
||||
> Screenshots/GIFs
|
||||
|
||||
<!-- Add images/recordings to better visualize the change: expected/current behviour -->
|
||||
35
.github/config.yml
vendored
Normal file
35
.github/config.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
|
||||
|
||||
# Comment to be posted to on first time issues
|
||||
newIssueWelcomeComment: >
|
||||
Thanks for opening your first issue here! Be sure to follow the issue template!
|
||||
|
||||
# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
|
||||
|
||||
# Comment to be posted to on PRs from first time contributors in your repository
|
||||
newPRWelcomeComment: >
|
||||
Thanks for opening this pull request! Please check out our contributing guidelines.
|
||||
|
||||
# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
|
||||
|
||||
# Comment to be posted to on pull requests merged by a first time user
|
||||
firstPRMergeComment: >
|
||||
:tada: Congrats on merging your first pull request! We here at behaviorbot are proud of you!
|
||||
|
||||
# It is recommend to include as many gifs and emojis as possible
|
||||
|
||||
# Configuration for request-info - https://github.com/behaviorbot/request-info
|
||||
|
||||
# *Required* Comment to reply with
|
||||
requestInfoReplyComment: >
|
||||
We would appreciate it if you could provide us with more info about this issue/pr!
|
||||
|
||||
# *OPTIONAL* default titles to check against for lack of descriptiveness
|
||||
# MUST BE ALL LOWERCASE
|
||||
requestInfoDefaultTitles:
|
||||
- update readme.md
|
||||
- updates
|
||||
|
||||
|
||||
# *OPTIONAL* Label to be added to Issues and Pull Requests with insufficient information given
|
||||
requestInfoLabelToAdd: needs-more-info
|
||||
23
.github/stale.yml
vendored
Normal file
23
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 60
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 30
|
||||
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- pinned
|
||||
- security
|
||||
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: wontfix
|
||||
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
74
.gitignore
vendored
74
.gitignore
vendored
@@ -1,19 +1,59 @@
|
||||
/resources/public/js/compiled/**
|
||||
figwheel_server.log
|
||||
pom.xml
|
||||
*jar
|
||||
/lib/
|
||||
/classes/
|
||||
/out/
|
||||
/target/
|
||||
.lein-deps-sum
|
||||
.lein-failures
|
||||
.lein-repl-history
|
||||
.lein-plugins/
|
||||
.repl
|
||||
*-init.clj
|
||||
*.jar
|
||||
*.orig
|
||||
*.penpot
|
||||
.calva
|
||||
.clj-kondo
|
||||
.cpcache
|
||||
.lsp
|
||||
.nrepl-port
|
||||
/hicv/
|
||||
.nyc_output
|
||||
.rebel_readline_history
|
||||
.repl
|
||||
.shadow-cljs
|
||||
/*.jpg
|
||||
/*.md
|
||||
/*.png
|
||||
/*.sql
|
||||
/*.txt
|
||||
/*.yml
|
||||
/*.zip
|
||||
/.clj-kondo/.cache
|
||||
/_dump
|
||||
/backend/*.md
|
||||
/backend/*.sql
|
||||
/backend/*.txt
|
||||
/backend/assets/
|
||||
/backend/builtin-templates
|
||||
/backend/dist/
|
||||
/backend/logs/
|
||||
/backend/resources/public/assets
|
||||
/backend/resources/public/media
|
||||
/backend/target/
|
||||
/bundle*
|
||||
/cd.md
|
||||
/clj-profiler/
|
||||
/common/coverage
|
||||
/common/target
|
||||
/deploy
|
||||
/docker/images/bundle*
|
||||
/exporter/target
|
||||
/frontend/cypress/fixtures/validuser.json
|
||||
/frontend/cypress/videos/*/
|
||||
/frontend/cypress/videos/*/
|
||||
/frontend/dist/
|
||||
/frontend/npm-debug.log
|
||||
/frontend/out/
|
||||
/frontend/package-lock.json
|
||||
/frontend/resources/fonts/experiments
|
||||
/frontend/resources/public/*
|
||||
/frontend/target/
|
||||
/other/
|
||||
/scripts/
|
||||
/telemetry/
|
||||
/tmp/
|
||||
/vendor/**/target
|
||||
/vendor/svgclean/bundle*.js
|
||||
/web
|
||||
clj-profiler/
|
||||
node_modules
|
||||
/resources/public/css
|
||||
/resources/public/js
|
||||
/*-init.clj
|
||||
105
.gitpod.yml
Normal file
105
.gitpod.yml
Normal file
@@ -0,0 +1,105 @@
|
||||
image:
|
||||
file: docker/gitpod/Dockerfile
|
||||
|
||||
ports:
|
||||
# nginx
|
||||
- port: 3449
|
||||
onOpen: open-preview
|
||||
|
||||
# frontend nREPL
|
||||
- port: 3447
|
||||
onOpen: ignore
|
||||
visibility: private
|
||||
|
||||
# frontend shadow server
|
||||
- port: 3448
|
||||
onOpen: ignore
|
||||
visibility: private
|
||||
|
||||
# backend
|
||||
- port: 6060
|
||||
onOpen: ignore
|
||||
|
||||
# exporter shadow server
|
||||
- port: 9630
|
||||
onOpen: ignore
|
||||
visibility: private
|
||||
|
||||
# exporter http server
|
||||
- port: 6061
|
||||
onOpen: ignore
|
||||
|
||||
# mailhog web interface
|
||||
- port: 8025
|
||||
onOpen: ignore
|
||||
|
||||
# mailhog postfix
|
||||
- port: 1025
|
||||
onOpen: ignore
|
||||
|
||||
# postgres
|
||||
- port: 5432
|
||||
onOpen: ignore
|
||||
|
||||
# redis
|
||||
- port: 6379
|
||||
onOpen: ignore
|
||||
|
||||
# openldap
|
||||
- port: 389
|
||||
onOpen: ignore
|
||||
|
||||
tasks:
|
||||
# https://github.com/gitpod-io/gitpod/issues/666#issuecomment-534347856
|
||||
- name: gulp
|
||||
command: >
|
||||
cd $GITPOD_REPO_ROOT/frontend/;
|
||||
yarn && gp sync-done 'frontend-yarn';
|
||||
npx gulp --theme=${PENPOT_THEME} watch
|
||||
|
||||
- name: frontend shadow watch
|
||||
command: >
|
||||
cd $GITPOD_REPO_ROOT/frontend/;
|
||||
gp sync-await 'frontend-yarn';
|
||||
npx shadow-cljs watch main
|
||||
|
||||
- init: gp await-port 5432 && psql -f $GITPOD_REPO_ROOT/docker/gitpod/files/postgresql_init.sql
|
||||
name: backend
|
||||
command: >
|
||||
cd $GITPOD_REPO_ROOT/backend/;
|
||||
./scripts/start-dev
|
||||
|
||||
- name: exporter shadow watch
|
||||
command:
|
||||
cd $GITPOD_REPO_ROOT/exporter/;
|
||||
gp sync-await 'frontend-yarn';
|
||||
yarn && npx shadow-cljs watch main
|
||||
|
||||
- name: exporter web server
|
||||
command: >
|
||||
cd $GITPOD_REPO_ROOT/exporter/;
|
||||
./scripts/wait-and-start.sh
|
||||
|
||||
- name: signed terminal
|
||||
before: >
|
||||
[[ ! -z ${GNUGPG} ]] &&
|
||||
cd ~ &&
|
||||
rm -rf .gnupg &&
|
||||
echo ${GNUGPG} | base64 -d | tar --no-same-owner -xzvf -
|
||||
init: >
|
||||
[[ ! -z ${GNUGPG_KEY} ]] &&
|
||||
git config --global commit.gpgsign true &&
|
||||
git config --global user.signingkey ${GNUGPG_KEY}
|
||||
command: cd $GITPOD_REPO_ROOT
|
||||
|
||||
- name: redis
|
||||
command: redis-server
|
||||
|
||||
- before: go get github.com/mailhog/MailHog
|
||||
name: mailhog
|
||||
command: MailHog
|
||||
|
||||
- name: Nginx
|
||||
command: >
|
||||
nginx &&
|
||||
multitail /var/log/nginx/access.log -I /var/log/nginx/error.log
|
||||
43
.travis.yml
43
.travis.yml
@@ -1,7 +1,40 @@
|
||||
language: clojure
|
||||
lein: lein2
|
||||
dist: xenial
|
||||
|
||||
language: generic
|
||||
sudo: required
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- develop
|
||||
|
||||
install:
|
||||
- curl -O https://download.clojure.org/install/linux-install-1.10.1.447.sh
|
||||
- chmod +x linux-install-1.10.1.447.sh
|
||||
- sudo ./linux-install-1.10.1.447.sh
|
||||
|
||||
before_script:
|
||||
- env | sort
|
||||
|
||||
script:
|
||||
- lein with-profile +front cljsbuild once test
|
||||
- ./manage.sh build-devenv
|
||||
- ./manage.sh run-frontend-tests
|
||||
- ./manage.sh run-backend-tests
|
||||
- ./manage.sh build-images
|
||||
- ./manage.sh run
|
||||
|
||||
after_script:
|
||||
- docker images
|
||||
|
||||
notifications:
|
||||
slack:
|
||||
secure: XR/sm/0FlZmiQ3uOQ9vS45F0gx2A0U4xoW29iyWO4MMQe+FbOLS/W/3FDbocnkpFDuTJkbGYQKFXp5OM3smWGrwd4w/kZA+T8tiJjSyHxXhuHyfpAikMS2IopjxbVY+7Y92urjWzXwd00lkvAHCrcApH2TYsEOAHTNldIItzOZ1mE7zN47XqY7a+zS0eWkRoQbABwN3IuDcpqP34vRAMS7tVEBJnqcymmJcqTfqHCJZwiW5XqNoOW5X/DAsSQKdJPHFBmF+rzr5P5t8Z2kjxFcsHVFm2zJYdFEa5w159XkL3FN5By0EFy1h1VpdjRqEmhEnORHlF9A4apLLb0HqvCBiSaBbdFLgZmbIEtWNhSALtIEa0EbrIuTi5L/RDDpZ3Wz098ubb8tlbmgaNzPvg4ic783DmVjyXt+NFaAW++g1Lki3l8cAHfuk9GHSu/olilRGTYI0dpIB9dNx4W7MFRg6n3+FU9xw+vbXNouCjOZ8330b+Zu19VdfUDZigHS0iR7JvNvpY0b5m1vnC/AR8yLkCLIlxKT0lOcTfWMHn5eptjwn5tRRwGPvIK7Jj1x3FxCqNNogpUp32M9jJbiJQxk216Qn7XxD7poz8Zt9cInxdyC1zVKT8e2fYsJFLJ8+0Hos1uR8fsfqqwGgIDrBqAp0LIzKBdZ7UNqYwPqcWcmE=
|
||||
email: false
|
||||
|
||||
env:
|
||||
- NODE_VERSION=10.16.0
|
||||
|
||||
1351
CHANGES.md
Normal file
1351
CHANGES.md
Normal file
File diff suppressed because it is too large
Load Diff
189
CONTRIBUTING.md
Normal file
189
CONTRIBUTING.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# Contributing Guide #
|
||||
|
||||
Thank you for your interest in contributing to Penpot. This is a
|
||||
generic guide that details how to contribute to Penpot in a way that
|
||||
is efficient for everyone. If you want a specific documentation for
|
||||
different parts of the platform, please refer to `docs/` directory.
|
||||
|
||||
|
||||
## Reporting Bugs ##
|
||||
|
||||
We are using [GitHub Issues](https://github.com/penpot/penpot/issues)
|
||||
for our public bugs. We keep a close eye on this and try to make it
|
||||
clear when we have an internal fix in progress. Before filing a new
|
||||
task, try to make sure your problem doesn't already exist.
|
||||
|
||||
If you found a bug, please report it, as far as possible with:
|
||||
|
||||
- a detailed explanation of steps to reproduce the error
|
||||
- a browser and the browser version used
|
||||
- a dev tools console exception stack trace (if it is available)
|
||||
|
||||
If you found a bug that you consider better discuss in private (for
|
||||
example: security bugs), consider first send an email to
|
||||
`support@penpot.app`.
|
||||
|
||||
**We don't have formal bug bounty program for security reports; this
|
||||
is an open source application and your contribution will be recognized
|
||||
in the changelog.**
|
||||
|
||||
|
||||
## Pull requests ##
|
||||
|
||||
If you want propose a change or bug fix with the Pull-Request system
|
||||
firstly you should carefully read the **DCO** section and format your
|
||||
commits accordingly.
|
||||
|
||||
If you intend to fix a bug it's fine to submit a pull request right
|
||||
away but we still recommend to file an issue detailing what you're
|
||||
fixing. This is helpful in case we don't accept that specific fix but
|
||||
want to keep track of the issue.
|
||||
|
||||
If you want to implement or start working in a new feature, please
|
||||
open a **question** / **discussion** issue for it. No pull-request
|
||||
will be accepted without previous chat about the changes,
|
||||
independently if it is a new feature, already planned feature or small
|
||||
quick win.
|
||||
|
||||
If is going to be your first pull request, You can learn how from this
|
||||
free video series:
|
||||
|
||||
https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github
|
||||
|
||||
We will use the `easy fix` mark for tag for indicate issues that are
|
||||
easy for beginners.
|
||||
|
||||
|
||||
## Commit Guidelines ##
|
||||
|
||||
We have very precise rules over how our git commit messages can be formatted.
|
||||
|
||||
The commit message format is:
|
||||
|
||||
```
|
||||
<type> <subject>
|
||||
|
||||
[body]
|
||||
|
||||
[footer]
|
||||
```
|
||||
|
||||
Where type is:
|
||||
|
||||
- :bug: `:bug:` a commit that fixes a bug
|
||||
- :sparkles: `:sparkles:` a commit that an improvement
|
||||
- :tada: `:tada:` a commit with new feature
|
||||
- :recycle: `:recycle:` a commit that introduces a refactor
|
||||
- :lipstick: `:lipstick:` a commit with cosmetic changes
|
||||
- :ambulance: `:ambulance:` a commit that fixes critical bug
|
||||
- :books: `:books:` a commit that improves or adds documentation
|
||||
- :construction: `:construction:`: a wip commit
|
||||
- :boom: `:boom:` a commit with breaking changes
|
||||
- :wrench: `:wrench:` a commit for config updates
|
||||
- :zap: `:zap:` a commit with performance improvements
|
||||
- :whale: `:whale:` a commit for docker related stuff
|
||||
- :rewind: `:rewind:` a commit that reverts changes
|
||||
- :paperclip: `:paperclip:` a commit with other not relevant changes
|
||||
- :arrow_up: `:arrow_up:` a commit with dependencies updates
|
||||
|
||||
More info:
|
||||
- https://gist.github.com/parmentf/035de27d6ed1dce0b36a
|
||||
- https://gist.github.com/rxaviers/7360908
|
||||
|
||||
Each commit should have:
|
||||
|
||||
- A concise subject using imperative mood.
|
||||
- The subject should have capitalized the first letter, without period
|
||||
at the end and no larger than 65 characters.
|
||||
- A blank line between the subject line and the body.
|
||||
- An entry on the CHANGES.md file if applicable, referencing the
|
||||
github or taiga issue/user-story using the these same rules.
|
||||
|
||||
Examples of good commit 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
|
||||
|
||||
|
||||
## Code of conduct ##
|
||||
|
||||
As contributors and maintainers of this project, we pledge to respect
|
||||
all people who contribute through reporting issues, posting feature
|
||||
requests, updating documentation, submitting pull requests or patches,
|
||||
and other activities.
|
||||
|
||||
We are committed to making participation in this project a
|
||||
harassment-free experience for everyone, regardless of level of
|
||||
experience, gender, gender identity and expression, sexual
|
||||
orientation, disability, personal appearance, body size, race,
|
||||
ethnicity, age, or religion.
|
||||
|
||||
Examples of unacceptable behavior by participants include the use of
|
||||
sexual language or imagery, derogatory comments or personal attacks,
|
||||
trolling, public or private harassment, insults, or other
|
||||
unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit,
|
||||
or reject comments, commits, code, wiki edits, issues, and other
|
||||
contributions that are not aligned to this Code of Conduct. Project
|
||||
maintainers who do not follow the Code of Conduct may be removed from
|
||||
the project team.
|
||||
|
||||
This code of conduct applies both within project spaces and in public
|
||||
spaces when an individual is representing the project or its
|
||||
community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||
may be reported by opening an issue or contacting one or more of the
|
||||
project maintainers.
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version
|
||||
1.1.0, available from http://contributor-covenant.org/version/1/1/0/
|
||||
|
||||
|
||||
## Developer's Certificate of Origin (DCO) ##
|
||||
|
||||
By submitting code you are agree and can certify the below:
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
|
||||
Then, all your code patches (**documentation are excluded**) should
|
||||
contain a sign-off at the end of the patch/commit description body. It
|
||||
can be automatically added on adding `-s` parameter to `git commit`.
|
||||
|
||||
This is an example of the aspect of the line:
|
||||
|
||||
Signed-off-by: Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
Please, use your real name (sorry, no pseudonyms or anonymous
|
||||
contributions are allowed).
|
||||
373
LICENSE
Normal file
373
LICENSE
Normal file
@@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
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/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
197
README.md
197
README.md
@@ -1,122 +1,141 @@
|
||||
# UXBox #
|
||||
|
||||
[](https://travis-ci.org/uxbox/front "Travis Badge (frontend)")
|
||||
[](https://travis-ci.org/uxbox/back "Travis Badge (backend)")
|
||||
[uri_license]: https://www.mozilla.org/en-US/MPL/2.0
|
||||
[uri_license_image]: https://img.shields.io/badge/MPL-2.0-blue.svg
|
||||
|
||||
<h1 align="center">
|
||||
<br>
|
||||
<img src="https://penpot.app/images/readme/readme-logo.jpg" 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>
|
||||
<a href="https://gitter.im/penpot/community" rel="nofollow"><img src="https://camo.githubusercontent.com/5b0aecb33434f82a7b158eab7247544235ada0cf7eeb9ce8e52562dd67f614b7/68747470733a2f2f6261646765732e6769747465722e696d2f736572656e6f2d78797a2f636f6d6d756e6974792e737667" alt="Gitter" data-canonical-src="https://badges.gitter.im/sereno-xyz/community.svg" style="max-width:100%;"></a>
|
||||
<a href="https://tree.taiga.io/project/penpot/" title="Managed with Taiga.io" rel="nofollow"><img src="https://camo.githubusercontent.com/4a1d1112f0272e3393b1e8da312ff4435418e9e2eb4c0964881e3680f90a653c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6d616e61676564253230776974682d54414947412e696f2d3730396631342e737667" alt="Managed with Taiga.io" data-canonical-src="https://img.shields.io/badge/managed%20with-TAIGA.io-709f14.svg" style="max-width:100%;"></a>
|
||||
<a href="https://gitpod.io/#https://github.com/penpot/penpot" rel="nofollow"><img src="https://camo.githubusercontent.com/daadb4894128d1e19b72d80236f5959f1f2b47f9fe081373f3246131f0189f6c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f476974706f642d72656164792d2d746f2d2d636f64652d626c75653f6c6f676f3d676974706f64" alt="Gitpod ready-to-code" data-canonical-src="https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod" style="max-width:100%;"></a></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://penpot.app/"><b>Website</b></a> •
|
||||
<a href="https://help.penpot.app/technical-guide/getting-started/"><b>Getting Started</b></a> •
|
||||
<a href="https://help.penpot.app/user-guide/"><b>User Guide</b></a> •
|
||||
<a href="https://help.penpot.app/user-guide/introduction/info/"><b>Tutorials & Info</b></a> •
|
||||
<a href="https://community.penpot.app/"><b>Community</b></a> •
|
||||
<a href="https://twitter.com/penpotapp"><b>Twitter</b></a> •
|
||||
<a href="https://instagram.com/penpot.app"><b>Instagram</b></a> •
|
||||
<a href="https://fosstodon.org/@penpot/"><b>Mastodon</b></a> •
|
||||
<a href="https://www.youtube.com/channel/UCAqS8G72uv9P5HG1IfgnQ9g"><b>Youtube</b></a>
|
||||
</p>
|
||||
|
||||

|
||||
|
||||
Penpot is the first **Open Source** design and prototyping platform meant for cross-domain teams. Non dependent on operating systems, Penpot is web based and works with open standards (SVG). Penpot invites designers all over the world to fall in love with open source while getting developers excited about the design process in return.
|
||||
|
||||
## Table of contents ##
|
||||
|
||||
- [Why Penpot](#why-penpot)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Community](#community)
|
||||
- [Resources](#resources)
|
||||
- [License](#license)
|
||||
|
||||
## Why Penpot ##
|
||||
|
||||
Penpot makes design and prototyping accessible to every team in the world.
|
||||
|
||||
### For cross-domain teams ###
|
||||
We have a clear focus on design and code teams and our capabilities reflect exactly that. The less hand-off mindset, the more fun for everyone.
|
||||
|
||||
### Multiplatform ###
|
||||
Being web based, Penpot is not dependent on operating systems or local installations, you will only need to run a modern browser.
|
||||
|
||||
### Open Standards ###
|
||||
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">
|
||||
</p>
|
||||
|
||||
|
||||
## Development ##
|
||||
## Getting started ##
|
||||
|
||||
### Frontend ###
|
||||
### Install with Elestio ###
|
||||
[Elestio](https://elest.io/) offers a fully managed service for on-premise instances of a selection of open-source software! This means you can deploy a dedicated instance of Penpot in just 3 minutes with no technical knowledge needed.
|
||||
|
||||
Grab the code and run:
|
||||
You don’t need to worry about DNS configuration, SMTP, backups, SSL certificates, OS & Penpot upgrades, and much more.
|
||||
|
||||
```
|
||||
$ lein with-profile +front figwheel
|
||||
```
|
||||
[Get started with Elestio.](https://help.penpot.app/technical-guide/getting-started/#install-with-elestio)
|
||||
|
||||
This will compile ClojureScript whenever you make changes and serve the application in [localhost](http://localhost:3449/).
|
||||
Open the page.
|
||||
### Install with Docker ###
|
||||
|
||||
#### ClojureScript browser-connected REPL ####
|
||||
You can also get started with Penpot locally or self-host it with **docker** and **docker-compose**.
|
||||
|
||||
The aforementioned command also starts a [nrepl](https://github.com/clojure/tools.nrepl) (network REPL) in the port 7888.
|
||||
Here’s a step-by-step guide on [getting started with Docker.](https://help.penpot.app/technical-guide/getting-started/#install-with-docker)
|
||||
|
||||
You can connect to it from a shell using the following command:
|
||||
### Penpot cloud app ###
|
||||
|
||||
```
|
||||
$ lein repl :connect 7888
|
||||
```
|
||||
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.
|
||||
|
||||
In Emacs you can use [cider's](https://github.com/clojure-emacs/cider) `M-x cider-connect` command and tell it that nREPL is
|
||||
running on `localhost:7888` to connect.
|
||||
<p align="center">
|
||||
<img src="https://help.penpot.app/img/home-techguide.png" alt="Getting started">
|
||||
</p>
|
||||
|
||||
After connecting to nREPL, run the following Clojure code in it:
|
||||
## Community ##
|
||||
|
||||
```
|
||||
user> (use 'figwheel-sidecar.repl-api)
|
||||
user> (cljs-repl)
|
||||
```
|
||||
We love the open source software community. Contributing is our passion and if it’s yours too, [participate](https://community.penpot.app/) and [improve](https://community.penpot.app/c/help-us-improve-penpot/7) Penpot. All your ideas and code are welcome!
|
||||
|
||||
After that, a figwheel message will appear and the prompt will change to `cljs.user>`. We can now evaluate ClojureScript in the
|
||||
browser from the REPL.
|
||||
If you need help or have any questions; if you’d like to share your experience using Penpot or get inspired; if you’d rather meet our community of developers and designers, [join our Community](https://community.penpot.app/)!
|
||||
|
||||
#### Static resources generation ####
|
||||
You will find the following categories:
|
||||
- [Ask the Community](https://community.penpot.app/c/ask-for-help-using-penpot/6)
|
||||
- [Troubleshooting](https://community.penpot.app/c/technical/8)
|
||||
- [Help us Improve Penpot](https://community.penpot.app/c/help-us-improve-penpot/7)
|
||||
- [#MadeWithPenpot](https://community.penpot.app/c/madewithpenpot/9)
|
||||
- [Events and Announcements](https://community.penpot.app/c/announcements/5)
|
||||
- [Inside Penpot](https://community.penpot.app/c/inside-penpot/21)
|
||||
- [Penpot in your language](https://community.penpot.app/c/penpot-in-your-language/12)
|
||||
|
||||
The project's static resources are processed using [gulp](http://gulpjs.com/). First of all, install the npm dependencies running:
|
||||
<p align="center">
|
||||
<img src="https://penpot.app/images/cross-teams.webp" alt="Community">
|
||||
</p>
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
## Contributing ##
|
||||
|
||||
To start watching the files and process them with each change, run:
|
||||
Every sort of contribution will be very helpful to enhance Penpot. How you’ll participate? All your ideas, designs and code are welcome:
|
||||
|
||||
```
|
||||
npm run watch
|
||||
```
|
||||
- Invite your [team to join](https://design.penpot.app/#/auth/register)
|
||||
- Star this repo and follow us on Social Media: [Twitter](https://twitter.com/penpotapp), [Instagram](https://instagram.com/penpot.app), [Youtube](https://www.youtube.com/c/Penpot) or [Mastodon](https://fosstodon.org/@penpot/).
|
||||
- Participate in the [Community](https://community.penpot.app/) asking and answering questions, reacting to others’ articles or opening your own conversations.
|
||||
- Report bugs with our easy [guide for bugs hunting](https://help.penpot.app/contributing-guide/reporting-bugs/) or [GitHub issues](https://github.com/penpot/penpot/issues)
|
||||
- Create and [share Libraries & templates](https://penpot.app/libraries-templates.html) that will be helpful for the community
|
||||
- Become a [translator](https://help.penpot.app/contributing-guide/translations)
|
||||
- Give feedback: [Mail us](mailto:support@penpot.app)
|
||||
|
||||
To process the resources just once, run:
|
||||
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/).
|
||||
|
||||
```
|
||||
npm run dist
|
||||
```
|
||||
<p align="center">
|
||||
<img src="https://help.penpot.app/img/home-contributing.png" alt="Contributing">
|
||||
</p>
|
||||
|
||||
#### Testing ####
|
||||
## Resources ##
|
||||
|
||||
For running the tests from a shell, run the following command:
|
||||
You can ask and answer questions, have open-ended conversations, and follow along on decisions affecting the project.
|
||||
|
||||
```
|
||||
$ lein cljsbuild once test
|
||||
```
|
||||
💾 [Documentation](https://help.penpot.app/technical-guide/)
|
||||
|
||||
If you want to run the tests from a ClojureScript REPL, you can do it like so (given that you want to run the tests contained in the `uxbox.core-test` namespace):
|
||||
🚀 [Getting Started](https://help.penpot.app/technical-guide/getting-started/)
|
||||
|
||||
```
|
||||
cljs.user> (require '[cljs.test :as t])
|
||||
cljs.user> (t/run-tests 'uxbox.core-test)
|
||||
```
|
||||
✏️ [Tutorials](https://www.youtube.com/playlist?list=PLgcCPfOv5v54WpXhHmNO7T-YC7AE-SRsr)
|
||||
|
||||
Note that the test output will appear in the browser and in the shell where you launched the `lein fighweel` command.
|
||||
🏘️ [Architecture](https://help.penpot.app/technical-guide/architecture/)
|
||||
|
||||
|
||||
#### Transformation from HTML to hiccup ####
|
||||
|
||||
For transforming the generated HTMLs to hiccup form, execute the following command:
|
||||
|
||||
```
|
||||
$ lein with-profile +front hicv 2clj resources/public/templates/*.html
|
||||
```
|
||||
|
||||
The `.clj` files in the `hicv` directory will contain the hiccup versions of the HTML templates.
|
||||
|
||||
### Backend ###
|
||||
|
||||
#### REPL ####
|
||||
|
||||
You can start a Clojure REPL with the following command:
|
||||
|
||||
```
|
||||
$ lein repl
|
||||
```
|
||||
|
||||
In Emacs you can use [cider's](https://github.com/clojure-emacs/cider) `M-x cider-jack-in` command in the proyect directory
|
||||
to have a REPL in your editor.
|
||||
|
||||
#### Testing ####
|
||||
|
||||
For running the tests from a shell, run the following command:
|
||||
|
||||
```
|
||||
$ lein test
|
||||
```
|
||||
|
||||
If you want to run the tests from a Clojure REPL, you can do it like so (given that you want to run the tests contained in the `uxbox.core-test` namespace):
|
||||
|
||||
```
|
||||
user> (require '[clojure.test :as t])
|
||||
user> (t/run-tests 'uxbox.core-test)
|
||||
```
|
||||
📚 [Dev Diaries](https://penpot.app/dev-diaries.html)
|
||||
|
||||
|
||||
## License ##
|
||||
|
||||
TODO
|
||||
```
|
||||
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
|
||||
```
|
||||
Penpot is a Kaleidos’ [open source project](https://kaleidos.net/products)
|
||||
|
||||
5
SECURITY.md
Normal file
5
SECURITY.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report security issues to `support@penpot.app`
|
||||
97
THANKYOU.md
Normal file
97
THANKYOU.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# THANK YOU
|
||||
|
||||
We want to thank to the amazing people that help us! Thank you! You're the best!
|
||||
|
||||
## Security
|
||||
* Husnain Iqbal (CEO OF ALPHA INFERNO PVT LTD)
|
||||
* [Shiraz Ali Khan](https://www.linkedin.com/in/shiraz-ali-khan-1ba508180/)
|
||||
|
||||
## Internationalization
|
||||
* [00ff88](https://hosted.weblate.org/user/00ff88)
|
||||
* [AhmadHB](https://hosted.weblate.org/user/AhmadHB)
|
||||
* [Aimee](https://hosted.weblate.org/user/Aimee)
|
||||
* [alejandro.alonso](alejandro.https://hosted.weblate.org/user/alonso)
|
||||
* [alexpawlak](https://hosted.weblate.org/user/alexpawlak)
|
||||
* [allytiago](https://hosted.weblate.org/user/allytiago)
|
||||
* [alonso.torres](alonso.https://hosted.weblate.org/user/torres)
|
||||
* [andres.moya](andres.https://hosted.weblate.org/user/moya)
|
||||
* [antoniofsm](https://hosted.weblate.org/user/antoniofsm)
|
||||
* [ascarida](https://hosted.weblate.org/user/ascarida)
|
||||
* [Bechii](https://hosted.weblate.org/user/Bechii)
|
||||
* [Beeby](https://hosted.weblate.org/user/Beeby)
|
||||
* [bingling-sama](bingling-https://hosted.weblate.org/user/sama)
|
||||
* [devadarta](https://hosted.weblate.org/user/devadarta)
|
||||
* [diacritica](https://hosted.weblate.org/user/diacritica)
|
||||
* [dundzys.vincas](dundzys.https://hosted.weblate.org/user/vincas)
|
||||
* [Eranot](https://hosted.weblate.org/user/Eranot)
|
||||
* [erral](https://hosted.weblate.org/user/erral)
|
||||
* [ersen](https://hosted.weblate.org/user/ersen)
|
||||
* [filipepessanha](https://hosted.weblate.org/user/filipepessanha)
|
||||
* [fortx](https://hosted.weblate.org/user/fortx)
|
||||
* [foxbit](https://hosted.weblate.org/user/foxbit)
|
||||
* [georgelemon](https://hosted.weblate.org/user/georgelemon)
|
||||
* [girafic](https://hosted.weblate.org/user/girafic)
|
||||
* [gizemb](https://hosted.weblate.org/user/gizemb)
|
||||
* [greench](https://hosted.weblate.org/user/greench)
|
||||
* [guidimas](https://hosted.weblate.org/user/guidimas)
|
||||
* [hfigueira_1](https://hosted.weblate.org/user/hfigueira_1)
|
||||
* [hifiaz](https://hosted.weblate.org/user/hifiaz)
|
||||
* [httpsterio](https://hosted.weblate.org/user/httpsterio)
|
||||
* [humteus](https://hosted.weblate.org/user/humteus)
|
||||
* [iblueer](https://hosted.weblate.org/user/iblueer)
|
||||
* [insan](https://hosted.weblate.org/user/insan)
|
||||
* [Iphi](https://hosted.weblate.org/user/Iphi)
|
||||
* [iWangJiaxiang](https://hosted.weblate.org/user/iWangJiaxiang)
|
||||
* [jancborchardt](https://hosted.weblate.org/user/jancborchardt)
|
||||
* [jazz](https://hosted.weblate.org/user/jazz)
|
||||
* [johnterroa](https://hosted.weblate.org/user/johnterroa)
|
||||
* [jponsa](https://hosted.weblate.org/user/jponsa)
|
||||
* [kapler](https://hosted.weblate.org/user/kapler)
|
||||
* [kingu](https://hosted.weblate.org/user/kingu)
|
||||
* [KnahkAmath](https://hosted.weblate.org/user/KnahkAmath)
|
||||
* [laminne](https://hosted.weblate.org/user/laminne)
|
||||
* [lenildoleite](https://hosted.weblate.org/user/lenildoleite)
|
||||
* [liimee](https://hosted.weblate.org/user/liimee)
|
||||
* [lixeix](https://hosted.weblate.org/user/lixeix)
|
||||
* [locness3](https://hosted.weblate.org/user/locness3)
|
||||
* [maiwann](https://hosted.weblate.org/user/maiwann)
|
||||
* [MidooDj](https://hosted.weblate.org/user/MidooDj)
|
||||
* [Mohamed_amine_gdoura](https://hosted.weblate.org/user/Mohamed_amine_gdoura)
|
||||
* [myfunnyandy](https://hosted.weblate.org/user/myfunnyandy)
|
||||
* [NampoinaRal](https://hosted.weblate.org/user/NampoinaRal)
|
||||
* [nautilusx](https://hosted.weblate.org/user/nautilusx)
|
||||
* [niwinz](https://hosted.weblate.org/user/niwinz)
|
||||
* [pablo.alba](pablo.https://hosted.weblate.org/user/alba)
|
||||
* [PhilippeAccorsi](https://hosted.weblate.org/user/PhilippeAccorsi)
|
||||
* [rnarius](https://hosted.weblate.org/user/rnarius)
|
||||
* [rnd](https://hosted.weblate.org/user/rnd)
|
||||
* [RuanAragao](https://hosted.weblate.org/user/RuanAragao)
|
||||
* [ruben](https://hosted.weblate.org/user/ruben)
|
||||
* [semonxue](https://hosted.weblate.org/user/semonxue)
|
||||
* [shahab](https://hosted.weblate.org/user/shahab)
|
||||
* [shuaib85](https://hosted.weblate.org/user/shuaib85)
|
||||
* [SiderealArt](https://hosted.weblate.org/user/SiderealArt)
|
||||
* [swapnil.cx](swapnil.https://hosted.weblate.org/user/cx)
|
||||
* [syuza](https://hosted.weblate.org/user/syuza)
|
||||
* [th3ph4nt0m](https://hosted.weblate.org/user/th3ph4nt0m)
|
||||
* [tiwb](https://hosted.weblate.org/user/tiwb)
|
||||
* [tommi](https://hosted.weblate.org/user/tommi)
|
||||
* [val](https://hosted.weblate.org/user/val)
|
||||
* [vikt](https://hosted.weblate.org/user/vikt)
|
||||
* [VinLin](https://hosted.weblate.org/user/VinLin)
|
||||
* [vintprox](https://hosted.weblate.org/user/vintprox)
|
||||
* [Voxybuns](https://hosted.weblate.org/user/Voxybuns)
|
||||
* [winie](https://hosted.weblate.org/user/winie)
|
||||
* [Yaron](https://hosted.weblate.org/user/Yaron)
|
||||
* [yrd](https://hosted.weblate.org/user/yrd)
|
||||
* [YukiYuigishi](https://hosted.weblate.org/user/YukiYuigishi)
|
||||
* [zcraber](https://hosted.weblate.org/user/zcraber)
|
||||
|
||||
## Libraries & templates
|
||||
* systxema
|
||||
* plumilla
|
||||
* victor crespo
|
||||
* xtech
|
||||
* candidexmedia
|
||||
* merih güz
|
||||
* klarr agency
|
||||
36
backend/build.clj
Normal file
36
backend/build.clj
Normal file
@@ -0,0 +1,36 @@
|
||||
(ns build
|
||||
(:refer-clojure :exclude [compile])
|
||||
(:require
|
||||
[clojure.tools.build.api :as b]
|
||||
[clojure.java.io]))
|
||||
|
||||
(def class-dir "target/classes")
|
||||
(def basis (b/create-basis {:project "deps.edn"}))
|
||||
(def jar-file "target/penpot.jar")
|
||||
|
||||
(defn clean [_]
|
||||
(b/delete {:path "target"}))
|
||||
|
||||
(defn jar [_]
|
||||
(b/copy-dir
|
||||
{:src-dirs ["src" "resources"]
|
||||
:target-dir class-dir})
|
||||
|
||||
(b/compile-clj
|
||||
{:basis basis
|
||||
:src-dirs ["src"]
|
||||
:class-dir class-dir})
|
||||
|
||||
(b/uber
|
||||
{:class-dir class-dir
|
||||
:uber-file jar-file
|
||||
:main 'clojure.main
|
||||
:exclude [#"goog.*" #"^javasist.*"]
|
||||
:basis basis}))
|
||||
|
||||
(defn compile [_]
|
||||
(b/javac
|
||||
{:src-dirs ["dev/java"]
|
||||
:class-dir class-dir
|
||||
:basis basis
|
||||
:javac-opts ["-source" "17" "-target" "17"]}))
|
||||
100
backend/deps.edn
Normal file
100
backend/deps.edn
Normal file
@@ -0,0 +1,100 @@
|
||||
{:deps
|
||||
{penpot/common {:local/root "../common"}
|
||||
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"}
|
||||
|
||||
io.prometheus/simpleclient {:mvn/version "0.16.0"}
|
||||
io.prometheus/simpleclient_hotspot {:mvn/version "0.16.0"}
|
||||
io.prometheus/simpleclient_jetty
|
||||
{:mvn/version "0.16.0"
|
||||
:exclusions [org.eclipse.jetty/jetty-server
|
||||
org.eclipse.jetty/jetty-servlet]}
|
||||
|
||||
io.prometheus/simpleclient_httpserver {:mvn/version "0.16.0"}
|
||||
|
||||
io.lettuce/lettuce-core {:mvn/version "6.2.2.RELEASE"}
|
||||
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
|
||||
|
||||
funcool/yetti
|
||||
{: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"}
|
||||
com.zaxxer/HikariCP {:mvn/version "5.0.1"}
|
||||
|
||||
io.whitfin/siphash {:mvn/version "2.0.0"}
|
||||
|
||||
buddy/buddy-hashers {:mvn/version "1.8.158"}
|
||||
buddy/buddy-sign {:mvn/version "3.4.333"}
|
||||
|
||||
com.github.ben-manes.caffeine/caffeine {:mvn/version "3.1.2"}
|
||||
|
||||
org.jsoup/jsoup {:mvn/version "1.15.3"}
|
||||
org.im4java/im4java
|
||||
{:git/tag "1.4.0-penpot-2"
|
||||
:git/sha "e2b3e16"
|
||||
:git/url "https://github.com/penpot/im4java"}
|
||||
|
||||
org.lz4/lz4-java {:mvn/version "1.8.0"}
|
||||
|
||||
org.clojars.pntblnk/clj-ldap {:mvn/version "0.0.17"}
|
||||
integrant/integrant {:mvn/version "0.8.0"}
|
||||
|
||||
dawran6/emoji {:mvn/version "0.1.5"}
|
||||
markdown-clj/markdown-clj {:mvn/version "1.11.4"}
|
||||
|
||||
;; Pretty Print specs
|
||||
pretty-spec/pretty-spec {:mvn/version "0.1.4"}
|
||||
software.amazon.awssdk/s3 {:mvn/version "2.19.8"}
|
||||
}
|
||||
|
||||
:paths ["src" "resources" "target/classes"]
|
||||
:aliases
|
||||
{:dev
|
||||
{:extra-deps
|
||||
{com.bhauman/rebel-readline {:mvn/version "RELEASE"}
|
||||
org.clojure/tools.namespace {:mvn/version "RELEASE"}
|
||||
clojure-humanize/clojure-humanize {:mvn/version "0.2.2"}
|
||||
org.clojure/data.csv {:mvn/version "RELEASE"}
|
||||
com.clojure-goes-fast/clj-async-profiler {:mvn/version "RELEASE"}
|
||||
mockery/mockery {:mvn/version "RELEASE"}}
|
||||
:extra-paths ["test" "dev"]}
|
||||
|
||||
|
||||
:build
|
||||
{:extra-deps
|
||||
{io.github.clojure/tools.build {:git/tag "v0.9.0" :git/sha "8c93e0c"}}
|
||||
:ns-default build}
|
||||
|
||||
:test
|
||||
{:extra-paths ["test"]
|
||||
:extra-deps
|
||||
{io.github.cognitect-labs/test-runner
|
||||
{:git/tag "v0.5.1" :git/sha "dfb30dd"}}
|
||||
:main-opts ["-m" "cognitect.test-runner"]
|
||||
:exec-fn cognitect.test-runner.api/test}
|
||||
|
||||
:outdated
|
||||
{:extra-deps {com.github.liquidz/antq {:mvn/version "RELEASE"}}
|
||||
:main-opts ["-m" "antq.core"]}
|
||||
|
||||
:jmx-remote
|
||||
{:jvm-opts ["-Dcom.sun.management.jmxremote"
|
||||
"-Dcom.sun.management.jmxremote.port=9090"
|
||||
"-Dcom.sun.management.jmxremote.rmi.port=9090"
|
||||
"-Dcom.sun.management.jmxremote.local.only=false"
|
||||
"-Dcom.sun.management.jmxremote.authenticate=false"
|
||||
"-Dcom.sun.management.jmxremote.ssl=false"
|
||||
"-Djava.rmi.server.hostname=localhost"]}
|
||||
|
||||
}}
|
||||
114
backend/dev/script-fix-sobjects.clj
Normal file
114
backend/dev/script-fix-sobjects.clj
Normal file
@@ -0,0 +1,114 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
;; This is an example on how it can be executed:
|
||||
;; clojure -Scp $(cat classpath) -M dev/script-fix-sobjects.clj
|
||||
|
||||
(require
|
||||
'[app.common.logging :as l]
|
||||
'[app.common.data :as d]
|
||||
'[app.common.pprint]
|
||||
'[app.db :as db]
|
||||
'[app.storage :as sto]
|
||||
'[app.storage.impl :as impl]
|
||||
'[app.util.time :as dt]
|
||||
'[integrant.core :as ig])
|
||||
|
||||
;; --- HELPERS
|
||||
|
||||
(l/info :hint "initializing script" :args *command-line-args*)
|
||||
|
||||
(def noop? (some #(= % "noop") *command-line-args*))
|
||||
(def chunk-size 10)
|
||||
|
||||
(def sql:retrieve-sobjects-chunk
|
||||
"SELECT * FROM storage_object
|
||||
WHERE created_at < ? AND deleted_at is NULL
|
||||
ORDER BY created_at desc LIMIT ?")
|
||||
|
||||
(defn get-chunk
|
||||
[conn cursor]
|
||||
(let [rows (db/exec! conn [sql:retrieve-sobjects-chunk cursor chunk-size])]
|
||||
[(some->> rows peek :created-at) (seq rows)]))
|
||||
|
||||
(defn get-candidates
|
||||
[conn]
|
||||
(->> (d/iteration (partial get-chunk conn)
|
||||
:vf second
|
||||
:kf first
|
||||
:initk (dt/now))
|
||||
(sequence cat)))
|
||||
|
||||
(def modules
|
||||
[:app.db/pool
|
||||
:app.storage/storage
|
||||
[:app.main/default :app.worker/executor]
|
||||
[:app.main/assets :app.storage.s3/backend]
|
||||
[:app.main/assets :app.storage.fs/backend]])
|
||||
|
||||
(def system
|
||||
(let [config (select-keys app.main/system-config modules)
|
||||
config (-> config
|
||||
(assoc :app.migrations/all {})
|
||||
(assoc :app.metrics/metrics nil))]
|
||||
(ig/load-namespaces config)
|
||||
(-> config ig/prep ig/init)))
|
||||
|
||||
(defn update-fn
|
||||
[{:keys [conn] :as storage} {:keys [id backend] :as row}]
|
||||
(cond
|
||||
(= backend "s3")
|
||||
(do
|
||||
(l/info :hint "rename storage object backend"
|
||||
:id id
|
||||
:from-backend backend
|
||||
:to-backend :assets-s3)
|
||||
(assoc row :backend "assets-s3"))
|
||||
|
||||
(= backend "assets-s3")
|
||||
(do
|
||||
(l/info :hint "ignoring storage object" :id id :backend backend)
|
||||
nil)
|
||||
|
||||
(or (= backend "fs")
|
||||
(= backend "assets-fs"))
|
||||
(let [sobj (sto/row->storage-object row)
|
||||
path (-> (sto/get-object-path storage sobj) deref)]
|
||||
(l/info :hint "change storage object backend"
|
||||
:id id
|
||||
:from-backend backend
|
||||
:to-backend :assets-s3)
|
||||
(when-not noop?
|
||||
(-> (impl/resolve-backend storage :assets-s3)
|
||||
(impl/put-object sobj (sto/content path))
|
||||
(deref)))
|
||||
(assoc row :backend "assets-s3"))
|
||||
|
||||
:else
|
||||
(throw (IllegalArgumentException. "unexpected backend found"))))
|
||||
|
||||
(try
|
||||
(db/with-atomic [conn (:app.db/pool system)]
|
||||
(let [storage (:app.storage/storage system)
|
||||
storage (assoc storage :conn conn)]
|
||||
(loop [items (get-candidates conn)]
|
||||
(when-let [item (first items)]
|
||||
(when-let [{:keys [id] :as row} (update-fn storage item)]
|
||||
(db/update! conn :storage-object (dissoc row :id) {:id (:id item)}))
|
||||
(recur (rest items))))
|
||||
(when noop?
|
||||
(throw (ex-info "explicit rollback" {})))))
|
||||
|
||||
(catch Throwable cause
|
||||
(cond
|
||||
(= "explicit rollback" (ex-message cause))
|
||||
(l/warn :hint "transaction aborted")
|
||||
|
||||
:else
|
||||
(l/error :hint "unexpected exception" :cause cause))))
|
||||
|
||||
(ig/halt! system)
|
||||
(System/exit 0)
|
||||
132
backend/dev/user.clj
Normal file
132
backend/dev/user.clj
Normal file
@@ -0,0 +1,132 @@
|
||||
;; 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 user
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.logging :as l]
|
||||
[app.common.perf :as perf]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.spec :as us]
|
||||
[app.common.transit :as t]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cfg]
|
||||
[app.main :as main]
|
||||
[app.srepl.helpers]
|
||||
[app.srepl.main :as srepl]
|
||||
[app.util.blob :as blob]
|
||||
[app.util.fressian :as fres]
|
||||
[app.util.json :as json]
|
||||
[app.util.time :as dt]
|
||||
[clj-async-profiler.core :as prof]
|
||||
[clojure.contrib.humanize :as hum]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.pprint :refer [pprint print-table]]
|
||||
[clojure.repl :refer :all]
|
||||
[clojure.spec.alpha :as s]
|
||||
[clojure.stacktrace :as trace]
|
||||
[clojure.test :as test]
|
||||
[clojure.test.check.generators :as gen]
|
||||
[clojure.tools.namespace.repl :as repl]
|
||||
[clojure.walk :refer [macroexpand-all]]
|
||||
[criterium.core :as crit]
|
||||
[cuerdas.core :as str]
|
||||
[datoteka.core]
|
||||
[integrant.core :as ig]))
|
||||
|
||||
(repl/disable-reload! (find-ns 'integrant.core))
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
(defonce system nil)
|
||||
|
||||
;; --- Benchmarking Tools
|
||||
|
||||
(defmacro run-quick-bench
|
||||
[& exprs]
|
||||
`(crit/with-progress-reporting (crit/quick-bench (do ~@exprs) :verbose)))
|
||||
|
||||
(defmacro run-quick-bench'
|
||||
[& exprs]
|
||||
`(crit/quick-bench (do ~@exprs)))
|
||||
|
||||
(defmacro run-bench
|
||||
[& exprs]
|
||||
`(crit/with-progress-reporting (crit/bench (do ~@exprs) :verbose)))
|
||||
|
||||
(defmacro run-bench'
|
||||
[& exprs]
|
||||
`(crit/bench (do ~@exprs)))
|
||||
|
||||
;; --- Development Stuff
|
||||
|
||||
(defn- run-tests
|
||||
([] (run-tests #"^backend-tests.*-test$"))
|
||||
([o]
|
||||
(repl/refresh)
|
||||
(cond
|
||||
(instance? java.util.regex.Pattern o)
|
||||
(test/run-all-tests o)
|
||||
|
||||
(symbol? o)
|
||||
(if-let [sns (namespace o)]
|
||||
(do (require (symbol sns))
|
||||
(test/test-vars [(resolve o)]))
|
||||
(test/test-ns o)))))
|
||||
|
||||
(defn- start
|
||||
[]
|
||||
(try
|
||||
(alter-var-root #'system (fn [sys]
|
||||
(when sys (ig/halt! sys))
|
||||
(-> (merge main/system-config main/worker-config)
|
||||
(ig/prep)
|
||||
(ig/init))))
|
||||
:started
|
||||
(catch Throwable cause
|
||||
(ex/print-throwable cause))))
|
||||
|
||||
(defn- stop
|
||||
[]
|
||||
(alter-var-root #'system (fn [sys]
|
||||
(when sys (ig/halt! sys))
|
||||
nil))
|
||||
:stopped)
|
||||
|
||||
(defn restart
|
||||
[]
|
||||
(stop)
|
||||
(repl/refresh :after 'user/start))
|
||||
|
||||
(defn restart-all
|
||||
[]
|
||||
(stop)
|
||||
(repl/refresh-all :after 'user/start))
|
||||
|
||||
(defn compression-bench
|
||||
[data]
|
||||
(let [humanize (fn [v] (hum/filesize v :binary true :format " %.4f "))
|
||||
v1 (time (humanize (alength (blob/encode data {:version 1}))))
|
||||
v3 (time (humanize (alength (blob/encode data {:version 3}))))
|
||||
v4 (time (humanize (alength (blob/encode data {:version 4}))))
|
||||
v5 (time (humanize (alength (blob/encode data {:version 5}))))
|
||||
v6 (time (humanize (alength (blob/encode data {:version 6}))))
|
||||
]
|
||||
(print-table
|
||||
[{
|
||||
:v1 v1
|
||||
:v3 v3
|
||||
:v4 v4
|
||||
:v5 v5
|
||||
:v6 v6
|
||||
}])))
|
||||
|
||||
(defonce debug-tap
|
||||
(do
|
||||
(add-tap #(locking debug-tap
|
||||
(prn "tap debug:" %)))
|
||||
1))
|
||||
18
backend/package.json
Normal file
18
backend/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "uxbox-back",
|
||||
"version": "0.1.0",
|
||||
"description": "The Open-Source prototyping tool",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build-emails": "./scripts/build-email-templates.sh"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/uxbox/uxbox.git"
|
||||
},
|
||||
"author": "Uxbox",
|
||||
"license": "SEE LICENSE IN <LICENSE>",
|
||||
"devDependencies": {
|
||||
"mjml": "^4.6.3"
|
||||
}
|
||||
}
|
||||
66
backend/resources/app/emails-mjml/change-email/en.mjml
Normal file
66
backend/resources/app/emails-mjml/change-email/en.mjml
Normal file
@@ -0,0 +1,66 @@
|
||||
<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 <3 and Open Source
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
</mg-body>
|
||||
</mjml>
|
||||
59
backend/resources/app/emails-mjml/invite-to-team/en.mjml
Normal file
59
backend/resources/app/emails-mjml/invite-to-team/en.mjml
Normal file
@@ -0,0 +1,59 @@
|
||||
<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 <3 and Open Source
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
</mg-body>
|
||||
</mjml>
|
||||
68
backend/resources/app/emails-mjml/password-recovery/en.mjml
Normal file
68
backend/resources/app/emails-mjml/password-recovery/en.mjml
Normal file
@@ -0,0 +1,68 @@
|
||||
<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 <3 and Open Source
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
</mg-body>
|
||||
</mjml>
|
||||
65
backend/resources/app/emails-mjml/register/en.mjml
Normal file
65
backend/resources/app/emails-mjml/register/en.mjml
Normal file
@@ -0,0 +1,65 @@
|
||||
<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 <3 and Open Source
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
</mg-body>
|
||||
</mjml>
|
||||
478
backend/resources/app/emails/change-email/en.html
Normal file
478
backend/resources/app/emails/change-email/en.html
Normal file
@@ -0,0 +1,478 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
|
||||
<head>
|
||||
<title>
|
||||
</title>
|
||||
<!--[if !mso]><!-- -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<!--<![endif]-->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type="text/css">
|
||||
#outlook a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
table,
|
||||
td {
|
||||
border-collapse: collapse;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin: 13px 0;
|
||||
}
|
||||
</style>
|
||||
<!--[if mso]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<!--[if lte mso 11]>
|
||||
<style type="text/css">
|
||||
.mj-outlook-group-fix { width:100% !important; }
|
||||
</style>
|
||||
<![endif]-->
|
||||
<!--[if !mso]><!-->
|
||||
<link href="https://fonts.googleapis.com/css?family=Source%20Sans%20Pro" rel="stylesheet" type="text/css">
|
||||
<style type="text/css">
|
||||
@import url(https://fonts.googleapis.com/css?family=Source%20Sans%20Pro);
|
||||
</style>
|
||||
<!--<![endif]-->
|
||||
<style type="text/css">
|
||||
@media only screen and (min-width:480px) {
|
||||
.mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.mj-column-px-425 {
|
||||
width: 425px !important;
|
||||
max-width: 425px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
@media only screen and (max-width:480px) {
|
||||
table.mj-full-width-mobile {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
td.mj-full-width-mobile {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="background-color:#E5E5E5;">
|
||||
<div style="background-color:#E5E5E5;">
|
||||
<!--[if mso | IE]>
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:16px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:97px;">
|
||||
<img height="32" src="{{ public-uri }}/images/email/uxbox-title.png" style="border:0;display:block;outline:none;text-decoration:none;height:32px;width:100%;font-size:13px;" width="97" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:24px;font-weight:600;line-height:150%;text-align:left;color:#000000;">Hello {{name}}!</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">We received a request to change your current email to {{ pending-email }}.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">Click to the link below to confirm the change:</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" vertical-align="middle" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
|
||||
<tr>
|
||||
<td align="center" bgcolor="#31EFB8" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#31EFB8;" valign="middle">
|
||||
<a href="{{ public-uri }}/#/auth/verify-token?token={{token}}" style="display:inline-block;background:#31EFB8;color:#1F1F1F;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;" target="_blank"> Confirm email change </a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">If you received this email by mistake, please consider changing your password for security reasons.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">Enjoy!</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">The Penpot team.</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-px-425 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<!--[if mso | IE]>
|
||||
<table
|
||||
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;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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://penpot.app/" target="_blank">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-uxbox.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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://twitter.com/penpotapp" target="_blank">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-twitter.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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://github.com/penpot/" target="_blank">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-github.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-instagram.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-taiga.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot | Made with <3 and Open Source</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1
backend/resources/app/emails/change-email/en.subj
Normal file
1
backend/resources/app/emails/change-email/en.subj
Normal file
@@ -0,0 +1 @@
|
||||
Email change
|
||||
13
backend/resources/app/emails/change-email/en.txt
Normal file
13
backend/resources/app/emails/change-email/en.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
Hello {{name}}!
|
||||
|
||||
We received a request to change your current email to {{ pending-email }}.
|
||||
|
||||
Click to the link below to confirm the change:
|
||||
|
||||
{{ public-uri }}/#/auth/verify-token?token={{token}}
|
||||
|
||||
If you received this email by mistake, please consider changing your password
|
||||
for security reasons.
|
||||
|
||||
Enjoy!
|
||||
The Penpot team.
|
||||
45
backend/resources/app/emails/feedback/en.html
Normal file
45
backend/resources/app/emails/feedback/en.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title></title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
<strong>Feedback from:</strong><br />
|
||||
{% if profile %}
|
||||
<span>
|
||||
<span>Name: </span>
|
||||
<span><code>{{profile.fullname}}</code></span>
|
||||
</span>
|
||||
<br />
|
||||
|
||||
<span>
|
||||
<span>Email: </span>
|
||||
<span>{{profile.email}}</span>
|
||||
</span>
|
||||
<br />
|
||||
|
||||
<span>
|
||||
<span>ID: </span>
|
||||
<span><code>{{profile.id}}</code></span>
|
||||
</span>
|
||||
{% else %}
|
||||
<span>
|
||||
<span>Email: </span>
|
||||
<span>{{profile.email}}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Subject:</strong><br />
|
||||
<span>{{subject}}</span>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Message:</strong><br />
|
||||
{{content|linebreaks-br|safe}}
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
1
backend/resources/app/emails/feedback/en.subj
Normal file
1
backend/resources/app/emails/feedback/en.subj
Normal file
@@ -0,0 +1 @@
|
||||
[PENPOT FEEDBACK]: {{subject}}
|
||||
9
backend/resources/app/emails/feedback/en.txt
Normal file
9
backend/resources/app/emails/feedback/en.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
{% if profile %}
|
||||
Feedback profile: {{profile.fullname}} <{{profile.email}}> / {{profile.id}}
|
||||
{% else %}
|
||||
Feedback from: {{email}}
|
||||
{% endif %}
|
||||
|
||||
Subject: {{subject}}
|
||||
|
||||
{{content}}
|
||||
468
backend/resources/app/emails/invite-to-team/en.html
Normal file
468
backend/resources/app/emails/invite-to-team/en.html
Normal file
@@ -0,0 +1,468 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
|
||||
<head>
|
||||
<title>
|
||||
</title>
|
||||
<!--[if !mso]><!-- -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<!--<![endif]-->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type="text/css">
|
||||
#outlook a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
table,
|
||||
td {
|
||||
border-collapse: collapse;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin: 13px 0;
|
||||
}
|
||||
</style>
|
||||
<!--[if mso]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<!--[if lte mso 11]>
|
||||
<style type="text/css">
|
||||
.mj-outlook-group-fix { width:100% !important; }
|
||||
</style>
|
||||
<![endif]-->
|
||||
<!--[if !mso]><!-->
|
||||
<link href="https://fonts.googleapis.com/css?family=Source%20Sans%20Pro" rel="stylesheet" type="text/css">
|
||||
<style type="text/css">
|
||||
@import url(https://fonts.googleapis.com/css?family=Source%20Sans%20Pro);
|
||||
</style>
|
||||
<!--<![endif]-->
|
||||
<style type="text/css">
|
||||
@media only screen and (min-width:480px) {
|
||||
.mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.mj-column-px-425 {
|
||||
width: 425px !important;
|
||||
max-width: 425px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
@media only screen and (max-width:480px) {
|
||||
table.mj-full-width-mobile {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
td.mj-full-width-mobile {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="background-color:#E5E5E5;">
|
||||
<div style="background-color:#E5E5E5;">
|
||||
<!--[if mso | IE]>
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:16px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:97px;">
|
||||
<img height="32" src="{{ public-uri }}/images/email/uxbox-title.png" style="border:0;display:block;outline:none;text-decoration:none;height:32px;width:100%;font-size:13px;" width="97" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:24px;font-weight:600;line-height:150%;text-align:left;color:#000000;">Hello!</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">{{invited-by}} has invited you to join the team “{{ team }}”.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" vertical-align="middle" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
|
||||
<tr>
|
||||
<td align="center" bgcolor="#31EFB8" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#31EFB8;" valign="middle">
|
||||
<a href="{{ public-uri }}/#/auth/verify-token?token={{token}}" style="display:inline-block;background:#31EFB8;color:#1F1F1F;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;" target="_blank"> Accept invite </a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">Enjoy!</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">The Penpot team.</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-px-425 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<!--[if mso | IE]>
|
||||
<table
|
||||
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;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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://penpot.app/" target="_blank">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-uxbox.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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://twitter.com/penpotapp" target="_blank">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-twitter.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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://github.com/penpot/" target="_blank">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-github.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-instagram.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-taiga.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot | Made with <3 and Open Source</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1
backend/resources/app/emails/invite-to-team/en.subj
Normal file
1
backend/resources/app/emails/invite-to-team/en.subj
Normal file
@@ -0,0 +1 @@
|
||||
Invitation to join {{team}}
|
||||
10
backend/resources/app/emails/invite-to-team/en.txt
Normal file
10
backend/resources/app/emails/invite-to-team/en.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
Hello!
|
||||
|
||||
{{invited-by}} has invited you to join the team “{{ team }}”.
|
||||
|
||||
Accept invitation using this link:
|
||||
|
||||
{{ public-uri }}/#/auth/verify-token?token={{token}}
|
||||
|
||||
Enjoy!
|
||||
The Penpot team.
|
||||
473
backend/resources/app/emails/password-recovery/en.html
Normal file
473
backend/resources/app/emails/password-recovery/en.html
Normal file
@@ -0,0 +1,473 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
|
||||
<head>
|
||||
<title>
|
||||
</title>
|
||||
<!--[if !mso]><!-- -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<!--<![endif]-->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type="text/css">
|
||||
#outlook a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
table,
|
||||
td {
|
||||
border-collapse: collapse;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin: 13px 0;
|
||||
}
|
||||
</style>
|
||||
<!--[if mso]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<!--[if lte mso 11]>
|
||||
<style type="text/css">
|
||||
.mj-outlook-group-fix { width:100% !important; }
|
||||
</style>
|
||||
<![endif]-->
|
||||
<!--[if !mso]><!-->
|
||||
<link href="https://fonts.googleapis.com/css?family=Source%20Sans%20Pro" rel="stylesheet" type="text/css">
|
||||
<style type="text/css">
|
||||
@import url(https://fonts.googleapis.com/css?family=Source%20Sans%20Pro);
|
||||
</style>
|
||||
<!--<![endif]-->
|
||||
<style type="text/css">
|
||||
@media only screen and (min-width:480px) {
|
||||
.mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.mj-column-px-425 {
|
||||
width: 425px !important;
|
||||
max-width: 425px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
@media only screen and (max-width:480px) {
|
||||
table.mj-full-width-mobile {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
td.mj-full-width-mobile {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="background-color:#E5E5E5;">
|
||||
<div style="background-color:#E5E5E5;">
|
||||
<!--[if mso | IE]>
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:16px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:97px;">
|
||||
<img height="32" src="{{ public-uri }}/images/email/uxbox-title.png" style="border:0;display:block;outline:none;text-decoration:none;height:32px;width:100%;font-size:13px;" width="97" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:24px;font-weight:600;line-height:150%;text-align:left;color:#000000;">Hello {{name}}!</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">We have received a request to reset your password. Click the link below to choose a new one:</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" vertical-align="middle" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
|
||||
<tr>
|
||||
<td align="center" bgcolor="#31EFB8" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#31EFB8;" valign="middle">
|
||||
<a href="{{ public-uri }}/#/auth/recovery?token={{token}}" style="display:inline-block;background:#31EFB8;color:#1F1F1F;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;" target="_blank"> Reset password </a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">If you received this email by mistake, you can safely ignore it. Your password won't be changed.</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">Enjoy!</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">The Penpot team.</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-px-425 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<!--[if mso | IE]>
|
||||
<table
|
||||
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;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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://penpot.app/" target="_blank">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-uxbox.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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://twitter.com/penpotapp" target="_blank">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-twitter.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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://github.com/penpot/" target="_blank">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-github.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-instagram.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-taiga.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot | Made with <3 and Open Source</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1
backend/resources/app/emails/password-recovery/en.subj
Normal file
1
backend/resources/app/emails/password-recovery/en.subj
Normal file
@@ -0,0 +1 @@
|
||||
Password reset
|
||||
12
backend/resources/app/emails/password-recovery/en.txt
Normal file
12
backend/resources/app/emails/password-recovery/en.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
Hello {{name}}!
|
||||
|
||||
We received a request to reset your password. Click the link below to choose a
|
||||
new one:
|
||||
|
||||
{{ public-uri }}/#/auth/recovery?token={{token}}
|
||||
|
||||
If you received this email by mistake, you can safely ignore it. Your password
|
||||
won't be changed.
|
||||
|
||||
Enjoy!
|
||||
The Penpot team.
|
||||
468
backend/resources/app/emails/register/en.html
Normal file
468
backend/resources/app/emails/register/en.html
Normal file
@@ -0,0 +1,468 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
|
||||
<head>
|
||||
<title>
|
||||
</title>
|
||||
<!--[if !mso]><!-- -->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<!--<![endif]-->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style type="text/css">
|
||||
#outlook a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
table,
|
||||
td {
|
||||
border-collapse: collapse;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
height: auto;
|
||||
line-height: 100%;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin: 13px 0;
|
||||
}
|
||||
</style>
|
||||
<!--[if mso]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<!--[if lte mso 11]>
|
||||
<style type="text/css">
|
||||
.mj-outlook-group-fix { width:100% !important; }
|
||||
</style>
|
||||
<![endif]-->
|
||||
<!--[if !mso]><!-->
|
||||
<link href="https://fonts.googleapis.com/css?family=Source%20Sans%20Pro" rel="stylesheet" type="text/css">
|
||||
<style type="text/css">
|
||||
@import url(https://fonts.googleapis.com/css?family=Source%20Sans%20Pro);
|
||||
</style>
|
||||
<!--<![endif]-->
|
||||
<style type="text/css">
|
||||
@media only screen and (min-width:480px) {
|
||||
.mj-column-per-100 {
|
||||
width: 100% !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.mj-column-px-425 {
|
||||
width: 425px !important;
|
||||
max-width: 425px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
@media only screen and (max-width:480px) {
|
||||
table.mj-full-width-mobile {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
td.mj-full-width-mobile {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="background-color:#E5E5E5;">
|
||||
<div style="background-color:#E5E5E5;">
|
||||
<!--[if mso | IE]>
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:16px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:collapse;border-spacing:0px;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:97px;">
|
||||
<img height="32" src="{{ public-uri }}/images/email/uxbox-title.png" style="border:0;display:block;outline:none;text-decoration:none;height:32px;width:100%;font-size:13px;" width="97" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="background:#FFFFFF;background-color:#FFFFFF;margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="background:#FFFFFF;background-color:#FFFFFF;width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:24px;font-weight:600;line-height:150%;text-align:left;color:#000000;">Hello {{name}}!</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">Thanks for signing up for your Penpot account! Please verify your email using the link below and get started building mockups and prototypes today!</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" vertical-align="middle" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="border-collapse:separate;line-height:100%;">
|
||||
<tr>
|
||||
<td align="center" bgcolor="#31EFB8" role="presentation" style="border:none;border-radius:3px;cursor:auto;mso-padding-alt:10px 25px;background:#31EFB8;" valign="middle">
|
||||
<a href="{{ public-uri }}/#/auth/verify-token?token={{token}}" style="display:inline-block;background:#31EFB8;color:#1F1F1F;font-family:Source Sans Pro, sans-serif;font-size:16px;font-weight:normal;line-height:120%;margin:0;text-decoration:none;text-transform:none;padding:10px 25px;mso-padding-alt:0px;border-radius:3px;" target="_blank"> Verify email </a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">Enjoy!</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:16px;line-height:150%;text-align:left;color:#000000;">The Penpot team.</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-px-425 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot is the first Open Source design and prototyping platform meant for cross-domain teams.</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<!--[if mso | IE]>
|
||||
<table
|
||||
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;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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://penpot.app/" target="_blank">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-uxbox.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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://twitter.com/penpotapp" target="_blank">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-twitter.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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://github.com/penpot/" target="_blank">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-github.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-instagram.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<![endif]-->
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="float:none;display:inline-table;">
|
||||
<tr>
|
||||
<td style="padding:0 8px;">
|
||||
<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">
|
||||
<img height="24" src="{{ public-uri }}/images/email/logo-taiga.png" style="border-radius:3px;display:block;" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table
|
||||
align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600"
|
||||
>
|
||||
<tr>
|
||||
<td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;">
|
||||
<![endif]-->
|
||||
<div style="margin:0px auto;max-width:600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<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;"
|
||||
>
|
||||
<![endif]-->
|
||||
<div class="mj-column-per-100 mj-outlook-group-fix" style="font-size:0px;text-align:left;direction:ltr;display:inline-block;vertical-align:top;width:100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="vertical-align:top;" width="100%">
|
||||
<tr>
|
||||
<td align="center" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Source Sans Pro, sans-serif;font-size:14px;line-height:150%;text-align:center;color:#64666A;">Penpot | Made with <3 and Open Source</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!--[if mso | IE]>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<![endif]-->
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1
backend/resources/app/emails/register/en.subj
Normal file
1
backend/resources/app/emails/register/en.subj
Normal file
@@ -0,0 +1 @@
|
||||
Verify email.
|
||||
9
backend/resources/app/emails/register/en.txt
Normal file
9
backend/resources/app/emails/register/en.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Hello {{name}}!
|
||||
|
||||
Thanks for signing up for your Penpot account! Please verify your email using the
|
||||
link below and get started building mockups and prototypes today!
|
||||
|
||||
{{ public-uri }}/#/auth/verify-token?token={{token}}
|
||||
|
||||
Enjoy!
|
||||
The Penpot team.
|
||||
36
backend/resources/app/onboarding.edn
Normal file
36
backend/resources/app/onboarding.edn
Normal file
@@ -0,0 +1,36 @@
|
||||
[{:id "tutorial-for-beginners"
|
||||
:name "Tutorial for beginners"
|
||||
:thumbnail-uri "https://penpot.app/images/libraries/tutorial-for-beginners.jpg"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/tutorial-for-beginners.penpot"}
|
||||
{:id "penpot-design-system"
|
||||
:name "Penpot Design System"
|
||||
:thumbnail-uri "https://penpot.app/images/libraries/cover-ds-penpot.jpg"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Penpot-Design-system.penpot"}
|
||||
{:id "wireframing-kit"
|
||||
:name "Wireframing Kit"
|
||||
:thumbnail-uri "https://penpot.app/images/libraries/cover-wireframes.jpg"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/wireframing-kit.penpot"}
|
||||
{:id "ant-design"
|
||||
:name "Ant Design UI Kit (lite)"
|
||||
:thumbnail-uri "https://penpot.app/images/libraries/cover-ant-design.jpg"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Ant-Design-UI-Kit-Lite.penpot"}
|
||||
{:id "cocomaterial"
|
||||
:name "Cocomaterial"
|
||||
:thumbnail-uri "https://penpot.app/images/libraries/cover-cocomaterial.jpg"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Cocomaterial.penpot"}
|
||||
{:id "circum-icons"
|
||||
:name "Circum Icons pack"
|
||||
:thumbnail-uri "https://penpot.app/images/libraries/cover-circum.jpg"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/CircumIcons.penpot"}
|
||||
{:id "coreui"
|
||||
:name "CoreUI"
|
||||
:thumbnail-uri "https://penpot.app/images/libraries/cover-coreui.jpg"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/main/CoreUI%20DesignSystem%20(DEMO).penpot"}
|
||||
{:id "whiteboarding-kit"
|
||||
:name "Whiteboarding Kit"
|
||||
:thumbnail-uri "https://penpot.app/images/libraries/cover-whiteboards.jpg"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Whiteboarding-mapping-kit.penpot"}
|
||||
{:id "material-design-baseline"
|
||||
:name "Material Design (baseline)"
|
||||
:thumbnail-uri "https://penpot.app/images/libraries/cover-material.jpg"
|
||||
:file-uri "https://github.com/penpot/penpot-files/raw/binary-files/Material-Design-Kit.penpot"}]
|
||||
61
backend/resources/app/templates/api-doc-entry.tmpl
Normal file
61
backend/resources/app/templates/api-doc-entry.tmpl
Normal file
@@ -0,0 +1,61 @@
|
||||
<li class="rpc-item">
|
||||
<div class="rpc-row-info">
|
||||
{# <div class="type">{{item.type}}</div> #}
|
||||
<div class="module">{{item.module}}:</div>
|
||||
<div class="name">{{item.name}}</div>
|
||||
<div class="tags">
|
||||
{% if item.deprecated %}
|
||||
<span class="tag">
|
||||
<span>DEPRECATED</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if item.auth %}
|
||||
<span class="tag">
|
||||
<span>AUTH</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if item.webhook %}
|
||||
<span class="tag">
|
||||
<span>WEBHOOK</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rpc-row-detail hidden">
|
||||
<h3>DOCSTRING:</h3>
|
||||
|
||||
<section class="padded-section">
|
||||
|
||||
{% if item.added %}
|
||||
<p class="small"><strong>Added:</strong> on v{{item.added}}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if item.deprecated %}
|
||||
<p class="small"><strong>Deprecated:</strong> since v{{item.deprecated}}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if item.docs %}
|
||||
<p class="docstring"> {{item.docs}}</p>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
{% if item.changes %}
|
||||
<h3>CHANGES:</h3>
|
||||
<section class="padded-section">
|
||||
|
||||
<ul class="changes">
|
||||
{% for change in item.changes %}
|
||||
<li><strong>{{change.0}}</strong> - {{change.1}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<h3>SPEC EXPLAIN:</h3>
|
||||
<section class="padded-section">
|
||||
<pre class="spec-explain">{{item.spec}}</pre>
|
||||
</section>
|
||||
</div>
|
||||
</li>
|
||||
145
backend/resources/app/templates/api-doc.css
Normal file
145
backend/resources/app/templates/api-doc.css
Normal file
@@ -0,0 +1,145 @@
|
||||
* {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 900px;
|
||||
width: 900px;
|
||||
}
|
||||
|
||||
header {
|
||||
border-bottom: 1px solid #c0c0c0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rpc-doc-content {
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* border: 1px solid red; */
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.rpc-doc-content > h2:not(:first-child) {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
|
||||
.rpc-items {
|
||||
list-style: none;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.rpc-item {
|
||||
/* border: 1px solid red; */
|
||||
/* cursor: pointer; */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.rpc-item:not(:last-child) {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.rpc-row-info {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
background-color: #eeeeee;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.rpc-row-info > *:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.rpc-row-info > * {
|
||||
/* border: 1px solid green; */
|
||||
}
|
||||
|
||||
.rpc-row-info > .type {
|
||||
font-weight: bold;
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.rpc-row-info > .name {
|
||||
width: 280px;
|
||||
/* font-weight: bold; */
|
||||
border-right: 1px dotted #777;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.rpc-row-info > .module {
|
||||
width: 120px;
|
||||
font-weight: bold;
|
||||
border-right: 1px dotted #777;
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.rpc-row-info > .tags > .tag > span:first-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rpc-row-detail {
|
||||
padding: 5px 10px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.rpc-row-detail p {
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
.rpc-row-detail p.small {
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.rpc-row-detail p.small {
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.rpc-row-detail strong {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.rpc-row-detail .changes {
|
||||
font-weight: 200;
|
||||
list-style: none;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.rpc-row-detail .padded-section {
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
p.small strong {
|
||||
font-size: 10px;
|
||||
}
|
||||
27
backend/resources/app/templates/api-doc.js
Normal file
27
backend/resources/app/templates/api-doc.js
Normal file
@@ -0,0 +1,27 @@
|
||||
(function() {
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
const rows = document.querySelectorAll(".rpc-row-info");
|
||||
|
||||
const onRowClick = (event) => {
|
||||
const target = event.currentTarget;
|
||||
for (let node of rows) {
|
||||
if (node !== target) {
|
||||
node.nextElementSibling.classList.add("hidden");
|
||||
} else {
|
||||
const sibling = target.nextElementSibling;
|
||||
|
||||
if (sibling.classList.contains("hidden")) {
|
||||
sibling.classList.remove("hidden");
|
||||
} else {
|
||||
sibling.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (let node of rows) {
|
||||
node.addEventListener("click", onRowClick);
|
||||
}
|
||||
|
||||
});
|
||||
})();
|
||||
50
backend/resources/app/templates/api-doc.tmpl
Normal file
50
backend/resources/app/templates/api-doc.tmpl
Normal file
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<title>Builtin API Documentation - Penpot</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@200;300;400;500;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
{% include "app/templates/api-doc.css" %}
|
||||
</style>
|
||||
<script>
|
||||
{% include "app/templates/api-doc.js" %}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<header>
|
||||
<h1>Penpot API Documentation (v{{version}})</h1>
|
||||
</header>
|
||||
<section class="rpc-doc-content">
|
||||
|
||||
<h2>RPC COMMAND METHODS:</h2>
|
||||
<ul class="rpc-items">
|
||||
{% for item in command-methods %}
|
||||
{% include "app/templates/api-doc-entry.tmpl" with item=item %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<h2>RPC QUERY METHODS:</h2>
|
||||
<ul class="rpc-items">
|
||||
{% for item in query-methods %}
|
||||
{% include "app/templates/api-doc-entry.tmpl" with item=item %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<h2>RPC MUTATION METHODS:</h2>
|
||||
<ul class="rpc-items">
|
||||
{% for item in mutation-methods %}
|
||||
{% include "app/templates/api-doc-entry.tmpl" with item=item %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
18
backend/resources/app/templates/base.tmpl
Normal file
18
backend/resources/app/templates/base.tmpl
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono">
|
||||
<style>
|
||||
{% include "app/templates/styles.css" %}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
127
backend/resources/app/templates/debug.tmpl
Normal file
127
backend/resources/app/templates/debug.tmpl
Normal file
@@ -0,0 +1,127 @@
|
||||
{% extends "app/templates/base.tmpl" %}
|
||||
|
||||
{% block title %}
|
||||
Debug Main Page
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<nav>
|
||||
<h1>Debug INDEX:</h1>
|
||||
<div>[<a href="/dbg/error">ERRORS</a>]</div>
|
||||
</nav>
|
||||
<main class="index">
|
||||
<section class="widget">
|
||||
<fieldset>
|
||||
<legend>Download file data:</legend>
|
||||
<desc>Given an FILE-ID, downloads the file data as file. The file data is encoded using transit.</desc>
|
||||
<form method="get" action="/dbg/file/data">
|
||||
<div class="row">
|
||||
<input type="text" style="width:300px" name="file-id" placeholder="file-id" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<input type="submit" name="download" value="Download" />
|
||||
<input type="submit" name="clone" value="Clone" />
|
||||
</div>
|
||||
</form>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Upload File Data:</legend>
|
||||
<desc>Create a new file on your draft projects using the file downloaded from the previous section.</desc>
|
||||
<form method="post" enctype="multipart/form-data" action="/dbg/file/data">
|
||||
<div class="row">
|
||||
<input type="file" name="file" value="" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<label>Import with same id?</label>
|
||||
<input type="checkbox" name="reuseid" />
|
||||
</div>
|
||||
|
||||
<input type="submit" value="Upload" />
|
||||
</form>
|
||||
</fieldset>
|
||||
</section>
|
||||
|
||||
<section class="widget">
|
||||
<fieldset>
|
||||
<legend>Export binfile:</legend>
|
||||
<desc>Given an FILE-ID, downloads the file and optionally all
|
||||
the related libraries in a single custom formatted binary
|
||||
file.</desc>
|
||||
|
||||
<form method="get" action="/dbg/file/export">
|
||||
<div class="row set-of-inputs">
|
||||
<input type="text" style="width:300px" name="file-ids" placeholder="file-id" />
|
||||
<input type="text" style="width:300px" name="file-ids" placeholder="file-id" />
|
||||
<input type="text" style="width:300px" name="file-ids" placeholder="file-id" />
|
||||
<input type="text" style="width:300px" name="file-ids" placeholder="file-id" />
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Include libraries?</label>
|
||||
<input type="checkbox" name="includelibs" />
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Embed assets?</label>
|
||||
<input type="checkbox" name="embedassets" checked/>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<input type="submit" name="download" value="Download" />
|
||||
<input type="submit" name="clone" value="Clone" />
|
||||
</div>
|
||||
</form>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Import binfile:</legend>
|
||||
<desc>Import penpot file in binary
|
||||
format. If <strong>overwrite</strong> is checked, all files will
|
||||
be overwritten using the same ids found in the file instead of
|
||||
generating a new ones.</desc>
|
||||
|
||||
<form method="post" enctype="multipart/form-data" action="/dbg/file/import">
|
||||
<div class="row">
|
||||
<input type="file" name="file" value="" />
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Overwrite?</label>
|
||||
<input type="checkbox" name="overwrite" />
|
||||
<br />
|
||||
<small>
|
||||
Instead of creating a new file with all relations remapped,
|
||||
reuses all ids and updates/overwrites the objects that are
|
||||
already exists on the database.
|
||||
<strong>Warning, this operation should be used with caution.</strong>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Migrate?</label>
|
||||
<input type="checkbox" name="migrate" />
|
||||
<br />
|
||||
<small>
|
||||
Applies the file migrations on the importation process.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label>Ignore index errors?</label>
|
||||
<input type="checkbox" name="ignore-index-errors" checked/>
|
||||
<br />
|
||||
<small>
|
||||
Do not break on index lookup errors (remap operation).
|
||||
Useful when importing a broken file that has broken
|
||||
relations or missing pieces.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<input type="submit" name="upload" value="Upload" />
|
||||
</div>
|
||||
</form>
|
||||
</fieldset>
|
||||
</section>
|
||||
</main>
|
||||
{% endblock %}
|
||||
19
backend/resources/app/templates/error-list.tmpl
Normal file
19
backend/resources/app/templates/error-list.tmpl
Normal file
@@ -0,0 +1,19 @@
|
||||
{% extends "app/templates/base.tmpl" %}
|
||||
|
||||
{% block title %}
|
||||
penpot - error list
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<nav>
|
||||
<h1>Latest error reports:</h1>
|
||||
</nav>
|
||||
<main class="horizontal-list">
|
||||
<ul>
|
||||
{% for item in items %}
|
||||
<li><a class="date" href="/dbg/error/{{item.id}}">{{item.created-at}}</a>
|
||||
<span class="title">{{item.hint|abbreviate:150}}</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</main>
|
||||
{% endblock %}
|
||||
98
backend/resources/app/templates/error-report.tmpl
Normal file
98
backend/resources/app/templates/error-report.tmpl
Normal file
@@ -0,0 +1,98 @@
|
||||
{% extends "app/templates/base.tmpl" %}
|
||||
|
||||
{% block title %}
|
||||
penpot - error report {{id}}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<nav>
|
||||
<div>[<a href="/dbg/error">⮜</a>]</div>
|
||||
<div>[<a href="#context">context</a>]</div>
|
||||
<div>[<a href="#params">request params</a>]</div>
|
||||
{% if data %}
|
||||
<div>[<a href="#edata">error data</a>]</div>
|
||||
{% endif %}
|
||||
{% if spec-explain %}
|
||||
<div>[<a href="#spec-explain">spec explain</a>]</div>
|
||||
{% endif %}
|
||||
{% if spec-problems %}
|
||||
<div>[<a href="#spec-problems">spec problems</a>]</div>
|
||||
{% endif %}
|
||||
{% if spec-value %}
|
||||
<div>[<a href="#spec-value">spec value</a>]</div>
|
||||
{% endif %}
|
||||
|
||||
{% if trace %}
|
||||
<div>[<a href="#trace">error trace</a>]</div>
|
||||
{% endif %}
|
||||
</nav>
|
||||
<main>
|
||||
<div class="table">
|
||||
<div class="table-row multiline">
|
||||
<div id="context" class="table-key">CONTEXT: </div>
|
||||
|
||||
<div class="table-val">
|
||||
<h1>{{hint}}</h1>
|
||||
</div>
|
||||
|
||||
<div class="table-val">
|
||||
<pre>{{context}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if params %}
|
||||
<div class="table-row multiline">
|
||||
<div id="params" class="table-key">REQUEST PARAMS: </div>
|
||||
<div class="table-val">
|
||||
<pre>{{params}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if data %}
|
||||
<div class="table-row multiline">
|
||||
<div id="edata" class="table-key">ERROR DATA: </div>
|
||||
<div class="table-val">
|
||||
<pre>{{data}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if spec-explain %}
|
||||
<div class="table-row multiline">
|
||||
<div id="spec-explain" class="table-key">SPEC EXPLAIN: </div>
|
||||
<div class="table-val">
|
||||
<pre>{{spec-explain}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if spec-problems %}
|
||||
<div class="table-row multiline">
|
||||
<div id="spec-problems" class="table-key">SPEC PROBLEMS: </div>
|
||||
<div class="table-val">
|
||||
<pre>{{spec-problems}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if spec-value %}
|
||||
<div class="table-row multiline">
|
||||
<div id="spec-value" class="table-key">SPEC VALUE: </div>
|
||||
<div class="table-val">
|
||||
<pre>{{spec-value}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if trace %}
|
||||
<div class="table-row multiline">
|
||||
<div id="trace" class="table-key">TRACE:</div>
|
||||
<div class="table-val">
|
||||
<pre>{{trace}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
189
backend/resources/app/templates/styles.css
Normal file
189
backend/resources/app/templates/styles.css
Normal file
@@ -0,0 +1,189 @@
|
||||
* {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
desc {
|
||||
margin-bottom: 10px;
|
||||
font-size: 10px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
input[type=text], input[type=submit] {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
main {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 9px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
small > strong {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
nav {
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 5px 20px;
|
||||
display: flex;
|
||||
background: #e3e3e3;
|
||||
}
|
||||
|
||||
nav > h1 {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
nav > div {
|
||||
text-transform: uppercase;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
nav > div:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.table {
|
||||
margin-top: 25px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
padding-bottom: 15px;
|
||||
/* width: 100%; */
|
||||
/* border: 1px solid red; */
|
||||
}
|
||||
|
||||
.table-key {
|
||||
font-weight: 600;
|
||||
width: 60px;
|
||||
padding: 4px;
|
||||
|
||||
padding-top: 40px;
|
||||
margin-top: -40px;
|
||||
}
|
||||
|
||||
.table-val {
|
||||
font-weight: 200;
|
||||
color: #333;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.multiline {
|
||||
margin-top: 15px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.multiline .table-key {
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px dashed #dddddd;
|
||||
/* padding: 4px; */
|
||||
width: unset;
|
||||
}
|
||||
|
||||
.index {
|
||||
margin-top: 40px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.index > section {
|
||||
padding: 10px;
|
||||
background-color: #e3e3e3;
|
||||
max-width: 400px;
|
||||
margin: 5px;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.index fieldset:not(:first-child) {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
/* .index > section:not(:last-child) { */
|
||||
/* margin-bottom: 10px; */
|
||||
/* } */
|
||||
|
||||
|
||||
.index > section > h2 {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.horizontal-list {
|
||||
margin: 20px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.horizontal-list ul {
|
||||
display: flex;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.horizontal-list li {
|
||||
list-style: none;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
line-height: 18px;
|
||||
min-width: 210px;
|
||||
margin: 0px 20px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.horizontal-list li:hover {
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
|
||||
.horizontal-list li > *:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.horizontal-list li > a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.horizontal-list li > .date {
|
||||
font-weight: 200;
|
||||
color: #686868;
|
||||
min-width: 210px;
|
||||
}
|
||||
|
||||
|
||||
form .row {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.set-of-inputs {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.set-of-inputs input:not(:last-child) {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
9
backend/resources/climit.edn
Normal file
9
backend/resources/climit.edn
Normal file
@@ -0,0 +1,9 @@
|
||||
;; Example climit.edn file
|
||||
;; Required: concurrency
|
||||
;; Optional: queue-size, ommited means Integer/MAX_VALUE
|
||||
{:update-file {:concurrency 1 :queue-size 3}
|
||||
:auth {:concurrency 128}
|
||||
:process-font {:concurrency 4 :queue-size 32}
|
||||
:process-image {:concurrency 8 :queue-size 32}
|
||||
:push-audit-events
|
||||
{:concurrency 1 :queue-size 3}}
|
||||
61
backend/resources/log4j2-devenv.xml
Normal file
61
backend/resources/log4j2-devenv.xml
Normal file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="info" monitorInterval="30">
|
||||
<Appenders>
|
||||
<Console name="console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="[%d{YYYY-MM-dd HH:mm:ss.SSS}] %level{length=1} %logger{36} - %msg%n"
|
||||
alwaysWriteExceptions="false" />
|
||||
</Console>
|
||||
|
||||
<RollingFile name="main" fileName="logs/main.log" filePattern="logs/main-%i.log">
|
||||
<PatternLayout pattern="[%d{YYYY-MM-dd HH:mm:ss.SSS}] %level{length=1} %logger{36} - %msg%n"
|
||||
alwaysWriteExceptions="false" />
|
||||
<Policies>
|
||||
<SizeBasedTriggeringPolicy size="50M"/>
|
||||
</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>
|
||||
<Logger name="io.lettuce" level="error" />
|
||||
<Logger name="com.zaxxer.hikari" level="error"/>
|
||||
<Logger name="org.postgresql" level="error" />
|
||||
|
||||
<Logger name="app.rpc.commands.binfile" level="debug" />
|
||||
<Logger name="app.storage.tmp" level="debug" />
|
||||
<Logger name="app.worker" level="info" />
|
||||
<Logger name="app.msgbus" level="info" />
|
||||
<Logger name="app.http.websocket" level="info" />
|
||||
<Logger name="app.util.websocket" level="info" />
|
||||
<Logger name="app.redis" level="info" />
|
||||
<Logger name="app.rpc.rlimit" level="info" />
|
||||
<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">
|
||||
<AppenderRef ref="main" level="trace" />
|
||||
</Logger>
|
||||
|
||||
<Root level="info">
|
||||
<AppenderRef ref="main" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
23
backend/resources/log4j2.xml
Normal file
23
backend/resources/log4j2.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="info" monitorInterval="60">
|
||||
<Appenders>
|
||||
<Console name="console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="[%d{YYYY-MM-dd HH:mm:ss.SSS}] %level{length=1} %logger{36} - %msg%n"
|
||||
alwaysWriteExceptions="false" />
|
||||
</Console>
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<Logger name="io.lettuce" level="error" />
|
||||
<Logger name="com.zaxxer.hikari" level="error" />
|
||||
<Logger name="org.postgresql" level="error" />
|
||||
|
||||
<Logger name="app" level="info" additivity="false">
|
||||
<AppenderRef ref="console" />
|
||||
</Logger>
|
||||
|
||||
<Root level="info">
|
||||
<AppenderRef ref="console" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
10
backend/resources/rlimit.edn
Normal file
10
backend/resources/rlimit.edn
Normal file
@@ -0,0 +1,10 @@
|
||||
;; Example rlimit.edn file
|
||||
^{:refresh "30s"}
|
||||
{:default
|
||||
[[:default :window "200000/h"]]
|
||||
|
||||
#{:query/teams}
|
||||
[[:burst :bucket "5/1/5s"]]
|
||||
|
||||
#{:query/profile}
|
||||
[[:burst :bucket "100/60/1m"]]}
|
||||
22
backend/scripts/build
Executable file
22
backend/scripts/build
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
CURRENT_VERSION=$1;
|
||||
|
||||
set -ex
|
||||
|
||||
rm -rf target;
|
||||
mkdir -p target/classes;
|
||||
mkdir -p target/dist;
|
||||
echo "$CURRENT_VERSION" > target/classes/version.txt;
|
||||
cp ../CHANGES.md target/classes/changelog.md;
|
||||
|
||||
clojure -T:build jar;
|
||||
mv target/penpot.jar target/dist/penpot.jar
|
||||
cp scripts/run.template.sh target/dist/run.sh;
|
||||
cp scripts/manage.py target/dist/manage.py
|
||||
chmod +x target/dist/run.sh;
|
||||
chmod +x target/dist/manage.py
|
||||
|
||||
# Prefetch
|
||||
bb ./scripts/prefetch-templates.clj resources/app/onboarding.edn builtin-templates/
|
||||
cp -r builtin-templates target/dist/
|
||||
7
backend/scripts/build-email-templates.sh
Executable file
7
backend/scripts/build-email-templates.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
for f in resources/emails-mjml/*/*.mjml
|
||||
do
|
||||
npx mjml $f -o `echo $f | sed -e "s/-mjml//" | sed -e "s/mjml/html/"`
|
||||
done
|
||||
|
||||
4
backend/scripts/kill-repl.sh
Executable file
4
backend/scripts/kill-repl.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
set -x
|
||||
|
||||
jcmd |grep "rebel" |sed -nE 's/^([0-9]+).*$/\1/p' | xargs kill -9
|
||||
167
backend/scripts/manage.py
Executable file
167
backend/scripts/manage.py
Executable file
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# 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
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import socket
|
||||
import sys
|
||||
|
||||
from getpass import getpass
|
||||
from urllib.parse import urlparse
|
||||
|
||||
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}")
|
||||
|
||||
if not isinstance(uri_data.netloc, str):
|
||||
raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
|
||||
|
||||
host, port = uri_data.netloc.split(":", 2)
|
||||
|
||||
if port is None:
|
||||
port = 6063
|
||||
|
||||
if isinstance(port, str):
|
||||
port = int(port)
|
||||
|
||||
return host, port
|
||||
|
||||
def send_eval(expr):
|
||||
host, port = get_prepl_conninfo()
|
||||
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.connect((host, port))
|
||||
s.send(expr.encode("utf-8"))
|
||||
s.send(b":repl/quit\n\n")
|
||||
|
||||
with s.makefile() as f:
|
||||
result = json.load(f)
|
||||
tag = result.get("tag", None)
|
||||
if tag != "ret":
|
||||
raise RuntimeException("unexpected response from PREPL")
|
||||
return result.get("val", None), result.get("exception", None)
|
||||
|
||||
def encode(val):
|
||||
return json.dumps(json.dumps(val))
|
||||
|
||||
def print_error(res):
|
||||
for error in res["via"]:
|
||||
print("ERR:", error["message"])
|
||||
break
|
||||
|
||||
def run_cmd(params):
|
||||
expr = "(app.srepl.ext/run-json-cmd {})".format(encode(params))
|
||||
res, failed = send_eval(expr)
|
||||
if failed:
|
||||
print_error(res)
|
||||
sys.exit(-1)
|
||||
|
||||
return res
|
||||
|
||||
def create_profile(fullname, email, password):
|
||||
params = {
|
||||
"cmd": "create-profile",
|
||||
"params": {
|
||||
"fullname": fullname,
|
||||
"email": email,
|
||||
"password": password
|
||||
}
|
||||
}
|
||||
|
||||
res = run_cmd(params)
|
||||
print(f"Created: {res['email']} / {res['id']}")
|
||||
|
||||
def update_profile(email, fullname, password, is_active):
|
||||
params = {
|
||||
"cmd": "update-profile",
|
||||
"params": {
|
||||
"email": email,
|
||||
"fullname": fullname,
|
||||
"password": password,
|
||||
"is_active": is_active
|
||||
}
|
||||
}
|
||||
|
||||
res = run_cmd(params)
|
||||
if res is True:
|
||||
print(f"Updated")
|
||||
else:
|
||||
print(f"No profile found with email {email}")
|
||||
|
||||
def derive_password(password):
|
||||
params = {
|
||||
"cmd": "derive-password",
|
||||
"params": {
|
||||
"password": password,
|
||||
}
|
||||
}
|
||||
|
||||
res = run_cmd(params)
|
||||
print(f"Derived password: \"{res}\"")
|
||||
|
||||
available_commands = [
|
||||
"create-profile",
|
||||
"update-profile",
|
||||
"derive-password"
|
||||
]
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Penpot Command Line Interface (CLI)"
|
||||
)
|
||||
)
|
||||
|
||||
parser.add_argument("-V", "--version", action="version", version="Penpot CLI %%develop%%")
|
||||
parser.add_argument("action", action="store", choices=available_commands)
|
||||
parser.add_argument("-n", "--fullname", help="Fullname", action="store")
|
||||
parser.add_argument("-e", "--email", help="Email", action="store")
|
||||
parser.add_argument("-p", "--password", help="Password", action="store")
|
||||
parser.add_argument("-c", "--connect", help="Connect to PREPL", action="store", default="tcp://localhost:6063")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
PREPL_URI = args.connect
|
||||
|
||||
if args.action == "create-profile":
|
||||
email = args.email
|
||||
password = args.password
|
||||
fullname = args.fullname
|
||||
|
||||
if email is None:
|
||||
email = input("Email: ")
|
||||
|
||||
if fullname is None:
|
||||
fullname = input("Fullname: ")
|
||||
|
||||
if password is None:
|
||||
password = getpass("Password: ")
|
||||
|
||||
create_profile(fullname, email, password)
|
||||
|
||||
elif args.action == "update-profile":
|
||||
email = args.email
|
||||
password = args.password
|
||||
|
||||
if email is None:
|
||||
email = input("Email: ")
|
||||
|
||||
if password is None:
|
||||
password = getpass("Password: ")
|
||||
|
||||
update_profile(email, None, password, None)
|
||||
|
||||
elif args.action == "derive-password":
|
||||
password = args.password
|
||||
|
||||
if password is None:
|
||||
password = getpass("Password: ")
|
||||
|
||||
derive_password(password)
|
||||
40
backend/scripts/prefetch-templates.clj
Executable file
40
backend/scripts/prefetch-templates.clj
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bb
|
||||
|
||||
(require '[babashka.curl :as curl]
|
||||
'[babashka.fs :as fs])
|
||||
|
||||
(defn download-if-needed!
|
||||
[dest data]
|
||||
(doseq [{:keys [id file-uri] :as item} data]
|
||||
(let [file (fs/file dest id)
|
||||
rsp (curl/get file-uri {:as :stream})]
|
||||
|
||||
(when (not= 200 (:status rsp))
|
||||
(println (format "unable to download %s (uri: %s)" id file-uri))
|
||||
(System/exit -1))
|
||||
|
||||
(when-not (fs/exists? (str file))
|
||||
(println (format "=> downloading %s" id))
|
||||
(with-open [output (io/output-stream file)]
|
||||
(io/copy (:body rsp) output))))))
|
||||
|
||||
(defn read-defs-file
|
||||
[path]
|
||||
(with-open [content (io/reader path)]
|
||||
(edn/read-string (slurp content))))
|
||||
|
||||
(let [[path dest] *command-line-args*]
|
||||
(when (or (nil? path)
|
||||
(nil? dest))
|
||||
(println "invalid arguments")
|
||||
(System/exit -1))
|
||||
|
||||
(when-not (fs/exists? path)
|
||||
(println (format "file %s does not exists" path))
|
||||
(System/exit -1))
|
||||
|
||||
(when-not (fs/exists? dest)
|
||||
(fs/create-dirs dest))
|
||||
|
||||
(let [data (read-defs-file path)]
|
||||
(download-if-needed! dest data)))
|
||||
63
backend/scripts/repl
Executable file
63
backend/scripts/repl
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export PENPOT_HOST=devenv
|
||||
export PENPOT_TENANT=dev
|
||||
export PENPOT_FLAGS="\
|
||||
$PENPOT_FLAGS \
|
||||
enable-backend-asserts \
|
||||
enable-audit-log \
|
||||
enable-transit-readable-response \
|
||||
enable-demo-users \
|
||||
disable-secure-session-cookies \
|
||||
enable-smtp \
|
||||
enable-prepl-server \
|
||||
enable-urepl-server \
|
||||
enable-rpc-climit \
|
||||
enable-rpc-rlimit \
|
||||
enable-soft-rpc-rlimit \
|
||||
enable-webhooks \
|
||||
enable-access-tokens";
|
||||
|
||||
# export PENPOT_DATABASE_URI="postgresql://172.17.0.1:5432/penpot"
|
||||
# export PENPOT_DATABASE_USERNAME="penpot"
|
||||
# export PENPOT_DATABASE_PASSWORD="penpot"
|
||||
# export PENPOT_DATABASE_READONLY=true
|
||||
|
||||
# export PENPOT_DATABASE_URI="postgresql://172.17.0.1:5432/penpot_pre"
|
||||
# export PENPOT_DATABASE_USERNAME="penpot_pre"
|
||||
# export PENPOT_DATABASE_PASSWORD="penpot_pre"
|
||||
|
||||
# export PENPOT_LOGGERS_LOKI_URI="http://172.17.0.1:3100/loki/api/v1/push"
|
||||
# export PENPOT_AUDIT_LOG_ARCHIVE_URI="http://localhost:6070/api/audit"
|
||||
|
||||
# Initialize MINIO config
|
||||
mc alias set penpot-s3/ http://minio:9000 minioadmin minioadmin
|
||||
mc admin user add penpot-s3 penpot-devenv penpot-devenv
|
||||
mc admin policy set penpot-s3 readwrite user=penpot-devenv
|
||||
mc mb penpot-s3/penpot -p
|
||||
|
||||
export AWS_ACCESS_KEY_ID=penpot-devenv
|
||||
export AWS_SECRET_ACCESS_KEY=penpot-devenv
|
||||
export PENPOT_ASSETS_STORAGE_BACKEND=assets-s3
|
||||
export PENPOT_STORAGE_ASSETS_S3_ENDPOINT=http://minio:9000
|
||||
export PENPOT_STORAGE_ASSETS_S3_BUCKET=penpot
|
||||
|
||||
export OPTIONS="
|
||||
-A:jmx-remote -A:dev \
|
||||
-J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
|
||||
-J-Dlog4j2.configurationFile=log4j2-devenv.xml \
|
||||
-J-XX:+UseG1GC \
|
||||
-J-XX:-OmitStackTraceInFastThrow \
|
||||
-J-Xms50m -J-Xmx1024m \
|
||||
-J-Djdk.attach.allowAttachSelf \
|
||||
-J-XX:+UnlockDiagnosticVMOptions \
|
||||
-J-XX:+DebugNonSafepoints";
|
||||
|
||||
# Uncomment for use the ImageMagick v7.x
|
||||
# export OPTIONS="-J-Dim4java.useV7=true $OPTIONS";
|
||||
|
||||
export OPTIONS_EVAL="nil"
|
||||
# export OPTIONS_EVAL="(set! *warn-on-reflection* true)"
|
||||
|
||||
set -ex
|
||||
exec clojure $OPTIONS -M -e "$OPTIONS_EVAL" -m rebel-readline.main
|
||||
22
backend/scripts/run.template.sh
Normal file
22
backend/scripts/run.template.sh
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [[ ! -n "$JAVA_CMD" ]]; then
|
||||
if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
|
||||
JAVA_CMD="$JAVA_HOME/bin/java"
|
||||
else
|
||||
set +e
|
||||
JAVA_CMD=$(type -p java)
|
||||
set -e
|
||||
if [[ ! -n "$JAVA_CMD" ]]; then
|
||||
>&2 echo "Couldn't find 'java'. Please set JAVA_HOME."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f ./environ ]; then
|
||||
source ./environ
|
||||
fi
|
||||
|
||||
set -x
|
||||
exec $JAVA_CMD $JVM_OPTS "$@" -jar penpot.jar -m app.main
|
||||
25
backend/scripts/start-dev
Executable file
25
backend/scripts/start-dev
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export PENPOT_HOST=devenv
|
||||
export PENPOT_TENANT=dev
|
||||
export PENPOT_FLAGS="$PENPOT_FLAGS enable-backend-asserts enable-audit-log enable-transit-readable-response enable-demo-users disable-secure-session-cookies enable-smtp enable-webhooks"
|
||||
|
||||
set -ex
|
||||
|
||||
if [ "$1" = "--watch" ]; then
|
||||
echo "Start Watch..."
|
||||
|
||||
clojure -A:dev -M -m app.main &
|
||||
PID=$!
|
||||
|
||||
npx nodemon \
|
||||
--watch src \
|
||||
--watch ../common \
|
||||
--ext "clj" \
|
||||
--signal SIGKILL \
|
||||
--exec 'echo "(user/restart)" | nc -N localhost 6062'
|
||||
|
||||
kill -9 $PID
|
||||
else
|
||||
clojure -A:dev -M -m app.main
|
||||
fi
|
||||
26
backend/src/app/auth.clj
Normal file
26
backend/src/app/auth.clj
Normal file
@@ -0,0 +1,26 @@
|
||||
;; 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.auth
|
||||
(:require
|
||||
[buddy.hashers :as hashers]))
|
||||
|
||||
(defn derive-password
|
||||
[password]
|
||||
(hashers/derive password
|
||||
{:alg :argon2id
|
||||
:memory 16384
|
||||
:iterations 20
|
||||
:parallelism 2}))
|
||||
|
||||
(defn verify-password
|
||||
[attempt password]
|
||||
(try
|
||||
(hashers/verify attempt password)
|
||||
(catch Throwable _
|
||||
{:update false
|
||||
:valid false})))
|
||||
|
||||
139
backend/src/app/auth/ldap.clj
Normal file
139
backend/src/app/auth/ldap.clj
Normal file
@@ -0,0 +1,139 @@
|
||||
;; 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.auth.ldap
|
||||
(:require
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.logging :as l]
|
||||
[app.common.spec :as us]
|
||||
[app.config :as cf]
|
||||
[clj-ldap.client :as ldap]
|
||||
[clojure.spec.alpha :as s]
|
||||
[clojure.string]
|
||||
[integrant.core :as ig]))
|
||||
|
||||
(defn- prepare-params
|
||||
[cfg]
|
||||
{:ssl? (:ssl cfg)
|
||||
:startTLS? (:tls cfg)
|
||||
:bind-dn (:bind-dn cfg)
|
||||
:password (:bind-password cfg)
|
||||
:host {:address (:host cfg)
|
||||
:port (:port cfg)}})
|
||||
|
||||
(defn- connect
|
||||
"Connects to the LDAP provider and returns a connection. An
|
||||
exception is raised if no connection is possible."
|
||||
^java.lang.AutoCloseable
|
||||
[cfg]
|
||||
(try
|
||||
(-> cfg prepare-params ldap/connect)
|
||||
(catch Throwable cause
|
||||
(ex/raise :type :restriction
|
||||
:code :unable-to-connect-to-ldap
|
||||
:hint "unable to connect to ldap server"
|
||||
:cause cause))))
|
||||
|
||||
(defn- replace-several [s & {:as replacements}]
|
||||
(reduce-kv clojure.string/replace s replacements))
|
||||
|
||||
(defn- search-user
|
||||
[{:keys [::conn base-dn] :as cfg} email]
|
||||
(let [query (replace-several (:query cfg) ":username" email)
|
||||
attrs [(:attrs-username cfg)
|
||||
(:attrs-email cfg)
|
||||
(:attrs-fullname cfg)]
|
||||
params {:filter query
|
||||
:sizelimit 1
|
||||
:attributes attrs}]
|
||||
(first (ldap/search conn base-dn params))))
|
||||
|
||||
(defn- retrieve-user
|
||||
[{:keys [::conn] :as cfg} {:keys [email password]}]
|
||||
(when-let [{:keys [dn] :as user} (search-user cfg email)]
|
||||
(when (ldap/bind? conn dn password)
|
||||
{:fullname (get user (-> cfg :attrs-fullname keyword))
|
||||
:email email
|
||||
:backend "ldap"})))
|
||||
|
||||
(s/def ::fullname ::us/not-empty-string)
|
||||
(s/def ::email ::us/email)
|
||||
(s/def ::backend ::us/not-empty-string)
|
||||
|
||||
(s/def ::info-data
|
||||
(s/keys :req-un [::fullname ::email ::backend]))
|
||||
|
||||
(defn authenticate
|
||||
[cfg params]
|
||||
(with-open [conn (connect cfg)]
|
||||
(when-let [user (-> (assoc cfg ::conn conn)
|
||||
(retrieve-user params))]
|
||||
(when-not (s/valid? ::info-data user)
|
||||
(let [explain (s/explain-str ::info-data user)]
|
||||
(l/warn ::l/raw (str "invalid response from ldap, looks like ldap is not configured correctly\n" explain))
|
||||
(ex/raise :type :restriction
|
||||
:code :wrong-ldap-response
|
||||
:explain explain)))
|
||||
user)))
|
||||
|
||||
(defn- try-connectivity
|
||||
[cfg]
|
||||
;; If we have ldap parameters, try to establish connection
|
||||
(when (and (:bind-dn cfg)
|
||||
(:bind-password cfg)
|
||||
(:host cfg)
|
||||
(:port cfg))
|
||||
(try
|
||||
(with-open [_ (connect cfg)]
|
||||
(l/info :hint "provider initialized"
|
||||
:provider "ldap"
|
||||
:host (:host cfg)
|
||||
:port (:port cfg)
|
||||
:tls? (:tls cfg)
|
||||
:ssl? (:ssl cfg)
|
||||
:bind-dn (:bind-dn cfg)
|
||||
:base-dn (:base-dn cfg)
|
||||
:query (:query cfg))
|
||||
cfg)
|
||||
(catch Throwable cause
|
||||
(l/error :hint "unable to connect to LDAP server (LDAP auth provider disabled)"
|
||||
:host (:host cfg) :port (:port cfg) :cause cause)
|
||||
nil))))
|
||||
|
||||
(s/def ::enabled? ::us/boolean)
|
||||
(s/def ::host ::cf/ldap-host)
|
||||
(s/def ::port ::cf/ldap-port)
|
||||
(s/def ::ssl ::cf/ldap-ssl)
|
||||
(s/def ::tls ::cf/ldap-starttls)
|
||||
(s/def ::query ::cf/ldap-user-query)
|
||||
(s/def ::base-dn ::cf/ldap-base-dn)
|
||||
(s/def ::bind-dn ::cf/ldap-bind-dn)
|
||||
(s/def ::bind-password ::cf/ldap-bind-password)
|
||||
(s/def ::attrs-email ::cf/ldap-attrs-email)
|
||||
(s/def ::attrs-fullname ::cf/ldap-attrs-fullname)
|
||||
(s/def ::attrs-username ::cf/ldap-attrs-username)
|
||||
|
||||
(s/def ::provider-params
|
||||
(s/keys :opt-un [::host ::port
|
||||
::ssl ::tls
|
||||
::enabled?
|
||||
::bind-dn
|
||||
::bind-password
|
||||
::query
|
||||
::attrs-email
|
||||
::attrs-username
|
||||
::attrs-fullname]))
|
||||
(s/def ::provider
|
||||
(s/nilable ::provider-params))
|
||||
|
||||
(defmethod ig/pre-init-spec ::provider
|
||||
[_]
|
||||
(s/spec ::provider))
|
||||
|
||||
(defmethod ig/init-key ::provider
|
||||
[_ cfg]
|
||||
(when (:enabled? cfg)
|
||||
(try-connectivity cfg)))
|
||||
551
backend/src/app/auth/oidc.clj
Normal file
551
backend/src/app/auth/oidc.clj
Normal file
@@ -0,0 +1,551 @@
|
||||
;; 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.auth.oidc
|
||||
"OIDC client implementation."
|
||||
(:require
|
||||
[app.auth.oidc.providers :as-alias providers]
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.logging :as l]
|
||||
[app.common.spec :as us]
|
||||
[app.common.uri :as u]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.http.client :as http]
|
||||
[app.http.middleware :as hmw]
|
||||
[app.http.session :as session]
|
||||
[app.loggers.audit :as audit]
|
||||
[app.main :as-alias main]
|
||||
[app.rpc.queries.profile :as profile]
|
||||
[app.tokens :as tokens]
|
||||
[app.util.json :as json]
|
||||
[app.util.time :as dt]
|
||||
[app.worker :as wrk]
|
||||
[clojure.set :as set]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[integrant.core :as ig]
|
||||
[promesa.core :as p]
|
||||
[promesa.exec :as px]
|
||||
[yetti.response :as yrs]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; HELPERS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn- obfuscate-string
|
||||
[s]
|
||||
(if (< (count s) 10)
|
||||
(apply str (take (count s) (repeat "*")))
|
||||
(str (subs s 0 5)
|
||||
(apply str (take (- (count s) 5) (repeat "*"))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; OIDC PROVIDER (GENERIC)
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn- discover-oidc-config
|
||||
[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)}
|
||||
{:sync? true}))]
|
||||
(cond
|
||||
(ex/exception? response)
|
||||
(do
|
||||
(l/warn :hint "unable to discover oidc configuration"
|
||||
:discover-uri (str discovery-uri)
|
||||
:cause response)
|
||||
nil)
|
||||
|
||||
(= 200 (:status response))
|
||||
(let [data (json/decode (:body response))]
|
||||
{:token-uri (get data :token_endpoint)
|
||||
:auth-uri (get data :authorization_endpoint)
|
||||
:user-uri (get data :userinfo_endpoint)})
|
||||
|
||||
:else
|
||||
(do
|
||||
(l/warn :hint "unable to discover OIDC configuration"
|
||||
:uri (str discovery-uri)
|
||||
:response-status-code (:status response))
|
||||
nil))))
|
||||
|
||||
(defn- prepare-oidc-opts
|
||||
[cfg]
|
||||
(let [opts {:base-uri (cf/get :oidc-base-uri)
|
||||
:client-id (cf/get :oidc-client-id)
|
||||
:client-secret (cf/get :oidc-client-secret)
|
||||
:token-uri (cf/get :oidc-token-uri)
|
||||
:auth-uri (cf/get :oidc-auth-uri)
|
||||
:user-uri (cf/get :oidc-user-uri)
|
||||
:scopes (cf/get :oidc-scopes #{"openid" "profile" "email"})
|
||||
:roles-attr (cf/get :oidc-roles-attr)
|
||||
:roles (cf/get :oidc-roles)
|
||||
:name "oidc"}
|
||||
|
||||
opts (d/without-nils opts)]
|
||||
|
||||
(when (and (string? (:base-uri opts))
|
||||
(string? (:client-id opts))
|
||||
(string? (:client-secret opts)))
|
||||
(if (and (string? (:token-uri opts))
|
||||
(string? (:user-uri opts))
|
||||
(string? (:auth-uri opts)))
|
||||
opts
|
||||
(some-> (discover-oidc-config cfg opts)
|
||||
(merge opts {:discover? true}))))))
|
||||
|
||||
(defmethod ig/pre-init-spec ::providers/generic [_]
|
||||
(s/keys :req [::http/client]))
|
||||
|
||||
(defmethod ig/init-key ::providers/generic
|
||||
[_ cfg]
|
||||
(when (contains? cf/flags :login-with-oidc)
|
||||
(if-let [opts (prepare-oidc-opts cfg)]
|
||||
(do
|
||||
(l/info :hint "provider initialized"
|
||||
:provider :oidc
|
||||
:method (if (:discover? opts) "discover" "manual")
|
||||
:client-id (:client-id opts)
|
||||
:client-secret (obfuscate-string (:client-secret opts))
|
||||
:scopes (str/join "," (:scopes opts))
|
||||
:auth-uri (:auth-uri opts)
|
||||
:user-uri (:user-uri opts)
|
||||
:token-uri (:token-uri opts)
|
||||
:roles-attr (:roles-attr opts)
|
||||
:roles (:roles opts))
|
||||
opts)
|
||||
(do
|
||||
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :oidc)
|
||||
nil))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; GOOGLE AUTH PROVIDER
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defmethod ig/init-key ::providers/google
|
||||
[_ _]
|
||||
(let [opts {:client-id (cf/get :google-client-id)
|
||||
:client-secret (cf/get :google-client-secret)
|
||||
:scopes #{"openid" "email" "profile"}
|
||||
:auth-uri "https://accounts.google.com/o/oauth2/v2/auth"
|
||||
:token-uri "https://oauth2.googleapis.com/token"
|
||||
:user-uri "https://openidconnect.googleapis.com/v1/userinfo"
|
||||
:name "google"}]
|
||||
|
||||
(when (contains? cf/flags :login-with-google)
|
||||
(if (and (string? (:client-id opts))
|
||||
(string? (:client-secret opts)))
|
||||
(do
|
||||
(l/info :hint "provider initialized"
|
||||
:provider :google
|
||||
:client-id (:client-id opts)
|
||||
:client-secret (obfuscate-string (:client-secret opts)))
|
||||
opts)
|
||||
|
||||
(do
|
||||
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :google)
|
||||
nil)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; GITHUB AUTH PROVIDER
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn- retrieve-github-email
|
||||
[cfg tdata info]
|
||||
(or (some-> info :email p/resolved)
|
||||
(->> (http/req! cfg
|
||||
{:uri "https://api.github.com/user/emails"
|
||||
:headers {"Authorization" (dm/str (:type tdata) " " (:token tdata))}
|
||||
:timeout 6000
|
||||
:method :get})
|
||||
(p/map (fn [{:keys [status body] :as response}]
|
||||
(when-not (s/int-in-range? 200 300 status)
|
||||
(ex/raise :type :internal
|
||||
:code :unable-to-retrieve-github-emails
|
||||
:hint "unable to retrieve github emails"
|
||||
:http-status status
|
||||
:http-body body))
|
||||
(->> response :body json/decode (filter :primary) first :email))))))
|
||||
|
||||
(defmethod ig/pre-init-spec ::providers/github [_]
|
||||
(s/keys :req [::http/client]))
|
||||
|
||||
(defmethod ig/init-key ::providers/github
|
||||
[_ cfg]
|
||||
(let [opts {:client-id (cf/get :github-client-id)
|
||||
:client-secret (cf/get :github-client-secret)
|
||||
:scopes #{"read:user" "user:email"}
|
||||
:auth-uri "https://github.com/login/oauth/authorize"
|
||||
:token-uri "https://github.com/login/oauth/access_token"
|
||||
:user-uri "https://api.github.com/user"
|
||||
:name "github"
|
||||
|
||||
;; Additional hooks for provider specific way of
|
||||
;; retrieve emails.
|
||||
:get-email-fn (partial retrieve-github-email cfg)}]
|
||||
|
||||
(when (contains? cf/flags :login-with-github)
|
||||
(if (and (string? (:client-id opts))
|
||||
(string? (:client-secret opts)))
|
||||
(do
|
||||
(l/info :hint "provider initialized"
|
||||
:provider :github
|
||||
:client-id (:client-id opts)
|
||||
:client-secret (obfuscate-string (:client-secret opts)))
|
||||
opts)
|
||||
|
||||
(do
|
||||
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :github)
|
||||
nil)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; GITLAB AUTH PROVIDER
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defmethod ig/init-key ::providers/gitlab
|
||||
[_ _]
|
||||
(let [base (cf/get :gitlab-base-uri "https://gitlab.com")
|
||||
opts {:base-uri base
|
||||
:client-id (cf/get :gitlab-client-id)
|
||||
:client-secret (cf/get :gitlab-client-secret)
|
||||
:scopes #{"openid" "profile" "email"}
|
||||
:auth-uri (str base "/oauth/authorize")
|
||||
:token-uri (str base "/oauth/token")
|
||||
:user-uri (str base "/oauth/userinfo")
|
||||
:name "gitlab"}]
|
||||
(when (contains? cf/flags :login-with-gitlab)
|
||||
(if (and (string? (:client-id opts))
|
||||
(string? (:client-secret opts)))
|
||||
(do
|
||||
(l/info :hint "provider initialized"
|
||||
:provider :gitlab
|
||||
:base-uri base
|
||||
:client-id (:client-id opts)
|
||||
:client-secret (obfuscate-string (:client-secret opts)))
|
||||
opts)
|
||||
|
||||
(do
|
||||
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :gitlab)
|
||||
nil)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; HANDLERS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn- build-redirect-uri
|
||||
[{:keys [provider] :as cfg}]
|
||||
(let [public (u/uri (cf/get :public-uri))]
|
||||
(str (assoc public :path (str "/api/auth/oauth/" (:name provider) "/callback")))))
|
||||
|
||||
(defn- build-auth-uri
|
||||
[{:keys [provider] :as cfg} state]
|
||||
(let [params {:client_id (:client-id provider)
|
||||
:redirect_uri (build-redirect-uri cfg)
|
||||
:response_type "code"
|
||||
:state state
|
||||
:scope (str/join " " (:scopes provider []))}
|
||||
query (u/map->query-string params)]
|
||||
(-> (u/uri (:auth-uri provider))
|
||||
(assoc :query query)
|
||||
(str))))
|
||||
|
||||
(defn- qualify-props
|
||||
[provider props]
|
||||
(reduce-kv (fn [result k v]
|
||||
(assoc result (keyword (:name provider) (name k)) v))
|
||||
{}
|
||||
props))
|
||||
|
||||
(defn retrieve-access-token
|
||||
[{:keys [provider] :as cfg} code]
|
||||
(let [params {:client_id (:client-id provider)
|
||||
:client_secret (:client-secret provider)
|
||||
:code code
|
||||
:grant_type "authorization_code"
|
||||
:redirect_uri (build-redirect-uri cfg)}
|
||||
req {:method :post
|
||||
:headers {"content-type" "application/x-www-form-urlencoded"
|
||||
"accept" "application/json"}
|
||||
:uri (:token-uri provider)
|
||||
:body (u/map->query-string params)}]
|
||||
(->> (http/req! cfg req)
|
||||
(p/map (fn [{:keys [status body] :as res}]
|
||||
(if (= status 200)
|
||||
(let [data (json/decode body)]
|
||||
{:token (get data :access_token)
|
||||
:type (get data :token_type)})
|
||||
(ex/raise :type :internal
|
||||
:code :unable-to-retrieve-token
|
||||
:http-status status
|
||||
:http-body body)))))))
|
||||
|
||||
(defn- retrieve-user-info
|
||||
[{:keys [provider] :as cfg} tdata]
|
||||
(letfn [(retrieve []
|
||||
(http/req! cfg
|
||||
{:uri (:user-uri provider)
|
||||
:headers {"Authorization" (str (:type tdata) " " (:token tdata))}
|
||||
:timeout 6000
|
||||
:method :get}))
|
||||
(validate-response [response]
|
||||
(when-not (s/int-in-range? 200 300 (:status response))
|
||||
(ex/raise :type :internal
|
||||
:code :unable-to-retrieve-user-info
|
||||
:hint "unable to retrieve user info"
|
||||
:http-status (:status response)
|
||||
:http-body (:body response)))
|
||||
response)
|
||||
|
||||
(get-email [info]
|
||||
;; Allow providers hook into this for custom email
|
||||
;; retrieval method.
|
||||
(if-let [get-email-fn (:get-email-fn provider)]
|
||||
(get-email-fn tdata info)
|
||||
(let [attr-kw (cf/get :oidc-email-attr :email)]
|
||||
(get info attr-kw))))
|
||||
|
||||
(get-name [info]
|
||||
(let [attr-kw (cf/get :oidc-name-attr :name)]
|
||||
(get info attr-kw)))
|
||||
|
||||
(process-response [response]
|
||||
(p/let [info (-> response :body json/decode)
|
||||
email (get-email info)]
|
||||
{:backend (:name provider)
|
||||
:email email
|
||||
:fullname (or (get-name info) email)
|
||||
:props (->> (dissoc info :name :email)
|
||||
(qualify-props provider))}))
|
||||
|
||||
(validate-info [info]
|
||||
(when-not (s/valid? ::info info)
|
||||
(l/warn :hint "received incomplete profile info object (please set correct scopes)"
|
||||
:info (pr-str info))
|
||||
(ex/raise :type :internal
|
||||
:code :incomplete-user-info
|
||||
:hint "inconmplete user info"
|
||||
:info info))
|
||||
info)]
|
||||
|
||||
(-> (retrieve)
|
||||
(p/then validate-response)
|
||||
(p/then process-response)
|
||||
(p/then validate-info))))
|
||||
|
||||
(s/def ::backend ::us/not-empty-string)
|
||||
(s/def ::email ::us/not-empty-string)
|
||||
(s/def ::fullname ::us/not-empty-string)
|
||||
(s/def ::props (s/map-of ::us/keyword any?))
|
||||
(s/def ::info
|
||||
(s/keys :req-un [::backend
|
||||
::email
|
||||
::fullname
|
||||
::props]))
|
||||
|
||||
(defn retrieve-info
|
||||
[{:keys [provider] :as cfg} {:keys [params] :as request}]
|
||||
(letfn [(validate-oidc [info]
|
||||
;; If the provider is OIDC, we can proceed to check
|
||||
;; roles if they are defined.
|
||||
(when (and (= "oidc" (:name provider))
|
||||
(seq (:roles provider)))
|
||||
(let [provider-roles (into #{} (:roles provider))
|
||||
profile-roles (let [attr (cf/get :oidc-roles-attr :roles)
|
||||
roles (get info attr)]
|
||||
(cond
|
||||
(string? roles) (into #{} (str/words roles))
|
||||
(vector? roles) (into #{} roles)
|
||||
:else #{}))]
|
||||
|
||||
;; check if profile has a configured set of roles
|
||||
(when-not (set/subset? provider-roles profile-roles)
|
||||
(ex/raise :type :internal
|
||||
:code :unable-to-auth
|
||||
:hint "not enough permissions"))))
|
||||
info)
|
||||
|
||||
(post-process [state info]
|
||||
(cond-> info
|
||||
(some? (:invitation-token state))
|
||||
(assoc :invitation-token (:invitation-token state))
|
||||
|
||||
;; If state token comes with props, merge them. The state token
|
||||
;; props can contain pm_ and utm_ prefixed query params.
|
||||
(map? (:props state))
|
||||
(update :props merge (:props state))))]
|
||||
|
||||
(when-let [error (get params :error)]
|
||||
(ex/raise :type :internal
|
||||
:code :error-on-retrieving-code
|
||||
:error-id error
|
||||
:error-desc (get params :error_description)))
|
||||
|
||||
(let [state (get params :state)
|
||||
code (get params :code)
|
||||
state (tokens/verify (::main/props cfg) {:token state :iss :oauth})]
|
||||
(-> (p/resolved code)
|
||||
(p/then #(retrieve-access-token cfg %))
|
||||
(p/then #(retrieve-user-info cfg %))
|
||||
(p/then' validate-oidc)
|
||||
(p/then' (partial post-process state))))))
|
||||
|
||||
(defn- retrieve-profile
|
||||
[{:keys [::db/pool ::wrk/executor] :as cfg} info]
|
||||
(px/with-dispatch executor
|
||||
(with-open [conn (db/open pool)]
|
||||
(some->> (:email info)
|
||||
(profile/retrieve-profile-data-by-email conn)
|
||||
(profile/populate-additional-data conn)
|
||||
(profile/decode-profile-row)))))
|
||||
|
||||
(defn- redirect-response
|
||||
[uri]
|
||||
(yrs/response :status 302 :headers {"location" (str uri)}))
|
||||
|
||||
(defn- generate-error-redirect
|
||||
[_ error]
|
||||
(let [uri (-> (u/uri (cf/get :public-uri))
|
||||
(assoc :path "/#/auth/login")
|
||||
(assoc :query (u/map->query-string {:error "unable-to-auth" :hint (ex-message error)})))]
|
||||
(redirect-response uri)))
|
||||
|
||||
(defn- generate-redirect
|
||||
[{:keys [::session/session] :as cfg} request info profile]
|
||||
(if profile
|
||||
(let [sxf (session/create-fn session (:id profile))
|
||||
token (or (:invitation-token info)
|
||||
(tokens/generate (::main/props cfg)
|
||||
{:iss :auth
|
||||
:exp (dt/in-future "15m")
|
||||
:profile-id (:id profile)}))
|
||||
params {:token token}
|
||||
uri (-> (u/uri (cf/get :public-uri))
|
||||
(assoc :path "/#/auth/verify-token")
|
||||
(assoc :query (u/map->query-string params)))]
|
||||
|
||||
(when (:is-blocked profile)
|
||||
(ex/raise :type :restriction
|
||||
:code :profile-blocked))
|
||||
|
||||
(when-let [collector (::audit/collector cfg)]
|
||||
(audit/submit! collector {:type "command"
|
||||
:name "login"
|
||||
:profile-id (:id profile)
|
||||
:ip-addr (audit/parse-client-ip request)
|
||||
:props (audit/profile->props profile)}))
|
||||
|
||||
(->> (redirect-response uri)
|
||||
(sxf request)))
|
||||
|
||||
(let [info (assoc info
|
||||
:iss :prepared-register
|
||||
:is-active true
|
||||
:exp (dt/in-future {:hours 48}))
|
||||
token (tokens/generate (::main/props cfg) info)
|
||||
params (d/without-nils
|
||||
{:token token
|
||||
:fullname (:fullname info)})
|
||||
uri (-> (u/uri (cf/get :public-uri))
|
||||
(assoc :path "/#/auth/register/validate")
|
||||
(assoc :query (u/map->query-string params)))]
|
||||
(redirect-response uri))))
|
||||
|
||||
(defn- auth-handler
|
||||
[cfg {:keys [params] :as request}]
|
||||
(let [props (audit/extract-utm-params params)
|
||||
state (tokens/generate (::main/props cfg)
|
||||
{:iss :oauth
|
||||
:invitation-token (:invitation-token params)
|
||||
:props props
|
||||
:exp (dt/in-future "4h")})
|
||||
uri (build-auth-uri cfg state)]
|
||||
(yrs/response 200 {:redirect-uri uri})))
|
||||
|
||||
(defn- callback-handler
|
||||
[cfg request]
|
||||
(letfn [(process-request []
|
||||
(p/let [info (retrieve-info cfg request)
|
||||
profile (retrieve-profile cfg info)]
|
||||
(generate-redirect cfg request info profile)))
|
||||
|
||||
(handle-error [cause]
|
||||
(l/error :hint "error on oauth process" :cause cause)
|
||||
(generate-error-redirect cfg cause))]
|
||||
|
||||
(-> (process-request)
|
||||
(p/catch handle-error))))
|
||||
|
||||
(def provider-lookup
|
||||
{:compile
|
||||
(fn [& _]
|
||||
(fn [handler]
|
||||
(fn [{:keys [::providers] :as cfg} request]
|
||||
(let [provider (some-> request :path-params :provider keyword)]
|
||||
(if-let [provider (get providers provider)]
|
||||
(handler (assoc cfg :provider provider) request)
|
||||
(ex/raise :type :restriction
|
||||
:code :provider-not-configured
|
||||
:provider provider
|
||||
:hint "provider not configured"))))))})
|
||||
|
||||
|
||||
(s/def ::client-id ::cf/oidc-client-id)
|
||||
(s/def ::client-secret ::cf/oidc-client-secret)
|
||||
(s/def ::base-uri ::cf/oidc-base-uri)
|
||||
(s/def ::token-uri ::cf/oidc-token-uri)
|
||||
(s/def ::auth-uri ::cf/oidc-auth-uri)
|
||||
(s/def ::user-uri ::cf/oidc-user-uri)
|
||||
(s/def ::scopes ::cf/oidc-scopes)
|
||||
(s/def ::roles ::cf/oidc-roles)
|
||||
(s/def ::roles-attr ::cf/oidc-roles-attr)
|
||||
(s/def ::email-attr ::cf/oidc-email-attr)
|
||||
(s/def ::name-attr ::cf/oidc-name-attr)
|
||||
|
||||
;; FIXME: migrate to qualified-keywords
|
||||
(s/def ::provider
|
||||
(s/keys :req-un [::client-id
|
||||
::client-secret]
|
||||
:opt-un [::base-uri
|
||||
::token-uri
|
||||
::auth-uri
|
||||
::user-uri
|
||||
::scopes
|
||||
::roles
|
||||
::roles-attr
|
||||
::email-attr
|
||||
::name-attr]))
|
||||
|
||||
(s/def ::providers (s/map-of ::us/keyword (s/nilable ::provider)))
|
||||
|
||||
(defmethod ig/pre-init-spec ::routes
|
||||
[_]
|
||||
(s/keys :req [::http/client
|
||||
::wrk/executor
|
||||
::main/props
|
||||
::db/pool
|
||||
::providers
|
||||
::session/session]))
|
||||
|
||||
(defmethod ig/init-key ::routes
|
||||
[_ {:keys [::wrk/executor ::session/session] :as cfg}]
|
||||
(let [cfg (update cfg :provider d/without-nils)]
|
||||
["" {:middleware [[(:middleware session)]
|
||||
[hmw/with-dispatch executor]
|
||||
[hmw/with-config cfg]
|
||||
[provider-lookup]
|
||||
]}
|
||||
["/auth/oauth"
|
||||
["/:provider"
|
||||
{:handler auth-handler
|
||||
:allowed-methods #{:post}}]
|
||||
["/:provider/callback"
|
||||
{:handler callback-handler
|
||||
:allowed-methods #{:get}}]]]))
|
||||
|
||||
169
backend/src/app/cli/manage.clj
Normal file
169
backend/src/app/cli/manage.clj
Normal file
@@ -0,0 +1,169 @@
|
||||
;; 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.cli.manage
|
||||
"A manage cli api."
|
||||
(:require
|
||||
[app.common.logging :as l]
|
||||
[app.db :as db]
|
||||
[app.main :as main]
|
||||
[app.rpc.commands.auth :as cmd.auth]
|
||||
[app.rpc.mutations.profile :as profile]
|
||||
[app.rpc.queries.profile :refer [retrieve-profile-data-by-email]]
|
||||
[clojure.string :as str]
|
||||
[clojure.tools.cli :refer [parse-opts]]
|
||||
[integrant.core :as ig])
|
||||
(:import
|
||||
java.io.Console))
|
||||
|
||||
;; --- IMPL
|
||||
|
||||
(defn init-system
|
||||
[]
|
||||
(let [data (-> main/system-config
|
||||
(select-keys [:app.db/pool :app.metrics/metrics])
|
||||
(assoc :app.migrations/all {}))]
|
||||
(-> data ig/prep ig/init)))
|
||||
|
||||
(defn- read-from-console
|
||||
[{:keys [label type] :or {type :text}}]
|
||||
(let [^Console console (System/console)]
|
||||
(when-not console
|
||||
(l/error :hint "no console found, can proceed")
|
||||
(System/exit 1))
|
||||
|
||||
(binding [*out* (.writer console)]
|
||||
(print label " ")
|
||||
(.flush *out*))
|
||||
|
||||
(case type
|
||||
:text (.readLine console)
|
||||
:password (String. (.readPassword console)))))
|
||||
|
||||
(defn create-profile
|
||||
[options]
|
||||
(let [system (init-system)
|
||||
email (or (:email options)
|
||||
(read-from-console {:label "Email:"}))
|
||||
fullname (or (:fullname options)
|
||||
(read-from-console {:label "Full Name:"}))
|
||||
password (or (:password options)
|
||||
(read-from-console {:label "Password:"
|
||||
:type :password}))]
|
||||
(try
|
||||
(db/with-atomic [conn (:app.db/pool system)]
|
||||
(->> (cmd.auth/create-profile conn
|
||||
{:fullname fullname
|
||||
:email email
|
||||
:password password
|
||||
:is-active true
|
||||
:is-demo false})
|
||||
(cmd.auth/create-profile-relations conn)))
|
||||
|
||||
(when (pos? (:verbosity options))
|
||||
(println "User created successfully."))
|
||||
(System/exit 0)
|
||||
|
||||
(catch Exception _e
|
||||
(when (pos? (:verbosity options))
|
||||
(println "Unable to create user, already exists."))
|
||||
(System/exit 1)))))
|
||||
|
||||
(defn reset-password
|
||||
[options]
|
||||
(let [system (init-system)]
|
||||
(try
|
||||
(db/with-atomic [conn (:app.db/pool system)]
|
||||
(let [email (or (:email options)
|
||||
(read-from-console {:label "Email:"}))
|
||||
profile (retrieve-profile-data-by-email conn email)]
|
||||
(when-not profile
|
||||
(when (pos? (:verbosity options))
|
||||
(println "Profile does not exists."))
|
||||
(System/exit 1))
|
||||
|
||||
(let [password (or (:password options)
|
||||
(read-from-console {:label "Password:"
|
||||
:type :password}))]
|
||||
(profile/update-profile-password! conn (assoc profile :password password))
|
||||
(when (pos? (:verbosity options))
|
||||
(println "Password changed successfully.")))))
|
||||
(System/exit 0)
|
||||
(catch Exception e
|
||||
(when (pos? (:verbosity options))
|
||||
(println "Unable to change password."))
|
||||
(when (= 2 (:verbosity options))
|
||||
(.printStackTrace e))
|
||||
(System/exit 1)))))
|
||||
|
||||
;; --- CLI PARSE
|
||||
|
||||
(def cli-options
|
||||
;; An option with a required argument
|
||||
[["-u" "--email EMAIL" "Email Address"]
|
||||
["-p" "--password PASSWORD" "Password"]
|
||||
["-n" "--name FULLNAME" "Full Name"
|
||||
:id :fullname]
|
||||
["-v" nil "Verbosity level"
|
||||
:id :verbosity
|
||||
:default 1
|
||||
:update-fn inc]
|
||||
["-q" nil "Don't print to console"
|
||||
:id :verbosity
|
||||
:update-fn (constantly 0)]
|
||||
["-h" "--help"]])
|
||||
|
||||
(defn usage
|
||||
[options-summary]
|
||||
(->> ["Penpot CLI management."
|
||||
""
|
||||
"Usage: manage [options] action"
|
||||
""
|
||||
"Options:"
|
||||
options-summary
|
||||
""
|
||||
"Actions:"
|
||||
" create-profile Create new profile."
|
||||
" reset-password Reset profile password."
|
||||
""]
|
||||
(str/join \newline)))
|
||||
|
||||
(defn error-msg [errors]
|
||||
(str "The following errors occurred while parsing your command:\n\n"
|
||||
(str/join \newline errors)))
|
||||
|
||||
(defn validate-args
|
||||
"Validate command line arguments. Either return a map indicating the program
|
||||
should exit (with a error message, and optional ok status), or a map
|
||||
indicating the action the program should take and the options provided."
|
||||
[args]
|
||||
(let [{:keys [options arguments errors summary] :as opts} (parse-opts args cli-options)]
|
||||
(cond
|
||||
(:help options) ; help => exit OK with usage summary
|
||||
{:exit-message (usage summary) :ok? true}
|
||||
|
||||
errors ; errors => exit with description of errors
|
||||
{:exit-message (error-msg errors)}
|
||||
|
||||
;; custom validation on arguments
|
||||
:else
|
||||
(let [action (first arguments)]
|
||||
(if (#{"create-profile" "reset-password"} action)
|
||||
{:action (first arguments) :options options}
|
||||
{:exit-message (usage summary)})))))
|
||||
|
||||
(defn exit [status msg]
|
||||
(println msg)
|
||||
(System/exit status))
|
||||
|
||||
(defn -main
|
||||
[& args]
|
||||
(let [{:keys [action options exit-message ok?]} (validate-args args)]
|
||||
(if exit-message
|
||||
(exit (if ok? 0 1) exit-message)
|
||||
(case action
|
||||
"create-profile" (create-profile options)
|
||||
"reset-password" (reset-password options)))))
|
||||
386
backend/src/app/config.clj
Normal file
386
backend/src/app/config.clj
Normal file
@@ -0,0 +1,386 @@
|
||||
;; 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.config
|
||||
"A configuration management."
|
||||
(:refer-clojure :exclude [get])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.flags :as flags]
|
||||
[app.common.spec :as us]
|
||||
[app.common.version :as v]
|
||||
[app.util.time :as dt]
|
||||
[clojure.core :as c]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.pprint :as pprint]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[datoteka.fs :as fs]
|
||||
[environ.core :refer [env]]
|
||||
[integrant.core :as ig]))
|
||||
|
||||
(prefer-method print-method
|
||||
clojure.lang.IRecord
|
||||
clojure.lang.IDeref)
|
||||
|
||||
(prefer-method print-method
|
||||
clojure.lang.IPersistentMap
|
||||
clojure.lang.IDeref)
|
||||
|
||||
(prefer-method pprint/simple-dispatch
|
||||
clojure.lang.IPersistentMap
|
||||
clojure.lang.IDeref)
|
||||
|
||||
(defmethod ig/init-key :default
|
||||
[_ data]
|
||||
(d/without-nils data))
|
||||
|
||||
(defmethod ig/prep-key :default
|
||||
[_ data]
|
||||
(if (map? data)
|
||||
(d/without-nils data)
|
||||
data))
|
||||
|
||||
(def defaults
|
||||
{:database-uri "postgresql://postgres/penpot"
|
||||
:database-username "penpot"
|
||||
: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")
|
||||
|
||||
:file-change-snapshot-every 5
|
||||
:file-change-snapshot-timeout "3h"
|
||||
|
||||
:public-uri "http://localhost:3449"
|
||||
:host "localhost"
|
||||
:tenant "default"
|
||||
|
||||
:redis-uri "redis://redis/0"
|
||||
|
||||
:assets-storage-backend :assets-fs
|
||||
:storage-assets-fs-directory "assets"
|
||||
|
||||
:assets-path "/internal/assets/"
|
||||
:smtp-default-reply-to "Penpot <no-reply@example.com>"
|
||||
:smtp-default-from "Penpot <no-reply@example.com>"
|
||||
|
||||
:profile-complaint-max-age (dt/duration {:days 7})
|
||||
:profile-complaint-threshold 2
|
||||
|
||||
:profile-bounce-max-age (dt/duration {:days 7})
|
||||
:profile-bounce-threshold 10
|
||||
|
||||
:telemetry-uri "https://telemetry.penpot.app/"
|
||||
|
||||
:ldap-user-query "(|(uid=:username)(mail=:username))"
|
||||
:ldap-attrs-username "uid"
|
||||
:ldap-attrs-email "mail"
|
||||
:ldap-attrs-fullname "cn"
|
||||
|
||||
;; a server prop key where initial project is stored.
|
||||
:initial-project-skey "initial-project"})
|
||||
|
||||
(s/def ::default-rpc-rlimit ::us/vector-of-strings)
|
||||
(s/def ::rpc-rlimit-config ::fs/path)
|
||||
(s/def ::rpc-climit-config ::fs/path)
|
||||
|
||||
(s/def ::media-max-file-size ::us/integer)
|
||||
|
||||
(s/def ::flags ::us/vector-of-keywords)
|
||||
(s/def ::telemetry-enabled ::us/boolean)
|
||||
|
||||
(s/def ::audit-log-archive-uri ::us/string)
|
||||
(s/def ::audit-log-http-handler-concurrency ::us/integer)
|
||||
|
||||
(s/def ::admins ::us/set-of-valid-emails)
|
||||
(s/def ::file-change-snapshot-every ::us/integer)
|
||||
(s/def ::file-change-snapshot-timeout ::dt/duration)
|
||||
|
||||
(s/def ::default-executor-parallelism ::us/integer)
|
||||
(s/def ::scheduled-executor-parallelism ::us/integer)
|
||||
|
||||
(s/def ::worker-default-parallelism ::us/integer)
|
||||
(s/def ::worker-webhook-parallelism ::us/integer)
|
||||
|
||||
(s/def ::authenticated-cookie-domain ::us/string)
|
||||
(s/def ::authenticated-cookie-name ::us/string)
|
||||
(s/def ::auth-token-cookie-name ::us/string)
|
||||
(s/def ::auth-token-cookie-max-age ::dt/duration)
|
||||
|
||||
(s/def ::secret-key ::us/string)
|
||||
(s/def ::allow-demo-users ::us/boolean)
|
||||
(s/def ::assets-path ::us/string)
|
||||
(s/def ::database-password (s/nilable ::us/string))
|
||||
(s/def ::database-uri ::us/string)
|
||||
(s/def ::database-username (s/nilable ::us/string))
|
||||
(s/def ::database-readonly ::us/boolean)
|
||||
(s/def ::database-min-pool-size ::us/integer)
|
||||
(s/def ::database-max-pool-size ::us/integer)
|
||||
|
||||
(s/def ::quotes-teams-per-profile ::us/integer)
|
||||
(s/def ::quotes-projects-per-team ::us/integer)
|
||||
(s/def ::quotes-invitations-per-team ::us/integer)
|
||||
(s/def ::quotes-profiles-per-team ::us/integer)
|
||||
(s/def ::quotes-files-per-project ::us/integer)
|
||||
(s/def ::quotes-files-per-team ::us/integer)
|
||||
(s/def ::quotes-font-variants-per-team ::us/integer)
|
||||
(s/def ::quotes-comment-threads-per-file ::us/integer)
|
||||
(s/def ::quotes-comments-per-file ::us/integer)
|
||||
|
||||
(s/def ::default-blob-version ::us/integer)
|
||||
(s/def ::error-report-webhook ::us/string)
|
||||
(s/def ::user-feedback-destination ::us/string)
|
||||
(s/def ::github-client-id ::us/string)
|
||||
(s/def ::github-client-secret ::us/string)
|
||||
(s/def ::gitlab-base-uri ::us/string)
|
||||
(s/def ::gitlab-client-id ::us/string)
|
||||
(s/def ::gitlab-client-secret ::us/string)
|
||||
(s/def ::google-client-id ::us/string)
|
||||
(s/def ::google-client-secret ::us/string)
|
||||
(s/def ::oidc-client-id ::us/string)
|
||||
(s/def ::oidc-client-secret ::us/string)
|
||||
(s/def ::oidc-base-uri ::us/string)
|
||||
(s/def ::oidc-token-uri ::us/string)
|
||||
(s/def ::oidc-auth-uri ::us/string)
|
||||
(s/def ::oidc-user-uri ::us/string)
|
||||
(s/def ::oidc-scopes ::us/set-of-strings)
|
||||
(s/def ::oidc-roles ::us/set-of-strings)
|
||||
(s/def ::oidc-roles-attr ::us/keyword)
|
||||
(s/def ::oidc-email-attr ::us/keyword)
|
||||
(s/def ::oidc-name-attr ::us/keyword)
|
||||
(s/def ::host ::us/string)
|
||||
(s/def ::http-server-port ::us/integer)
|
||||
(s/def ::http-server-host ::us/string)
|
||||
(s/def ::http-server-max-body-size ::us/integer)
|
||||
(s/def ::http-server-max-multipart-body-size ::us/integer)
|
||||
(s/def ::http-server-io-threads ::us/integer)
|
||||
(s/def ::http-server-worker-threads ::us/integer)
|
||||
(s/def ::ldap-attrs-email ::us/string)
|
||||
(s/def ::ldap-attrs-fullname ::us/string)
|
||||
(s/def ::ldap-attrs-username ::us/string)
|
||||
(s/def ::ldap-base-dn ::us/string)
|
||||
(s/def ::ldap-bind-dn ::us/string)
|
||||
(s/def ::ldap-bind-password ::us/string)
|
||||
(s/def ::ldap-host ::us/string)
|
||||
(s/def ::ldap-port ::us/integer)
|
||||
(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)
|
||||
(s/def ::profile-bounce-threshold ::us/integer)
|
||||
(s/def ::profile-complaint-max-age ::dt/duration)
|
||||
(s/def ::profile-complaint-threshold ::us/integer)
|
||||
(s/def ::public-uri ::us/string)
|
||||
(s/def ::redis-uri ::us/string)
|
||||
(s/def ::registration-domain-whitelist ::us/set-of-strings)
|
||||
|
||||
(s/def ::smtp-default-from ::us/string)
|
||||
(s/def ::smtp-default-reply-to ::us/string)
|
||||
(s/def ::smtp-host ::us/string)
|
||||
(s/def ::smtp-password (s/nilable ::us/string))
|
||||
(s/def ::smtp-port ::us/integer)
|
||||
(s/def ::smtp-ssl ::us/boolean)
|
||||
(s/def ::smtp-tls ::us/boolean)
|
||||
(s/def ::smtp-username (s/nilable ::us/string))
|
||||
(s/def ::urepl-host ::us/string)
|
||||
(s/def ::urepl-port ::us/integer)
|
||||
(s/def ::prepl-host ::us/string)
|
||||
(s/def ::prepl-port ::us/integer)
|
||||
(s/def ::assets-storage-backend ::us/keyword)
|
||||
(s/def ::storage-assets-fs-directory ::us/string)
|
||||
(s/def ::storage-assets-s3-bucket ::us/string)
|
||||
(s/def ::storage-assets-s3-region ::us/keyword)
|
||||
(s/def ::storage-assets-s3-endpoint ::us/string)
|
||||
(s/def ::telemetry-uri ::us/string)
|
||||
(s/def ::telemetry-with-taiga ::us/boolean)
|
||||
(s/def ::tenant ::us/string)
|
||||
|
||||
(s/def ::config
|
||||
(s/keys :opt-un [::secret-key
|
||||
::flags
|
||||
::admins
|
||||
::allow-demo-users
|
||||
::audit-log-archive-uri
|
||||
::audit-log-http-handler-concurrency
|
||||
::auth-token-cookie-name
|
||||
::auth-token-cookie-max-age
|
||||
::authenticated-cookie-name
|
||||
::authenticated-cookie-domain
|
||||
::database-password
|
||||
::database-uri
|
||||
::database-username
|
||||
::database-readonly
|
||||
::database-min-pool-size
|
||||
::database-max-pool-size
|
||||
::default-blob-version
|
||||
::default-rpc-rlimit
|
||||
::error-report-webhook
|
||||
::default-executor-parallelism
|
||||
::scheduled-executor-parallelism
|
||||
::worker-default-parallelism
|
||||
::worker-webhook-parallelism
|
||||
::file-change-snapshot-every
|
||||
::file-change-snapshot-timeout
|
||||
::user-feedback-destination
|
||||
::github-client-id
|
||||
::github-client-secret
|
||||
::gitlab-base-uri
|
||||
::gitlab-client-id
|
||||
::gitlab-client-secret
|
||||
::google-client-id
|
||||
::google-client-secret
|
||||
::oidc-client-id
|
||||
::oidc-client-secret
|
||||
::oidc-base-uri
|
||||
::oidc-token-uri
|
||||
::oidc-auth-uri
|
||||
::oidc-user-uri
|
||||
::oidc-scopes
|
||||
::oidc-roles-attr
|
||||
::oidc-email-attr
|
||||
::oidc-name-attr
|
||||
::oidc-roles
|
||||
::host
|
||||
::http-server-host
|
||||
::http-server-port
|
||||
::http-server-max-body-size
|
||||
::http-server-max-multipart-body-size
|
||||
::http-server-io-threads
|
||||
::http-server-worker-threads
|
||||
::ldap-attrs-email
|
||||
::ldap-attrs-fullname
|
||||
::ldap-attrs-username
|
||||
::ldap-base-dn
|
||||
::ldap-bind-dn
|
||||
::ldap-bind-password
|
||||
::ldap-host
|
||||
::ldap-port
|
||||
::ldap-ssl
|
||||
::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
|
||||
::profile-complaint-max-age
|
||||
::profile-complaint-threshold
|
||||
::public-uri
|
||||
|
||||
::quotes-teams-per-profile
|
||||
::quotes-projects-per-team
|
||||
::quotes-invitations-per-team
|
||||
::quotes-profiles-per-team
|
||||
::quotes-files-per-project
|
||||
::quotes-files-per-team
|
||||
::quotes-font-variants-per-team
|
||||
::quotes-comment-threads-per-file
|
||||
::quotes-comments-per-file
|
||||
|
||||
::redis-uri
|
||||
::registration-domain-whitelist
|
||||
::rpc-rlimit-config
|
||||
|
||||
::semaphore-process-font
|
||||
::semaphore-process-image
|
||||
::semaphore-update-file
|
||||
::semaphore-auth
|
||||
|
||||
::smtp-default-from
|
||||
::smtp-default-reply-to
|
||||
::smtp-host
|
||||
::smtp-password
|
||||
::smtp-port
|
||||
::smtp-ssl
|
||||
::smtp-tls
|
||||
::smtp-username
|
||||
|
||||
::urepl-host
|
||||
::urepl-port
|
||||
::prepl-host
|
||||
::prepl-port
|
||||
|
||||
::assets-storage-backend
|
||||
::storage-assets-fs-directory
|
||||
::storage-assets-s3-bucket
|
||||
::storage-assets-s3-region
|
||||
::storage-assets-s3-endpoint
|
||||
::telemetry-enabled
|
||||
::telemetry-uri
|
||||
::telemetry-referer
|
||||
::telemetry-with-taiga
|
||||
::tenant]))
|
||||
|
||||
(def default-flags
|
||||
[:enable-backend-api-doc
|
||||
:enable-backend-worker
|
||||
:enable-secure-session-cookies
|
||||
:enable-email-verification])
|
||||
|
||||
(defn- parse-flags
|
||||
[config]
|
||||
(flags/parse flags/default
|
||||
default-flags
|
||||
(:flags config)))
|
||||
|
||||
(defn read-env
|
||||
[prefix]
|
||||
(let [prefix (str prefix "-")
|
||||
len (count prefix)]
|
||||
(reduce-kv
|
||||
(fn [acc k v]
|
||||
(cond-> acc
|
||||
(str/starts-with? (name k) prefix)
|
||||
(assoc (keyword (subs (name k) len)) v)))
|
||||
{}
|
||||
env)))
|
||||
|
||||
(defn- read-config
|
||||
[]
|
||||
(try
|
||||
(->> (read-env "penpot")
|
||||
(merge defaults)
|
||||
(us/conform ::config))
|
||||
(catch Throwable e
|
||||
(when (ex/ex-info? e)
|
||||
(println ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;")
|
||||
(println "Error on validating configuration:")
|
||||
(println (some-> e ex-data ex/explain))
|
||||
(println (ex/explain (ex-data e)))
|
||||
(println ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"))
|
||||
(throw e))))
|
||||
|
||||
(def version
|
||||
(v/parse (or (some-> (io/resource "version.txt")
|
||||
(slurp)
|
||||
(str/trim))
|
||||
"%version%")))
|
||||
|
||||
(defonce ^:dynamic config (read-config))
|
||||
(defonce ^:dynamic flags (parse-flags config))
|
||||
|
||||
(def deletion-delay
|
||||
(dt/duration {:days 7}))
|
||||
|
||||
(defn get
|
||||
"A configuration getter. Helps code be more testable."
|
||||
([key]
|
||||
(c/get config key))
|
||||
([key default]
|
||||
(c/get config key default)))
|
||||
|
||||
;; Set value for all new threads bindings.
|
||||
(alter-var-root #'*assert* (constantly (contains? flags :backend-asserts)))
|
||||
517
backend/src/app/db.clj
Normal file
517
backend/src/app/db.clj
Normal file
@@ -0,0 +1,517 @@
|
||||
;; 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.db
|
||||
(:refer-clojure :exclude [get])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.logging :as l]
|
||||
[app.common.spec :as us]
|
||||
[app.common.transit :as t]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.db.sql :as sql]
|
||||
[app.metrics :as mtx]
|
||||
[app.util.json :as json]
|
||||
[app.util.migrations :as mg]
|
||||
[app.util.time :as dt]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.spec.alpha :as s]
|
||||
[integrant.core :as ig]
|
||||
[next.jdbc :as jdbc]
|
||||
[next.jdbc.date-time :as jdbc-dt])
|
||||
(:import
|
||||
com.zaxxer.hikari.HikariConfig
|
||||
com.zaxxer.hikari.HikariDataSource
|
||||
com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory
|
||||
io.whitfin.siphash.SipHasher
|
||||
io.whitfin.siphash.SipHasherContainer
|
||||
java.io.InputStream
|
||||
java.io.OutputStream
|
||||
java.lang.AutoCloseable
|
||||
java.sql.Connection
|
||||
java.sql.Savepoint
|
||||
org.postgresql.PGConnection
|
||||
org.postgresql.geometric.PGpoint
|
||||
org.postgresql.jdbc.PgArray
|
||||
org.postgresql.largeobject.LargeObject
|
||||
org.postgresql.largeobject.LargeObjectManager
|
||||
org.postgresql.util.PGInterval
|
||||
org.postgresql.util.PGobject))
|
||||
|
||||
(declare open)
|
||||
(declare create-pool)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; 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)
|
||||
(s/def ::username ::us/string)
|
||||
(s/def ::validation-timeout ::us/integer)
|
||||
(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?]))
|
||||
|
||||
(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})
|
||||
|
||||
(defmethod ig/prep-key ::pool
|
||||
[_ cfg]
|
||||
(merge defaults (d/without-nils cfg)))
|
||||
|
||||
;; Don't validate here, just validate that a map is received.
|
||||
(defmethod ig/pre-init-spec ::pool [_] ::pool-options)
|
||||
|
||||
(defmethod ig/init-key ::pool
|
||||
[_ {:keys [migrations read-only? uri] :as cfg}]
|
||||
(if uri
|
||||
(let [pool (create-pool cfg)]
|
||||
(l/info :hint "initialize connection pool"
|
||||
:name (d/name (:name cfg))
|
||||
:uri uri
|
||||
:read-only read-only?
|
||||
:with-credentials (and (contains? cfg :username)
|
||||
(contains? cfg :password))
|
||||
:min-size (:min-size cfg)
|
||||
:max-size (:max-size cfg))
|
||||
(when-not read-only?
|
||||
(some->> (seq migrations) (apply-migrations! pool)))
|
||||
pool)
|
||||
|
||||
(do
|
||||
(l/warn :hint "unable to initialize pool, missing url"
|
||||
:name (d/name (:name cfg))
|
||||
:read-only read-only?)
|
||||
nil)))
|
||||
|
||||
(defmethod ig/halt-key! ::pool
|
||||
[_ pool]
|
||||
(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
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def initsql
|
||||
(str "SET statement_timeout = 300000;\n"
|
||||
"SET idle_in_transaction_session_timeout = 300000;"))
|
||||
|
||||
(defn- create-datasource-config
|
||||
[{:keys [metrics uri] :as cfg}]
|
||||
(let [config (HikariConfig.)]
|
||||
(doto config
|
||||
(.setJdbcUrl (str "jdbc:" uri))
|
||||
(.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))
|
||||
(.setConnectionInitSql initsql)
|
||||
(.setInitializationFailTimeout -1))
|
||||
|
||||
;; When metrics namespace is provided
|
||||
(when metrics
|
||||
(->> (::mtx/registry metrics)
|
||||
(PrometheusMetricsTrackerFactory.)
|
||||
(.setMetricsTrackerFactory config)))
|
||||
|
||||
(some->> ^String (:username cfg) (.setUsername config))
|
||||
(some->> ^String (:password cfg) (.setPassword config))
|
||||
|
||||
config))
|
||||
|
||||
(defn pool?
|
||||
[v]
|
||||
(instance? javax.sql.DataSource v))
|
||||
|
||||
(s/def ::pool pool?)
|
||||
(s/def ::conn-or-pool some?)
|
||||
|
||||
(defn closed?
|
||||
[pool]
|
||||
(.isClosed ^HikariDataSource pool))
|
||||
|
||||
(defn read-only?
|
||||
[pool]
|
||||
(.isReadOnly ^HikariDataSource pool))
|
||||
|
||||
(defn create-pool
|
||||
[cfg]
|
||||
(let [dsc (create-datasource-config cfg)]
|
||||
(jdbc-dt/read-as-instant)
|
||||
(HikariDataSource. dsc)))
|
||||
|
||||
(defn unwrap
|
||||
[conn klass]
|
||||
(.unwrap ^Connection conn klass))
|
||||
|
||||
(defn lobj-manager
|
||||
[conn]
|
||||
(let [conn (unwrap conn org.postgresql.PGConnection)]
|
||||
(.getLargeObjectAPI ^PGConnection conn)))
|
||||
|
||||
(defn lobj-create
|
||||
[manager]
|
||||
(.createLO ^LargeObjectManager manager LargeObjectManager/READWRITE))
|
||||
|
||||
(defn lobj-open
|
||||
([manager oid]
|
||||
(lobj-open manager oid {}))
|
||||
([manager oid {:keys [mode] :or {mode :rw}}]
|
||||
(let [mode (case mode
|
||||
(:r :read) LargeObjectManager/READ
|
||||
(:w :write) LargeObjectManager/WRITE
|
||||
(:rw :read+write) LargeObjectManager/READWRITE)]
|
||||
(.open ^LargeObjectManager manager (long oid) mode))))
|
||||
|
||||
(defn lobj-unlink
|
||||
[manager oid]
|
||||
(.unlink ^LargeObjectManager manager (long oid)))
|
||||
|
||||
(extend-type LargeObject
|
||||
io/IOFactory
|
||||
(make-reader [lobj opts]
|
||||
(let [^InputStream is (.getInputStream ^LargeObject lobj)]
|
||||
(io/make-reader is opts)))
|
||||
(make-writer [lobj opts]
|
||||
(let [^OutputStream os (.getOutputStream ^LargeObject lobj)]
|
||||
(io/make-writer os opts)))
|
||||
(make-input-stream [lobj opts]
|
||||
(let [^InputStream is (.getInputStream ^LargeObject lobj)]
|
||||
(io/make-input-stream is opts)))
|
||||
(make-output-stream [lobj opts]
|
||||
(let [^OutputStream os (.getOutputStream ^LargeObject lobj)]
|
||||
(io/make-output-stream os opts))))
|
||||
|
||||
(defmacro with-atomic
|
||||
[& args]
|
||||
`(jdbc/with-transaction ~@args))
|
||||
|
||||
(defn open
|
||||
[pool]
|
||||
(jdbc/get-connection pool))
|
||||
|
||||
(defn exec!
|
||||
([ds sv]
|
||||
(exec! ds sv {}))
|
||||
([ds sv opts]
|
||||
(jdbc/execute! ds sv (assoc opts :builder-fn sql/as-kebab-maps))))
|
||||
|
||||
(defn exec-one!
|
||||
([ds sv] (exec-one! ds sv {}))
|
||||
([ds sv opts]
|
||||
(jdbc/execute-one! ds sv (assoc opts :builder-fn sql/as-kebab-maps))))
|
||||
|
||||
(defn insert!
|
||||
([ds table params] (insert! ds table params nil))
|
||||
([ds table params opts]
|
||||
(exec-one! ds
|
||||
(sql/insert table params opts)
|
||||
(merge {:return-keys true} opts))))
|
||||
|
||||
(defn insert-multi!
|
||||
([ds table cols rows] (insert-multi! ds table cols rows nil))
|
||||
([ds table cols rows opts]
|
||||
(exec! ds
|
||||
(sql/insert-multi table cols rows opts)
|
||||
(merge {:return-keys true} opts))))
|
||||
|
||||
(defn update!
|
||||
([ds table params where] (update! ds table params where nil))
|
||||
([ds table params where opts]
|
||||
(exec-one! ds
|
||||
(sql/update table params where opts)
|
||||
(merge {:return-keys true} opts))))
|
||||
|
||||
(defn delete!
|
||||
([ds table params] (delete! ds table params nil))
|
||||
([ds table params opts]
|
||||
(exec-one! ds
|
||||
(sql/delete table params opts)
|
||||
(assoc opts :return-keys true))))
|
||||
|
||||
(defn is-row-deleted?
|
||||
[{:keys [deleted-at]}]
|
||||
(and (dt/instant? deleted-at)
|
||||
(< (inst-ms deleted-at)
|
||||
(inst-ms (dt/now)))))
|
||||
|
||||
(defn get*
|
||||
"Internal function for retrieve a single row from database that
|
||||
matches a simple filters."
|
||||
([ds table params]
|
||||
(get* ds table params nil))
|
||||
([ds table params {:keys [check-deleted?] :or {check-deleted? true} :as opts}]
|
||||
(let [rows (exec! ds (sql/select table params opts))
|
||||
rows (cond->> rows
|
||||
check-deleted?
|
||||
(remove is-row-deleted?))]
|
||||
(first rows))))
|
||||
|
||||
(defn get
|
||||
([ds table params]
|
||||
(get ds table params nil))
|
||||
([ds table params {:keys [check-deleted?] :or {check-deleted? true} :as opts}]
|
||||
(let [row (get* ds table params opts)]
|
||||
(when (and (not row) check-deleted?)
|
||||
(ex/raise :type :not-found
|
||||
:code :object-not-found
|
||||
:table table
|
||||
:hint "database object not found"))
|
||||
row)))
|
||||
|
||||
(defn get-by-params
|
||||
"DEPRECATED"
|
||||
([ds table params]
|
||||
(get-by-params ds table params nil))
|
||||
([ds table params {:keys [check-not-found] :or {check-not-found true} :as opts}]
|
||||
(let [row (get* ds table params (assoc opts :check-deleted? check-not-found))]
|
||||
(when (and (not row) check-not-found)
|
||||
(ex/raise :type :not-found
|
||||
:code :object-not-found
|
||||
:table table
|
||||
:hint "database object not found"))
|
||||
row)))
|
||||
|
||||
(defn get-by-id
|
||||
([ds table id]
|
||||
(get ds table {:id id} nil))
|
||||
([ds table id opts]
|
||||
(let [opts (cond-> opts
|
||||
(contains? opts :check-not-found)
|
||||
(assoc :check-deleted? (:check-not-found opts)))]
|
||||
(get ds table {:id id} opts))))
|
||||
|
||||
(defn query
|
||||
([ds table params]
|
||||
(query ds table params nil))
|
||||
([ds table params opts]
|
||||
(exec! ds (sql/select table params opts))))
|
||||
|
||||
(defn pgobject?
|
||||
([v]
|
||||
(instance? PGobject v))
|
||||
([v type]
|
||||
(and (instance? PGobject v)
|
||||
(= type (.getType ^PGobject v)))))
|
||||
|
||||
(defn pginterval?
|
||||
[v]
|
||||
(instance? PGInterval v))
|
||||
|
||||
(defn pgpoint?
|
||||
[v]
|
||||
(instance? PGpoint v))
|
||||
|
||||
(defn pgarray?
|
||||
([v] (instance? PgArray v))
|
||||
([v type]
|
||||
(and (instance? PgArray v)
|
||||
(= type (.getBaseTypeName ^PgArray v)))))
|
||||
|
||||
(defn pgarray-of-uuid?
|
||||
[v]
|
||||
(and (pgarray? v) (= "uuid" (.getBaseTypeName ^PgArray v))))
|
||||
|
||||
;; TODO rename to decode-pgarray-into
|
||||
(defn decode-pgarray
|
||||
([v] (decode-pgarray v []))
|
||||
([v in]
|
||||
(into in (some-> ^PgArray v .getArray)))
|
||||
([v in xf]
|
||||
(into in xf (some-> ^PgArray v .getArray))))
|
||||
|
||||
(defn pgarray->set
|
||||
[v]
|
||||
(set (.getArray ^PgArray v)))
|
||||
|
||||
(defn pgarray->vector
|
||||
[v]
|
||||
(vec (.getArray ^PgArray v)))
|
||||
|
||||
(defn pgpoint
|
||||
[p]
|
||||
(PGpoint. (:x p) (:y p)))
|
||||
|
||||
(defn create-array
|
||||
[conn type objects]
|
||||
(let [^PGConnection conn (unwrap conn org.postgresql.PGConnection)]
|
||||
(if (coll? objects)
|
||||
(.createArrayOf conn ^String type (into-array Object objects))
|
||||
(.createArrayOf conn ^String type objects))))
|
||||
|
||||
(defn decode-pgpoint
|
||||
[^PGpoint v]
|
||||
(gpt/point (.-x v) (.-y v)))
|
||||
|
||||
(defn pginterval
|
||||
[data]
|
||||
(org.postgresql.util.PGInterval. ^String data))
|
||||
|
||||
(defn savepoint
|
||||
([^Connection conn]
|
||||
(.setSavepoint conn))
|
||||
([^Connection conn label]
|
||||
(.setSavepoint conn (name label))))
|
||||
|
||||
(defn rollback!
|
||||
([^Connection conn]
|
||||
(.rollback conn))
|
||||
([^Connection conn ^Savepoint sp]
|
||||
(.rollback conn sp)))
|
||||
|
||||
(defn interval
|
||||
[o]
|
||||
(cond
|
||||
(or (integer? o)
|
||||
(float? o))
|
||||
(->> (/ o 1000.0)
|
||||
(format "%s seconds")
|
||||
(pginterval))
|
||||
|
||||
(string? o)
|
||||
(pginterval o)
|
||||
|
||||
(dt/duration? o)
|
||||
(interval (inst-ms o))
|
||||
|
||||
:else
|
||||
(ex/raise :type :not-implemented
|
||||
:hint (format "no implementation found for value %s" (pr-str o)))))
|
||||
|
||||
(defn decode-json-pgobject
|
||||
[^PGobject o]
|
||||
(when o
|
||||
(let [typ (.getType o)
|
||||
val (.getValue o)]
|
||||
(if (or (= typ "json")
|
||||
(= typ "jsonb"))
|
||||
(json/decode val)
|
||||
val))))
|
||||
|
||||
(defn decode-transit-pgobject
|
||||
[^PGobject o]
|
||||
(when o
|
||||
(let [typ (.getType o)
|
||||
val (.getValue o)]
|
||||
(if (or (= typ "json")
|
||||
(= typ "jsonb"))
|
||||
(t/decode-str val)
|
||||
val))))
|
||||
|
||||
(defn inet
|
||||
[ip-addr]
|
||||
(when ip-addr
|
||||
(doto (org.postgresql.util.PGobject.)
|
||||
(.setType "inet")
|
||||
(.setValue (str ip-addr)))))
|
||||
|
||||
(defn decode-inet
|
||||
[^PGobject o]
|
||||
(when o
|
||||
(if (= "inet" (.getType o))
|
||||
(.getValue o)
|
||||
nil)))
|
||||
|
||||
(defn tjson
|
||||
"Encode as transit json."
|
||||
[data]
|
||||
(when data
|
||||
(doto (org.postgresql.util.PGobject.)
|
||||
(.setType "jsonb")
|
||||
(.setValue (t/encode-str data {:type :json-verbose})))))
|
||||
|
||||
(defn json
|
||||
"Encode as plain json."
|
||||
[data]
|
||||
(when data
|
||||
(doto (org.postgresql.util.PGobject.)
|
||||
(.setType "jsonb")
|
||||
(.setValue (json/encode-str data)))))
|
||||
|
||||
;; --- Locks
|
||||
|
||||
(def ^:private siphash-state
|
||||
(SipHasher/container
|
||||
(uuid/get-bytes uuid/zero)))
|
||||
|
||||
(defn uuid->hash-code
|
||||
[o]
|
||||
(.hash ^SipHasherContainer siphash-state
|
||||
^bytes (uuid/get-bytes o)))
|
||||
|
||||
(defn- xact-check-param
|
||||
[n]
|
||||
(cond
|
||||
(uuid? n) (uuid->hash-code n)
|
||||
(int? n) n
|
||||
:else (throw (IllegalArgumentException. "uuid or number allowed"))))
|
||||
|
||||
(defn xact-lock!
|
||||
[conn n]
|
||||
(let [n (xact-check-param n)]
|
||||
(exec-one! conn ["select pg_advisory_xact_lock(?::bigint) as lock" n])
|
||||
true))
|
||||
|
||||
(defn xact-try-lock!
|
||||
[conn n]
|
||||
(let [n (xact-check-param n)
|
||||
row (exec-one! conn ["select pg_try_advisory_xact_lock(?::bigint) as lock" n])]
|
||||
(:lock row)))
|
||||
|
||||
(defn sql-exception?
|
||||
[cause]
|
||||
(instance? java.sql.SQLException cause))
|
||||
|
||||
(defn connection-error?
|
||||
[cause]
|
||||
(and (sql-exception? cause)
|
||||
(contains? #{"08003" "08006" "08001" "08004"}
|
||||
(.getSQLState ^java.sql.SQLException cause))))
|
||||
|
||||
(defn serialization-error?
|
||||
[cause]
|
||||
(and (sql-exception? cause)
|
||||
(= "40001" (.getSQLState ^java.sql.SQLException cause))))
|
||||
62
backend/src/app/db/sql.clj
Normal file
62
backend/src/app/db/sql.clj
Normal file
@@ -0,0 +1,62 @@
|
||||
;; 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.db.sql
|
||||
(:refer-clojure :exclude [update])
|
||||
(:require
|
||||
[clojure.string :as str]
|
||||
[next.jdbc.optional :as jdbc-opt]
|
||||
[next.jdbc.sql.builder :as sql]))
|
||||
|
||||
(defn kebab-case [s] (str/replace s #"_" "-"))
|
||||
(defn snake-case [s] (str/replace s #"-" "_"))
|
||||
|
||||
(def default-opts
|
||||
{:table-fn snake-case
|
||||
:column-fn snake-case})
|
||||
|
||||
(defn as-kebab-maps
|
||||
[rs opts]
|
||||
(jdbc-opt/as-unqualified-modified-maps rs (assoc opts :label-fn kebab-case)))
|
||||
|
||||
(defn insert
|
||||
([table key-map]
|
||||
(insert table key-map nil))
|
||||
([table key-map opts]
|
||||
(let [opts (merge default-opts opts)
|
||||
opts (cond-> opts
|
||||
(:on-conflict-do-nothing opts)
|
||||
(assoc :suffix "ON CONFLICT DO NOTHING"))]
|
||||
(sql/for-insert table key-map opts))))
|
||||
|
||||
(defn insert-multi
|
||||
[table cols rows opts]
|
||||
(let [opts (merge default-opts opts)]
|
||||
(sql/for-insert-multi table cols rows opts)))
|
||||
|
||||
(defn select
|
||||
([table where-params]
|
||||
(select table where-params nil))
|
||||
([table where-params opts]
|
||||
(let [opts (merge default-opts opts)
|
||||
opts (cond-> opts
|
||||
(:for-update opts) (assoc :suffix "FOR UPDATE")
|
||||
(:for-key-share opts) (assoc :suffix "FOR KEY SHARE"))]
|
||||
(sql/for-query table where-params opts))))
|
||||
|
||||
(defn update
|
||||
([table key-map where-params]
|
||||
(update table key-map where-params nil))
|
||||
([table key-map where-params opts]
|
||||
(let [opts (merge default-opts opts)]
|
||||
(sql/for-update table key-map where-params opts))))
|
||||
|
||||
(defn delete
|
||||
([table where-params]
|
||||
(delete table where-params nil))
|
||||
([table where-params opts]
|
||||
(let [opts (merge default-opts opts)]
|
||||
(sql/for-delete table where-params opts))))
|
||||
444
backend/src/app/emails.clj
Normal file
444
backend/src/app/emails.clj
Normal file
@@ -0,0 +1,444 @@
|
||||
;; 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.emails
|
||||
"Main api for send emails."
|
||||
(:require
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.logging :as l]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.spec :as us]
|
||||
[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.metrics :as mtx]
|
||||
[app.util.template :as tmpl]
|
||||
[app.worker :as wrk]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[integrant.core :as ig])
|
||||
(:import
|
||||
jakarta.mail.Message$RecipientType
|
||||
jakarta.mail.Session
|
||||
jakarta.mail.Transport
|
||||
jakarta.mail.internet.InternetAddress
|
||||
jakarta.mail.internet.MimeBodyPart
|
||||
jakarta.mail.internet.MimeMessage
|
||||
jakarta.mail.internet.MimeMultipart
|
||||
java.util.Properties))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; EMAIL IMPL
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn- parse-address
|
||||
[v]
|
||||
(InternetAddress/parse ^String v))
|
||||
|
||||
(defn- resolve-recipient-type
|
||||
^Message$RecipientType
|
||||
[type]
|
||||
(case type
|
||||
:to Message$RecipientType/TO
|
||||
:cc Message$RecipientType/CC
|
||||
:bcc Message$RecipientType/BCC))
|
||||
|
||||
(defn- assign-recipient
|
||||
[^MimeMessage mmsg type address]
|
||||
(if (sequential? address)
|
||||
(reduce #(assign-recipient %1 type %2) mmsg address)
|
||||
(let [address (parse-address address)
|
||||
type (resolve-recipient-type type)]
|
||||
(.addRecipients mmsg type address)
|
||||
mmsg)))
|
||||
(defn- assign-recipients
|
||||
[mmsg {:keys [to cc bcc] :as params}]
|
||||
(cond-> mmsg
|
||||
(some? to) (assign-recipient :to to)
|
||||
(some? cc) (assign-recipient :cc cc)
|
||||
(some? bcc) (assign-recipient :bcc bcc)))
|
||||
|
||||
(defn- assign-from
|
||||
[mmsg {:keys [default-from]} {:keys [from] :as props}]
|
||||
(let [from (or from default-from)]
|
||||
(when from
|
||||
(let [from (parse-address from)]
|
||||
(.addFrom ^MimeMessage mmsg from)))))
|
||||
|
||||
(defn- assign-reply-to
|
||||
[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)]
|
||||
(.setReplyTo ^MimeMessage mmsg reply-to)))))
|
||||
|
||||
(defn- assign-subject
|
||||
[mmsg {:keys [subject charset] :or {charset "utf-8"} :as params}]
|
||||
(assert (string? subject) "subject is mandatory")
|
||||
(.setSubject ^MimeMessage mmsg
|
||||
^String subject
|
||||
^String charset))
|
||||
|
||||
(defn- assign-extra-headers
|
||||
[^MimeMessage mmsg {:keys [headers extra-data] :as params}]
|
||||
(let [headers (assoc headers "X-Penpot-Data" extra-data)]
|
||||
(reduce-kv (fn [^MimeMessage mmsg k v]
|
||||
(doto mmsg
|
||||
(.addHeader (name k) (str v))))
|
||||
mmsg
|
||||
headers)))
|
||||
|
||||
(defn- assign-body
|
||||
[^MimeMessage mmsg {:keys [body charset] :or {charset "utf-8"}}]
|
||||
(let [mpart (MimeMultipart. "mixed")]
|
||||
(cond
|
||||
(string? body)
|
||||
(let [bpart (MimeBodyPart.)]
|
||||
(.setContent bpart ^String body (str "text/plain; charset=" charset))
|
||||
(.addBodyPart mpart bpart))
|
||||
|
||||
(vector? body)
|
||||
(let [mmp (MimeMultipart. "alternative")
|
||||
mbp (MimeBodyPart.)]
|
||||
(.addBodyPart mpart mbp)
|
||||
(.setContent mbp mmp)
|
||||
(doseq [item body]
|
||||
(let [mbp (MimeBodyPart.)]
|
||||
(.setContent mbp
|
||||
^String (:content item)
|
||||
^String (str (:type item "text/plain") "; charset=" charset))
|
||||
(.addBodyPart mmp mbp))))
|
||||
|
||||
(map? body)
|
||||
(let [bpart (MimeBodyPart.)]
|
||||
(.setContent bpart
|
||||
^String (:content body)
|
||||
^String (str (:type body "text/plain") "; charset=" charset))
|
||||
(.addBodyPart mpart bpart))
|
||||
|
||||
:else
|
||||
(throw (ex-info "Unsupported type" {:body body})))
|
||||
(.setContent mmsg mpart)
|
||||
mmsg))
|
||||
|
||||
(defn- opts->props
|
||||
[{:keys [username tls host port timeout default-from]
|
||||
:or {timeout 30000}
|
||||
:as opts}]
|
||||
(reduce-kv
|
||||
(fn [^Properties props k v]
|
||||
(if (nil? v)
|
||||
props
|
||||
(doto props (.put ^String k ^String (str v)))))
|
||||
(Properties.)
|
||||
{"mail.user" username
|
||||
"mail.host" host
|
||||
"mail.debug" (contains? cf/flags :smtp-debug)
|
||||
"mail.from" default-from
|
||||
"mail.smtp.auth" (boolean username)
|
||||
"mail.smtp.starttls.enable" tls
|
||||
"mail.smtp.starttls.required" tls
|
||||
"mail.smtp.host" host
|
||||
"mail.smtp.port" port
|
||||
"mail.smtp.user" username
|
||||
"mail.smtp.timeout" timeout
|
||||
"mail.smtp.connectiontimeout" timeout}))
|
||||
|
||||
(defn- create-smtp-session
|
||||
[opts]
|
||||
(let [props (opts->props opts)]
|
||||
(Session/getInstance props)))
|
||||
|
||||
(defn- create-smtp-message
|
||||
^MimeMessage
|
||||
[cfg session params]
|
||||
(let [mmsg (MimeMessage. ^Session session)]
|
||||
(assign-recipients mmsg params)
|
||||
(assign-from mmsg cfg params)
|
||||
(assign-reply-to mmsg cfg params)
|
||||
(assign-subject mmsg params)
|
||||
(assign-extra-headers mmsg params)
|
||||
(assign-body mmsg params)
|
||||
(.saveChanges ^MimeMessage mmsg)
|
||||
mmsg))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; TEMPLATE EMAIL IMPL
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def ^:private email-path "app/emails/%(id)s/%(lang)s.%(type)s")
|
||||
|
||||
(defn- render-email-template-part
|
||||
[type id context]
|
||||
(let [lang (:lang context :en)
|
||||
path (str/format email-path {:id (name id)
|
||||
:lang (name lang)
|
||||
:type (name type)})]
|
||||
(some-> (io/resource path)
|
||||
(tmpl/render context))))
|
||||
|
||||
(defn- build-email-template
|
||||
[id context]
|
||||
(let [subj (render-email-template-part :subj id context)
|
||||
text (render-email-template-part :txt id context)
|
||||
html (render-email-template-part :html id context)]
|
||||
(when (or (not subj)
|
||||
(and (not text)
|
||||
(not html)))
|
||||
(ex/raise :type :internal
|
||||
:code :missing-email-templates))
|
||||
{:subject subj
|
||||
:body (into
|
||||
[{:type "text/plain"
|
||||
:content text}]
|
||||
(when html
|
||||
[{:type "text/html"
|
||||
:content html}]))}))
|
||||
|
||||
(s/def ::priority #{:high :low})
|
||||
(s/def ::to (s/or :single ::us/email
|
||||
:multi (s/coll-of ::us/email)))
|
||||
(s/def ::from ::us/email)
|
||||
(s/def ::reply-to ::us/email)
|
||||
(s/def ::lang string?)
|
||||
(s/def ::extra-data ::us/string)
|
||||
|
||||
(s/def ::context
|
||||
(s/keys :req-un [::to]
|
||||
:opt-un [::reply-to ::from ::lang ::priority ::extra-data]))
|
||||
|
||||
(defn template-factory
|
||||
([id] (template-factory id {}))
|
||||
([id extra-context]
|
||||
(s/assert keyword? id)
|
||||
(fn [context]
|
||||
(us/verify ::context context)
|
||||
(when-let [spec (s/get-spec id)]
|
||||
(s/assert spec context))
|
||||
|
||||
(let [context (merge (if (fn? extra-context)
|
||||
(extra-context)
|
||||
extra-context)
|
||||
context)
|
||||
email (build-email-template id context)]
|
||||
(when-not email
|
||||
(ex/raise :type :internal
|
||||
:code :email-template-does-not-exists
|
||||
:hint "seems like the template is wrong or does not exists."
|
||||
:context {:id id}))
|
||||
(cond-> (assoc email :id (name id))
|
||||
(:extra-data context)
|
||||
(assoc :extra-data (:extra-data context))
|
||||
|
||||
(:from context)
|
||||
(assoc :from (:from context))
|
||||
|
||||
(:reply-to context)
|
||||
(assoc :reply-to (:reply-to context))
|
||||
|
||||
(:to context)
|
||||
(assoc :to (:to context)))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; PUBLIC HIGH-LEVEL API
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn render
|
||||
[email-factory context]
|
||||
(email-factory context))
|
||||
|
||||
(defn send!
|
||||
"Schedule an already defined email to be sent using asynchronously
|
||||
using worker task."
|
||||
[{:keys [::conn ::factory] :as context}]
|
||||
(us/verify some? conn)
|
||||
(let [email (if factory
|
||||
(factory context)
|
||||
(dissoc context ::conn))]
|
||||
(wrk/submit! (merge
|
||||
{::wrk/task :sendmail
|
||||
::wrk/delay 0
|
||||
::wrk/max-retries 4
|
||||
::wrk/priority 200
|
||||
::wrk/conn conn}
|
||||
email))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SENDMAIL FN / TASK HANDLER
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(s/def ::username ::cf/smtp-username)
|
||||
(s/def ::password ::cf/smtp-password)
|
||||
(s/def ::tls ::cf/smtp-tls)
|
||||
(s/def ::ssl ::cf/smtp-ssl)
|
||||
(s/def ::host ::cf/smtp-host)
|
||||
(s/def ::port ::cf/smtp-port)
|
||||
(s/def ::default-reply-to ::cf/smtp-default-reply-to)
|
||||
(s/def ::default-from ::cf/smtp-default-from)
|
||||
|
||||
(s/def ::smtp-config
|
||||
(s/keys :opt-un [::username
|
||||
::password
|
||||
::tls
|
||||
::ssl
|
||||
::host
|
||||
::port
|
||||
::default-from
|
||||
::default-reply-to]))
|
||||
|
||||
(declare send-to-logger!)
|
||||
|
||||
(s/def ::sendmail fn?)
|
||||
|
||||
(defmethod ig/pre-init-spec ::sendmail [_]
|
||||
(s/spec ::smtp-config))
|
||||
|
||||
(defmethod ig/init-key ::sendmail
|
||||
[_ cfg]
|
||||
(fn [params]
|
||||
(when (contains? cf/flags :smtp)
|
||||
(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))
|
||||
|
||||
(let [^MimeMessage message (create-smtp-message cfg session params)]
|
||||
(.sendMessage ^Transport transport
|
||||
^MimeMessage message
|
||||
(.getAllRecipients message))))))
|
||||
|
||||
(when (or (contains? cf/flags :log-emails)
|
||||
(not (contains? cf/flags :smtp)))
|
||||
(send-to-logger! cfg params))))
|
||||
|
||||
(defmethod ig/pre-init-spec ::handler [_]
|
||||
(s/keys :req-un [::sendmail ::mtx/metrics]))
|
||||
|
||||
(defmethod ig/init-key ::handler
|
||||
[_ {:keys [sendmail]}]
|
||||
(fn [{:keys [props] :as task}]
|
||||
(sendmail props)))
|
||||
|
||||
(defn- send-to-logger!
|
||||
[_ email]
|
||||
(let [body (:body email)
|
||||
out (with-out-str
|
||||
(println "email console dump:")
|
||||
(println "******** start email" (:id email) "**********")
|
||||
(pp/pprint (dissoc email :body))
|
||||
(if (string? body)
|
||||
(println body)
|
||||
(println (->> body
|
||||
(filter #(= "text/plain" (:type %)))
|
||||
(map :content)
|
||||
first)))
|
||||
(println "******** end email" (:id email) "**********"))]
|
||||
(l/info ::l/raw out)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; EMAIL FACTORIES
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(s/def ::subject ::us/string)
|
||||
(s/def ::content ::us/string)
|
||||
|
||||
(s/def ::feedback
|
||||
(s/keys :req-un [::subject ::content]))
|
||||
|
||||
(def feedback
|
||||
"A profile feedback email."
|
||||
(template-factory ::feedback))
|
||||
|
||||
(s/def ::name ::us/string)
|
||||
(s/def ::register
|
||||
(s/keys :req-un [::name]))
|
||||
|
||||
(def register
|
||||
"A new profile registration welcome email."
|
||||
(template-factory ::register))
|
||||
|
||||
(s/def ::token ::us/string)
|
||||
(s/def ::password-recovery
|
||||
(s/keys :req-un [::name ::token]))
|
||||
|
||||
(def password-recovery
|
||||
"A password recovery notification email."
|
||||
(template-factory ::password-recovery))
|
||||
|
||||
(s/def ::pending-email ::us/email)
|
||||
(s/def ::change-email
|
||||
(s/keys :req-un [::name ::pending-email ::token]))
|
||||
|
||||
(def change-email
|
||||
"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 ::invite-to-team
|
||||
(s/keys :req-un [::emails.invite-to-team/invited-by
|
||||
::emails.invite-to-team/token
|
||||
::emails.invite-to-team/team]))
|
||||
|
||||
(def invite-to-team
|
||||
"Teams member invitation email."
|
||||
(template-factory ::invite-to-team))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; BOUNCE/COMPLAINS HELPERS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def sql:profile-complaint-report
|
||||
"select (select count(*)
|
||||
from profile_complaint_report
|
||||
where type = 'complaint'
|
||||
and profile_id = ?
|
||||
and created_at > now() - ?::interval) as complaints,
|
||||
(select count(*)
|
||||
from profile_complaint_report
|
||||
where type = 'bounce'
|
||||
and profile_id = ?
|
||||
and created_at > now() - ?::interval) as bounces;")
|
||||
|
||||
(defn allow-send-emails?
|
||||
[conn profile]
|
||||
(when-not (:is-muted profile false)
|
||||
(let [complaint-threshold (cf/get :profile-complaint-threshold)
|
||||
complaint-max-age (cf/get :profile-complaint-max-age)
|
||||
bounce-threshold (cf/get :profile-bounce-threshold)
|
||||
bounce-max-age (cf/get :profile-bounce-max-age)
|
||||
|
||||
{:keys [complaints bounces] :as result}
|
||||
(db/exec-one! conn [sql:profile-complaint-report
|
||||
(:id profile)
|
||||
(db/interval complaint-max-age)
|
||||
(:id profile)
|
||||
(db/interval bounce-max-age)])]
|
||||
|
||||
(and (< (or complaints 0) complaint-threshold)
|
||||
(< (or bounces 0) bounce-threshold)))))
|
||||
|
||||
(defn has-complaint-reports?
|
||||
([conn email] (has-complaint-reports? conn email nil))
|
||||
([conn email {:keys [threshold] :or {threshold 1}}]
|
||||
(let [reports (db/exec! conn (sql/select :global-complaint-report
|
||||
{:email email :type "complaint"}
|
||||
{:limit 10}))]
|
||||
(>= (count reports) threshold))))
|
||||
|
||||
(defn has-bounce-reports?
|
||||
([conn email] (has-bounce-reports? conn email nil))
|
||||
([conn email {:keys [threshold] :or {threshold 1}}]
|
||||
(let [reports (db/exec! conn (sql/select :global-complaint-report
|
||||
{:email email :type "bounce"}
|
||||
{:limit 10}))]
|
||||
(>= (count reports) threshold))))
|
||||
176
backend/src/app/http.clj
Normal file
176
backend/src/app/http.clj
Normal file
@@ -0,0 +1,176 @@
|
||||
;; 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.http
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.logging :as l]
|
||||
[app.common.transit :as t]
|
||||
[app.http.errors :as errors]
|
||||
[app.http.middleware :as mw]
|
||||
[app.http.session :as session]
|
||||
[app.metrics :as mtx]
|
||||
[app.worker :as wrk]
|
||||
[clojure.spec.alpha :as s]
|
||||
[integrant.core :as ig]
|
||||
[reitit.core :as r]
|
||||
[reitit.middleware :as rr]
|
||||
[yetti.adapter :as yt]
|
||||
[yetti.request :as yrq]
|
||||
[yetti.response :as yrs]))
|
||||
|
||||
(declare wrap-router)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; HTTP SERVER
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(s/def ::handler fn?)
|
||||
(s/def ::router some?)
|
||||
(s/def ::port integer?)
|
||||
(s/def ::host string?)
|
||||
(s/def ::name string?)
|
||||
|
||||
(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
|
||||
(d/without-nils cfg)))
|
||||
|
||||
(defmethod ig/pre-init-spec ::server [_]
|
||||
(s/and
|
||||
(s/keys :req-un [::port ::host ::name ::max-body-size ::max-multipart-body-size]
|
||||
:opt-un [::router ::handler ::io-threads ::worker-threads ::wrk/executor])
|
||||
(fn [cfg]
|
||||
(or (contains? cfg :router)
|
||||
(contains? cfg :handler)))))
|
||||
|
||||
(defmethod ig/init-key ::server
|
||||
[_ {:keys [handler router port name host] :as cfg}]
|
||||
(l/info :hint "starting http server" :port port :host host :name name)
|
||||
(let [options {:http/port port
|
||||
:http/host host
|
||||
:http/max-body-size (:max-body-size cfg)
|
||||
:http/max-multipart-body-size (:max-multipart-body-size cfg)
|
||||
:xnio/io-threads (:io-threads cfg)
|
||||
:xnio/worker-threads (:worker-threads cfg)
|
||||
:xnio/dispatch (:executor cfg)
|
||||
:ring/async true}
|
||||
|
||||
handler (if (some? router)
|
||||
(wrap-router router)
|
||||
|
||||
handler)
|
||||
server (yt/server handler (d/without-nils options))]
|
||||
(assoc cfg :server (yt/start! server))))
|
||||
|
||||
(defmethod ig/halt-key! ::server
|
||||
[_ {:keys [server name port] :as cfg}]
|
||||
(l/info :msg "stopping http server" :name name :port port)
|
||||
(yt/stop! server))
|
||||
|
||||
(defn- not-found-handler
|
||||
[_ respond _]
|
||||
(respond (yrs/response 404)))
|
||||
|
||||
(defn- wrap-router
|
||||
[router]
|
||||
(letfn [(handler [request respond raise]
|
||||
(if-let [match (r/match-by-path router (yrq/path request))]
|
||||
(let [params (:path-params match)
|
||||
result (:result match)
|
||||
handler (or (:handler result) not-found-handler)
|
||||
request (assoc request :path-params params)]
|
||||
(handler request respond raise))
|
||||
(not-found-handler request respond raise)))
|
||||
|
||||
(on-error [cause request respond]
|
||||
(let [{:keys [body] :as response} (errors/handle cause request)]
|
||||
(respond
|
||||
(cond-> response
|
||||
(map? body)
|
||||
(-> (update :headers assoc "content-type" "application/transit+json")
|
||||
(assoc :body (t/encode-str body {:type :json-verbose})))))))]
|
||||
|
||||
(fn [request respond _]
|
||||
(try
|
||||
(handler request respond #(on-error % request respond))
|
||||
(catch Throwable cause
|
||||
(on-error cause request respond))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; HTTP ROUTER
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(s/def ::assets map?)
|
||||
(s/def ::awsns-handler fn?)
|
||||
(s/def ::debug-routes (s/nilable vector?))
|
||||
(s/def ::doc-routes (s/nilable vector?))
|
||||
(s/def ::feedback fn?)
|
||||
(s/def ::oauth map?)
|
||||
(s/def ::oidc-routes (s/nilable vector?))
|
||||
(s/def ::rpc-routes (s/nilable vector?))
|
||||
(s/def ::session ::session/session)
|
||||
(s/def ::storage map?)
|
||||
(s/def ::ws fn?)
|
||||
|
||||
(defmethod ig/pre-init-spec ::router [_]
|
||||
(s/keys :req-un [::mtx/metrics
|
||||
::ws
|
||||
::storage
|
||||
::assets
|
||||
::session
|
||||
::feedback
|
||||
::awsns-handler
|
||||
::debug-routes
|
||||
::oidc-routes
|
||||
::rpc-routes
|
||||
::doc-routes]))
|
||||
|
||||
(defmethod ig/init-key ::router
|
||||
[_ {:keys [ws session metrics assets feedback] :as cfg}]
|
||||
(rr/router
|
||||
[["" {:middleware [[mw/server-timing]
|
||||
[mw/format-response]
|
||||
[mw/params]
|
||||
[mw/parse-request]
|
||||
[session/middleware-1 session]
|
||||
[mw/errors errors/handle]
|
||||
[mw/restrict-methods]]}
|
||||
|
||||
["/metrics" {:handler (::mtx/handler metrics)
|
||||
:allowed-methods #{:get}}]
|
||||
|
||||
["/assets" {:middleware [[session/middleware-2 session]]}
|
||||
["/by-id/:id" {:handler (:objects-handler assets)}]
|
||||
["/by-file-media-id/:id" {:handler (:file-objects-handler assets)}]
|
||||
["/by-file-media-id/:id/thumbnail" {:handler (:file-thumbnails-handler assets)}]]
|
||||
|
||||
(:debug-routes cfg)
|
||||
|
||||
["/webhooks"
|
||||
["/sns" {:handler (:awsns-handler cfg)
|
||||
:allowed-methods #{:post}}]]
|
||||
|
||||
["/ws/notifications" {:middleware [[session/middleware-2 session]]
|
||||
:handler ws
|
||||
:allowed-methods #{:get}}]
|
||||
|
||||
["/api" {:middleware [[mw/cors]
|
||||
[session/middleware-2 session]]}
|
||||
["/feedback" {:handler feedback
|
||||
:allowed-methods #{:post}}]
|
||||
(:doc-routes cfg)
|
||||
(:oidc-routes cfg)
|
||||
(:rpc-routes cfg)]]]))
|
||||
131
backend/src/app/http/assets.clj
Normal file
131
backend/src/app/http/assets.clj
Normal file
@@ -0,0 +1,131 @@
|
||||
;; 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.http.assets
|
||||
"Assets related handlers."
|
||||
(:require
|
||||
[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
|
||||
(dt/duration {:hours 24}))
|
||||
|
||||
(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-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)))
|
||||
|
||||
(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))})))))
|
||||
|
||||
(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)))
|
||||
|
||||
(defn- generic-handler
|
||||
"A generic handler helper/common code for file-media based handlers."
|
||||
[{:keys [storage] :as cfg} request kf]
|
||||
(p/let [id (get-in request [:path-params :id])
|
||||
mobj (get-file-media-object storage id)
|
||||
obj (sto/get-object storage (kf mobj))]
|
||||
(if obj
|
||||
(serve-object cfg obj)
|
||||
(yrs/response 404))))
|
||||
|
||||
(defn file-objects-handler
|
||||
"Handler that serves storage objects by file media id."
|
||||
[cfg request respond raise]
|
||||
(-> (generic-handler cfg request :media-id)
|
||||
(p/then respond)
|
||||
(p/catch raise)))
|
||||
|
||||
(defn file-thumbnails-handler
|
||||
"Handler that serves storage objects by thumbnail-id and quick
|
||||
fallback to file-media-id if no thumbnail is available."
|
||||
[cfg request respond raise]
|
||||
(-> (generic-handler cfg request #(or (:thumbnail-id %) (:media-id %)))
|
||||
(p/then respond)
|
||||
(p/catch raise)))
|
||||
|
||||
;; --- Initialization
|
||||
|
||||
(s/def ::storage some?)
|
||||
(s/def ::assets-path ::us/string)
|
||||
(s/def ::cache-max-age ::dt/duration)
|
||||
(s/def ::signature-max-age ::dt/duration)
|
||||
|
||||
(defmethod ig/pre-init-spec ::handlers [_]
|
||||
(s/keys :req-un [::storage
|
||||
::wrk/executor
|
||||
::mtx/metrics
|
||||
::assets-path
|
||||
::cache-max-age
|
||||
::signature-max-age]))
|
||||
|
||||
(defmethod ig/init-key ::handlers
|
||||
[_ cfg]
|
||||
{:objects-handler (partial objects-handler cfg)
|
||||
:file-objects-handler (partial file-objects-handler cfg)
|
||||
:file-thumbnails-handler (partial file-thumbnails-handler cfg)})
|
||||
|
||||
217
backend/src/app/http/awsns.clj
Normal file
217
backend/src/app/http/awsns.clj
Normal file
@@ -0,0 +1,217 @@
|
||||
;; 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.http.awsns
|
||||
"AWS SNS webhook handler for bounces."
|
||||
(:require
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.logging :as l]
|
||||
[app.db :as db]
|
||||
[app.db.sql :as sql]
|
||||
[app.http.client :as http]
|
||||
[app.main :as-alias main]
|
||||
[app.tokens :as tokens]
|
||||
[app.worker :as-alias wrk]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[integrant.core :as ig]
|
||||
[jsonista.core :as j]
|
||||
[promesa.exec :as px]
|
||||
[yetti.request :as yrq]
|
||||
[yetti.response :as yrs]))
|
||||
|
||||
(declare parse-json)
|
||||
(declare handle-request)
|
||||
(declare parse-notification)
|
||||
(declare process-report)
|
||||
|
||||
(defmethod ig/pre-init-spec ::handler [_]
|
||||
(s/keys :req [::http/client
|
||||
::main/props
|
||||
::db/pool
|
||||
::wrk/executor]))
|
||||
|
||||
(defmethod ig/init-key ::handler
|
||||
[_ {:keys [::wrk/executor] :as cfg}]
|
||||
(fn [request respond _]
|
||||
(let [data (-> request yrq/body slurp)]
|
||||
(px/run! executor #(handle-request cfg data)))
|
||||
(respond (yrs/response 200))))
|
||||
|
||||
(defn handle-request
|
||||
[cfg data]
|
||||
(try
|
||||
(let [body (parse-json data)
|
||||
mtype (get body "Type")]
|
||||
(cond
|
||||
(= mtype "SubscriptionConfirmation")
|
||||
(let [surl (get body "SubscribeURL")
|
||||
stopic (get body "TopicArn")]
|
||||
(l/info :action "subscription received" :topic stopic :url surl)
|
||||
(http/req! cfg {:uri surl :method :post :timeout 10000} {:sync? true}))
|
||||
|
||||
(= mtype "Notification")
|
||||
(when-let [message (parse-json (get body "Message"))]
|
||||
(let [notification (parse-notification cfg message)]
|
||||
(process-report cfg notification)))
|
||||
|
||||
:else
|
||||
(l/warn :hint "unexpected data received"
|
||||
:report (pr-str body))))
|
||||
|
||||
(catch Throwable cause
|
||||
(l/error :hint "unexpected exception on awsns"
|
||||
:cause cause))))
|
||||
|
||||
(defn- parse-bounce
|
||||
[data]
|
||||
{:type "bounce"
|
||||
:kind (str/lower (get data "bounceType"))
|
||||
:category (str/lower (get data "bounceSubType"))
|
||||
:feedback-id (get data "feedbackId")
|
||||
:timestamp (get data "timestamp")
|
||||
:recipients (->> (get data "bouncedRecipients")
|
||||
(mapv (fn [item]
|
||||
{:email (str/lower (get item "emailAddress"))
|
||||
:status (get item "status")
|
||||
:action (get item "action")
|
||||
:dcode (get item "diagnosticCode")})))})
|
||||
|
||||
(defn- parse-complaint
|
||||
[data]
|
||||
{:type "complaint"
|
||||
:user-agent (get data "userAgent")
|
||||
:kind (get data "complaintFeedbackType")
|
||||
:category (get data "complaintSubType")
|
||||
:timestamp (get data "arrivalDate")
|
||||
:feedback-id (get data "feedbackId")
|
||||
:recipients (->> (get data "complainedRecipients")
|
||||
(mapv #(get % "emailAddress"))
|
||||
(mapv str/lower))})
|
||||
|
||||
(defn- extract-headers
|
||||
[mail]
|
||||
(reduce (fn [acc item]
|
||||
(let [key (get item "name")
|
||||
val (get item "value")]
|
||||
(assoc acc (str/lower key) val)))
|
||||
{}
|
||||
(get mail "headers")))
|
||||
|
||||
(defn- extract-identity
|
||||
[cfg headers]
|
||||
(let [tdata (get headers "x-penpot-data")]
|
||||
(when-not (str/empty? tdata)
|
||||
(let [sprops (::main/props cfg)
|
||||
result (tokens/verify sprops {:token tdata :iss :profile-identity})]
|
||||
(:profile-id result)))))
|
||||
|
||||
(defn- parse-notification
|
||||
[cfg message]
|
||||
(let [type (get message "notificationType")
|
||||
data (case type
|
||||
"Bounce" (parse-bounce (get message "bounce"))
|
||||
"Complaint" (parse-complaint (get message "complaint"))
|
||||
{:type (keyword (str/lower type))
|
||||
:message message})]
|
||||
(when data
|
||||
(let [mail (get message "mail")]
|
||||
(when-not mail
|
||||
(ex/raise :type :internal
|
||||
:code :incomplete-notification
|
||||
:hint "no email data received, please enable full headers report"))
|
||||
(let [headers (extract-headers mail)
|
||||
mail {:destination (get mail "destination")
|
||||
:source (get mail "source")
|
||||
:timestamp (get mail "timestamp")
|
||||
:subject (get-in mail ["commonHeaders" "subject"])
|
||||
:headers headers}]
|
||||
(assoc data
|
||||
:mail mail
|
||||
:profile-id (extract-identity cfg headers)))))))
|
||||
|
||||
(defn- parse-json
|
||||
[v]
|
||||
(ex/ignoring
|
||||
(j/read-value v)))
|
||||
|
||||
(defn- register-bounce-for-profile
|
||||
[{:keys [::db/pool]} {:keys [type kind profile-id] :as report}]
|
||||
(when (= kind "permanent")
|
||||
(db/with-atomic [conn pool]
|
||||
(db/insert! conn :profile-complaint-report
|
||||
{:profile-id profile-id
|
||||
:type (name type)
|
||||
:content (db/tjson report)})
|
||||
|
||||
;; TODO: maybe also try to find profiles by mail and if exists
|
||||
;; register profile reports for them?
|
||||
(doseq [recipient (:recipients report)]
|
||||
(db/insert! conn :global-complaint-report
|
||||
{:email (:email recipient)
|
||||
:type (name type)
|
||||
:content (db/tjson report)}))
|
||||
|
||||
(let [profile (db/exec-one! conn (sql/select :profile {:id profile-id}))]
|
||||
(when (some #(= (:email profile) (:email %)) (:recipients report))
|
||||
;; If the report matches the profile email, this means that
|
||||
;; the report is for itself, can be caused when a user
|
||||
;; registers with an invalid email or the user email is
|
||||
;; permanently rejecting receiving the email. In this case we
|
||||
;; have no option to mark the user as muted (and in this case
|
||||
;; the profile will be also inactive.
|
||||
(db/update! conn :profile
|
||||
{:is-muted true}
|
||||
{:id profile-id}))))))
|
||||
|
||||
(defn- register-complaint-for-profile
|
||||
[{:keys [::db/pool]} {:keys [type profile-id] :as report}]
|
||||
(db/with-atomic [conn pool]
|
||||
(db/insert! conn :profile-complaint-report
|
||||
{:profile-id profile-id
|
||||
:type (name type)
|
||||
:content (db/tjson report)})
|
||||
|
||||
;; TODO: maybe also try to find profiles by email and if exists
|
||||
;; register profile reports for them?
|
||||
(doseq [email (:recipients report)]
|
||||
(db/insert! conn :global-complaint-report
|
||||
{:email email
|
||||
:type (name type)
|
||||
:content (db/tjson report)}))
|
||||
|
||||
(let [profile (db/exec-one! conn (sql/select :profile {:id profile-id}))]
|
||||
(when (some #(= % (:email profile)) (:recipients report))
|
||||
;; If the report matches the profile email, this means that
|
||||
;; the report is for itself, rare case but can happen; In this
|
||||
;; case just mark profile as muted (very rare case).
|
||||
(db/update! conn :profile
|
||||
{:is-muted true}
|
||||
{:id profile-id})))))
|
||||
|
||||
(defn- process-report
|
||||
[cfg {:keys [type profile-id] :as report}]
|
||||
(l/trace :action "processing report" :report (pr-str report))
|
||||
(cond
|
||||
;; In this case we receive a bounce/complaint notification without
|
||||
;; confirmed identity, we just emit a warning but do nothing about
|
||||
;; it because this is not a normal case. All notifications should
|
||||
;; come with profile identity.
|
||||
(nil? profile-id)
|
||||
(l/warn :msg "a notification without identity received from AWS"
|
||||
:report (pr-str report))
|
||||
|
||||
(= "bounce" type)
|
||||
(register-bounce-for-profile cfg report)
|
||||
|
||||
(= "complaint" type)
|
||||
(register-complaint-for-profile cfg report)
|
||||
|
||||
:else
|
||||
(l/warn :msg "unrecognized report received from AWS"
|
||||
:report (pr-str report))))
|
||||
|
||||
|
||||
51
backend/src/app/http/client.clj
Normal file
51
backend/src/app/http/client.clj
Normal file
@@ -0,0 +1,51 @@
|
||||
;; 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.http.client
|
||||
"Http client abstraction layer."
|
||||
(:require
|
||||
[app.common.spec :as us]
|
||||
[app.worker :as wrk]
|
||||
[clojure.spec.alpha :as s]
|
||||
[integrant.core :as ig]
|
||||
[java-http-clj.core :as http]
|
||||
[promesa.core :as p])
|
||||
(:import
|
||||
java.net.http.HttpClient))
|
||||
|
||||
(s/def ::client #(instance? HttpClient %))
|
||||
(s/def ::client-holder
|
||||
(s/keys :req [::client]))
|
||||
|
||||
(defmethod ig/pre-init-spec ::client [_]
|
||||
(s/keys :req [::wrk/executor]))
|
||||
|
||||
(defmethod ig/init-key ::client
|
||||
[_ {:keys [::wrk/executor] :as cfg}]
|
||||
(http/build-client {:executor executor
|
||||
:connect-timeout 30000 ;; 10s
|
||||
:follow-redirects :always}))
|
||||
|
||||
(defn send!
|
||||
([client req] (send! client req {}))
|
||||
([client req {:keys [response-type sync?] :or {response-type :string sync? false}}]
|
||||
(us/assert! ::client client)
|
||||
(if sync?
|
||||
(http/send req {:client client :as response-type})
|
||||
(try
|
||||
(http/send-async req {:client client :as response-type})
|
||||
(catch Throwable cause
|
||||
(p/rejected cause))))))
|
||||
|
||||
(defn req!
|
||||
"A convencience toplevel function for gradual migration to a new API
|
||||
convention."
|
||||
([{:keys [::client] :as holder} request]
|
||||
(us/assert! ::client-holder holder)
|
||||
(send! client request {}))
|
||||
([{:keys [::client] :as holder} request options]
|
||||
(us/assert! ::client-holder holder)
|
||||
(send! client request options)))
|
||||
406
backend/src/app/http/debug.clj
Normal file
406
backend/src/app/http/debug.clj
Normal file
@@ -0,0 +1,406 @@
|
||||
;; 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.http.debug
|
||||
(:refer-clojure :exclude [error-handler])
|
||||
(:require
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.logging :as l]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.http.middleware :as mw]
|
||||
[app.http.session :as session]
|
||||
[app.rpc.commands.binfile :as binf]
|
||||
[app.rpc.commands.files.create :refer [create-file]]
|
||||
[app.rpc.queries.profile :as profile]
|
||||
[app.util.blob :as blob]
|
||||
[app.util.template :as tmpl]
|
||||
[app.util.time :as dt]
|
||||
[app.worker :as wrk]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[datoteka.io :as io]
|
||||
[emoji.core :as emj]
|
||||
[integrant.core :as ig]
|
||||
[markdown.core :as md]
|
||||
[markdown.transformers :as mdt]
|
||||
[yetti.request :as yrq]
|
||||
[yetti.response :as yrs]))
|
||||
|
||||
;; (selmer.parser/cache-off!)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; HELPERS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn authorized?
|
||||
[pool {:keys [profile-id]}]
|
||||
(or (= "devenv" (cf/get :host))
|
||||
(let [profile (ex/ignoring (profile/retrieve-profile-data pool profile-id))
|
||||
admins (or (cf/get :admins) #{})]
|
||||
(contains? admins (:email profile)))))
|
||||
|
||||
(defn prepare-response
|
||||
[body]
|
||||
(let [headers {"content-type" "application/transit+json"}]
|
||||
(yrs/response :status 200 :body body :headers headers)))
|
||||
|
||||
(defn prepare-download-response
|
||||
[body filename]
|
||||
(let [headers {"content-disposition" (str "attachment; filename=" filename)
|
||||
"content-type" "application/octet-stream"}]
|
||||
(yrs/response :status 200 :body body :headers headers)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; INDEX
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn index-handler
|
||||
[{:keys [pool]} request]
|
||||
(when-not (authorized? pool request)
|
||||
(ex/raise :type :authentication
|
||||
:code :only-admins-allowed))
|
||||
(yrs/response :status 200
|
||||
:headers {"content-type" "text/html"}
|
||||
:body (-> (io/resource "app/templates/debug.tmpl")
|
||||
(tmpl/render {}))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; FILE CHANGES
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def sql:retrieve-range-of-changes
|
||||
"select revn, changes from file_change where file_id=? and revn >= ? and revn <= ? order by revn")
|
||||
|
||||
(def sql:retrieve-single-change
|
||||
"select revn, changes, data from file_change where file_id=? and revn = ?")
|
||||
|
||||
(defn- retrieve-file-data
|
||||
[{:keys [pool]} {:keys [params profile-id] :as request}]
|
||||
(when-not (authorized? pool request)
|
||||
(ex/raise :type :authentication
|
||||
:code :only-admins-allowed))
|
||||
|
||||
(let [file-id (some-> params :file-id parse-uuid)
|
||||
revn (some-> params :revn parse-long)
|
||||
filename (str file-id)]
|
||||
|
||||
(when-not file-id
|
||||
(ex/raise :type :validation
|
||||
:code :missing-arguments))
|
||||
|
||||
(let [data (if (integer? revn)
|
||||
(some-> (db/exec-one! pool [sql:retrieve-single-change file-id revn]) :data)
|
||||
(some-> (db/get-by-id pool :file file-id) :data))]
|
||||
|
||||
(when-not data
|
||||
(ex/raise :type :not-found
|
||||
:code :enpty-data
|
||||
:hint "empty response"))
|
||||
(cond
|
||||
(contains? params :download)
|
||||
(prepare-download-response data filename)
|
||||
|
||||
(contains? params :clone)
|
||||
(let [project-id (some-> (profile/retrieve-additional-data pool profile-id) :default-project-id)
|
||||
data (some-> data blob/decode)]
|
||||
(create-file pool {:id (uuid/next)
|
||||
:name (str "Cloned file: " filename)
|
||||
:project-id project-id
|
||||
:profile-id profile-id
|
||||
:data data})
|
||||
(yrs/response 201 "OK CREATED"))
|
||||
|
||||
:else
|
||||
(prepare-response (some-> data blob/decode))))))
|
||||
|
||||
(defn- is-file-exists?
|
||||
[pool id]
|
||||
(let [sql "select exists (select 1 from file where id=?) as exists;"]
|
||||
(-> (db/exec-one! pool [sql id]) :exists)))
|
||||
|
||||
(defn- upload-file-data
|
||||
[{:keys [pool]} {:keys [profile-id params] :as request}]
|
||||
(let [project-id (some-> (profile/retrieve-additional-data pool profile-id) :default-project-id)
|
||||
data (some-> params :file :path io/read-as-bytes blob/decode)]
|
||||
|
||||
(if (and data project-id)
|
||||
(let [fname (str "Imported file *: " (dt/now))
|
||||
overwrite? (contains? params :overwrite?)
|
||||
file-id (or (and overwrite? (ex/ignoring (-> params :file :filename parse-uuid)))
|
||||
(uuid/next))]
|
||||
|
||||
(if (and overwrite? file-id
|
||||
(is-file-exists? pool file-id))
|
||||
(do
|
||||
(db/update! pool :file
|
||||
{:data (blob/encode data)}
|
||||
{:id file-id})
|
||||
(yrs/response 200 "OK UPDATED"))
|
||||
|
||||
(do
|
||||
(create-file pool {:id file-id
|
||||
:name fname
|
||||
:project-id project-id
|
||||
:profile-id profile-id
|
||||
:data data})
|
||||
(yrs/response 201 "OK CREATED"))))
|
||||
|
||||
(yrs/response 500 "ERROR"))))
|
||||
|
||||
(defn file-data-handler
|
||||
[cfg request]
|
||||
(case (yrq/method request)
|
||||
:get (retrieve-file-data cfg request)
|
||||
:post (upload-file-data cfg request)
|
||||
(ex/raise :type :http
|
||||
:code :method-not-found)))
|
||||
|
||||
(defn file-changes-handler
|
||||
[{:keys [pool]} {:keys [params] :as request}]
|
||||
(when-not (authorized? pool request)
|
||||
(ex/raise :type :authentication
|
||||
:code :only-admins-allowed))
|
||||
|
||||
(letfn [(retrieve-changes [file-id revn]
|
||||
(if (str/includes? revn ":")
|
||||
(let [[start end] (->> (str/split revn #":")
|
||||
(map str/trim)
|
||||
(map parse-long))]
|
||||
(some->> (db/exec! pool [sql:retrieve-range-of-changes file-id start end])
|
||||
(map :changes)
|
||||
(map blob/decode)
|
||||
(mapcat identity)
|
||||
(vec)))
|
||||
|
||||
(if-let [revn (parse-long revn)]
|
||||
(let [item (db/exec-one! pool [sql:retrieve-single-change file-id revn])]
|
||||
(some-> item :changes blob/decode vec))
|
||||
(ex/raise :type :validation :code :invalid-arguments))))]
|
||||
|
||||
(let [file-id (some-> params :id parse-uuid)
|
||||
revn (or (some-> params :revn parse-long) "latest")
|
||||
filename (str file-id)]
|
||||
|
||||
(when (or (not file-id) (not revn))
|
||||
(ex/raise :type :validation
|
||||
:code :invalid-arguments
|
||||
:hint "missing arguments"))
|
||||
|
||||
(let [data (retrieve-changes file-id revn)]
|
||||
(if (contains? params :download)
|
||||
(prepare-download-response data filename)
|
||||
(prepare-response data))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; ERROR BROWSER
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn error-handler
|
||||
[{:keys [pool]} request]
|
||||
(letfn [(parse-id [request]
|
||||
(let [id (get-in request [:path-params :id])
|
||||
id (parse-uuid id)]
|
||||
(when (uuid? id)
|
||||
id)))
|
||||
|
||||
(retrieve-report [id]
|
||||
(ex/ignoring
|
||||
(some-> (db/get-by-id pool :server-error-report id) :content db/decode-transit-pgobject)))
|
||||
|
||||
(render-template [report]
|
||||
(let [context (dissoc report
|
||||
:trace :cause :params :data :spec-problems :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)}]
|
||||
(-> (io/resource "app/templates/error-report.tmpl")
|
||||
(tmpl/render params))))]
|
||||
|
||||
(when-not (authorized? pool request)
|
||||
(ex/raise :type :authentication
|
||||
:code :only-admins-allowed))
|
||||
|
||||
(let [result (some-> (parse-id request)
|
||||
(retrieve-report)
|
||||
(render-template))]
|
||||
(if result
|
||||
(yrs/response :status 200
|
||||
:body result
|
||||
:headers {"content-type" "text/html; charset=utf-8"
|
||||
"x-robots-tag" "noindex"})
|
||||
(yrs/response 404 "not found")))))
|
||||
|
||||
(def sql:error-reports
|
||||
"SELECT id, created_at,
|
||||
content->>'~:hint' AS hint
|
||||
FROM server_error_report
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 100")
|
||||
|
||||
(defn error-list-handler
|
||||
[{:keys [pool]} request]
|
||||
(when-not (authorized? pool request)
|
||||
(ex/raise :type :authentication
|
||||
:code :only-admins-allowed))
|
||||
(let [items (->> (db/exec! pool [sql:error-reports])
|
||||
(map #(update % :created-at dt/format-instant :rfc1123)))]
|
||||
(yrs/response :status 200
|
||||
:body (-> (io/resource "app/templates/error-list.tmpl")
|
||||
(tmpl/render {:items items}))
|
||||
:headers {"content-type" "text/html; charset=utf-8"
|
||||
"x-robots-tag" "noindex"})))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; EXPORT/IMPORT
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn export-handler
|
||||
[{:keys [pool] :as cfg} {:keys [params profile-id] :as request}]
|
||||
|
||||
(let [file-ids (->> (:file-ids params)
|
||||
(remove empty?)
|
||||
(mapv parse-uuid))
|
||||
libs? (contains? params :includelibs)
|
||||
clone? (contains? params :clone)
|
||||
embed? (contains? params :embedassets)]
|
||||
|
||||
(when-not (seq file-ids)
|
||||
(ex/raise :type :validation
|
||||
:code :missing-arguments))
|
||||
|
||||
(let [path (-> cfg
|
||||
(assoc ::binf/file-ids file-ids)
|
||||
(assoc ::binf/embed-assets? embed?)
|
||||
(assoc ::binf/include-libraries? libs?)
|
||||
(binf/export-to-tmpfile!))]
|
||||
(if clone?
|
||||
(let [project-id (some-> (profile/retrieve-additional-data pool profile-id) :default-project-id)]
|
||||
(binf/import!
|
||||
(assoc cfg
|
||||
::binf/input path
|
||||
::binf/overwrite? false
|
||||
::binf/ignore-index-errors? true
|
||||
::binf/profile-id profile-id
|
||||
::binf/project-id project-id))
|
||||
|
||||
(yrs/response
|
||||
:status 200
|
||||
:headers {"content-type" "text/plain"}
|
||||
:body "OK CLONED"))
|
||||
|
||||
(yrs/response
|
||||
:status 200
|
||||
:headers {"content-type" "application/octet-stream"
|
||||
"content-disposition" (str "attachmen; filename=" (first file-ids) ".penpot")}
|
||||
:body (io/input-stream path))))))
|
||||
|
||||
|
||||
(defn import-handler
|
||||
[{:keys [pool] :as cfg} {:keys [params profile-id] :as request}]
|
||||
(when-not (contains? params :file)
|
||||
(ex/raise :type :validation
|
||||
:code :missing-upload-file
|
||||
:hint "missing upload file"))
|
||||
|
||||
(let [project-id (some-> (profile/retrieve-additional-data pool profile-id) :default-project-id)
|
||||
overwrite? (contains? params :overwrite)
|
||||
migrate? (contains? params :migrate)
|
||||
ignore-index-errors? (contains? params :ignore-index-errors)]
|
||||
|
||||
(when-not project-id
|
||||
(ex/raise :type :validation
|
||||
:code :missing-project
|
||||
:hint "project not found"))
|
||||
|
||||
(binf/import!
|
||||
(assoc cfg
|
||||
::binf/input (-> params :file :path)
|
||||
::binf/overwrite? overwrite?
|
||||
::binf/migrate? migrate?
|
||||
::binf/ignore-index-errors? ignore-index-errors?
|
||||
::binf/profile-id profile-id
|
||||
::binf/project-id project-id))
|
||||
|
||||
(yrs/response
|
||||
:status 200
|
||||
:headers {"content-type" "text/plain"}
|
||||
:body "OK")))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; OTHER SMALL VIEWS/HANDLERS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn health-handler
|
||||
"Mainly a task that performs a health check."
|
||||
[{:keys [pool]} _]
|
||||
(db/with-atomic [conn pool]
|
||||
(try
|
||||
(db/exec-one! conn ["select count(*) as count from server_prop;"])
|
||||
(yrs/response 200 "OK")
|
||||
(catch Throwable cause
|
||||
(l/warn :hint "unable to execute query on health handler"
|
||||
:cause cause)
|
||||
(yrs/response 503 "KO")))))
|
||||
|
||||
(defn changelog-handler
|
||||
[_ _]
|
||||
(letfn [(transform-emoji [text state]
|
||||
[(emj/emojify text) state])
|
||||
(md->html [text]
|
||||
(md/md-to-html-string text :replacement-transformers (into [transform-emoji] mdt/transformer-vector)))]
|
||||
(if-let [clog (io/resource "changelog.md")]
|
||||
(yrs/response :status 200
|
||||
:headers {"content-type" "text/html; charset=utf-8"}
|
||||
:body (-> clog slurp md->html))
|
||||
(yrs/response :status 404 :body "NOT FOUND"))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; INIT
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def with-authorization
|
||||
{:compile
|
||||
(fn [& _]
|
||||
(fn [handler pool]
|
||||
(fn [request respond raise]
|
||||
(if (authorized? pool request)
|
||||
(handler request respond raise)
|
||||
(raise (ex/error :type :authentication
|
||||
:code :only-admins-allowed))))))})
|
||||
|
||||
|
||||
(defmethod ig/pre-init-spec ::routes [_]
|
||||
(s/keys :req-un [::db/pool ::wrk/executor ::session/session]))
|
||||
|
||||
(defmethod ig/init-key ::routes
|
||||
[_ {:keys [session pool executor] :as cfg}]
|
||||
[["/readyz" {:middleware [[mw/with-dispatch executor]
|
||||
[mw/with-config cfg]]
|
||||
:handler health-handler}]
|
||||
["/dbg" {:middleware [[session/middleware-2 session]
|
||||
[with-authorization pool]
|
||||
[mw/with-dispatch executor]
|
||||
[mw/with-config cfg]]}
|
||||
["" {:handler index-handler}]
|
||||
["/health" {:handler health-handler}]
|
||||
["/changelog" {:handler changelog-handler}]
|
||||
;; ["/error-by-id/:id" {:handler error-handler}]
|
||||
["/error/:id" {:handler error-handler}]
|
||||
["/error" {:handler error-list-handler}]
|
||||
["/file/export" {:handler export-handler}]
|
||||
["/file/import" {:handler import-handler}]
|
||||
["/file/data" {:handler file-data-handler}]
|
||||
["/file/changes" {:handler file-changes-handler}]]])
|
||||
183
backend/src/app/http/errors.clj
Normal file
183
backend/src/app/http/errors.clj
Normal file
@@ -0,0 +1,183 @@
|
||||
;; 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.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]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[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
|
||||
[request]
|
||||
(let [claims (:session-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)}))))
|
||||
|
||||
(defmulti handle-exception
|
||||
(fn [err & _rest]
|
||||
(let [edata (ex-data err)]
|
||||
(or (:type edata)
|
||||
(class err)))))
|
||||
|
||||
(defmethod handle-exception :authentication
|
||||
[err _]
|
||||
(yrs/response 401 (ex-data err)))
|
||||
|
||||
(defmethod handle-exception :restriction
|
||||
[err _]
|
||||
(yrs/response 400 (ex-data err)))
|
||||
|
||||
(defmethod handle-exception :rate-limit
|
||||
[err _]
|
||||
(let [headers (-> err ex-data ::http/headers)]
|
||||
(yrs/response :status 429 :body "" :headers headers)))
|
||||
|
||||
(defmethod handle-exception :validation
|
||||
[err _]
|
||||
(let [{:keys [code] :as data} (ex-data err)]
|
||||
(cond
|
||||
(= code :spec-validation)
|
||||
(let [explain (ex/explain data)]
|
||||
(yrs/response :status 400
|
||||
:body (-> data
|
||||
(dissoc ::s/problems ::s/value)
|
||||
(cond-> explain (assoc :explain explain)))))
|
||||
|
||||
(= code :request-body-too-large)
|
||||
(yrs/response :status 413 :body data)
|
||||
|
||||
:else
|
||||
(yrs/response :status 400 :body data))))
|
||||
|
||||
(defmethod handle-exception :assertion
|
||||
[error request]
|
||||
(let [edata (ex-data error)
|
||||
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)))})))
|
||||
|
||||
(defmethod handle-exception :not-found
|
||||
[err _]
|
||||
(yrs/response 404 (ex-data err)))
|
||||
|
||||
(defmethod handle-exception :internal
|
||||
[error request]
|
||||
(let [{:keys [code] :as edata} (ex-data error)]
|
||||
(cond
|
||||
(= :concurrency-limit-reached code)
|
||||
(yrs/response 429)
|
||||
|
||||
:else
|
||||
(do
|
||||
(l/error :hint (ex-message error)
|
||||
:cause error
|
||||
::l/context (get-context request))
|
||||
(yrs/response 500 {:type :server-error
|
||||
:code :unhandled
|
||||
:hint (ex-message error)
|
||||
:data edata})))))
|
||||
|
||||
(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)})
|
||||
|
||||
(= state "25P03")
|
||||
(yrs/response 504 {:type :server-error
|
||||
:code :idle-in-transaction-timeout
|
||||
:hint (ex-message error)})
|
||||
|
||||
:else
|
||||
(yrs/response 500 {:type :server-error
|
||||
:code :unexpected
|
||||
:hint (ex-message error)
|
||||
:state state}))))
|
||||
|
||||
(defmethod handle-exception :default
|
||||
[error request]
|
||||
(let [edata (ex-data error)]
|
||||
(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))
|
||||
(yrs/response 500 {:type :server-error
|
||||
:code :unexpected
|
||||
:hint (ex-message error)}))
|
||||
|
||||
;; This is a special case for the idle-in-transaction error;
|
||||
;; when it happens, the connection is automatically closed and
|
||||
;; next-jdbc combines the two errors in a single ex-info. We
|
||||
;; only need the :handling error, because the :rollback error
|
||||
;; will be always "connection closed".
|
||||
(and (ex/exception? (:rollback edata))
|
||||
(ex/exception? (:handling edata)))
|
||||
(handle-exception (:handling edata) request)
|
||||
|
||||
:else
|
||||
(do
|
||||
(l/error :hint (ex-message error)
|
||||
:cause error
|
||||
::l/context (get-context request))
|
||||
(yrs/response 500 {:type :server-error
|
||||
:code :unhandled
|
||||
:hint (ex-message error)
|
||||
:data edata})))))
|
||||
|
||||
(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
|
||||
(handle-exception cause request)))
|
||||
80
backend/src/app/http/feedback.clj
Normal file
80
backend/src/app/http/feedback.clj
Normal file
@@ -0,0 +1,80 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.http.feedback
|
||||
"A general purpose feedback module."
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.spec :as us]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.emails :as eml]
|
||||
[app.rpc.queries.profile :as profile]
|
||||
[app.worker :as wrk]
|
||||
[clojure.spec.alpha :as s]
|
||||
[integrant.core :as ig]
|
||||
[promesa.core :as p]
|
||||
[promesa.exec :as px]
|
||||
[yetti.request :as yrq]
|
||||
[yetti.response :as yrs]))
|
||||
|
||||
(declare ^:private send-feedback)
|
||||
(declare ^:private handler)
|
||||
|
||||
(defmethod ig/pre-init-spec ::handler [_]
|
||||
(s/keys :req-un [::db/pool ::wrk/executor]))
|
||||
|
||||
(defmethod ig/init-key ::handler
|
||||
[_ {:keys [executor] :as cfg}]
|
||||
(let [enabled? (contains? cf/flags :user-feedback)]
|
||||
(if enabled?
|
||||
(fn [request respond raise]
|
||||
(-> (px/submit! executor #(handler cfg request))
|
||||
(p/then' respond)
|
||||
(p/catch raise)))
|
||||
(fn [_ _ raise]
|
||||
(raise (ex/error :type :validation
|
||||
:code :feedback-disabled
|
||||
:hint "feedback module is disabled"))))))
|
||||
|
||||
(defn- handler
|
||||
[{:keys [pool] :as cfg} {:keys [profile-id] :as request}]
|
||||
(let [ftoken (cf/get :feedback-token ::no-token)
|
||||
token (yrq/get-header request "x-feedback-token")
|
||||
params (d/merge (:params request)
|
||||
(:body-params request))]
|
||||
(cond
|
||||
(uuid? profile-id)
|
||||
(let [profile (profile/retrieve-profile-data pool profile-id)
|
||||
params (assoc params :from (:email profile))]
|
||||
(send-feedback pool profile params))
|
||||
|
||||
(= token ftoken)
|
||||
(send-feedback cfg nil params))
|
||||
|
||||
(yrs/response 204)))
|
||||
|
||||
(s/def ::content ::us/string)
|
||||
(s/def ::from ::us/email)
|
||||
(s/def ::subject ::us/string)
|
||||
(s/def ::feedback
|
||||
(s/keys :req-un [::from ::subject ::content]))
|
||||
|
||||
(defn- send-feedback
|
||||
[pool profile params]
|
||||
(let [params (us/conform ::feedback params)
|
||||
destination (cf/get :feedback-destination)]
|
||||
(eml/send! {::eml/conn pool
|
||||
::eml/factory eml/feedback
|
||||
:from destination
|
||||
:to destination
|
||||
:profile profile
|
||||
:reply-to (:from params)
|
||||
:email (:from params)
|
||||
:subject (:subject params)
|
||||
:content (:content params)})
|
||||
nil))
|
||||
264
backend/src/app/http/middleware.clj
Normal file
264
backend/src/app/http/middleware.clj
Normal file
@@ -0,0 +1,264 @@
|
||||
;; 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.http.middleware
|
||||
(:require
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.logging :as l]
|
||||
[app.common.transit :as t]
|
||||
[app.config :as cf]
|
||||
[app.util.json :as json]
|
||||
[cuerdas.core :as str]
|
||||
[promesa.core :as p]
|
||||
[promesa.exec :as px]
|
||||
[yetti.adapter :as yt]
|
||||
[yetti.middleware :as ymw]
|
||||
[yetti.request :as yrq]
|
||||
[yetti.response :as yrs])
|
||||
(:import
|
||||
com.fasterxml.jackson.core.JsonParseException
|
||||
com.fasterxml.jackson.core.io.JsonEOFException
|
||||
io.undertow.server.RequestTooBigException
|
||||
java.io.OutputStream))
|
||||
|
||||
(def server-timing
|
||||
{:name ::server-timing
|
||||
:compile (constantly ymw/wrap-server-timing)})
|
||||
|
||||
(def params
|
||||
{:name ::params
|
||||
:compile (constantly ymw/wrap-params)})
|
||||
|
||||
(def ^:private json-mapper
|
||||
(json/mapper
|
||||
{:encode-key-fn str/camel
|
||||
:decode-key-fn (comp keyword str/kebab)
|
||||
:pretty true}))
|
||||
|
||||
(defn wrap-parse-request
|
||||
[handler]
|
||||
(letfn [(process-request [request]
|
||||
(let [header (yrq/get-header request "content-type")]
|
||||
(cond
|
||||
(str/starts-with? header "application/transit+json")
|
||||
(with-open [is (yrq/body request)]
|
||||
(let [params (t/read! (t/reader is))]
|
||||
(-> request
|
||||
(assoc :body-params params)
|
||||
(update :params merge params))))
|
||||
|
||||
(str/starts-with? header "application/json")
|
||||
(with-open [is (yrq/body request)]
|
||||
(let [params (json/decode is json-mapper)]
|
||||
(-> request
|
||||
(assoc :body-params params)
|
||||
(update :params merge params))))
|
||||
|
||||
:else
|
||||
request)))
|
||||
|
||||
(handle-error [raise cause]
|
||||
(cond
|
||||
(instance? RequestTooBigException cause)
|
||||
(raise (ex/error :type :validation
|
||||
:code :request-body-too-large
|
||||
:hint (ex-message cause)))
|
||||
|
||||
|
||||
(or (instance? JsonEOFException cause)
|
||||
(instance? JsonParseException cause))
|
||||
(raise (ex/error :type :validation
|
||||
:code :malformed-json
|
||||
:hint (ex-message cause)
|
||||
:cause cause))
|
||||
:else
|
||||
(raise cause)))]
|
||||
|
||||
(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))
|
||||
(handle-error raise request))
|
||||
(handler request respond raise))))))
|
||||
|
||||
(def parse-request
|
||||
{:name ::parse-request
|
||||
:compile (constantly wrap-parse-request)})
|
||||
|
||||
(defn buffered-output-stream
|
||||
"Returns a buffered output stream that ignores flush calls. This is
|
||||
needed because transit-java calls flush very aggresivelly on each
|
||||
object write."
|
||||
[^java.io.OutputStream os ^long chunk-size]
|
||||
(proxy [java.io.BufferedOutputStream] [os (int chunk-size)]
|
||||
;; Explicitly do not forward flush
|
||||
(flush [])
|
||||
(close []
|
||||
(proxy-super flush)
|
||||
(proxy-super close))))
|
||||
|
||||
(def ^:const buffer-size (:xnio/buffer-size yt/defaults))
|
||||
|
||||
(defn wrap-format-response
|
||||
[handler]
|
||||
(letfn [(transit-streamable-body [data opts]
|
||||
(reify yrs/StreamableResponseBody
|
||||
(-write-body-to-stream [_ _ output-stream]
|
||||
(try
|
||||
(with-open [bos (buffered-output-stream output-stream buffer-size)]
|
||||
(let [tw (t/writer bos opts)]
|
||||
(t/write! tw data)))
|
||||
|
||||
(catch java.io.IOException _cause
|
||||
;; Do nothing, EOF means client closes connection abruptly
|
||||
nil)
|
||||
(catch Throwable cause
|
||||
(l/warn :hint "unexpected error on encoding response"
|
||||
:cause cause))
|
||||
(finally
|
||||
(.close ^OutputStream output-stream))))))
|
||||
|
||||
(json-streamable-body [data]
|
||||
(reify yrs/StreamableResponseBody
|
||||
(-write-body-to-stream [_ _ output-stream]
|
||||
(try
|
||||
|
||||
(with-open [bos (buffered-output-stream output-stream buffer-size)]
|
||||
(json/write! bos data json-mapper))
|
||||
|
||||
(catch java.io.IOException _cause
|
||||
;; Do nothing, EOF means client closes connection abruptly
|
||||
nil)
|
||||
(catch Throwable cause
|
||||
(l/warn :hint "unexpected error on encoding response"
|
||||
:cause cause))
|
||||
(finally
|
||||
(.close ^OutputStream output-stream))))))
|
||||
|
||||
(format-response-with-json [response _]
|
||||
(let [body (yrs/body response)]
|
||||
(if (or (boolean? body) (coll? body))
|
||||
(-> response
|
||||
(update :headers assoc "content-type" "application/json")
|
||||
(assoc :body (json-streamable-body body)))
|
||||
response)))
|
||||
|
||||
(format-response-with-transit [response request]
|
||||
(let [body (yrs/body response)]
|
||||
(if (or (boolean? body) (coll? body))
|
||||
(let [qs (yrq/query request)
|
||||
opts (if (or (contains? cf/flags :transit-readable-response)
|
||||
(str/includes? qs "transit_verbose"))
|
||||
{:type :json-verbose}
|
||||
{:type :json})]
|
||||
(-> response
|
||||
(update :headers assoc "content-type" "application/transit+json")
|
||||
(assoc :body (transit-streamable-body body opts))))
|
||||
response)))
|
||||
|
||||
(format-response [response request]
|
||||
(let [accept (yrq/get-header request "accept")]
|
||||
(cond
|
||||
(or (= accept "application/transit+json")
|
||||
(str/includes? accept "application/transit+json"))
|
||||
(format-response-with-transit response request)
|
||||
|
||||
(or (= accept "application/json")
|
||||
(str/includes? accept "application/json"))
|
||||
(format-response-with-json response request)
|
||||
|
||||
:else
|
||||
(format-response-with-transit response request))))
|
||||
|
||||
(process-response [response request]
|
||||
(cond-> response
|
||||
(map? response) (format-response request)))]
|
||||
|
||||
(fn [request respond raise]
|
||||
(handler request
|
||||
(fn [response]
|
||||
(let [response (process-response response request)]
|
||||
(respond response)))
|
||||
raise))))
|
||||
|
||||
(def format-response
|
||||
{:name ::format-response
|
||||
:compile (constantly wrap-format-response)})
|
||||
|
||||
(defn wrap-errors
|
||||
[handler on-error]
|
||||
(fn [request respond _]
|
||||
(handler request respond (fn [cause]
|
||||
(-> cause (on-error request) respond)))))
|
||||
|
||||
(def errors
|
||||
{:name ::errors
|
||||
:compile (constantly wrap-errors)})
|
||||
|
||||
(defn wrap-cors
|
||||
[handler]
|
||||
(if-not (contains? cf/flags :cors)
|
||||
handler
|
||||
(letfn [(add-headers [headers request]
|
||||
(let [origin (yrq/get-header request "origin")]
|
||||
(-> headers
|
||||
(assoc "access-control-allow-origin" origin)
|
||||
(assoc "access-control-allow-methods" "GET,POST,DELETE,OPTIONS,PUT,HEAD,PATCH")
|
||||
(assoc "access-control-allow-credentials" "true")
|
||||
(assoc "access-control-expose-headers" "x-requested-with, content-type, cookie")
|
||||
(assoc "access-control-allow-headers" "x-frontend-version, content-type, accept, x-requested-width"))))
|
||||
|
||||
(update-response [response request]
|
||||
(update response :headers add-headers request))]
|
||||
|
||||
(fn [request respond raise]
|
||||
(if (= (yrq/method request) :options)
|
||||
(-> (yrs/response 200)
|
||||
(update-response request)
|
||||
(respond))
|
||||
(handler request
|
||||
(fn [response]
|
||||
(respond (update-response response request)))
|
||||
raise))))))
|
||||
|
||||
(def cors
|
||||
{:name ::cors
|
||||
:compile (constantly wrap-cors)})
|
||||
|
||||
(defn compile-restrict-methods
|
||||
[data _]
|
||||
(when-let [allowed (:allowed-methods data)]
|
||||
(fn [handler]
|
||||
(fn [request respond raise]
|
||||
(let [method (yrq/method request)]
|
||||
(if (contains? allowed method)
|
||||
(handler request respond raise)
|
||||
(respond (yrs/response 405))))))))
|
||||
|
||||
(def restrict-methods
|
||||
{:name ::restrict-methods
|
||||
:compile compile-restrict-methods})
|
||||
|
||||
(def with-dispatch
|
||||
{:name ::with-dispatch
|
||||
:compile
|
||||
(fn [& _]
|
||||
(fn [handler executor]
|
||||
(fn [request respond raise]
|
||||
(-> (px/submit! executor #(handler request))
|
||||
(p/bind p/wrap)
|
||||
(p/then respond)
|
||||
(p/catch raise)))))})
|
||||
|
||||
(def with-config
|
||||
{:name ::with-config
|
||||
:compile
|
||||
(fn [& _]
|
||||
(fn [handler config]
|
||||
(fn
|
||||
([request] (handler config request))
|
||||
([request respond raise] (handler config request respond raise)))))})
|
||||
334
backend/src/app/http/session.clj
Normal file
334
backend/src/app/http/session.clj
Normal file
@@ -0,0 +1,334 @@
|
||||
;; 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.http.session
|
||||
(:refer-clojure :exclude [read])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.logging :as l]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.db.sql :as sql]
|
||||
[app.main :as-alias main]
|
||||
[app.tokens :as tokens]
|
||||
[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.request :as yrq]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; DEFAULTS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; A default cookie name for storing the session.
|
||||
(def default-auth-token-cookie-name "auth-token")
|
||||
|
||||
;; A cookie that we can use to check from other sites of the same
|
||||
;; domain if a user is authenticated.
|
||||
(def default-authenticated-cookie-name "authenticated")
|
||||
|
||||
;; Default value for cookie max-age
|
||||
(def default-cookie-max-age (dt/duration {:days 7}))
|
||||
|
||||
;; Default age for automatic session renewal
|
||||
(def default-renewal-max-age (dt/duration {:hours 6}))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; PROTOCOLS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defprotocol ISessionManager
|
||||
(read [_ key])
|
||||
(decode [_ key])
|
||||
(write! [_ key data])
|
||||
(update! [_ data])
|
||||
(delete! [_ key]))
|
||||
|
||||
(s/def ::session #(satisfies? ISessionManager %))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; STORAGE IMPL
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn- prepare-session-params
|
||||
[props data]
|
||||
(let [profile-id (:profile-id data)
|
||||
user-agent (:user-agent data)
|
||||
created-at (or (:created-at data) (dt/now))
|
||||
token (tokens/generate props {:iss "authentication"
|
||||
:iat created-at
|
||||
:uid profile-id})]
|
||||
{:user-agent user-agent
|
||||
:profile-id profile-id
|
||||
:created-at created-at
|
||||
:updated-at created-at
|
||||
:id token}))
|
||||
|
||||
(defn- database-manager
|
||||
[{:keys [::db/pool ::wrk/executor ::main/props]}]
|
||||
(reify ISessionManager
|
||||
(read [_ token]
|
||||
(px/with-dispatch executor
|
||||
(db/exec-one! pool (sql/select :http-session {:id token}))))
|
||||
|
||||
(decode [_ token]
|
||||
(px/with-dispatch executor
|
||||
(tokens/verify props {:token token :iss "authentication"})))
|
||||
|
||||
(write! [_ _ data]
|
||||
(px/with-dispatch executor
|
||||
(let [params (prepare-session-params props data)]
|
||||
(db/insert! pool :http-session params)
|
||||
params)))
|
||||
|
||||
(update! [_ data]
|
||||
(let [updated-at (dt/now)]
|
||||
(px/with-dispatch executor
|
||||
(db/update! pool :http-session
|
||||
{:updated-at updated-at}
|
||||
{:id (:id data)})
|
||||
(assoc data :updated-at updated-at))))
|
||||
|
||||
(delete! [_ token]
|
||||
(px/with-dispatch executor
|
||||
(db/delete! pool :http-session {:id token})
|
||||
nil))))
|
||||
|
||||
(defn inmemory-manager
|
||||
[{:keys [::wrk/executor ::main/props]}]
|
||||
(let [cache (atom {})]
|
||||
(reify ISessionManager
|
||||
(read [_ token]
|
||||
(p/do (get @cache token)))
|
||||
|
||||
(decode [_ token]
|
||||
(px/with-dispatch executor
|
||||
(tokens/verify props {:token token :iss "authentication"})))
|
||||
|
||||
(write! [_ _ data]
|
||||
(p/do
|
||||
(let [{:keys [token] :as params} (prepare-session-params props data)]
|
||||
(swap! cache assoc token params)
|
||||
params)))
|
||||
|
||||
(update! [_ data]
|
||||
(p/do
|
||||
(let [updated-at (dt/now)]
|
||||
(swap! cache update (:id data) assoc :updated-at updated-at)
|
||||
(assoc data :updated-at updated-at))))
|
||||
|
||||
(delete! [_ token]
|
||||
(p/do
|
||||
(swap! cache dissoc token)
|
||||
nil)))))
|
||||
|
||||
(defmethod ig/pre-init-spec ::manager [_]
|
||||
(s/keys :req [::db/pool ::wrk/executor ::main/props]))
|
||||
|
||||
(defmethod ig/init-key ::manager
|
||||
[_ {:keys [::db/pool] :as cfg}]
|
||||
(if (db/read-only? pool)
|
||||
(inmemory-manager cfg)
|
||||
(database-manager cfg)))
|
||||
|
||||
(defmethod ig/halt-key! ::manager
|
||||
[_ _])
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; MANAGER IMPL
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(declare assign-auth-token-cookie)
|
||||
(declare assign-authenticated-cookie)
|
||||
(declare clear-auth-token-cookie)
|
||||
(declare clear-authenticated-cookie)
|
||||
|
||||
(defn create-fn
|
||||
[manager profile-id]
|
||||
(fn [request response]
|
||||
(let [uagent (yrq/get-header request "user-agent")
|
||||
params {:profile-id profile-id
|
||||
:user-agent uagent}]
|
||||
(-> (write! manager nil params)
|
||||
(p/then (fn [session]
|
||||
(l/trace :hint "create" :profile-id profile-id)
|
||||
(-> response
|
||||
(assign-auth-token-cookie session)
|
||||
(assign-authenticated-cookie session))))))))
|
||||
(defn delete-fn
|
||||
[manager]
|
||||
(letfn [(delete [{:keys [profile-id] :as request}]
|
||||
(let [cname (cf/get :auth-token-cookie-name default-auth-token-cookie-name)
|
||||
cookie (yrq/get-cookie request cname)]
|
||||
(l/trace :hint "delete" :profile-id profile-id)
|
||||
(some->> (:value cookie) (delete! manager))))]
|
||||
(fn [request response]
|
||||
(p/do
|
||||
(delete request)
|
||||
(-> response
|
||||
(assoc :status 204)
|
||||
(assoc :body nil)
|
||||
(clear-auth-token-cookie)
|
||||
(clear-authenticated-cookie))))))
|
||||
|
||||
(def middleware-1
|
||||
(letfn [(decode-cookie [manager cookie]
|
||||
(if-let [value (:value cookie)]
|
||||
(decode manager value)
|
||||
(p/resolved nil)))
|
||||
|
||||
(wrap-handler [manager handler request respond raise]
|
||||
(let [cookie (some->> (cf/get :auth-token-cookie-name default-auth-token-cookie-name)
|
||||
(yrq/get-cookie request))]
|
||||
(->> (decode-cookie manager cookie)
|
||||
(p/fnly (fn [claims _]
|
||||
(cond-> request
|
||||
(some? claims) (assoc :session-token-claims claims)
|
||||
:always (handler respond raise)))))))]
|
||||
{:name :session-1
|
||||
:compile (fn [& _]
|
||||
(fn [handler manager]
|
||||
(partial wrap-handler manager handler)))}))
|
||||
|
||||
(def middleware-2
|
||||
(letfn [(wrap-handler [manager handler request respond raise]
|
||||
(-> (retrieve-session manager request)
|
||||
(p/finally (fn [session cause]
|
||||
(cond
|
||||
(some? cause)
|
||||
(raise cause)
|
||||
|
||||
(nil? session)
|
||||
(handler request respond raise)
|
||||
|
||||
:else
|
||||
(let [request (-> request
|
||||
(assoc :profile-id (:profile-id session))
|
||||
(assoc :session-id (:id session)))
|
||||
respond (cond-> respond
|
||||
(renew-session? session)
|
||||
(wrap-respond manager session))]
|
||||
(handler request respond raise)))))))
|
||||
|
||||
(retrieve-session [manager request]
|
||||
(let [cname (cf/get :auth-token-cookie-name default-auth-token-cookie-name)
|
||||
cookie (yrq/get-cookie request cname)]
|
||||
(some->> (:value cookie) (read manager))))
|
||||
|
||||
(renew-session? [{:keys [updated-at] :as session}]
|
||||
(and (dt/instant? updated-at)
|
||||
(let [elapsed (dt/diff updated-at (dt/now))]
|
||||
(neg? (compare default-renewal-max-age elapsed)))))
|
||||
|
||||
;; Wrap respond with session renewal code
|
||||
(wrap-respond [respond manager session]
|
||||
(fn [response]
|
||||
(p/let [session (update! manager session)]
|
||||
(-> response
|
||||
(assign-auth-token-cookie session)
|
||||
(assign-authenticated-cookie session)
|
||||
(respond)))))]
|
||||
|
||||
{:name :session-2
|
||||
:compile (fn [& _]
|
||||
(fn [handler manager]
|
||||
(partial wrap-handler manager handler)))}))
|
||||
|
||||
;; --- IMPL
|
||||
|
||||
(defn- assign-auth-token-cookie
|
||||
[response {token :id updated-at :updated-at}]
|
||||
(let [max-age (cf/get :auth-token-cookie-max-age default-cookie-max-age)
|
||||
created-at (or updated-at (dt/now))
|
||||
renewal (dt/plus created-at default-renewal-max-age)
|
||||
expires (dt/plus created-at max-age)
|
||||
secure? (contains? cf/flags :secure-session-cookies)
|
||||
cors? (contains? cf/flags :cors)
|
||||
name (cf/get :auth-token-cookie-name default-auth-token-cookie-name)
|
||||
comment (str "Renewal at: " (dt/format-instant renewal :rfc1123))
|
||||
cookie {:path "/"
|
||||
:http-only true
|
||||
:expires expires
|
||||
:value token
|
||||
:comment comment
|
||||
:same-site (if cors? :none :lax)
|
||||
:secure secure?}]
|
||||
(update response :cookies assoc name cookie)))
|
||||
|
||||
(defn- assign-authenticated-cookie
|
||||
[response {updated-at :updated-at}]
|
||||
(let [max-age (cf/get :auth-token-cookie-max-age default-cookie-max-age)
|
||||
created-at (or updated-at (dt/now))
|
||||
renewal (dt/plus created-at default-renewal-max-age)
|
||||
expires (dt/plus created-at max-age)
|
||||
comment (str "Renewal at: " (dt/format-instant renewal :rfc1123))
|
||||
secure? (contains? cf/flags :secure-session-cookies)
|
||||
domain (cf/get :authenticated-cookie-domain)
|
||||
name (cf/get :authenticated-cookie-name "authenticated")
|
||||
cookie {:domain domain
|
||||
:expires expires
|
||||
:path "/"
|
||||
:comment comment
|
||||
:value true
|
||||
:same-site :strict
|
||||
:secure secure?}]
|
||||
(cond-> response
|
||||
(string? domain)
|
||||
(update :cookies assoc name cookie))))
|
||||
|
||||
(defn- clear-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})))
|
||||
|
||||
(defn- clear-authenticated-cookie
|
||||
[response]
|
||||
(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}))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; TASK: SESSION GC
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(declare sql:delete-expired)
|
||||
|
||||
(s/def ::max-age ::dt/duration)
|
||||
|
||||
(defmethod ig/pre-init-spec ::gc-task [_]
|
||||
(s/keys :req-un [::db/pool]
|
||||
:opt-un [::max-age]))
|
||||
|
||||
(defmethod ig/prep-key ::gc-task
|
||||
[_ cfg]
|
||||
(merge {:max-age default-cookie-max-age}
|
||||
(d/without-nils cfg)))
|
||||
|
||||
(defmethod ig/init-key ::gc-task
|
||||
[_ {:keys [pool max-age] :as cfg}]
|
||||
(l/debug :hint "initializing session gc task" :max-age max-age)
|
||||
(fn [_]
|
||||
(db/with-atomic [conn pool]
|
||||
(let [interval (db/interval max-age)
|
||||
result (db/exec-one! conn [sql:delete-expired interval interval])
|
||||
result (:next.jdbc/update-count result)]
|
||||
(l/debug :task "gc"
|
||||
:hint "clean http sessions"
|
||||
:deleted result)
|
||||
result))))
|
||||
|
||||
(def ^:private
|
||||
sql:delete-expired
|
||||
"delete from http_session
|
||||
where updated_at < now() - ?::interval
|
||||
or (updated_at is null and
|
||||
created_at < now() - ?::interval)")
|
||||
351
backend/src/app/http/websocket.clj
Normal file
351
backend/src/app/http/websocket.clj
Normal file
@@ -0,0 +1,351 @@
|
||||
;; 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.http.websocket
|
||||
"A penpot notification service for file cooperative edition."
|
||||
(:require
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.logging :as l]
|
||||
[app.common.pprint :as pp]
|
||||
[app.common.spec :as us]
|
||||
[app.db :as db]
|
||||
[app.metrics :as mtx]
|
||||
[app.msgbus :as mbus]
|
||||
[app.util.time :as dt]
|
||||
[app.util.websocket :as ws]
|
||||
[clojure.core.async :as a]
|
||||
[clojure.spec.alpha :as s]
|
||||
[integrant.core :as ig]
|
||||
[yetti.websocket :as yws]))
|
||||
|
||||
(def recv-labels
|
||||
(into-array String ["recv"]))
|
||||
|
||||
(def send-labels
|
||||
(into-array String ["send"]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; WEBSOCKET HOOKS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def state (atom {}))
|
||||
|
||||
(defn- on-connect
|
||||
[{:keys [metrics]} wsp]
|
||||
(let [created-at (dt/now)]
|
||||
(swap! state assoc (::ws/id @wsp) wsp)
|
||||
(mtx/run! metrics
|
||||
:id :websocket-active-connections
|
||||
:inc 1)
|
||||
(fn []
|
||||
(swap! state dissoc (::ws/id @wsp))
|
||||
(mtx/run! metrics :id :websocket-active-connections :dec 1)
|
||||
(mtx/run! metrics
|
||||
:id :websocket-session-timing
|
||||
:val (/ (inst-ms (dt/diff created-at (dt/now))) 1000.0)))))
|
||||
|
||||
(defn- on-rcv-message
|
||||
[{:keys [metrics]} _ message]
|
||||
(mtx/run! metrics
|
||||
:id :websocket-messages-total
|
||||
:labels recv-labels
|
||||
:inc 1)
|
||||
message)
|
||||
|
||||
(defn- on-snd-message
|
||||
[{:keys [metrics]} _ message]
|
||||
(mtx/run! metrics
|
||||
:id :websocket-messages-total
|
||||
:labels send-labels
|
||||
:inc 1)
|
||||
message)
|
||||
|
||||
;; REPL HELPERS
|
||||
|
||||
(defn repl-get-connections-for-file
|
||||
[file-id]
|
||||
(->> (vals @state)
|
||||
(filter #(= file-id (-> % deref ::file-subscription :file-id)))
|
||||
(map deref)
|
||||
(map ::ws/id)))
|
||||
|
||||
(defn repl-get-connections-for-team
|
||||
[team-id]
|
||||
(->> (vals @state)
|
||||
(filter #(= team-id (-> % deref ::team-subscription :team-id)))
|
||||
(map deref)
|
||||
(map ::ws/id)))
|
||||
|
||||
(defn repl-close-connection
|
||||
[id]
|
||||
(when-let [wsp (get @state id)]
|
||||
(a/>!! (::ws/close-ch @wsp) [8899 "closed from server"])
|
||||
(a/close! (::ws/close-ch @wsp))))
|
||||
|
||||
(defn repl-get-connection-info
|
||||
[id]
|
||||
(when-let [wsp (get @state id)]
|
||||
{:id id
|
||||
:created-at (::created-at @wsp)
|
||||
:profile-id (::profile-id @wsp)
|
||||
:session-id (::session-id @wsp)
|
||||
:user-agent (::ws/user-agent @wsp)
|
||||
:ip-addr (::ws/remote-addr @wsp)
|
||||
:last-activity-at (::ws/last-activity-at @wsp)
|
||||
:http-session-id (::ws/http-session-id @wsp)
|
||||
:subscribed-file (-> wsp deref ::file-subscription :file-id)
|
||||
:subscribed-team (-> wsp deref ::team-subscription :team-id)}))
|
||||
|
||||
(defn repl-print-connection-info
|
||||
[id]
|
||||
(some-> id repl-get-connection-info pp/pprint))
|
||||
|
||||
(defn repl-print-connection-info-for-file
|
||||
[file-id]
|
||||
(some->> (repl-get-connections-for-file file-id)
|
||||
(map repl-get-connection-info)
|
||||
(pp/pprint)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; WEBSOCKET HANDLER
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defmulti handle-message
|
||||
(fn [_ _ message]
|
||||
(:type message)))
|
||||
|
||||
(defmethod handle-message :connect
|
||||
[cfg wsp _]
|
||||
|
||||
(let [msgbus (:msgbus cfg)
|
||||
conn-id (::ws/id @wsp)
|
||||
profile-id (::profile-id @wsp)
|
||||
session-id (::session-id @wsp)
|
||||
output-ch (::ws/output-ch @wsp)
|
||||
|
||||
xform (remove #(= (:session-id %) session-id))
|
||||
channel (a/chan (a/dropping-buffer 16) xform)]
|
||||
|
||||
(l/trace :fn "handle-message" :event "connect" :conn-id conn-id)
|
||||
|
||||
;; Subscribe to the profile channel and forward all messages to
|
||||
;; websocket output channel (send them to the client).
|
||||
(swap! wsp assoc ::profile-subscription channel)
|
||||
(a/pipe channel output-ch false)
|
||||
(mbus/sub! msgbus :topic profile-id :chan channel)))
|
||||
|
||||
(defmethod handle-message :disconnect
|
||||
[cfg wsp _]
|
||||
(let [msgbus (:msgbus cfg)
|
||||
conn-id (::ws/id @wsp)
|
||||
profile-id (::profile-id @wsp)
|
||||
session-id (::session-id @wsp)
|
||||
profile-ch (::profile-subscription @wsp)
|
||||
fsub (::file-subscription @wsp)
|
||||
tsub (::team-subscription @wsp)
|
||||
|
||||
message {:type :disconnect
|
||||
:subs-id profile-id
|
||||
:profile-id profile-id
|
||||
:session-id session-id}]
|
||||
|
||||
(l/trace :fn "handle-message"
|
||||
:event :disconnect
|
||||
:conn-id conn-id)
|
||||
|
||||
(a/go
|
||||
;; Close the main profile subscription
|
||||
(a/close! profile-ch)
|
||||
(a/<! (mbus/purge! msgbus [profile-ch]))
|
||||
|
||||
;; Close tram subscription if exists
|
||||
(when-let [channel (:channel tsub)]
|
||||
(a/close! channel)
|
||||
(a/<! (mbus/purge! msgbus channel)))
|
||||
|
||||
(when-let [{:keys [topic channel]} fsub]
|
||||
(a/close! channel)
|
||||
(a/<! (mbus/purge! msgbus channel))
|
||||
(a/<! (mbus/pub! msgbus :topic topic :message message))))))
|
||||
|
||||
(defmethod handle-message :subscribe-team
|
||||
[cfg wsp {:keys [team-id] :as params}]
|
||||
(let [msgbus (:msgbus cfg)
|
||||
conn-id (::ws/id @wsp)
|
||||
session-id (::session-id @wsp)
|
||||
output-ch (::ws/output-ch @wsp)
|
||||
prev-subs (get @wsp ::team-subscription)
|
||||
xform (comp
|
||||
(remove #(= (:session-id %) session-id))
|
||||
(map #(assoc % :subs-id team-id)))
|
||||
|
||||
channel (a/chan (a/dropping-buffer 64) xform)]
|
||||
|
||||
(l/trace :fn "handle-message"
|
||||
:event :subscribe-team
|
||||
:team-id team-id
|
||||
:conn-id conn-id)
|
||||
|
||||
(a/pipe channel output-ch false)
|
||||
|
||||
(let [state {:team-id team-id :channel channel :topic team-id}]
|
||||
(swap! wsp assoc ::team-subscription state))
|
||||
|
||||
(a/go
|
||||
;; Close previous subscription if exists
|
||||
(when-let [channel (:channel prev-subs)]
|
||||
(a/close! channel)
|
||||
(a/<! (mbus/purge! msgbus channel))))
|
||||
|
||||
(a/go
|
||||
(a/<! (mbus/sub! msgbus :topic team-id :chan channel)))))
|
||||
|
||||
(defmethod handle-message :subscribe-file
|
||||
[cfg wsp {:keys [file-id] :as params}]
|
||||
(let [msgbus (:msgbus cfg)
|
||||
conn-id (::ws/id @wsp)
|
||||
profile-id (::profile-id @wsp)
|
||||
session-id (::session-id @wsp)
|
||||
output-ch (::ws/output-ch @wsp)
|
||||
prev-subs (::file-subscription @wsp)
|
||||
xform (comp (remove #(= (:session-id %) session-id))
|
||||
(map #(assoc % :subs-id file-id)))
|
||||
channel (a/chan (a/dropping-buffer 64) xform)]
|
||||
|
||||
(l/trace :fn "handle-message"
|
||||
:event :subscribe-file
|
||||
:file-id file-id
|
||||
:conn-id conn-id)
|
||||
|
||||
(let [state {:file-id file-id :channel channel :topic file-id}]
|
||||
(swap! wsp assoc ::file-subscription state))
|
||||
|
||||
(a/go
|
||||
;; Close previous subscription if exists
|
||||
(when-let [channel (:channel prev-subs)]
|
||||
(a/close! channel)
|
||||
(a/<! (mbus/purge! msgbus channel))))
|
||||
|
||||
;; Message forwarding
|
||||
(a/go
|
||||
(loop []
|
||||
(when-let [{:keys [type] :as message} (a/<! channel)]
|
||||
(when (or (= :join-file type)
|
||||
(= :leave-file type)
|
||||
(= :disconnect type))
|
||||
(let [message {:type :presence
|
||||
:file-id file-id
|
||||
:session-id session-id
|
||||
:profile-id profile-id}]
|
||||
(a/<! (mbus/pub! msgbus :topic file-id :message message))))
|
||||
(a/>! output-ch message)
|
||||
(recur))))
|
||||
|
||||
(a/go
|
||||
;; Subscribe to file topic
|
||||
(a/<! (mbus/sub! msgbus :topic file-id :chan channel))
|
||||
|
||||
;; Notifify the rest of participants of the new connection.
|
||||
(let [message {:type :join-file
|
||||
:file-id file-id
|
||||
:subs-id file-id
|
||||
:session-id session-id
|
||||
:profile-id profile-id}]
|
||||
(a/<! (mbus/pub! msgbus :topic file-id :message message))))))
|
||||
|
||||
(defmethod handle-message :unsubscribe-file
|
||||
[cfg wsp {:keys [file-id] :as params}]
|
||||
(let [msgbus (:msgbus cfg)
|
||||
conn-id (::ws/id @wsp)
|
||||
session-id (::session-id @wsp)
|
||||
profile-id (::profile-id @wsp)
|
||||
subs (::file-subscription @wsp)
|
||||
|
||||
message {:type :leave-file
|
||||
:file-id file-id
|
||||
:session-id session-id
|
||||
:profile-id profile-id}]
|
||||
|
||||
(l/trace :fn "handle-message"
|
||||
:event :unsubscribe-file
|
||||
:file-id file-id
|
||||
:conn-id conn-id)
|
||||
|
||||
(a/go
|
||||
(when (= (:file-id subs) file-id)
|
||||
(let [channel (:channel subs)]
|
||||
(a/close! channel)
|
||||
(a/<! (mbus/purge! msgbus channel))
|
||||
(a/<! (mbus/pub! msgbus :topic file-id :message message)))))))
|
||||
|
||||
(defmethod handle-message :keepalive
|
||||
[_ _ _]
|
||||
(l/trace :fn "handle-message" :event :keepalive)
|
||||
(a/go :nothing))
|
||||
|
||||
(defmethod handle-message :pointer-update
|
||||
[cfg wsp {:keys [file-id] :as message}]
|
||||
(let [msgbus (:msgbus cfg)
|
||||
profile-id (::profile-id @wsp)
|
||||
session-id (::session-id @wsp)
|
||||
subs (::file-subscription @wsp)
|
||||
message (-> message
|
||||
(assoc :subs-id file-id)
|
||||
(assoc :profile-id profile-id)
|
||||
(assoc :session-id session-id))]
|
||||
(a/go
|
||||
;; Only allow receive pointer updates when active subscription
|
||||
(when subs
|
||||
(a/<! (mbus/pub! msgbus :topic file-id :message message))))))
|
||||
|
||||
(defmethod handle-message :default
|
||||
[_ wsp message]
|
||||
(let [conn-id (::ws/id @wsp)]
|
||||
(l/warn :hint "received unexpected message"
|
||||
:message message
|
||||
:conn-id conn-id)
|
||||
(a/go :none)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; HTTP HANDLER
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(s/def ::msgbus ::mbus/msgbus)
|
||||
(s/def ::session-id ::us/uuid)
|
||||
|
||||
(s/def ::handler-params
|
||||
(s/keys :req-un [::session-id]))
|
||||
|
||||
(defmethod ig/pre-init-spec ::handler [_]
|
||||
(s/keys :req-un [::msgbus ::db/pool ::mtx/metrics]))
|
||||
|
||||
(defmethod ig/init-key ::handler
|
||||
[_ cfg]
|
||||
(fn [{:keys [profile-id params] :as req} respond raise]
|
||||
(let [{:keys [session-id]} (us/conform ::handler-params params)]
|
||||
(cond
|
||||
(not profile-id)
|
||||
(raise (ex/error :type :authentication
|
||||
:hint "Authentication required."))
|
||||
|
||||
(not (yws/upgrade-request? req))
|
||||
(raise (ex/error :type :validation
|
||||
:code :websocket-request-expected
|
||||
:hint "this endpoint only accepts websocket connections"))
|
||||
|
||||
:else
|
||||
(do
|
||||
(l/trace :hint "websocket request" :profile-id profile-id :session-id session-id)
|
||||
|
||||
(->> (ws/handler
|
||||
::ws/on-rcv-message (partial on-rcv-message cfg)
|
||||
::ws/on-snd-message (partial on-snd-message cfg)
|
||||
::ws/on-connect (partial on-connect cfg)
|
||||
::ws/handler (partial handle-message cfg)
|
||||
::profile-id profile-id
|
||||
::session-id session-id)
|
||||
(yws/upgrade req)
|
||||
(respond)))))))
|
||||
338
backend/src/app/loggers/audit.clj
Normal file
338
backend/src/app/loggers/audit.clj
Normal file
@@ -0,0 +1,338 @@
|
||||
;; 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.audit
|
||||
"Services related to the user activity (audit log)."
|
||||
(: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.transit :as t]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.http.client :as http]
|
||||
[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]
|
||||
[app.util.time :as dt]
|
||||
[app.worker :as wrk]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[integrant.core :as ig]
|
||||
[lambdaisland.uri :as u]
|
||||
[promesa.core :as p]
|
||||
[promesa.exec :as px]
|
||||
[yetti.request :as yrq]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; HELPERS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn parse-client-ip
|
||||
[request]
|
||||
(or (some-> (yrq/get-header request "x-forwarded-for") (str/split ",") first)
|
||||
(yrq/get-header request "x-real-ip")
|
||||
(some-> (yrq/remote-addr request) str)))
|
||||
|
||||
(defn extract-utm-params
|
||||
"Extracts additional data from params and namespace them under
|
||||
`penpot` ns."
|
||||
[params]
|
||||
(letfn [(process-param [params k v]
|
||||
(let [sk (d/name k)]
|
||||
(cond-> params
|
||||
(str/starts-with? sk "utm_")
|
||||
(assoc (->> sk str/kebab (keyword "penpot")) v)
|
||||
|
||||
(str/starts-with? sk "mtm_")
|
||||
(assoc (->> sk str/kebab (keyword "penpot")) v))))]
|
||||
(reduce-kv process-param {} params)))
|
||||
|
||||
(def ^:private
|
||||
profile-props
|
||||
[:id
|
||||
:is-active
|
||||
:is-muted
|
||||
:auth-backend
|
||||
:email
|
||||
:default-team-id
|
||||
:default-project-id
|
||||
:fullname
|
||||
:lang])
|
||||
|
||||
(defn profile->props
|
||||
[profile]
|
||||
(-> profile
|
||||
(select-keys profile-props)
|
||||
(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)))))]
|
||||
|
||||
(update event :props #(into {} xform %))))
|
||||
|
||||
;; --- SPECS
|
||||
|
||||
(s/def ::profile-id ::us/uuid)
|
||||
(s/def ::name ::us/string)
|
||||
(s/def ::type ::us/string)
|
||||
(s/def ::props (s/map-of ::us/keyword any?))
|
||||
(s/def ::ip-addr ::us/string)
|
||||
|
||||
(s/def ::webhooks/event? ::us/boolean)
|
||||
(s/def ::webhooks/batch-timeout ::dt/duration)
|
||||
(s/def ::webhooks/batch-key
|
||||
(s/or :fn fn? :str string? :kw keyword?))
|
||||
|
||||
(s/def ::event
|
||||
(s/keys :req-un [::type ::name ::profile-id]
|
||||
:opt-un [::ip-addr ::props]
|
||||
:opt [::webhooks/event?
|
||||
::webhooks/batch-timeout
|
||||
::webhooks/batch-key]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; COLLECTOR
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; Defines a service that collects the audit/activity log using
|
||||
;; internal database. Later this audit log can be transferred to
|
||||
;; an external storage and data cleared.
|
||||
|
||||
(s/def ::collector
|
||||
(s/keys :req [::wrk/executor ::db/pool]))
|
||||
|
||||
(defmethod ig/pre-init-spec ::collector [_]
|
||||
(s/keys :req [::db/pool ::wrk/executor ::mtx/metrics]))
|
||||
|
||||
(defmethod ig/init-key ::collector
|
||||
[_ {:keys [::db/pool] :as cfg}]
|
||||
(cond
|
||||
(db/read-only? pool)
|
||||
(l/warn :hint "audit: disabled (db is read-only)")
|
||||
|
||||
:else
|
||||
cfg))
|
||||
|
||||
(defn- persist-event!
|
||||
[pool event]
|
||||
(us/verify! ::event event)
|
||||
(let [params {:id (uuid/next)
|
||||
:name (:name event)
|
||||
:type (:type event)
|
||||
:profile-id (:profile-id event)
|
||||
:ip-addr (:ip-addr event)
|
||||
:props (:props event)}]
|
||||
|
||||
(when (contains? cf/flags :audit-log)
|
||||
;; NOTE: this operation may cause primary key conflicts on inserts
|
||||
;; because of the timestamp precission (two concurrent requests), in
|
||||
;; this case we just retry the operation.
|
||||
(rtry/with-retry {::rtry/when rtry/conflict-exception?
|
||||
::rtry/max-retries 6
|
||||
::rtry/label "persist-audit-log-event"}
|
||||
(let [now (dt/now)]
|
||||
(db/insert! pool :audit-log
|
||||
(-> params
|
||||
(update :props db/tjson)
|
||||
(update :ip-addr db/inet)
|
||||
(assoc :created-at now)
|
||||
(assoc :tracked-at now)
|
||||
(assoc :source "backend"))))))
|
||||
|
||||
(when (and (contains? cf/flags :webhooks)
|
||||
(::webhooks/event? event))
|
||||
(let [batch-key (::webhooks/batch-key event)
|
||||
batch-timeout (::webhooks/batch-timeout event)
|
||||
label (dm/str "rpc:" (:name params))
|
||||
label (cond
|
||||
(ifn? batch-key) (dm/str label ":" (batch-key (::rpc/params event)))
|
||||
(string? batch-key) (dm/str label ":" batch-key)
|
||||
:else label)
|
||||
dedupe? (boolean (and batch-key batch-timeout))]
|
||||
|
||||
(wrk/submit! ::wrk/conn pool
|
||||
::wrk/task :process-webhook-event
|
||||
::wrk/queue :webhooks
|
||||
::wrk/max-retries 0
|
||||
::wrk/delay (or batch-timeout 0)
|
||||
::wrk/dedupe dedupe?
|
||||
::wrk/label label
|
||||
|
||||
::webhooks/event
|
||||
(-> params
|
||||
(dissoc :ip-addr)
|
||||
(dissoc :type)))))))
|
||||
|
||||
(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)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; TASK: ARCHIVE
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; This is a task responsible to send the accumulated events to
|
||||
;; external service for archival.
|
||||
|
||||
(declare archive-events)
|
||||
|
||||
(s/def ::tasks/uri ::us/string)
|
||||
|
||||
(defmethod ig/pre-init-spec ::tasks/archive-task [_]
|
||||
(s/keys :req [::db/pool ::main/props ::http/client]))
|
||||
|
||||
(defmethod ig/init-key ::tasks/archive
|
||||
[_ cfg]
|
||||
(fn [params]
|
||||
;; NOTE: this let allows overwrite default configured values from
|
||||
;; the repl, when manually invoking the task.
|
||||
(let [enabled (or (contains? cf/flags :audit-log-archive)
|
||||
(:enabled params false))
|
||||
uri (cf/get :audit-log-archive-uri)
|
||||
uri (or uri (:uri params))
|
||||
cfg (assoc cfg ::uri uri)]
|
||||
|
||||
(when (and enabled (not uri))
|
||||
(ex/raise :type :internal
|
||||
:code :task-not-configured
|
||||
:hint "archive task not configured, missing uri"))
|
||||
|
||||
(when enabled
|
||||
(loop [total 0]
|
||||
(let [n (archive-events cfg)]
|
||||
(if n
|
||||
(do
|
||||
(px/sleep 100)
|
||||
(recur (+ total n)))
|
||||
(when (pos? total)
|
||||
(l/debug :hint "events archived" :total total)))))))))
|
||||
|
||||
(def ^:private sql:retrieve-batch-of-audit-log
|
||||
"select *
|
||||
from audit_log
|
||||
where archived_at is null
|
||||
order by created_at asc
|
||||
limit 256
|
||||
for update skip locked;")
|
||||
|
||||
(defn archive-events
|
||||
[{:keys [::db/pool ::uri] :as cfg}]
|
||||
(letfn [(decode-row [{:keys [props ip-addr context] :as row}]
|
||||
(cond-> row
|
||||
(db/pgobject? props)
|
||||
(assoc :props (db/decode-transit-pgobject props))
|
||||
|
||||
(db/pgobject? context)
|
||||
(assoc :context (db/decode-transit-pgobject context))
|
||||
|
||||
(db/pgobject? ip-addr "inet")
|
||||
(assoc :ip-addr (db/decode-inet ip-addr))))
|
||||
|
||||
(row->event [row]
|
||||
(select-keys row [:type
|
||||
:name
|
||||
:source
|
||||
:created-at
|
||||
:tracked-at
|
||||
:profile-id
|
||||
:ip-addr
|
||||
:props
|
||||
:context]))
|
||||
|
||||
(send [events]
|
||||
(let [token (tokens/generate (::main/props cfg)
|
||||
{:iss "authentication"
|
||||
:iat (dt/now)
|
||||
:uid uuid/zero})
|
||||
body (t/encode {:events events})
|
||||
headers {"content-type" "application/transit+json"
|
||||
"origin" (cf/get :public-uri)
|
||||
"cookie" (u/map->query-string {:auth-token token})}
|
||||
params {:uri uri
|
||||
:timeout 6000
|
||||
:method :post
|
||||
:headers headers
|
||||
:body body}
|
||||
resp (http/req! cfg params {:sync? true})]
|
||||
(if (= (:status resp) 204)
|
||||
true
|
||||
(do
|
||||
(l/error :hint "unable to archive events"
|
||||
:resp-status (:status resp)
|
||||
:resp-body (:body resp))
|
||||
false))))
|
||||
|
||||
(mark-as-archived [conn rows]
|
||||
(db/exec-one! conn ["update audit_log set archived_at=now() where id = ANY(?)"
|
||||
(->> (map :id rows)
|
||||
(into-array java.util.UUID)
|
||||
(db/create-array conn "uuid"))]))]
|
||||
|
||||
(db/with-atomic [conn pool]
|
||||
(let [rows (db/exec! conn [sql:retrieve-batch-of-audit-log])
|
||||
xform (comp (map decode-row)
|
||||
(map row->event))
|
||||
events (into [] xform rows)]
|
||||
(when-not (empty? events)
|
||||
(l/trace :hint "archive events chunk" :uri uri :events (count events))
|
||||
(when (send events)
|
||||
(mark-as-archived conn rows)
|
||||
(count events)))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; GC Task
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def ^:private sql:clean-archived
|
||||
"delete from audit_log
|
||||
where archived_at is not null")
|
||||
|
||||
(defn- clean-archived
|
||||
[{: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)
|
||||
result))
|
||||
|
||||
(defmethod ig/pre-init-spec ::tasks/gc [_]
|
||||
(s/keys :req [::db/pool]))
|
||||
|
||||
(defmethod ig/init-key ::tasks/gc
|
||||
[_ cfg]
|
||||
(fn [_]
|
||||
(clean-archived cfg)))
|
||||
101
backend/src/app/loggers/database.clj
Normal file
101
backend/src/app/loggers/database.clj
Normal file
@@ -0,0 +1,101 @@
|
||||
;; 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.database
|
||||
"A specific logger impl that persists errors on the database."
|
||||
(:require
|
||||
[app.common.logging :as l]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.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]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Error Listener
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(declare handle-event)
|
||||
|
||||
(defonce enabled (atom true))
|
||||
|
||||
(defn- persist-on-database!
|
||||
[{:keys [::db/pool] :as cfg} {:keys [id] :as event}]
|
||||
(when-not (db/read-only? pool)
|
||||
(db/insert! pool :server-error-report {:id id :content (db/tjson event)})))
|
||||
|
||||
(defn- parse-event-data
|
||||
[event]
|
||||
(reduce-kv
|
||||
(fn [acc k v]
|
||||
(cond
|
||||
(= k :id) (assoc acc k (uuid/uuid v))
|
||||
(= k :profile-id) (assoc acc k (uuid/uuid v))
|
||||
(str/blank? v) acc
|
||||
:else (assoc acc k v)))
|
||||
{}
|
||||
event))
|
||||
|
||||
(defn parse-event
|
||||
[event]
|
||||
(-> (parse-event-data event)
|
||||
(assoc :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)))))
|
||||
|
||||
(defn- handle-event
|
||||
[cfg event]
|
||||
(try
|
||||
(let [event (parse-event event)
|
||||
uri (cf/get :public-uri)]
|
||||
|
||||
(l/debug :hint "registering error on database" :id (:id event)
|
||||
:uri (str uri "/dbg/error/" (:id event)))
|
||||
|
||||
(persist-on-database! cfg event))
|
||||
(catch Throwable cause
|
||||
(l/warn :hint "unexpected exception on database error logger" :cause cause))))
|
||||
|
||||
(defn- error-event?
|
||||
[event]
|
||||
(= "error" (:logger/level event)))
|
||||
|
||||
(defmethod ig/pre-init-spec ::reporter [_]
|
||||
(s/keys :req [::db/pool ::lzmq/receiver]))
|
||||
|
||||
(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?))]
|
||||
(try
|
||||
(lzmq/sub! receiver 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 error" :cause cause))
|
||||
(finally
|
||||
(a/close! input)
|
||||
(l/info :hint "reporter terminated"))))))
|
||||
|
||||
(defmethod ig/halt-key! ::reporter
|
||||
[_ thread]
|
||||
(some-> thread px/interrupt!))
|
||||
89
backend/src/app/loggers/loki.clj
Normal file
89
backend/src/app/loggers/loki.clj
Normal file
@@ -0,0 +1,89 @@
|
||||
;; 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))))
|
||||
77
backend/src/app/loggers/mattermost.clj
Normal file
77
backend/src/app/loggers/mattermost.clj
Normal file
@@ -0,0 +1,77 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.loggers.mattermost
|
||||
"A mattermost integration for error reporting."
|
||||
(:require
|
||||
[app.common.logging :as l]
|
||||
[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]))
|
||||
|
||||
(defonce enabled (atom true))
|
||||
|
||||
(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")))
|
||||
resp (http/req! cfg
|
||||
{:uri (cf/get :error-report-webhook)
|
||||
:method :post
|
||||
:headers {"content-type" "application/json"}
|
||||
:body (json/encode-str {:text text})}
|
||||
{:sync? true})]
|
||||
|
||||
(when (not= 200 (:status resp))
|
||||
(l/warn :hint "error on sending data"
|
||||
:response (pr-str resp)))))
|
||||
|
||||
(defn handle-event
|
||||
[cfg event]
|
||||
(when @enabled
|
||||
(try
|
||||
(let [event (ldb/parse-event event)]
|
||||
(send-mattermost-notification! cfg event))
|
||||
(catch Throwable cause
|
||||
(l/warn :hint "unhandled error"
|
||||
:cause cause)))))
|
||||
|
||||
(defmethod ig/pre-init-spec ::reporter [_]
|
||||
(s/keys :req [::http/client
|
||||
::lzmq/receiver]))
|
||||
|
||||
(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")))]
|
||||
(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 error" :cause cause))
|
||||
(finally
|
||||
(a/close! input)
|
||||
(l/info :hint "reporter terminated")))))))
|
||||
|
||||
(defmethod ig/halt-key! ::reporter
|
||||
[_ thread]
|
||||
(some-> thread px/interrupt!))
|
||||
186
backend/src/app/loggers/webhooks.clj
Normal file
186
backend/src/app/loggers/webhooks.clj
Normal file
@@ -0,0 +1,186 @@
|
||||
;; 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.webhooks
|
||||
"A mattermost integration for error reporting."
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.logging :as l]
|
||||
[app.common.transit :as t]
|
||||
[app.common.uri :as uri]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.http.client :as http]
|
||||
[app.util.json :as json]
|
||||
[app.util.time :as dt]
|
||||
[app.worker :as wrk]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[integrant.core :as ig]))
|
||||
|
||||
;; --- HELPERS
|
||||
|
||||
(defn key-fn
|
||||
[k & keys]
|
||||
(fn [params]
|
||||
(reduce #(dm/str %1 ":" (get params %2))
|
||||
(dm/str (get params k))
|
||||
keys)))
|
||||
|
||||
;; --- PROC
|
||||
|
||||
(defn- lookup-webhooks-by-team
|
||||
[pool team-id]
|
||||
(db/exec! pool ["select w.* from webhook as w where team_id=? and is_active=true" team-id]))
|
||||
|
||||
(defn- lookup-webhooks-by-project
|
||||
[pool project-id]
|
||||
(let [sql [(str "select w.* from webhook as w"
|
||||
" join project as p on (p.team_id = w.team_id)"
|
||||
" where p.id = ? and w.is_active = true")
|
||||
project-id]]
|
||||
(db/exec! pool sql)))
|
||||
|
||||
(defn- lookup-webhooks-by-file
|
||||
[pool file-id]
|
||||
(let [sql [(str "select w.* from webhook as w"
|
||||
" join project as p on (p.team_id = w.team_id)"
|
||||
" join file as f on (f.project_id = p.id)"
|
||||
" where f.id = ? and w.is_active = true")
|
||||
file-id]]
|
||||
(db/exec! pool sql)))
|
||||
|
||||
(defn- lookup-webhooks
|
||||
[{:keys [::db/pool]} {:keys [props] :as event}]
|
||||
(or (some->> (:team-id props) (lookup-webhooks-by-team pool))
|
||||
(some->> (:project-id props) (lookup-webhooks-by-project pool))
|
||||
(some->> (:file-id props) (lookup-webhooks-by-file pool))))
|
||||
|
||||
(defmethod ig/pre-init-spec ::process-event-handler [_]
|
||||
(s/keys :req [::db/pool]))
|
||||
|
||||
(defmethod ig/init-key ::process-event-handler
|
||||
[_ {:keys [::db/pool] :as cfg}]
|
||||
(fn [{:keys [props] :as task}]
|
||||
(let [event (::event props)]
|
||||
|
||||
(l/debug :hint "process webhook event"
|
||||
:name (:name event))
|
||||
|
||||
(when-let [items (lookup-webhooks cfg event)]
|
||||
(l/trace :hint "webhooks found for event" :total (count items))
|
||||
|
||||
(db/with-atomic [conn pool]
|
||||
(doseq [item items]
|
||||
(wrk/submit! ::wrk/conn conn
|
||||
::wrk/task :run-webhook
|
||||
::wrk/queue :webhooks
|
||||
::wrk/max-retries 3
|
||||
::event event
|
||||
::config item)))))))
|
||||
|
||||
;; --- RUN
|
||||
|
||||
(declare interpret-exception)
|
||||
(declare interpret-response)
|
||||
|
||||
(def ^:private json-mapper
|
||||
(json/mapper
|
||||
{:encode-key-fn str/camel
|
||||
:decode-key-fn (comp keyword str/kebab)
|
||||
:pretty true}))
|
||||
|
||||
(defmethod ig/pre-init-spec ::run-webhook-handler [_]
|
||||
(s/keys :req [::http/client ::db/pool]))
|
||||
|
||||
(defmethod ig/prep-key ::run-webhook-handler
|
||||
[_ cfg]
|
||||
(merge {::max-errors 3} (d/without-nils cfg)))
|
||||
|
||||
(defmethod ig/init-key ::run-webhook-handler
|
||||
[_ {:keys [::db/pool ::max-errors] :as cfg}]
|
||||
(letfn [(update-webhook! [whook err]
|
||||
(if err
|
||||
(let [sql [(str "update webhook "
|
||||
" set error_code=?, "
|
||||
" error_count=error_count+1 "
|
||||
" where id=?")
|
||||
err
|
||||
(:id whook)]
|
||||
res (db/exec-one! pool sql {:return-keys true})]
|
||||
(when (>= (:error-count res) max-errors)
|
||||
(db/update! pool :webhook {:is-active false} {:id (:id whook)})))
|
||||
|
||||
(db/update! pool :webhook
|
||||
{:updated-at (dt/now)
|
||||
:error-code nil
|
||||
:error-count 0}
|
||||
{:id (:id whook)})))
|
||||
|
||||
(report-delivery! [whook req rsp err]
|
||||
(db/insert! pool :webhook-delivery
|
||||
{:webhook-id (:id whook)
|
||||
:created-at (dt/now)
|
||||
:error-code err
|
||||
:req-data (db/tjson req)
|
||||
:rsp-data (db/tjson rsp)}))]
|
||||
|
||||
(fn [{:keys [props] :as task}]
|
||||
(let [event (::event props)
|
||||
whook (::config props)
|
||||
|
||||
body (case (:mtype whook)
|
||||
"application/json" (json/encode-str event json-mapper)
|
||||
"application/transit+json" (t/encode-str event)
|
||||
"application/x-www-form-urlencoded" (uri/map->query-string event))]
|
||||
|
||||
(l/debug :hint "run webhook"
|
||||
:event-name (:name event)
|
||||
:webhook-id (:id whook)
|
||||
:webhook-uri (:uri whook)
|
||||
:webhook-mtype (:mtype whook))
|
||||
|
||||
(let [req {:uri (:uri whook)
|
||||
:headers {"content-type" (:mtype whook)
|
||||
"user-agent" (str/ffmt "penpot/%" (:main cf/version))}
|
||||
:timeout (dt/duration "4s")
|
||||
:method :post
|
||||
:body body}]
|
||||
(try
|
||||
(let [rsp (http/req! cfg req {:response-type :input-stream :sync? true})
|
||||
err (interpret-response rsp)]
|
||||
(report-delivery! whook req rsp err)
|
||||
(update-webhook! whook err))
|
||||
(catch Throwable cause
|
||||
(let [err (interpret-exception cause)]
|
||||
(report-delivery! whook req nil err)
|
||||
(update-webhook! whook err)
|
||||
(when (= err "unknown")
|
||||
(l/error :hint "unknown error on webhook request"
|
||||
:cause cause))))))))))
|
||||
|
||||
(defn interpret-response
|
||||
[{:keys [status] :as response}]
|
||||
(when-not (or (= 200 status)
|
||||
(= 204 status))
|
||||
(str/ffmt "unexpected-status:%" status)))
|
||||
|
||||
(defn interpret-exception
|
||||
[cause]
|
||||
(cond
|
||||
(instance? javax.net.ssl.SSLHandshakeException cause)
|
||||
"ssl-validation-error"
|
||||
|
||||
(instance? java.net.ConnectException cause)
|
||||
"connection-error"
|
||||
|
||||
(instance? java.lang.IllegalArgumentException cause)
|
||||
"invalid-uri"
|
||||
|
||||
(instance? java.net.http.HttpConnectTimeoutException cause)
|
||||
"timeout"
|
||||
))
|
||||
130
backend/src/app/loggers/zmq.clj
Normal file
130
backend/src/app/loggers/zmq.clj
Normal file
@@ -0,0 +1,130 @@
|
||||
;; 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)))
|
||||
572
backend/src/app/main.clj
Normal file
572
backend/src/app/main.clj
Normal file
@@ -0,0 +1,572 @@
|
||||
;; 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.main
|
||||
(:require
|
||||
[app.auth.ldap :as-alias ldap]
|
||||
[app.auth.oidc :as-alias oidc]
|
||||
[app.auth.oidc.providers :as-alias oidc.providers]
|
||||
[app.common.logging :as l]
|
||||
[app.config :as cf]
|
||||
[app.db :as-alias db]
|
||||
[app.http.client :as-alias http.client]
|
||||
[app.http.session :as-alias http.session]
|
||||
[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.redis :as-alias rds]
|
||||
[app.srepl :as-alias srepl]
|
||||
[app.storage :as-alias sto]
|
||||
[app.util.time :as dt]
|
||||
[app.worker :as-alias wrk]
|
||||
[cuerdas.core :as str]
|
||||
[integrant.core :as ig])
|
||||
(:gen-class))
|
||||
|
||||
(def default-metrics
|
||||
{:update-file-changes
|
||||
{::mdef/name "penpot_rpc_update_file_changes_total"
|
||||
::mdef/help "A total number of changes submitted to update-file."
|
||||
::mdef/type :counter}
|
||||
|
||||
:update-file-bytes-processed
|
||||
{::mdef/name "penpot_rpc_update_file_bytes_processed_total"
|
||||
::mdef/help "A total number of bytes processed by update-file."
|
||||
::mdef/type :counter}
|
||||
|
||||
:rpc-mutation-timing
|
||||
{::mdef/name "penpot_rpc_mutation_timing"
|
||||
::mdef/help "RPC mutation method call timing."
|
||||
::mdef/labels ["name"]
|
||||
::mdef/type :histogram}
|
||||
|
||||
:rpc-command-timing
|
||||
{::mdef/name "penpot_rpc_command_timing"
|
||||
::mdef/help "RPC command method call timing."
|
||||
::mdef/labels ["name"]
|
||||
::mdef/type :histogram}
|
||||
|
||||
:rpc-query-timing
|
||||
{::mdef/name "penpot_rpc_query_timing"
|
||||
::mdef/help "RPC query method call timing."
|
||||
::mdef/labels ["name"]
|
||||
::mdef/type :histogram}
|
||||
|
||||
:websocket-active-connections
|
||||
{::mdef/name "penpot_websocket_active_connections"
|
||||
::mdef/help "Active websocket connections gauge"
|
||||
::mdef/type :gauge}
|
||||
|
||||
:websocket-messages-total
|
||||
{::mdef/name "penpot_websocket_message_total"
|
||||
::mdef/help "Counter of processed messages."
|
||||
::mdef/labels ["op"]
|
||||
::mdef/type :counter}
|
||||
|
||||
:websocket-session-timing
|
||||
{::mdef/name "penpot_websocket_session_timing"
|
||||
::mdef/help "Websocket session timing (seconds)."
|
||||
::mdef/type :summary}
|
||||
|
||||
:session-update-total
|
||||
{::mdef/name "penpot_http_session_update_total"
|
||||
::mdef/help "A counter of session update batch events."
|
||||
::mdef/type :counter}
|
||||
|
||||
:tasks-timing
|
||||
{::mdef/name "penpot_tasks_timing"
|
||||
::mdef/help "Background tasks timing (milliseconds)."
|
||||
::mdef/labels ["name"]
|
||||
::mdef/type :summary}
|
||||
|
||||
:redis-eval-timing
|
||||
{::mdef/name "penpot_redis_eval_timing"
|
||||
::mdef/help "Redis EVAL commands execution timings (ms)"
|
||||
::mdef/labels ["name"]
|
||||
::mdef/type :summary}
|
||||
|
||||
:rpc-climit-queue-size
|
||||
{::mdef/name "penpot_rpc_climit_queue_size"
|
||||
::mdef/help "Current number of queued submissions on the CLIMIT."
|
||||
::mdef/labels ["name"]
|
||||
::mdef/type :gauge}
|
||||
|
||||
:rpc-climit-concurrency
|
||||
{::mdef/name "penpot_rpc_climit_concurrency"
|
||||
::mdef/help "Current number of used concurrency capacity on the CLIMIT"
|
||||
::mdef/labels ["name"]
|
||||
::mdef/type :gauge}
|
||||
|
||||
:rpc-climit-timing
|
||||
{::mdef/name "penpot_rpc_climit_timing"
|
||||
::mdef/help "Summary of the time between queuing and executing on the CLIMIT"
|
||||
::mdef/labels ["name"]
|
||||
::mdef/type :summary}
|
||||
|
||||
:audit-http-handler-queue-size
|
||||
{::mdef/name "penpot_audit_http_handler_queue_size"
|
||||
::mdef/help "Current number of queued submissions on the audit log http handler"
|
||||
::mdef/labels []
|
||||
::mdef/type :gauge}
|
||||
|
||||
:audit-http-handler-concurrency
|
||||
{::mdef/name "penpot_audit_http_handler_concurrency"
|
||||
::mdef/help "Current number of used concurrency capacity on the audit log http handler"
|
||||
::mdef/labels []
|
||||
::mdef/type :gauge}
|
||||
|
||||
:audit-http-handler-timing
|
||||
{::mdef/name "penpot_audit_http_handler_timing"
|
||||
::mdef/help "Summary of the time between queuing and executing on the audit log http handler"
|
||||
::mdef/labels []
|
||||
::mdef/type :summary}
|
||||
|
||||
:executors-active-threads
|
||||
{::mdef/name "penpot_executors_active_threads"
|
||||
::mdef/help "Current number of threads available in the executor service."
|
||||
::mdef/labels ["name"]
|
||||
::mdef/type :gauge}
|
||||
|
||||
:executors-completed-tasks
|
||||
{::mdef/name "penpot_executors_completed_tasks_total"
|
||||
::mdef/help "Approximate number of completed tasks by the executor."
|
||||
::mdef/labels ["name"]
|
||||
::mdef/type :counter}
|
||||
|
||||
:executors-running-threads
|
||||
{::mdef/name "penpot_executors_running_threads"
|
||||
::mdef/help "Current number of threads with state RUNNING."
|
||||
::mdef/labels ["name"]
|
||||
::mdef/type :gauge}
|
||||
|
||||
:executors-queued-submissions
|
||||
{::mdef/name "penpot_executors_queued_submissions"
|
||||
::mdef/help "Current number of queued submissions."
|
||||
::mdef/labels ["name"]
|
||||
::mdef/type :gauge}})
|
||||
|
||||
(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)}
|
||||
|
||||
;; Default thread pool for IO operations
|
||||
::wrk/executor
|
||||
{::wrk/parallelism (cf/get :default-executor-parallelism 100)}
|
||||
|
||||
::wrk/scheduled-executor
|
||||
{::wrk/parallelism (cf/get :scheduled-executor-parallelism 20)}
|
||||
|
||||
::wrk/monitor
|
||||
{::mtx/metrics (ig/ref ::mtx/metrics)
|
||||
::wrk/name "default"
|
||||
::wrk/executor (ig/ref ::wrk/executor)}
|
||||
|
||||
:app.migrations/migrations
|
||||
{}
|
||||
|
||||
::mtx/metrics
|
||||
{:default default-metrics}
|
||||
|
||||
:app.migrations/all
|
||||
{:main (ig/ref :app.migrations/migrations)}
|
||||
|
||||
::rds/redis
|
||||
{::rds/uri (cf/get :redis-uri)
|
||||
::mtx/metrics (ig/ref ::mtx/metrics)}
|
||||
|
||||
:app.msgbus/msgbus
|
||||
{:backend (cf/get :msgbus-backend :redis)
|
||||
:executor (ig/ref ::wrk/executor)
|
||||
:redis (ig/ref ::rds/redis)}
|
||||
|
||||
:app.storage.tmp/cleaner
|
||||
{::wrk/executor (ig/ref ::wrk/executor)
|
||||
::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)}
|
||||
|
||||
::sto/gc-touched-task
|
||||
{:pool (ig/ref ::db/pool)}
|
||||
|
||||
::http.client/client
|
||||
{::wrk/executor (ig/ref ::wrk/executor)}
|
||||
|
||||
:app.http.session/manager
|
||||
{::db/pool (ig/ref ::db/pool)
|
||||
::wrk/executor (ig/ref ::wrk/executor)
|
||||
::props (ig/ref :app.setup/props)}
|
||||
|
||||
:app.http.session/gc-task
|
||||
{:pool (ig/ref ::db/pool)
|
||||
:max-age (cf/get :auth-token-cookie-max-age)}
|
||||
|
||||
:app.http.awsns/handler
|
||||
{::props (ig/ref :app.setup/props)
|
||||
::db/pool (ig/ref ::db/pool)
|
||||
::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)}
|
||||
|
||||
::ldap/provider
|
||||
{:host (cf/get :ldap-host)
|
||||
:port (cf/get :ldap-port)
|
||||
:ssl (cf/get :ldap-ssl)
|
||||
:tls (cf/get :ldap-starttls)
|
||||
:query (cf/get :ldap-user-query)
|
||||
:attrs-email (cf/get :ldap-attrs-email)
|
||||
:attrs-fullname (cf/get :ldap-attrs-fullname)
|
||||
:attrs-username (cf/get :ldap-attrs-username)
|
||||
:base-dn (cf/get :ldap-base-dn)
|
||||
:bind-dn (cf/get :ldap-bind-dn)
|
||||
:bind-password (cf/get :ldap-bind-password)
|
||||
:enabled? (contains? cf/flags :login-with-ldap)}
|
||||
|
||||
::oidc.providers/google
|
||||
{}
|
||||
|
||||
::oidc.providers/github
|
||||
{::http.client/client (ig/ref ::http.client/client)}
|
||||
|
||||
::oidc.providers/gitlab
|
||||
{}
|
||||
|
||||
::oidc.providers/generic
|
||||
{::http.client/client (ig/ref ::http.client/client)}
|
||||
|
||||
::oidc/routes
|
||||
{::http.client/client (ig/ref ::http.client/client)
|
||||
::db/pool (ig/ref ::db/pool)
|
||||
::props (ig/ref :app.setup/props)
|
||||
::wrk/executor (ig/ref ::wrk/executor)
|
||||
::oidc/providers {:google (ig/ref ::oidc.providers/google)
|
||||
:github (ig/ref ::oidc.providers/github)
|
||||
:gitlab (ig/ref ::oidc.providers/gitlab)
|
||||
:oidc (ig/ref ::oidc.providers/generic)}
|
||||
::audit/collector (ig/ref ::audit/collector)
|
||||
::http.session/session (ig/ref :app.http.session/manager)}
|
||||
|
||||
|
||||
;; TODO: revisit the dependencies of this service, looks they are too much unused of them
|
||||
:app.http/router
|
||||
{:assets (ig/ref :app.http.assets/handlers)
|
||||
:feedback (ig/ref :app.http.feedback/handler)
|
||||
:session (ig/ref :app.http.session/manager)
|
||||
:awsns-handler (ig/ref :app.http.awsns/handler)
|
||||
:debug-routes (ig/ref :app.http.debug/routes)
|
||||
:oidc-routes (ig/ref ::oidc/routes)
|
||||
:ws (ig/ref :app.http.websocket/handler)
|
||||
:metrics (ig/ref ::mtx/metrics)
|
||||
:public-uri (cf/get :public-uri)
|
||||
:storage (ig/ref ::sto/storage)
|
||||
:rpc-routes (ig/ref :app.rpc/routes)
|
||||
:doc-routes (ig/ref :app.rpc.doc/routes)
|
||||
:executor (ig/ref ::wrk/executor)}
|
||||
|
||||
:app.http.debug/routes
|
||||
{:pool (ig/ref ::db/pool)
|
||||
:executor (ig/ref ::wrk/executor)
|
||||
:storage (ig/ref ::sto/storage)
|
||||
:session (ig/ref :app.http.session/manager)
|
||||
|
||||
::db/pool (ig/ref ::db/pool)
|
||||
::wrk/executor (ig/ref ::wrk/executor)
|
||||
::sto/storage (ig/ref ::sto/storage)}
|
||||
|
||||
:app.http.websocket/handler
|
||||
{:pool (ig/ref ::db/pool)
|
||||
:metrics (ig/ref ::mtx/metrics)
|
||||
:msgbus (ig/ref :app.msgbus/msgbus)}
|
||||
|
||||
:app.http.assets/handlers
|
||||
{: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})}
|
||||
|
||||
:app.http.feedback/handler
|
||||
{:pool (ig/ref ::db/pool)
|
||||
:executor (ig/ref ::wrk/executor)}
|
||||
|
||||
:app.rpc/climit
|
||||
{:metrics (ig/ref ::mtx/metrics)
|
||||
:executor (ig/ref ::wrk/executor)}
|
||||
|
||||
:app.rpc/rlimit
|
||||
{:executor (ig/ref ::wrk/executor)
|
||||
:scheduled-executor (ig/ref ::wrk/scheduled-executor)}
|
||||
|
||||
:app.rpc/methods
|
||||
{::audit/collector (ig/ref ::audit/collector)
|
||||
::http.client/client (ig/ref ::http.client/client)
|
||||
::db/pool (ig/ref ::db/pool)
|
||||
::wrk/executor (ig/ref ::wrk/executor)
|
||||
::props (ig/ref :app.setup/props)
|
||||
::ldap/provider (ig/ref ::ldap/provider)
|
||||
:pool (ig/ref ::db/pool)
|
||||
:session (ig/ref :app.http.session/manager)
|
||||
:sprops (ig/ref :app.setup/props)
|
||||
:metrics (ig/ref ::mtx/metrics)
|
||||
:storage (ig/ref ::sto/storage)
|
||||
:msgbus (ig/ref :app.msgbus/msgbus)
|
||||
:public-uri (cf/get :public-uri)
|
||||
:redis (ig/ref ::rds/redis)
|
||||
:http-client (ig/ref ::http.client/client)
|
||||
:climit (ig/ref :app.rpc/climit)
|
||||
:rlimit (ig/ref :app.rpc/rlimit)
|
||||
:executor (ig/ref ::wrk/executor)
|
||||
:templates (ig/ref :app.setup/builtin-templates)
|
||||
}
|
||||
|
||||
:app.rpc.doc/routes
|
||||
{:methods (ig/ref :app.rpc/methods)}
|
||||
|
||||
:app.rpc/routes
|
||||
{:methods (ig/ref :app.rpc/methods)}
|
||||
|
||||
::wrk/registry
|
||||
{:metrics (ig/ref ::mtx/metrics)
|
||||
:tasks
|
||||
{:sendmail (ig/ref :app.emails/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)
|
||||
:storage-gc-deleted (ig/ref ::sto/gc-deleted-task)
|
||||
:storage-gc-touched (ig/ref ::sto/gc-touched-task)
|
||||
:tasks-gc (ig/ref :app.tasks.tasks-gc/handler)
|
||||
:telemetry (ig/ref :app.tasks.telemetry/handler)
|
||||
:session-gc (ig/ref :app.http.session/gc-task)
|
||||
:audit-log-archive (ig/ref ::audit.tasks/archive)
|
||||
:audit-log-gc (ig/ref ::audit.tasks/gc)
|
||||
|
||||
:process-webhook-event
|
||||
(ig/ref ::webhooks/process-event-handler)
|
||||
:run-webhook
|
||||
(ig/ref ::webhooks/run-webhook-handler)}}
|
||||
|
||||
|
||||
: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)}
|
||||
|
||||
:app.tasks.tasks-gc/handler
|
||||
{:pool (ig/ref ::db/pool)
|
||||
:max-age cf/deletion-delay}
|
||||
|
||||
: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)}
|
||||
|
||||
:app.tasks.file-xlog-gc/handler
|
||||
{:pool (ig/ref ::db/pool)}
|
||||
|
||||
:app.tasks.telemetry/handler
|
||||
{::db/pool (ig/ref ::db/pool)
|
||||
::http.client/client (ig/ref ::http.client/client)
|
||||
::props (ig/ref :app.setup/props)}
|
||||
|
||||
[::srepl/urepl ::srepl/server]
|
||||
{:port (cf/get :urepl-port 6062)
|
||||
:host (cf/get :urepl-host "localhost")}
|
||||
|
||||
[::srepl/prepl ::srepl/server]
|
||||
{:port (cf/get :prepl-port 6063)
|
||||
: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)}
|
||||
|
||||
::lzmq/receiver
|
||||
{}
|
||||
|
||||
::audit/collector
|
||||
{::db/pool (ig/ref ::db/pool)
|
||||
::wrk/executor (ig/ref ::wrk/executor)
|
||||
::mtx/metrics (ig/ref ::mtx/metrics)}
|
||||
|
||||
::audit.tasks/archive
|
||||
{::props (ig/ref :app.setup/props)
|
||||
::db/pool (ig/ref ::db/pool)
|
||||
::http.client/client (ig/ref ::http.client/client)}
|
||||
|
||||
::audit.tasks/gc
|
||||
{::db/pool (ig/ref ::db/pool)}
|
||||
|
||||
::webhooks/process-event-handler
|
||||
{::db/pool (ig/ref ::db/pool)
|
||||
::http.client/client (ig/ref ::http.client/client)}
|
||||
|
||||
::webhooks/run-webhook-handler
|
||||
{::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)}
|
||||
|
||||
:app.loggers.database/reporter
|
||||
{::lzmq/receiver (ig/ref :app.loggers.zmq/receiver)
|
||||
::db/pool (ig/ref ::db/pool)}
|
||||
|
||||
::sto/storage
|
||||
{:pool (ig/ref ::db/pool)
|
||||
:executor (ig/ref ::wrk/executor)
|
||||
|
||||
: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 :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)}
|
||||
|
||||
[::assets :app.storage.fs/backend]
|
||||
{:directory (cf/get :storage-assets-fs-directory)}
|
||||
})
|
||||
|
||||
|
||||
(def worker-config
|
||||
{::wrk/cron
|
||||
{::wrk/scheduled-executor (ig/ref ::wrk/scheduled-executor)
|
||||
::wrk/registry (ig/ref ::wrk/registry)
|
||||
::db/pool (ig/ref ::db/pool)
|
||||
::wrk/entries
|
||||
[{:cron #app/cron "0 0 * * * ?" ;; hourly
|
||||
:task :file-xlog-gc}
|
||||
|
||||
{:cron #app/cron "0 0 0 * * ?" ;; daily
|
||||
:task :session-gc}
|
||||
|
||||
{:cron #app/cron "0 0 0 * * ?" ;; daily
|
||||
:task :objects-gc}
|
||||
|
||||
{:cron #app/cron "0 0 0 * * ?" ;; daily
|
||||
:task :storage-gc-deleted}
|
||||
|
||||
{:cron #app/cron "0 0 0 * * ?" ;; daily
|
||||
:task :storage-gc-touched}
|
||||
|
||||
{:cron #app/cron "0 0 0 * * ?" ;; daily
|
||||
:task :tasks-gc}
|
||||
|
||||
{:cron #app/cron "0 0 2 * * ?" ;; daily
|
||||
:task :file-gc}
|
||||
|
||||
{:cron #app/cron "0 30 */3,23 * * ?"
|
||||
:task :telemetry}
|
||||
|
||||
(when (contains? cf/flags :audit-log-archive)
|
||||
{:cron #app/cron "0 */5 * * * ?" ;; every 5m
|
||||
:task :audit-log-archive})
|
||||
|
||||
(when (contains? cf/flags :audit-log-gc)
|
||||
{:cron #app/cron "30 */5 * * * ?" ;; every 5m
|
||||
:task :audit-log-gc})]}
|
||||
|
||||
::wrk/dispatcher
|
||||
{::rds/redis (ig/ref ::rds/redis)
|
||||
::mtx/metrics (ig/ref ::mtx/metrics)
|
||||
::db/pool (ig/ref ::db/pool)}
|
||||
|
||||
[::default ::wrk/worker]
|
||||
{::wrk/parallelism (cf/get ::worker-default-parallelism 1)
|
||||
::wrk/queue :default
|
||||
::rds/redis (ig/ref ::rds/redis)
|
||||
::wrk/registry (ig/ref ::wrk/registry)
|
||||
::mtx/metrics (ig/ref ::mtx/metrics)
|
||||
::db/pool (ig/ref ::db/pool)}
|
||||
|
||||
[::webhook ::wrk/worker]
|
||||
{::wrk/parallelism (cf/get ::worker-webhook-parallelism 1)
|
||||
::wrk/queue :webhooks
|
||||
::rds/redis (ig/ref ::rds/redis)
|
||||
::wrk/registry (ig/ref ::wrk/registry)
|
||||
::mtx/metrics (ig/ref ::mtx/metrics)
|
||||
::db/pool (ig/ref ::db/pool)}})
|
||||
|
||||
|
||||
(def system nil)
|
||||
|
||||
(defn start
|
||||
[]
|
||||
(ig/load-namespaces (merge system-config worker-config))
|
||||
(alter-var-root #'system (fn [sys]
|
||||
(when sys (ig/halt! sys))
|
||||
(-> system-config
|
||||
(cond-> (contains? cf/flags :backend-worker)
|
||||
(merge worker-config))
|
||||
(ig/prep)
|
||||
(ig/init))))
|
||||
(l/info :hint "welcome to penpot"
|
||||
:flags (str/join "," (map name cf/flags))
|
||||
:worker? (contains? cf/flags :backend-worker)
|
||||
:version (:full cf/version)))
|
||||
|
||||
(defn stop
|
||||
[]
|
||||
(alter-var-root #'system (fn [sys]
|
||||
(when sys (ig/halt! sys))
|
||||
nil)))
|
||||
|
||||
(defn -main
|
||||
[& _args]
|
||||
(try
|
||||
(start)
|
||||
(catch Throwable cause
|
||||
(l/error :hint (ex-message cause)
|
||||
:cause cause)
|
||||
(System/exit -1))))
|
||||
304
backend/src/app/media.clj
Normal file
304
backend/src/app/media.clj
Normal file
@@ -0,0 +1,304 @@
|
||||
;; 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.media
|
||||
"Media & Font postprocessing."
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.media :as cm]
|
||||
[app.common.spec :as us]
|
||||
[app.config :as cf]
|
||||
[app.storage.tmp :as tmp]
|
||||
[app.util.svg :as svg]
|
||||
[buddy.core.bytes :as bb]
|
||||
[buddy.core.codecs :as bc]
|
||||
[clojure.java.shell :as sh]
|
||||
[clojure.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[datoteka.fs :as fs]
|
||||
[datoteka.io :as io])
|
||||
(:import
|
||||
org.im4java.core.ConvertCmd
|
||||
org.im4java.core.IMOperation
|
||||
org.im4java.core.Info))
|
||||
|
||||
(s/def ::path fs/path?)
|
||||
(s/def ::filename string?)
|
||||
(s/def ::size integer?)
|
||||
(s/def ::headers (s/map-of string? string?))
|
||||
(s/def ::mtype string?)
|
||||
|
||||
(s/def ::upload
|
||||
(s/keys :req-un [::filename ::size ::path]
|
||||
:opt-un [::mtype ::headers]))
|
||||
|
||||
;; A subset of fields from the ::upload spec
|
||||
(s/def ::input
|
||||
(s/keys :req-un [::path]
|
||||
:opt-un [::mtype]))
|
||||
|
||||
(defn validate-media-type!
|
||||
([upload] (validate-media-type! upload cm/valid-image-types))
|
||||
([upload allowed]
|
||||
(when-not (contains? allowed (:mtype upload))
|
||||
(ex/raise :type :validation
|
||||
:code :media-type-not-allowed
|
||||
:hint "Seems like you are uploading an invalid media object"))
|
||||
|
||||
upload))
|
||||
|
||||
(defmulti process :cmd)
|
||||
(defmulti process-error class)
|
||||
|
||||
(defmethod process :default
|
||||
[{:keys [cmd] :as params}]
|
||||
(ex/raise :type :internal
|
||||
:code :not-implemented
|
||||
:hint (str/fmt "No impl found for process cmd: %s" cmd)))
|
||||
|
||||
(defmethod process-error :default
|
||||
[error]
|
||||
(throw error))
|
||||
|
||||
(defn run
|
||||
[params]
|
||||
(try
|
||||
(process params)
|
||||
(catch Throwable e
|
||||
(process-error e))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; IMAGE THUMBNAILS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(s/def ::width integer?)
|
||||
(s/def ::height integer?)
|
||||
(s/def ::format #{:jpeg :webp :png})
|
||||
(s/def ::quality #(< 0 % 101))
|
||||
|
||||
(s/def ::thumbnail-params
|
||||
(s/keys :req-un [::input ::format ::width ::height]))
|
||||
|
||||
;; Related info on how thumbnails generation
|
||||
;; http://www.imagemagick.org/Usage/thumbnails/
|
||||
|
||||
(defn- generic-process
|
||||
[{:keys [input format operation] :as params}]
|
||||
(let [{:keys [path mtype]} input
|
||||
format (or (cm/mtype->format mtype) format)
|
||||
ext (cm/format->extension format)
|
||||
tmp (tmp/tempfile :prefix "penpot.media." :suffix ext)]
|
||||
|
||||
(doto (ConvertCmd.)
|
||||
(.run operation (into-array (map str [path tmp]))))
|
||||
|
||||
(assoc params
|
||||
:format format
|
||||
:mtype (cm/format->mtype format)
|
||||
:size (fs/size tmp)
|
||||
:data tmp)))
|
||||
|
||||
(defmethod process :generic-thumbnail
|
||||
[{:keys [quality width height] :as params}]
|
||||
(us/assert ::thumbnail-params params)
|
||||
(let [op (doto (IMOperation.)
|
||||
(.addImage)
|
||||
(.autoOrient)
|
||||
(.strip)
|
||||
(.thumbnail ^Integer (int width) ^Integer (int height) ">")
|
||||
(.quality (double quality))
|
||||
(.addImage))]
|
||||
(generic-process (assoc params :operation op))))
|
||||
|
||||
(defmethod process :profile-thumbnail
|
||||
[{:keys [quality width height] :as params}]
|
||||
(us/assert ::thumbnail-params params)
|
||||
(let [op (doto (IMOperation.)
|
||||
(.addImage)
|
||||
(.autoOrient)
|
||||
(.strip)
|
||||
(.thumbnail ^Integer (int width) ^Integer (int height) "^")
|
||||
(.gravity "center")
|
||||
(.extent (int width) (int height))
|
||||
(.quality (double quality))
|
||||
(.addImage))]
|
||||
(generic-process (assoc params :operation op))))
|
||||
|
||||
(defn get-basic-info-from-svg
|
||||
[{:keys [tag attrs] :as data}]
|
||||
(when (not= tag :svg)
|
||||
(ex/raise :type :validation
|
||||
:code :unable-to-parse-svg
|
||||
:hint "uploaded svg has invalid content"))
|
||||
(reduce (fn [default f]
|
||||
(if-let [res (f attrs)]
|
||||
(reduced res)
|
||||
default))
|
||||
{:width 100 :height 100}
|
||||
[(fn parse-width-and-height
|
||||
[{:keys [width height]}]
|
||||
(when (and (string? width)
|
||||
(string? height))
|
||||
(let [width (d/parse-double width)
|
||||
height (d/parse-double height)]
|
||||
(when (and width height)
|
||||
{:width (int width)
|
||||
:height (int height)}))))
|
||||
(fn parse-viewbox
|
||||
[{:keys [viewBox]}]
|
||||
(let [[x y width height] (->> (str/split viewBox #"\s+" 4)
|
||||
(map d/parse-double))]
|
||||
(when (and x y width height)
|
||||
{:width (int width)
|
||||
:height (int height)})))]))
|
||||
|
||||
(defmethod process :info
|
||||
[{:keys [input] :as params}]
|
||||
(us/assert ::input input)
|
||||
(let [{:keys [path mtype]} input]
|
||||
(if (= mtype "image/svg+xml")
|
||||
(let [info (some-> path slurp svg/pre-process svg/parse get-basic-info-from-svg)]
|
||||
(when-not info
|
||||
(ex/raise :type :validation
|
||||
:code :invalid-svg-file
|
||||
:hint "uploaded svg does not provides dimensions"))
|
||||
(merge input info))
|
||||
|
||||
(let [instance (Info. (str path))
|
||||
mtype' (.getProperty instance "Mime type")]
|
||||
(when (and (string? mtype)
|
||||
(not= mtype mtype'))
|
||||
(ex/raise :type :validation
|
||||
:code :media-type-mismatch
|
||||
:hint (str "Seems like you are uploading a file whose content does not match the extension."
|
||||
"Expected: " mtype ". Got: " mtype')))
|
||||
;; For an animated GIF, getImageWidth/Height returns the delta size of one frame (if no frame given
|
||||
;; it returns size of the last one), whereas getPageWidth/Height always return the full size of
|
||||
;; any frame.
|
||||
(assoc input
|
||||
:width (.getPageWidth instance)
|
||||
:height (.getPageHeight instance))))))
|
||||
|
||||
(defmethod process-error org.im4java.core.InfoException
|
||||
[error]
|
||||
(ex/raise :type :validation
|
||||
:code :invalid-image
|
||||
:hint "invalid image"
|
||||
:cause error))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; FONTS
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defmethod process :generate-fonts
|
||||
[{:keys [input] :as params}]
|
||||
(letfn [(ttf->otf [data]
|
||||
(let [finput (tmp/tempfile :prefix "penpot.font." :suffix "")
|
||||
foutput (fs/path (str finput ".otf"))
|
||||
_ (io/write-to-file! data finput)
|
||||
res (sh/sh "fontforge" "-lang=ff" "-c"
|
||||
(str/fmt "Open('%s'); Generate('%s')"
|
||||
(str finput)
|
||||
(str foutput)))]
|
||||
(when (zero? (:exit res))
|
||||
foutput)))
|
||||
|
||||
(otf->ttf [data]
|
||||
(let [finput (tmp/tempfile :prefix "penpot.font." :suffix "")
|
||||
foutput (fs/path (str finput ".ttf"))
|
||||
_ (io/write-to-file! data finput)
|
||||
res (sh/sh "fontforge" "-lang=ff" "-c"
|
||||
(str/fmt "Open('%s'); Generate('%s')"
|
||||
(str finput)
|
||||
(str foutput)))]
|
||||
(when (zero? (:exit res))
|
||||
foutput)))
|
||||
|
||||
(ttf-or-otf->woff [data]
|
||||
;; NOTE: foutput is not used directly, it represents the
|
||||
;; default output of the execution of the underlying
|
||||
;; command.
|
||||
(let [finput (tmp/tempfile :prefix "penpot.font." :suffix "")
|
||||
foutput (fs/path (str finput ".woff"))
|
||||
_ (io/write-to-file! data finput)
|
||||
res (sh/sh "sfnt2woff" (str finput))]
|
||||
(when (zero? (:exit res))
|
||||
foutput)))
|
||||
|
||||
(woff->sfnt [data]
|
||||
(let [finput (tmp/tempfile :prefix "penpot" :suffix "")
|
||||
_ (io/write-to-file! data finput)
|
||||
res (sh/sh "woff2sfnt" (str finput)
|
||||
:out-enc :bytes)]
|
||||
(when (zero? (:exit res))
|
||||
(:out res))))
|
||||
|
||||
;; Documented here:
|
||||
;; https://docs.microsoft.com/en-us/typography/opentype/spec/otff#table-directory
|
||||
(get-sfnt-type [data]
|
||||
(let [buff (bb/slice data 0 4)
|
||||
type (bc/bytes->hex buff)]
|
||||
(case type
|
||||
"4f54544f" :otf
|
||||
"00010000" :ttf
|
||||
(ex/raise :type :internal
|
||||
:code :unexpected-data
|
||||
:hint "unexpected font data"))))
|
||||
|
||||
(gen-if-nil [val factory]
|
||||
(if (nil? val)
|
||||
(factory)
|
||||
val))]
|
||||
|
||||
(let [current (into #{} (keys input))]
|
||||
(cond
|
||||
(contains? current "font/ttf")
|
||||
(let [data (get input "font/ttf")]
|
||||
(-> input
|
||||
(update "font/otf" gen-if-nil #(ttf->otf data))
|
||||
(update "font/woff" gen-if-nil #(ttf-or-otf->woff data))))
|
||||
|
||||
(contains? current "font/otf")
|
||||
(let [data (get input "font/otf")]
|
||||
(-> input
|
||||
(update "font/woff" gen-if-nil #(ttf-or-otf->woff data))
|
||||
(assoc "font/ttf" (otf->ttf data))))
|
||||
|
||||
(contains? current "font/woff")
|
||||
(let [data (get input "font/woff")
|
||||
sfnt (woff->sfnt data)]
|
||||
(when-not sfnt
|
||||
(ex/raise :type :validation
|
||||
:code :invalid-woff-file
|
||||
:hint "invalid woff file"))
|
||||
(let [stype (get-sfnt-type sfnt)]
|
||||
(cond-> input
|
||||
true
|
||||
(-> (assoc "font/woff" data))
|
||||
|
||||
(= stype :otf)
|
||||
(-> (assoc "font/otf" sfnt)
|
||||
(assoc "font/ttf" (otf->ttf sfnt)))
|
||||
|
||||
(= stype :ttf)
|
||||
(-> (assoc "font/otf" (ttf->otf sfnt))
|
||||
(assoc "font/ttf" sfnt)))))))))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Utility functions
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(defn configure-assets-storage
|
||||
"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)))))
|
||||
218
backend/src/app/metrics.clj
Normal file
218
backend/src/app/metrics.clj
Normal file
@@ -0,0 +1,218 @@
|
||||
;; 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.metrics
|
||||
(:refer-clojure :exclude [run!])
|
||||
(:require
|
||||
[app.common.logging :as l]
|
||||
[app.common.spec :as us]
|
||||
[app.metrics.definition :as-alias mdef]
|
||||
[clojure.spec.alpha :as s]
|
||||
[integrant.core :as ig])
|
||||
(:import
|
||||
io.prometheus.client.CollectorRegistry
|
||||
io.prometheus.client.Counter
|
||||
io.prometheus.client.Counter$Child
|
||||
io.prometheus.client.Gauge
|
||||
io.prometheus.client.Gauge$Child
|
||||
io.prometheus.client.Histogram
|
||||
io.prometheus.client.Histogram$Child
|
||||
io.prometheus.client.SimpleCollector
|
||||
io.prometheus.client.Summary
|
||||
io.prometheus.client.Summary$Builder
|
||||
io.prometheus.client.Summary$Child
|
||||
io.prometheus.client.exporter.common.TextFormat
|
||||
io.prometheus.client.hotspot.DefaultExports
|
||||
java.io.StringWriter))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
(declare create-registry)
|
||||
(declare create-collector)
|
||||
(declare handler)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; METRICS SERVICE PROVIDER
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(s/def ::mdef/name string?)
|
||||
(s/def ::mdef/help string?)
|
||||
(s/def ::mdef/labels (s/every string? :kind vector?))
|
||||
(s/def ::mdef/type #{:gauge :counter :summary :histogram})
|
||||
|
||||
(s/def ::mdef/instance
|
||||
#(instance? SimpleCollector %))
|
||||
|
||||
(s/def ::mdef/definition
|
||||
(s/keys :req [::mdef/name
|
||||
::mdef/help
|
||||
::mdef/type]
|
||||
:opt [::mdef/labels
|
||||
::mdef/instance]))
|
||||
|
||||
(s/def ::definitions
|
||||
(s/map-of keyword? ::mdef/definition))
|
||||
|
||||
(s/def ::registry
|
||||
#(instance? CollectorRegistry %))
|
||||
|
||||
(s/def ::handler fn?)
|
||||
(s/def ::metrics
|
||||
(s/keys :req [::registry
|
||||
::handler
|
||||
::definitions]))
|
||||
|
||||
(s/def ::default ::definitions)
|
||||
|
||||
(defmethod ig/pre-init-spec ::metrics [_]
|
||||
(s/keys :req-un [::default]))
|
||||
|
||||
(defmethod ig/init-key ::metrics
|
||||
[_ cfg]
|
||||
(l/info :action "initialize metrics")
|
||||
(let [registry (create-registry)
|
||||
definitions (reduce-kv (fn [res k v]
|
||||
(->> (assoc v ::registry registry)
|
||||
(create-collector)
|
||||
(assoc res k)))
|
||||
{}
|
||||
(:default cfg))]
|
||||
|
||||
(us/verify! ::definitions definitions)
|
||||
|
||||
{::handler (partial handler registry)
|
||||
::definitions definitions
|
||||
::registry registry}))
|
||||
|
||||
(defn- handler
|
||||
[registry _ respond _]
|
||||
(let [samples (.metricFamilySamples ^CollectorRegistry registry)
|
||||
writer (StringWriter.)]
|
||||
(TextFormat/write004 writer samples)
|
||||
(respond {:headers {"content-type" TextFormat/CONTENT_TYPE_004}
|
||||
:body (.toString writer)})))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Implementation
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(def default-empty-labels (into-array String []))
|
||||
|
||||
(def default-quantiles
|
||||
[[0.5 0.01]
|
||||
[0.90 0.01]
|
||||
[0.99 0.001]])
|
||||
|
||||
(def default-histogram-buckets
|
||||
[1 5 10 25 50 75 100 250 500 750 1000 2500 5000 7500])
|
||||
|
||||
(defmulti run-collector! (fn [mdef _] (::mdef/type mdef)))
|
||||
(defmulti create-collector ::mdef/type)
|
||||
|
||||
(defn run!
|
||||
[{:keys [::definitions]} & {:keys [id] :as params}]
|
||||
(when-let [mobj (get definitions id)]
|
||||
(run-collector! mobj params)
|
||||
true))
|
||||
|
||||
(defn- create-registry
|
||||
[]
|
||||
(let [registry (CollectorRegistry.)]
|
||||
(DefaultExports/register registry)
|
||||
registry))
|
||||
|
||||
(defn- is-array?
|
||||
[o]
|
||||
(let [oc (class o)]
|
||||
(and (.isArray ^Class oc)
|
||||
(= (.getComponentType oc) String))))
|
||||
|
||||
(defmethod run-collector! :counter
|
||||
[{:keys [::mdef/instance]} {:keys [inc labels] :or {inc 1 labels default-empty-labels}}]
|
||||
(let [instance (.labels ^Counter instance (if (is-array? labels) labels (into-array String labels)))]
|
||||
(.inc ^Counter$Child instance (double inc))))
|
||||
|
||||
(defmethod run-collector! :gauge
|
||||
[{:keys [::mdef/instance]} {:keys [inc dec labels val] :or {labels default-empty-labels}}]
|
||||
(let [instance (.labels ^Gauge instance (if (is-array? labels) labels (into-array String labels)))]
|
||||
(cond (number? inc) (.inc ^Gauge$Child instance (double inc))
|
||||
(number? dec) (.dec ^Gauge$Child instance (double dec))
|
||||
(number? val) (.set ^Gauge$Child instance (double val)))))
|
||||
|
||||
(defmethod run-collector! :summary
|
||||
[{:keys [::mdef/instance]} {:keys [val labels] :or {labels default-empty-labels}}]
|
||||
(let [instance (.labels ^Summary instance (if (is-array? labels) labels (into-array String labels)))]
|
||||
(.observe ^Summary$Child instance val)))
|
||||
|
||||
(defmethod run-collector! :histogram
|
||||
[{:keys [::mdef/instance]} {:keys [val labels] :or {labels default-empty-labels}}]
|
||||
(let [instance (.labels ^Histogram instance (if (is-array? labels) labels (into-array String labels)))]
|
||||
(.observe ^Histogram$Child instance val)))
|
||||
|
||||
(defmethod create-collector :counter
|
||||
[{::mdef/keys [name help reg labels]
|
||||
::keys [registry]
|
||||
:as props}]
|
||||
|
||||
(let [registry (or registry reg)
|
||||
instance (.. (Counter/build)
|
||||
(name name)
|
||||
(help help))]
|
||||
(when (seq labels)
|
||||
(.labelNames instance (into-array String labels)))
|
||||
|
||||
(assoc props ::mdef/instance (.register instance registry))))
|
||||
|
||||
(defmethod create-collector :gauge
|
||||
[{::mdef/keys [name help reg labels]
|
||||
::keys [registry]
|
||||
:as props}]
|
||||
(let [registry (or registry reg)
|
||||
instance (.. (Gauge/build)
|
||||
(name name)
|
||||
(help help))]
|
||||
(when (seq labels)
|
||||
(.labelNames instance (into-array String labels)))
|
||||
|
||||
(assoc props ::mdef/instance (.register instance registry))))
|
||||
|
||||
(defmethod create-collector :summary
|
||||
[{::mdef/keys [name help reg labels max-age quantiles buckets]
|
||||
::keys [registry]
|
||||
:or {max-age 3600 buckets 12 quantiles default-quantiles}
|
||||
:as props}]
|
||||
(let [registry (or registry reg)
|
||||
builder (doto (Summary/build)
|
||||
(.name name)
|
||||
(.help help))]
|
||||
|
||||
(when (seq quantiles)
|
||||
(.maxAgeSeconds ^Summary$Builder builder ^long max-age)
|
||||
(.ageBuckets ^Summary$Builder builder buckets))
|
||||
|
||||
(doseq [[q e] quantiles]
|
||||
(.quantile ^Summary$Builder builder q e))
|
||||
|
||||
(when (seq labels)
|
||||
(.labelNames ^Summary$Builder builder (into-array String labels)))
|
||||
|
||||
(assoc props ::mdef/instance (.register ^Summary$Builder builder registry))))
|
||||
|
||||
(defmethod create-collector :histogram
|
||||
[{::mdef/keys [name help reg labels buckets]
|
||||
::keys [registry]
|
||||
:or {buckets default-histogram-buckets}
|
||||
:as props}]
|
||||
(let [registry (or registry reg)
|
||||
instance (doto (Histogram/build)
|
||||
(.name name)
|
||||
(.help help)
|
||||
(.buckets (into-array Double/TYPE buckets)))]
|
||||
|
||||
(when (seq labels)
|
||||
(.labelNames instance (into-array String labels)))
|
||||
|
||||
(assoc props ::mdef/instance (.register instance registry))))
|
||||
308
backend/src/app/migrations.clj
Normal file
308
backend/src/app/migrations.clj
Normal file
@@ -0,0 +1,308 @@
|
||||
;; 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.migrations
|
||||
(:require
|
||||
[app.migrations.clj.migration-0023 :as mg0023]
|
||||
[app.util.migrations :as mg]
|
||||
[integrant.core :as ig]))
|
||||
|
||||
(def migrations
|
||||
[{:name "0001-add-extensions"
|
||||
:fn (mg/resource "app/migrations/sql/0001-add-extensions.sql")}
|
||||
|
||||
{:name "0002-add-profile-tables"
|
||||
:fn (mg/resource "app/migrations/sql/0002-add-profile-tables.sql")}
|
||||
|
||||
{:name "0003-add-project-tables"
|
||||
:fn (mg/resource "app/migrations/sql/0003-add-project-tables.sql")}
|
||||
|
||||
{:name "0004-add-tasks-tables"
|
||||
:fn (mg/resource "app/migrations/sql/0004-add-tasks-tables.sql")}
|
||||
|
||||
{:name "0005-add-libraries-tables"
|
||||
:fn (mg/resource "app/migrations/sql/0005-add-libraries-tables.sql")}
|
||||
|
||||
{:name "0006-add-presence-tables"
|
||||
:fn (mg/resource "app/migrations/sql/0006-add-presence-tables.sql")}
|
||||
|
||||
{:name "0007-drop-version-field-from-page-table"
|
||||
:fn (mg/resource "app/migrations/sql/0007-drop-version-field-from-page-table.sql")}
|
||||
|
||||
{:name "0008-add-generic-token-table"
|
||||
:fn (mg/resource "app/migrations/sql/0008-add-generic-token-table.sql")}
|
||||
|
||||
{:name "0009-drop-profile-email-table"
|
||||
:fn (mg/resource "app/migrations/sql/0009-drop-profile-email-table.sql")}
|
||||
|
||||
{:name "0010-add-http-session-table"
|
||||
:fn (mg/resource "app/migrations/sql/0010-add-http-session-table.sql")}
|
||||
|
||||
{:name "0011-add-session-id-field-to-page-change-table"
|
||||
:fn (mg/resource "app/migrations/sql/0011-add-session-id-field-to-page-change-table.sql")}
|
||||
|
||||
{:name "0012-make-libraries-linked-to-a-file"
|
||||
:fn (mg/resource "app/migrations/sql/0012-make-libraries-linked-to-a-file.sql")}
|
||||
|
||||
{:name "0013-mark-files-shareable"
|
||||
:fn (mg/resource "app/migrations/sql/0013-mark-files-shareable.sql")}
|
||||
|
||||
{:name "0014-refactor-media-storage.sql"
|
||||
:fn (mg/resource "app/migrations/sql/0014-refactor-media-storage.sql")}
|
||||
|
||||
{:name "0015-improve-tasks-tables"
|
||||
:fn (mg/resource "app/migrations/sql/0015-improve-tasks-tables.sql")}
|
||||
|
||||
{:name "0016-truncate-and-alter-tokens-table"
|
||||
:fn (mg/resource "app/migrations/sql/0016-truncate-and-alter-tokens-table.sql")}
|
||||
|
||||
{:name "0017-link-files-to-libraries"
|
||||
:fn (mg/resource "app/migrations/sql/0017-link-files-to-libraries.sql")}
|
||||
|
||||
{:name "0018-add-file-trimming-triggers"
|
||||
:fn (mg/resource "app/migrations/sql/0018-add-file-trimming-triggers.sql")}
|
||||
|
||||
{:name "0019-add-improved-scheduled-tasks"
|
||||
:fn (mg/resource "app/migrations/sql/0019-add-improved-scheduled-tasks.sql")}
|
||||
|
||||
{:name "0020-minor-fixes-to-media-object"
|
||||
:fn (mg/resource "app/migrations/sql/0020-minor-fixes-to-media-object.sql")}
|
||||
|
||||
{:name "0021-http-session-improvements"
|
||||
:fn (mg/resource "app/migrations/sql/0021-http-session-improvements.sql")}
|
||||
|
||||
{:name "0022-page-file-refactor"
|
||||
:fn (mg/resource "app/migrations/sql/0022-page-file-refactor.sql")}
|
||||
|
||||
{:name "0023-adapt-old-pages-and-files"
|
||||
:fn mg0023/migrate}
|
||||
|
||||
{:name "0024-mod-profile-table"
|
||||
:fn (mg/resource "app/migrations/sql/0024-mod-profile-table.sql")}
|
||||
|
||||
{:name "0025-del-generic-tokens-table"
|
||||
:fn (mg/resource "app/migrations/sql/0025-del-generic-tokens-table.sql")}
|
||||
|
||||
{:name "0026-mod-file-library-rel-table-synced-date"
|
||||
:fn (mg/resource "app/migrations/sql/0026-mod-file-library-rel-table-synced-date.sql")}
|
||||
|
||||
{:name "0027-mod-file-table-ignore-sync"
|
||||
:fn (mg/resource "app/migrations/sql/0027-mod-file-table-ignore-sync.sql")}
|
||||
|
||||
{:name "0028-add-team-project-profile-rel-table"
|
||||
:fn (mg/resource "app/migrations/sql/0028-add-team-project-profile-rel-table.sql")}
|
||||
|
||||
{:name "0029-del-project-profile-rel-indexes"
|
||||
:fn (mg/resource "app/migrations/sql/0029-del-project-profile-rel-indexes.sql")}
|
||||
|
||||
{:name "0030-mod-file-table-add-missing-index"
|
||||
:fn (mg/resource "app/migrations/sql/0030-mod-file-table-add-missing-index.sql")}
|
||||
|
||||
{:name "0031-add-conversation-related-tables"
|
||||
:fn (mg/resource "app/migrations/sql/0031-add-conversation-related-tables.sql")}
|
||||
|
||||
{:name "0032-del-unused-tables"
|
||||
:fn (mg/resource "app/migrations/sql/0032-del-unused-tables.sql")}
|
||||
|
||||
{:name "0033-mod-comment-thread-table"
|
||||
:fn (mg/resource "app/migrations/sql/0033-mod-comment-thread-table.sql")}
|
||||
|
||||
{:name "0034-mod-profile-table-add-props-field"
|
||||
:fn (mg/resource "app/migrations/sql/0034-mod-profile-table-add-props-field.sql")}
|
||||
|
||||
{:name "0035-add-storage-tables"
|
||||
:fn (mg/resource "app/migrations/sql/0035-add-storage-tables.sql")}
|
||||
|
||||
{:name "0036-mod-storage-referenced-tables"
|
||||
:fn (mg/resource "app/migrations/sql/0036-mod-storage-referenced-tables.sql")}
|
||||
|
||||
{:name "0037-del-obsolete-triggers"
|
||||
:fn (mg/resource "app/migrations/sql/0037-del-obsolete-triggers.sql")}
|
||||
|
||||
{:name "0038-add-storage-on-delete-triggers"
|
||||
:fn (mg/resource "app/migrations/sql/0038-add-storage-on-delete-triggers.sql")}
|
||||
|
||||
{:name "0039-fix-some-on-delete-triggers"
|
||||
:fn (mg/resource "app/migrations/sql/0039-fix-some-on-delete-triggers.sql")}
|
||||
|
||||
{:name "0040-add-error-report-tables"
|
||||
:fn (mg/resource "app/migrations/sql/0040-add-error-report-tables.sql")}
|
||||
|
||||
{:name "0041-mod-pg-storage-options"
|
||||
:fn (mg/resource "app/migrations/sql/0041-mod-pg-storage-options.sql")}
|
||||
|
||||
{:name "0042-add-server-prop-table"
|
||||
:fn (mg/resource "app/migrations/sql/0042-add-server-prop-table.sql")}
|
||||
|
||||
{:name "0043-drop-old-tables-and-fields"
|
||||
:fn (mg/resource "app/migrations/sql/0043-drop-old-tables-and-fields.sql")}
|
||||
|
||||
{:name "0044-add-storage-refcount"
|
||||
:fn (mg/resource "app/migrations/sql/0044-add-storage-refcount.sql")}
|
||||
|
||||
{:name "0045-add-index-to-file-change-table"
|
||||
:fn (mg/resource "app/migrations/sql/0045-add-index-to-file-change-table.sql")}
|
||||
|
||||
{:name "0046-add-profile-complaint-table"
|
||||
:fn (mg/resource "app/migrations/sql/0046-add-profile-complaint-table.sql")}
|
||||
|
||||
{:name "0047-mod-file-change-table"
|
||||
:fn (mg/resource "app/migrations/sql/0047-mod-file-change-table.sql")}
|
||||
|
||||
{:name "0048-mod-storage-tables"
|
||||
:fn (mg/resource "app/migrations/sql/0048-mod-storage-tables.sql")}
|
||||
|
||||
{:name "0049-mod-http-session-table"
|
||||
:fn (mg/resource "app/migrations/sql/0049-mod-http-session-table.sql")}
|
||||
|
||||
{:name "0050-mod-server-prop-table"
|
||||
:fn (mg/resource "app/migrations/sql/0050-mod-server-prop-table.sql")}
|
||||
|
||||
{:name "0051-mod-file-library-rel-table"
|
||||
:fn (mg/resource "app/migrations/sql/0051-mod-file-library-rel-table.sql")}
|
||||
|
||||
{:name "0052-del-legacy-user-and-team"
|
||||
:fn (mg/resource "app/migrations/sql/0052-del-legacy-user-and-team.sql")}
|
||||
|
||||
{:name "0053-add-team-font-variant-table"
|
||||
:fn (mg/resource "app/migrations/sql/0053-add-team-font-variant-table.sql")}
|
||||
|
||||
{:name "0054-add-audit-log-table"
|
||||
:fn (mg/resource "app/migrations/sql/0054-add-audit-log-table.sql")}
|
||||
|
||||
{:name "0055-mod-file-media-object-table"
|
||||
:fn (mg/resource "app/migrations/sql/0055-mod-file-media-object-table.sql")}
|
||||
|
||||
{:name "0056-add-missing-index-on-deleted-at"
|
||||
:fn (mg/resource "app/migrations/sql/0056-add-missing-index-on-deleted-at.sql")}
|
||||
|
||||
{:name "0057-del-profile-on-delete-trigger"
|
||||
:fn (mg/resource "app/migrations/sql/0057-del-profile-on-delete-trigger.sql")}
|
||||
|
||||
{:name "0058-del-team-on-delete-trigger"
|
||||
:fn (mg/resource "app/migrations/sql/0058-del-team-on-delete-trigger.sql")}
|
||||
|
||||
{:name "0059-mod-audit-log-table"
|
||||
:fn (mg/resource "app/migrations/sql/0059-mod-audit-log-table.sql")}
|
||||
|
||||
{:name "0060-mod-file-change-table"
|
||||
:fn (mg/resource "app/migrations/sql/0060-mod-file-change-table.sql")}
|
||||
|
||||
{:name "0061-mod-file-table"
|
||||
:fn (mg/resource "app/migrations/sql/0061-mod-file-table.sql")}
|
||||
|
||||
{:name "0062-fix-metadata-media"
|
||||
:fn (mg/resource "app/migrations/sql/0062-fix-metadata-media.sql")}
|
||||
|
||||
{:name "0063-add-share-link-table"
|
||||
:fn (mg/resource "app/migrations/sql/0063-add-share-link-table.sql")}
|
||||
|
||||
{:name "0064-mod-audit-log-table"
|
||||
:fn (mg/resource "app/migrations/sql/0064-mod-audit-log-table.sql")}
|
||||
|
||||
{:name "0065-add-trivial-spelling-fixes"
|
||||
:fn (mg/resource "app/migrations/sql/0065-add-trivial-spelling-fixes.sql")}
|
||||
|
||||
{:name "0066-add-frame-thumbnail-table"
|
||||
:fn (mg/resource "app/migrations/sql/0066-add-frame-thumbnail-table.sql")}
|
||||
|
||||
{:name "0067-add-team-invitation-table"
|
||||
:fn (mg/resource "app/migrations/sql/0067-add-team-invitation-table.sql")}
|
||||
|
||||
{:name "0068-mod-storage-object-table"
|
||||
:fn (mg/resource "app/migrations/sql/0068-mod-storage-object-table.sql")}
|
||||
|
||||
{:name "0069-add-file-thumbnail-table"
|
||||
:fn (mg/resource "app/migrations/sql/0069-add-file-thumbnail-table.sql")}
|
||||
|
||||
{:name "0070-del-frame-thumbnail-table"
|
||||
:fn (mg/resource "app/migrations/sql/0070-del-frame-thumbnail-table.sql")}
|
||||
|
||||
{:name "0071-add-file-object-thumbnail-table"
|
||||
:fn (mg/resource "app/migrations/sql/0071-add-file-object-thumbnail-table.sql")}
|
||||
|
||||
{:name "0072-mod-file-object-thumbnail-table"
|
||||
:fn (mg/resource "app/migrations/sql/0072-mod-file-object-thumbnail-table.sql")}
|
||||
|
||||
{:name "0073-mod-file-media-object-constraints"
|
||||
:fn (mg/resource "app/migrations/sql/0073-mod-file-media-object-constraints.sql")}
|
||||
|
||||
{:name "0074-mod-file-library-rel-constraints"
|
||||
:fn (mg/resource "app/migrations/sql/0074-mod-file-library-rel-constraints.sql")}
|
||||
|
||||
{:name "0075-mod-share-link-table"
|
||||
:fn (mg/resource "app/migrations/sql/0075-mod-share-link-table.sql")}
|
||||
|
||||
{:name "0076-mod-storage-object-table"
|
||||
:fn (mg/resource "app/migrations/sql/0076-mod-storage-object-table.sql")}
|
||||
|
||||
{:name "0077-mod-comment-thread-table"
|
||||
:fn (mg/resource "app/migrations/sql/0077-mod-comment-thread-table.sql")}
|
||||
|
||||
{:name "0078-mod-file-media-object-table-drop-cascade"
|
||||
:fn (mg/resource "app/migrations/sql/0078-mod-file-media-object-table-drop-cascade.sql")}
|
||||
|
||||
{:name "0079-mod-profile-table"
|
||||
:fn (mg/resource "app/migrations/sql/0079-mod-profile-table.sql")}
|
||||
|
||||
{:name "0080-mod-index-names"
|
||||
:fn (mg/resource "app/migrations/sql/0080-mod-index-names.sql")}
|
||||
|
||||
{:name "0081-add-deleted-at-index-to-file-table"
|
||||
:fn (mg/resource "app/migrations/sql/0081-add-deleted-at-index-to-file-table.sql")}
|
||||
|
||||
{:name "0082-add-features-column-to-file-table"
|
||||
:fn (mg/resource "app/migrations/sql/0082-add-features-column-to-file-table.sql")}
|
||||
|
||||
{:name "0083-add-file-data-fragment-table"
|
||||
:fn (mg/resource "app/migrations/sql/0083-add-file-data-fragment-table.sql")}
|
||||
|
||||
{:name "0084-add-features-column-to-file-change-table"
|
||||
:fn (mg/resource "app/migrations/sql/0084-add-features-column-to-file-change-table.sql")}
|
||||
|
||||
{:name "0085-add-webhook-table"
|
||||
:fn (mg/resource "app/migrations/sql/0085-add-webhook-table.sql")}
|
||||
|
||||
{:name "0086-add-webhook-delivery-table"
|
||||
:fn (mg/resource "app/migrations/sql/0086-add-webhook-delivery-table.sql")}
|
||||
|
||||
{:name "0087-mod-task-table"
|
||||
:fn (mg/resource "app/migrations/sql/0087-mod-task-table.sql")}
|
||||
|
||||
{:name "0088-mod-team-profile-rel-table"
|
||||
:fn (mg/resource "app/migrations/sql/0088-mod-team-profile-rel-table.sql")}
|
||||
|
||||
{:name "0089-mod-project-profile-rel-table"
|
||||
:fn (mg/resource "app/migrations/sql/0089-mod-project-profile-rel-table.sql")}
|
||||
|
||||
{:name "0090-mod-http-session-table"
|
||||
:fn (mg/resource "app/migrations/sql/0090-mod-http-session-table.sql")}
|
||||
|
||||
{:name "0091-mod-team-project-profile-rel-table"
|
||||
:fn (mg/resource "app/migrations/sql/0091-mod-team-project-profile-rel-table.sql")}
|
||||
|
||||
{:name "0092-mod-team-invitation-table"
|
||||
:fn (mg/resource "app/migrations/sql/0092-mod-team-invitation-table.sql")}
|
||||
|
||||
{:name "0093-del-file-share-tokens-table"
|
||||
:fn (mg/resource "app/migrations/sql/0093-del-file-share-tokens-table.sql")}
|
||||
|
||||
{:name "0094-del-profile-attr-table"
|
||||
:fn (mg/resource "app/migrations/sql/0094-del-profile-attr-table.sql")}
|
||||
|
||||
{:name "0095-del-storage-data-table"
|
||||
:fn (mg/resource "app/migrations/sql/0095-del-storage-data-table.sql")}
|
||||
|
||||
{:name "0096-del-storage-pending-table"
|
||||
:fn (mg/resource "app/migrations/sql/0096-del-storage-pending-table.sql")}
|
||||
|
||||
{:name "0098-add-quotes-table"
|
||||
:fn (mg/resource "app/migrations/sql/0098-add-quotes-table.sql")}
|
||||
|
||||
])
|
||||
|
||||
|
||||
(defmethod ig/init-key ::migrations [_ _] migrations)
|
||||
61
backend/src/app/migrations/clj/migration_0023.clj
Normal file
61
backend/src/app/migrations/clj/migration_0023.clj
Normal file
@@ -0,0 +1,61 @@
|
||||
;; 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.migrations.clj.migration-0023
|
||||
(:require
|
||||
[app.db :as db]
|
||||
[app.util.blob :as blob]))
|
||||
|
||||
(defn decode-row
|
||||
[{:keys [data] :as row}]
|
||||
(when row
|
||||
(cond-> row
|
||||
data (assoc :data (blob/decode data)))))
|
||||
|
||||
(defn retrieve-files
|
||||
[conn]
|
||||
(->> (db/exec! conn ["select * from file;"])
|
||||
(map decode-row)))
|
||||
|
||||
(defn retrieve-pages
|
||||
[conn file-id]
|
||||
(->> (db/query conn :page {:file-id file-id})
|
||||
(map decode-row)
|
||||
(sort-by :ordering)))
|
||||
|
||||
(def empty-file-data
|
||||
{:version 1
|
||||
:pages []
|
||||
:pages-index {}})
|
||||
|
||||
(defn pages->data
|
||||
[pages]
|
||||
(reduce (fn [acc {:keys [id data name] :as page}]
|
||||
(let [data (-> data
|
||||
(dissoc :version)
|
||||
(assoc :id id :name name))]
|
||||
(-> acc
|
||||
(update :pages (fnil conj []) id)
|
||||
(update :pages-index assoc id data))))
|
||||
empty-file-data
|
||||
pages))
|
||||
|
||||
(defn migrate-file
|
||||
[conn {:keys [id] :as file}]
|
||||
(let [pages (retrieve-pages conn (:id file))
|
||||
data (pages->data pages)]
|
||||
(db/update! conn :file
|
||||
{:data (blob/encode data)}
|
||||
{:id id})))
|
||||
|
||||
(defn migrate
|
||||
[conn]
|
||||
(let [files (retrieve-files conn)]
|
||||
(doseq [file files]
|
||||
(when (nil? (:data file))
|
||||
(migrate-file conn file)))
|
||||
(db/exec-one! conn ["drop table page cascade;"])))
|
||||
|
||||
25
backend/src/app/migrations/sql/0001-add-extensions.sql
Normal file
25
backend/src/app/migrations/sql/0001-add-extensions.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
CREATE FUNCTION update_modified_at()
|
||||
RETURNS TRIGGER AS $updt$
|
||||
BEGIN
|
||||
NEW.modified_at := clock_timestamp();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$updt$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TABLE pending_to_delete (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
type text NOT NULL,
|
||||
data jsonb NOT NULL
|
||||
);
|
||||
|
||||
CREATE FUNCTION handle_delete()
|
||||
RETURNS TRIGGER AS $pagechange$
|
||||
BEGIN
|
||||
INSERT INTO pending_to_delete (type, data)
|
||||
VALUES (TG_TABLE_NAME, row_to_json(OLD));
|
||||
RETURN OLD;
|
||||
END;
|
||||
$pagechange$ LANGUAGE plpgsql;
|
||||
149
backend/src/app/migrations/sql/0002-add-profile-tables.sql
Normal file
149
backend/src/app/migrations/sql/0002-add-profile-tables.sql
Normal file
@@ -0,0 +1,149 @@
|
||||
CREATE TABLE profile (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
deleted_at timestamptz NULL,
|
||||
|
||||
fullname text NOT NULL DEFAULT '',
|
||||
email text NOT NULL,
|
||||
photo text NOT NULL,
|
||||
password text NOT NULL,
|
||||
|
||||
lang text NULL,
|
||||
theme text NULL,
|
||||
is_demo boolean NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX profile__email__idx
|
||||
ON profile (email)
|
||||
WHERE deleted_at IS null;
|
||||
|
||||
CREATE INDEX profile__is_demo
|
||||
ON profile (is_demo)
|
||||
WHERE deleted_at IS null
|
||||
AND is_demo IS true;
|
||||
|
||||
INSERT INTO profile (id, fullname, email, photo, password)
|
||||
VALUES ('00000000-0000-0000-0000-000000000000'::uuid,
|
||||
'System Profile',
|
||||
'system@uxbox.io',
|
||||
'',
|
||||
'!');
|
||||
|
||||
|
||||
--- NOTE: this table is deleted in the next migrations
|
||||
|
||||
CREATE TABLE profile_email (
|
||||
profile_id uuid NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
verified_at timestamptz NULL DEFAULT NULL,
|
||||
|
||||
email text NOT NULL,
|
||||
|
||||
is_main boolean NOT NULL DEFAULT false,
|
||||
is_verified boolean NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
CREATE INDEX profile_email__profile_id__idx
|
||||
ON profile_email (profile_id);
|
||||
|
||||
CREATE UNIQUE INDEX profile_email__email__idx
|
||||
ON profile_email (email);
|
||||
|
||||
|
||||
|
||||
CREATE TABLE team (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
deleted_at timestamptz NULL,
|
||||
|
||||
name text NOT NULL,
|
||||
photo text NOT NULL,
|
||||
|
||||
is_default boolean NOT NULL DEFAULT false
|
||||
);
|
||||
|
||||
CREATE TRIGGER team__modified_at__tgr
|
||||
BEFORE UPDATE ON team
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||
|
||||
INSERT INTO team (id, name, photo, is_default)
|
||||
VALUES ('00000000-0000-0000-0000-000000000000'::uuid,
|
||||
'System Team',
|
||||
'',
|
||||
true);
|
||||
|
||||
|
||||
CREATE TABLE team_profile_rel (
|
||||
team_id uuid NOT NULL REFERENCES team(id) ON DELETE CASCADE,
|
||||
profile_id uuid NOT NULL REFERENCES profile(id) ON DELETE RESTRICT,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
|
||||
is_admin boolean DEFAULT false,
|
||||
is_owner boolean DEFAULT false,
|
||||
can_edit boolean DEFAULT false,
|
||||
|
||||
PRIMARY KEY (team_id, profile_id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE team_profile_rel
|
||||
IS 'Relation between teams and profiles (NM)';
|
||||
|
||||
CREATE TRIGGER team_profile_rel__modified_at__tgr
|
||||
BEFORE UPDATE ON team_profile_rel
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||
|
||||
|
||||
|
||||
CREATE TABLE profile_attr (
|
||||
profile_id uuid NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
|
||||
key text NOT NULL,
|
||||
val bytea NOT NULL,
|
||||
|
||||
PRIMARY KEY (key, profile_id)
|
||||
);
|
||||
|
||||
CREATE INDEX profile_attr__profile_id__idx
|
||||
ON profile_attr(profile_id);
|
||||
|
||||
CREATE TRIGGER profile_attr__modified_at__tgr
|
||||
BEFORE UPDATE ON profile_attr
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||
|
||||
|
||||
--- NOTE: this table is removed in the following migrations
|
||||
|
||||
CREATE TABLE password_recovery_token (
|
||||
profile_id uuid NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
||||
token text NOT NULL,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
used_at timestamptz NULL,
|
||||
|
||||
PRIMARY KEY (profile_id, token)
|
||||
);
|
||||
|
||||
|
||||
|
||||
CREATE TABLE session (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
|
||||
profile_id uuid REFERENCES profile(id) ON DELETE CASCADE,
|
||||
user_agent text NULL
|
||||
);
|
||||
|
||||
CREATE INDEX session__profile_id__idx
|
||||
ON session(profile_id);
|
||||
215
backend/src/app/migrations/sql/0003-add-project-tables.sql
Normal file
215
backend/src/app/migrations/sql/0003-add-project-tables.sql
Normal file
@@ -0,0 +1,215 @@
|
||||
CREATE TABLE project (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
team_id uuid NOT NULL REFERENCES team(id) ON DELETE CASCADE,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
deleted_at timestamptz DEFAULT NULL,
|
||||
|
||||
is_default boolean NOT NULL DEFAULT false,
|
||||
|
||||
name text NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX project__team_id__idx
|
||||
ON project(team_id);
|
||||
|
||||
CREATE TABLE project_profile_rel (
|
||||
profile_id uuid NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
||||
project_id uuid NOT NULL REFERENCES project(id) ON DELETE CASCADE,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
|
||||
is_owner boolean DEFAULT false,
|
||||
is_admin boolean DEFAULT false,
|
||||
can_edit boolean DEFAULT false,
|
||||
|
||||
PRIMARY KEY (profile_id, project_id)
|
||||
);
|
||||
|
||||
CREATE INDEX project_profile_rel__profile_id__idx
|
||||
ON project_profile_rel(profile_id);
|
||||
|
||||
CREATE INDEX project_profile_rel__project_id__idx
|
||||
ON project_profile_rel(project_id);
|
||||
|
||||
|
||||
CREATE TABLE file (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
project_id uuid NOT NULL REFERENCES project(id) ON DELETE CASCADE,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
deleted_at timestamptz DEFAULT NULL,
|
||||
|
||||
name text NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE file_profile_rel (
|
||||
file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE,
|
||||
profile_id uuid NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
|
||||
is_owner boolean DEFAULT false,
|
||||
is_admin boolean DEFAULT false,
|
||||
can_edit boolean DEFAULT false,
|
||||
|
||||
PRIMARY KEY (file_id, profile_id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE file_profile_rel
|
||||
IS 'Relation between files and profiles (NM)';
|
||||
|
||||
CREATE INDEX file_profile_rel__profile_id__idx
|
||||
ON file_profile_rel(profile_id);
|
||||
|
||||
CREATE INDEX file_profile_rel__file_id__idx
|
||||
ON file_profile_rel(file_id);
|
||||
|
||||
CREATE TRIGGER file_profile_rel__modified_at__tgr
|
||||
BEFORE UPDATE ON file_profile_rel
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||
|
||||
CREATE TABLE file_image (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
deleted_at timestamptz DEFAULT NULL,
|
||||
|
||||
name text NOT NULL,
|
||||
|
||||
path text NOT NULL,
|
||||
width int NOT NULL,
|
||||
height int NOT NULL,
|
||||
mtype text NOT NULL,
|
||||
|
||||
thumb_path text NOT NULL,
|
||||
thumb_width int NOT NULL,
|
||||
thumb_height int NOT NULL,
|
||||
thumb_quality int NOT NULL,
|
||||
thumb_mtype text NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX file_image__file_id__idx
|
||||
ON file_image(file_id);
|
||||
|
||||
CREATE TRIGGER file_image__modified_at__tgr
|
||||
BEFORE UPDATE ON file_image
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||
|
||||
CREATE TRIGGER file_image__on_delete__tgr
|
||||
AFTER DELETE ON file_image
|
||||
FOR EACH ROW EXECUTE PROCEDURE handle_delete();
|
||||
|
||||
CREATE TABLE page (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
deleted_at timestamptz DEFAULT NULL,
|
||||
|
||||
version bigint NOT NULL DEFAULT 0,
|
||||
revn bigint NOT NULL DEFAULT 0,
|
||||
|
||||
share_token text NULL DEFAULT NULL,
|
||||
|
||||
ordering smallint NOT NULL,
|
||||
|
||||
name text NOT NULL,
|
||||
data bytea NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX page__file_id__idx
|
||||
ON page(file_id);
|
||||
|
||||
ALTER TABLE page
|
||||
ALTER COLUMN data SET STORAGE EXTERNAL,
|
||||
ALTER COLUMN share_token SET STORAGE PLAIN;
|
||||
|
||||
CREATE FUNCTION handle_page_update()
|
||||
RETURNS TRIGGER AS $pagechange$
|
||||
DECLARE
|
||||
current_dt timestamptz := clock_timestamp();
|
||||
proj_id uuid;
|
||||
BEGIN
|
||||
NEW.modified_at := current_dt;
|
||||
|
||||
UPDATE file
|
||||
SET modified_at = current_dt
|
||||
WHERE id = OLD.file_id
|
||||
RETURNING project_id
|
||||
INTO STRICT proj_id;
|
||||
|
||||
--- Update projects modified_at attribute when a
|
||||
--- page of that project is modified.
|
||||
UPDATE project
|
||||
SET modified_at = current_dt
|
||||
WHERE id = proj_id;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$pagechange$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER page__on_update__tgr
|
||||
BEFORE UPDATE ON page
|
||||
FOR EACH ROW EXECUTE PROCEDURE handle_page_update();
|
||||
|
||||
|
||||
CREATE TABLE page_version (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
page_id uuid NOT NULL REFERENCES page(id) ON DELETE CASCADE,
|
||||
profile_id uuid NULL REFERENCES profile(id) ON DELETE SET NULL,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
deleted_at timestamptz DEFAULT NULL,
|
||||
|
||||
version bigint NOT NULL DEFAULT 0,
|
||||
|
||||
label text NOT NULL DEFAULT '',
|
||||
data bytea NOT NULL,
|
||||
|
||||
changes bytea NULL DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX page_version__profile_id__idx
|
||||
ON page_version(profile_id);
|
||||
|
||||
CREATE INDEX page_version__page_id__idx
|
||||
ON page_version(page_id);
|
||||
|
||||
CREATE TRIGGER page_version__modified_at__tgr
|
||||
BEFORE UPDATE ON page_version
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||
|
||||
|
||||
|
||||
CREATE TABLE page_change (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
page_id uuid NOT NULL REFERENCES page(id) ON DELETE CASCADE,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
|
||||
revn bigint NOT NULL DEFAULT 0,
|
||||
|
||||
label text NOT NULL DEFAULT '',
|
||||
data bytea NOT NULL,
|
||||
|
||||
changes bytea NULL DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX page_change__page_id__idx
|
||||
ON page_change(page_id);
|
||||
|
||||
CREATE TRIGGER page_change__modified_at__tgr
|
||||
BEFORE UPDATE ON page_change
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||
|
||||
40
backend/src/app/migrations/sql/0004-add-tasks-tables.sql
Normal file
40
backend/src/app/migrations/sql/0004-add-tasks-tables.sql
Normal file
@@ -0,0 +1,40 @@
|
||||
CREATE TABLE task (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
completed_at timestamptz NULL DEFAULT NULL,
|
||||
scheduled_at timestamptz NOT NULL,
|
||||
priority smallint DEFAULT 100,
|
||||
|
||||
queue text NOT NULL,
|
||||
|
||||
name text NOT NULL,
|
||||
props bytea NOT NULL,
|
||||
|
||||
error text NULL DEFAULT NULL,
|
||||
|
||||
retry_num smallint NOT NULL DEFAULT 0,
|
||||
status text NOT NULL DEFAULT 'new'
|
||||
);
|
||||
|
||||
CREATE INDEX task__scheduled_at__queue__idx
|
||||
ON task (scheduled_at, queue);
|
||||
|
||||
CREATE TRIGGER task__modified_at__tgr
|
||||
BEFORE UPDATE ON task
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||
|
||||
CREATE TABLE scheduled_task (
|
||||
id text PRIMARY KEY,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
executed_at timestamptz NULL DEFAULT NULL,
|
||||
|
||||
cron_expr text NOT NULL
|
||||
);
|
||||
|
||||
CREATE TRIGGER scheduled_task__modified_at__tgr
|
||||
BEFORE UPDATE ON scheduled_task
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||
136
backend/src/app/migrations/sql/0005-add-libraries-tables.sql
Normal file
136
backend/src/app/migrations/sql/0005-add-libraries-tables.sql
Normal file
@@ -0,0 +1,136 @@
|
||||
CREATE TABLE image_library (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
team_id uuid NOT NULL REFERENCES team(id) ON DELETE CASCADE,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
deleted_at timestamptz DEFAULT NULL,
|
||||
|
||||
name text NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX image_library__team_id__idx
|
||||
ON image_library(team_id);
|
||||
|
||||
CREATE TRIGGER image_library__modified_at__tgr
|
||||
BEFORE UPDATE ON image_library
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||
|
||||
|
||||
|
||||
CREATE TABLE image (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
library_id uuid NOT NULL REFERENCES image_library(id) ON DELETE CASCADE,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
deleted_at timestamptz DEFAULT NULL,
|
||||
|
||||
name text NOT NULL,
|
||||
|
||||
path text NOT NULL,
|
||||
width int NOT NULL,
|
||||
height int NOT NULL,
|
||||
mtype text NOT NULL,
|
||||
|
||||
thumb_path text NOT NULL,
|
||||
thumb_width int NOT NULL,
|
||||
thumb_height int NOT NULL,
|
||||
thumb_quality int NOT NULL,
|
||||
thumb_mtype text NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX image__library_id__idx
|
||||
ON image(library_id);
|
||||
|
||||
CREATE TRIGGER image__modified_at__tgr
|
||||
BEFORE UPDATE ON image
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||
|
||||
CREATE TRIGGER image__on_delete__tgr
|
||||
AFTER DELETE ON image
|
||||
FOR EACH ROW EXECUTE PROCEDURE handle_delete();
|
||||
|
||||
|
||||
|
||||
CREATE TABLE icon_library (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
team_id uuid NOT NULL REFERENCES team(id) ON DELETE CASCADE,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
deleted_at timestamptz DEFAULT NULL,
|
||||
|
||||
name text NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX icon_colection__team_id__idx
|
||||
ON icon_library (team_id);
|
||||
|
||||
CREATE TRIGGER icon_library__modified_at__tgr
|
||||
BEFORE UPDATE ON icon_library
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||
|
||||
|
||||
|
||||
CREATE TABLE icon (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
library_id uuid REFERENCES icon_library(id) ON DELETE CASCADE,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
deleted_at timestamptz DEFAULT NULL,
|
||||
|
||||
|
||||
name text NOT NULL,
|
||||
content text NOT NULL,
|
||||
metadata bytea NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX icon__library_id__idx
|
||||
ON icon(library_id);
|
||||
|
||||
CREATE TRIGGER icon__modified_at__tgr
|
||||
BEFORE UPDATE ON icon
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||
|
||||
|
||||
|
||||
CREATE TABLE color_library (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
team_id uuid NOT NULL REFERENCES team(id) ON DELETE CASCADE,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
deleted_at timestamptz DEFAULT NULL,
|
||||
|
||||
name text NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX color_colection__team_id__idx
|
||||
ON color_library (team_id);
|
||||
|
||||
CREATE TRIGGER color_library__modified_at__tgr
|
||||
BEFORE UPDATE ON color_library
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||
|
||||
|
||||
|
||||
CREATE TABLE color (
|
||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
library_id uuid REFERENCES color_library(id) ON DELETE CASCADE,
|
||||
|
||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
deleted_at timestamptz DEFAULT NULL,
|
||||
|
||||
name text NOT NULL,
|
||||
content text NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX color__library_id__idx
|
||||
ON color(library_id);
|
||||
|
||||
CREATE TRIGGER color__modified_at__tgr
|
||||
BEFORE UPDATE ON color
|
||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE presence (
|
||||
file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE,
|
||||
profile_id uuid NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
||||
session_id uuid NOT NULL,
|
||||
|
||||
updated_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||
|
||||
PRIMARY KEY (file_id, session_id, profile_id)
|
||||
);
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE page
|
||||
DROP COLUMN version;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user