forked from potsda.mn/mobilizon
Compare commits
929 commits
mobilizon-
...
main
Author | SHA1 | Date | |
---|---|---|---|
778a69cd | 4cdbf78037 | ||
778a69cd | 59c3c87281 | ||
778a69cd | 81ae56d850 | ||
5770d6f0f9 | |||
summersamara | c2fc80cade | ||
summersamara | 87a7738842 | ||
08bf4a90da | |||
fe09e43be2 | |||
5d409d8029 | |||
ee49f57ee7 | |||
51c7dfc593 | |||
eb8d65d1c8 | |||
25bbab3827 | |||
baf75dd890 | |||
602798da0f | |||
778a69cd | a1d7adc538 | ||
6178f5a76e | |||
08f80341c2 | |||
summersamara | a48b315f16 | ||
5997e9e14c | |||
57d0372ce8 | |||
b317fe6163 | |||
fb173414c9 | |||
d0835232d6 | |||
fe0cf93604 | |||
b3ba45e8a7 | |||
428537df1f | |||
f7585cfc75 | |||
eb43b7c79c | |||
c3aa145148 | |||
778a69cd | 9028332b0d | ||
5cdcc2e985 | |||
11cb0a4a81 | |||
e8d7663f2a | |||
606f3df866 | |||
0bd7b670ae | |||
9308c5399d | |||
0948cce83e | |||
7c51ef79b9 | |||
2f4b8feeba | |||
6d2f08f3c1 | |||
da3b074619 | |||
778a69cd | 953033b58b | ||
7d3b46d905 | |||
summersamara | 81ca3e052f | ||
summersamara | dec26525c0 | ||
99867d6dda | |||
881695ca19 | |||
7351468842 | |||
778a69cd | 8013eb95f7 | ||
b89f4f47fa | |||
a6a80cabc6 | |||
112019d21c | |||
72c0b18aa9 | |||
b7cb2b9bd1 | |||
4ddbc20e2b | |||
778a69cd | 6fa7e23655 | ||
58e4239aae | |||
99b2339424 | |||
778a69cd | 05381e47e8 | ||
b09f9053b2 | |||
ff0440c634 | |||
3c75856149 | |||
e73fd9b370 | |||
summersamara | 31411bfc03 | ||
778a69cd | 15c9518877 | ||
summersamara | 1d430f5707 | ||
summersamara | 5fb5897c34 | ||
summersamara | f3051d5f11 | ||
778a69cd | 00c408a502 | ||
2fba6379f1 | |||
889cb91f26 | |||
75d7816a6c | |||
f4e51c2a90 | |||
001a0ed1a5 | |||
64237cfc26 | |||
8544278c9a | |||
78d3e76e46 | |||
227c176628 | |||
00250ff33a | |||
dd775b6ae2 | |||
d3b0647b11 | |||
summersamara | 1e0db0d8c9 | ||
summersamara | 4ceee2efc7 | ||
778a69cd | c90ad81879 | ||
summersamara | 69717f26f3 | ||
summersamara | 9c0c5b6e83 | ||
778a69cd | 81948b45ca | ||
summersamara | 5095749157 | ||
707add36dd | |||
d5eb235661 | |||
8b2e885159 | |||
48e1b7a5ec | |||
91fdaf5abc | |||
778a69cd | 863a1dbe3f | ||
f10977a99a | |||
778a69cd | 1eaad40c66 | ||
778a69cd | 7d7abd0dda | ||
708bb6b353 | |||
049ffd61b7 | |||
a9676d6481 | |||
76668e0beb | |||
cecbea6db5 | |||
1d39eb5488 | |||
a408b476cf | |||
45f8757d72 | |||
89641c502e | |||
442d072857 | |||
f4ee116112 | |||
83eb5c6a69 | |||
ee6381463d | |||
00d8bc733d | |||
51d43aa2d1 | |||
68c40e6bf5 | |||
a47f4f6444 | |||
d4489f691b | |||
ae466b879c | |||
820d4adb69 | |||
459f486a90 | |||
778a69cd | 744008273d | ||
f6ff99987f | |||
778a69cd | 5030b755a0 | ||
778a69cd | 63129b0769 | ||
summersamara | 9c60c6d1ad | ||
778a69cd | 0e0272fb5a | ||
summersamara | 6b26aaa1ef | ||
987c5b59d3 | |||
0427e42f8a | |||
1af8e37e9b | |||
778a69cd | b91b5f8133 | ||
5e3d8a861f | |||
0caaf2bf2e | |||
795ef2463f | |||
38f2443d7b | |||
97c53bb8d3 | |||
e08b057e06 | |||
ec5e4366e5 | |||
211d07b68e | |||
db385501aa | |||
7210f86889 | |||
4855af8f87 | |||
9907f887c9 | |||
126727bf58 | |||
7d725bd942 | |||
28063bd1d9 | |||
09f41328ab | |||
778a69cd | 3dc3e7e972 | ||
94bf2e53bf | |||
dc6647f5dc | |||
58e50e3c9f | |||
41227d994c | |||
2c12fbfd09 | |||
77518deb54 | |||
ffff379d47 | |||
1a1ad5295b | |||
7b4c31d66a | |||
ded59bec27 | |||
935799f123 | |||
5b337f952a | |||
114e850682 | |||
9c88faeafb | |||
778a69cd | daf33b747c | ||
623f4ee556 | |||
1162dd0f7d | |||
3a55baeffd | |||
f93457131a | |||
147096cc3d | |||
f81472e081 | |||
c4d2ec69ad | |||
89d1ee42f4 | |||
f1084c101f | |||
749e90b6c9 | |||
a26ff98b13 | |||
1683f01662 | |||
aa7f870a79 | |||
1ce34eaffb | |||
5e7edc0784 | |||
d777d8874c | |||
0118d974e9 | |||
5677f8170f | |||
805e931e53 | |||
778a69cd | d8ba0bc12c | ||
c8f85df3db | |||
5e8f9afb62 | |||
10ce812660 | |||
24d92f60f7 | |||
3851392225 | |||
9e41bc1ad6 | |||
51e1ed642c | |||
6c992cade5 | |||
778a69cd | 2d69dc2076 | ||
3a2c6afc0d | |||
32caebb1d0 | |||
8795576865 | |||
778a69cd | 9a10666aa8 | ||
778a69cd | bedada2efc | ||
778a69cd | 059814f751 | ||
778a69cd | 87fd975182 | ||
778a69cd | 0ff5418978 | ||
778a69cd | 69fe7b44c5 | ||
778a69cd | 4bd97ebfcb | ||
0d31872737 | |||
55287b016f | |||
5fcf3d5267 | |||
6000c41364 | |||
d831dff9fc | |||
11e42d6601 | |||
6df16ef114 | |||
3f2a88fcfa | |||
9e6b232a78 | |||
b315e1d7ff | |||
778a69cd | 696f0e4901 | ||
4f15535fa9 | |||
2e4b70cf93 | |||
1a4c4b41e3 | |||
49b070d939 | |||
summersamara | e4607567ce | ||
f81804d57f | |||
summersamara | 83da88ca28 | ||
summersamara | c0d530be92 | ||
778a69cd | 1eb3afca56 | ||
778a69cd | bd89ca3355 | ||
ad597db271 | |||
9f78c73c68 | |||
802ab78968 | |||
778a69cd | 0530612b78 | ||
summersamara | d31a5b68ec | ||
0ed48d5b0a | |||
summersamara | bfb7e3ca40 | ||
42ddf3f653 | |||
f34099d384 | |||
b2bacbf6eb | |||
cd53062c01 | |||
262d1fcd4d | |||
summersamara | 970597876b | ||
summersamara | d668067fee | ||
797eb2334b | |||
summersamara | 8a1b122711 | ||
778a69cd | 55dfb554a4 | ||
778a69cd | 115a094b88 | ||
778a69cd | 1ab82cdfb8 | ||
summersamara | 69e4a5c532 | ||
778a69cd | 599c4ce0f5 | ||
778a69cd | 257da414bf | ||
summersamara | 7d7f925907 | ||
778a69cd | 0a300ea5e5 | ||
1334bad8a8 | |||
c731f0f084 | |||
f47889b5e0 | |||
5e86ef1e8c | |||
16cd377357 | |||
5602164c62 | |||
99c80c6490 | |||
d7daafc4ea | |||
b97f1c997f | |||
18314956ca | |||
6ecfa48511 | |||
1f3f3e0d81 | |||
9aa9cd2056 | |||
1228ec122f | |||
70e9ce0a26 | |||
a11fab6e6d | |||
d065193546 | |||
d702ca203c | |||
9224f89d9d | |||
c14dffb234 | |||
dcfcf066f9 | |||
7ef85fe19b | |||
778a69cd | 5b2fb5dde7 | ||
b635937091 | |||
778a69cd | b441fa3761 | ||
778a69cd | a755fd39b7 | ||
074099a36e | |||
a7d70d5b44 | |||
3c288c5858 | |||
f24866012b | |||
8d11073965 | |||
e051df1ab3 | |||
5d65981dcd | |||
c255ceacbb | |||
105d3b5814 | |||
bfbc299f37 | |||
2e72f6faf4 | |||
32055122c3 | |||
3d9beaa1ca | |||
435bd9dccc | |||
1d0398df42 | |||
ec397aa489 | |||
b5672cee7e | |||
0613f7f736 | |||
778a69cd | 4259f47ebd | ||
3574256025 | |||
c529a83ff4 | |||
7ca54d2b91 | |||
778a69cd | 3936eb4cc5 | ||
778a69cd | e7b441493d | ||
778a69cd | 036e2924b4 | ||
778a69cd | 970420760b | ||
778a69cd | 4bc52f527d | ||
778a69cd | a0b001576b | ||
778a69cd | 4fb5be561f | ||
778a69cd | e56858ccc8 | ||
778a69cd | ef95e543f1 | ||
778a69cd | 2527ea7764 | ||
778a69cd | e31bb5d615 | ||
778a69cd | cea9b15361 | ||
778a69cd | f7892cfc41 | ||
63a167b167 | |||
d62c31e769 | |||
ee5ee8d2f1 | |||
66c49e4d26 | |||
a8ea217c0b | |||
42537afc6e | |||
fb0a74e5ee | |||
2458076650 | |||
778a69cd | c9b6653bf2 | ||
778a69cd | 4401f32ca8 | ||
778a69cd | afe1cb57e3 | ||
778a69cd | dad52ae432 | ||
778a69cd | dad96e81bd | ||
f8f9b4d231 | |||
d130b156eb | |||
f451eab140 | |||
b5e9f62b26 | |||
abeceba397 | |||
e8e1a6291b | |||
84fc175875 | |||
5b6438860f | |||
7732f87e0b | |||
778a69cd | 224b5607b6 | ||
2fe7028482 | |||
5e3dedb2dd | |||
afe4dd2ff8 | |||
fa0ae83a79 | |||
181a5a7955 | |||
827caa34e9 | |||
d08d350596 | |||
e9d38c20c2 | |||
a4578f376e | |||
0f103df25d | |||
d7ad934a74 | |||
2ecd55df96 | |||
778a69cd | 7cd4b34cb8 | ||
5681e6e8b8 | |||
7a1bfcac49 | |||
da7a08d130 | |||
778a69cd | 0b7642b161 | ||
7e4310b293 | |||
2ecdf05bf9 | |||
2cf4554345 | |||
c7ba0039f5 | |||
778a69cd | 6aa5c06462 | ||
summersamara | bd46f1fea1 | ||
summersamara | 81c85df847 | ||
summersamara | b624d6e4ba | ||
summersamara | 7abfcf2b1c | ||
778a69cd | 240dfb4e40 | ||
c0b3217e9a | |||
46ffc8ceb6 | |||
8a6d38c051 | |||
f0d78079e1 | |||
02af9a437d | |||
778a69cd | b578195553 | ||
summersamara | 70c4488f1e | ||
summersamara | a883f2467a | ||
summersamara | 49a9d27020 | ||
summersamara | 6bfaf97284 | ||
summersamara | 7f77694d20 | ||
summersamara | 82e9493f44 | ||
04315f91ea | |||
495d163a44 | |||
778a69cd | 8b1598f8cc | ||
778a69cd | 61d98308f4 | ||
a5c260fb52 | |||
66e89b9ee2 | |||
017bffc155 | |||
cfebc355de | |||
e09910852a | |||
85e4715412 | |||
daa68533d5 | |||
b3be7c6a20 | |||
6797ceea4b | |||
9a563efcff | |||
bd38449eda | |||
4960387174 | |||
778a69cd | 2b01de96e6 | ||
8324a7b4a7 | |||
ba5f8f8a12 | |||
cefdaf84f1 | |||
e510e09e4a | |||
778a69cd | 7522bb6134 | ||
778a69cd | 6e456a80f3 | ||
778a69cd | c8398b07a9 | ||
778a69cd | 5b910788b1 | ||
778a69cd | 45b63be9f3 | ||
778a69cd | a74c99a731 | ||
778a69cd | 54e09ed919 | ||
49e4174f85 | |||
384864cc82 | |||
778a69cd | f273c69e75 | ||
3d63c12e88 | |||
7e4934513a | |||
7e13e2baa7 | |||
bde7206a1c | |||
28c4ae2e48 | |||
9b4991844e | |||
cea66094d0 | |||
b755908e00 | |||
778a69cd | 73aa8bd0f1 | ||
97439535f2 | |||
1c1afcb069 | |||
f28109ad50 | |||
13099e0f11 | |||
8c8daae006 | |||
895378a96b | |||
778a69cd | 71d2a50356 | ||
a8ddc90ea2 | |||
2881717f6d | |||
f3ea98822b | |||
23adeaee47 | |||
92b222b091 | |||
8c14ba441c | |||
31b2d065a9 | |||
cfc984345e | |||
778a69cd | 311c989167 | ||
b86928b6b3 | |||
814927a213 | |||
ed5232bb84 | |||
626cdc01b8 | |||
778a69cd | 25836b5971 | ||
19f595c2d8 | |||
f6611e8eb5 | |||
af670f3947 | |||
2de6937407 | |||
4fb1282e76 | |||
3f60174877 | |||
4ce79f5136 | |||
7c019e59c6 | |||
778a69cd | 239ca025bb | ||
84f62cd043 | |||
5999252e02 | |||
89b57a5ec1 | |||
bd4f61cc1a | |||
fdda088b3d | |||
3d4999e7e2 | |||
e3d9a76074 | |||
afd2ffe722 | |||
b9a165a7fc | |||
4f530cabcf | |||
69588dbf4c | |||
b105c508c0 | |||
f2ac3e2e5d | |||
538139eefa | |||
482369d5fe | |||
778a69cd | 0aada9ae98 | ||
778a69cd | 5e4a36b4e4 | ||
778a69cd | f7fb0283cb | ||
4fc1cfc3a2 | |||
cd1549752b | |||
5e49b5457b | |||
ccc3d228e4 | |||
778a69cd | 779563d767 | ||
778a69cd | d02d62bd0d | ||
b1aa9ea92d | |||
2bf45348cc | |||
a8ac41bfb8 | |||
da532c7059 | |||
f99267c611 | |||
beef3ff16d | |||
010a5e426d | |||
fef60ed0f9 | |||
467f1ec60f | |||
8617382af2 | |||
fdf87ea991 | |||
fa94d145d3 | |||
778a69cd | 340aba5624 | ||
7cc4abd47b | |||
d2490f9304 | |||
f61001f81e | |||
860b4eb9a3 | |||
bc50ab66f3 | |||
3d491fc034 | |||
778a69cd | 95a69ab1f4 | ||
d29f1e1ee2 | |||
8e3f90f713 | |||
778a69cd | 8d9ddbdfe8 | ||
98470f3d8d | |||
c987d7b2e7 | |||
64c28ec271 | |||
5160c3e526 | |||
f3c218f841 | |||
8c313b5397 | |||
7cbbf84217 | |||
de30b46a87 | |||
7688bf88ee | |||
95f3be2da9 | |||
f04907c2a6 | |||
15cf103dfa | |||
abd609ecc8 | |||
75dddeb792 | |||
778a69cd | f52ddfffa2 | ||
4820307dad | |||
f54fff56fc | |||
3de4d84329 | |||
b2492a3870 | |||
52b3e5b151 | |||
af46bea7f7 | |||
7cc9a37c78 | |||
1b0a7499f8 | |||
c49d816253 | |||
e84da72ab9 | |||
7bbdae9101 | |||
107bab33c9 | |||
675ac38289 | |||
ed3cd5858c | |||
778a69cd | 922ce515f9 | ||
a6721ec4ae | |||
21149cee65 | |||
1c7383235d | |||
b1f2d4e22d | |||
da40a63737 | |||
d037642f55 | |||
778a69cd | 223eca1fa3 | ||
22e3e30f8f | |||
5d32370a6f | |||
9072403eb5 | |||
778a69cd | 21e22e9514 | ||
6e7701b3c8 | |||
50695fcfd5 | |||
3de90a3c73 | |||
778a69cd | 33094bb160 | ||
778a69cd | a6b29f1745 | ||
778a69cd | d1d49d827e | ||
027013eba3 | |||
e351d3cb2f | |||
04c5ac1163 | |||
778a69cd | 2e08aa2573 | ||
45a1a3a2dd | |||
1316a9e350 | |||
778a69cd | 951ff96b2a | ||
778a69cd | 3f747459d1 | ||
778a69cd | 733d58a49b | ||
778a69cd | 17f59c866e | ||
778a69cd | c1299bddf6 | ||
778a69cd | 3cc51c015f | ||
778a69cd | 634438da38 | ||
778a69cd | 1f9ebc8f14 | ||
778a69cd | e6c6e0c0e2 | ||
90dca68a46 | |||
d9b34e4ebc | |||
d8ef2e0690 | |||
bac255136b | |||
1901bb64ae | |||
b98156854e | |||
add5ce1efc | |||
1f7503f6dd | |||
d8a6b551ea | |||
56d6395a5d | |||
8faf9a5c79 | |||
0a0f18ae9c | |||
a3d885e6b7 | |||
7e98097c71 | |||
1a6095d27a | |||
086d208ee5 | |||
2c8c332ad0 | |||
12cbff154a | |||
c76dba3dbf | |||
c6b83c42d6 | |||
cc8f02d0a6 | |||
fee0e388af | |||
8f84ba1d08 | |||
60aceb442a | |||
johndoe4 | 77274ed29d | ||
johndoe4 | bfcdae945f | ||
johndoe4 | a722d71dae | ||
778a69cd | 3bf7db8caa | ||
778a69cd | d4bee06d24 | ||
778a69cd | 7a1390825f | ||
778a69cd | 51ee15e659 | ||
778a69cd | 8038f967cc | ||
778a69cd | 6deb3f4226 | ||
778a69cd | 2c5b4ca3bc | ||
778a69cd | 3a8c1074c0 | ||
778a69cd | d27bd79178 | ||
778a69cd | e046a6ef16 | ||
778a69cd | 11c1590cac | ||
778a69cd | 496ba488b6 | ||
7916261c5c | |||
da70427e32 | |||
5373f1378f | |||
5ac1d9048a | |||
25a53b44dc | |||
8db31c99df | |||
7c5f8b2431 | |||
1858580714 | |||
edb0445137 | |||
8f4333b528 | |||
4b8f54a31b | |||
3714925896 | |||
93f175da2c | |||
0f0fca0b9f | |||
e2d58906e3 | |||
e46a38ca2a | |||
a1f5d1dacc | |||
f749518bf7 | |||
f04d2b9225 | |||
d5021647d7 | |||
56f341e960 | |||
2729d5ed7a | |||
552ab4c80b | |||
5cc5c9943c | |||
02098bbb3d | |||
a76b1ca66d | |||
96b4ef08c6 | |||
ca06ec397f | |||
f2ffcfec5b | |||
49b04c9b19 | |||
a5a86a5e1b | |||
d63999c081 | |||
45ea9cc774 | |||
80f73a34a6 | |||
cde171d497 | |||
dd00620b9a | |||
5381eaae22 | |||
6745590e54 | |||
d300f9deea | |||
e8d34b4ea9 | |||
b981f91cf7 | |||
99db295310 | |||
b6f5eed67c | |||
d14a1e1288 | |||
8e21c30f92 | |||
6c1e1e98d8 | |||
c8f49e1837 | |||
845bb6ac90 | |||
2dd0e13eba | |||
cffc1d6563 | |||
ceec4a36a4 | |||
f1e119cb7a | |||
b69ffacff5 | |||
39e24c328a | |||
a6c77fe39e | |||
ce15160e87 | |||
c971287624 | |||
618b3d23d9 | |||
1798acc3c0 | |||
7320e4cbf9 | |||
06594e8dd0 | |||
8809db582c | |||
e0488dd87f | |||
14d93120b5 | |||
0812890d2f | |||
9a457fb011 | |||
c9d20748a4 | |||
5664625c1c | |||
fa16537290 | |||
9eb76703d6 | |||
3e7acab674 | |||
4643f00d9b | |||
6593616194 | |||
9ef6c21a2a | |||
d0f4721925 | |||
20b4aaabc9 | |||
02867e6e14 | |||
0e14a36c6d | |||
b0975672c1 | |||
ec7ca4ddf1 | |||
8e626dce78 | |||
779812c746 | |||
54f656d239 | |||
98e8546c9a | |||
759b26e203 | |||
b0a564f64f | |||
850b4e2a73 | |||
878cd84bc4 | |||
778a69cd | b507b2ddd3 | ||
6de84a115f | |||
778a69cd | 4e757b0f9e | ||
5a24982f12 | |||
a01e895d79 | |||
eecae5fe9d | |||
f5c2708066 | |||
ed93978784 | |||
863655117e | |||
johndoe4 | 1041cf7cd7 | ||
778a69cd | 88e11925df | ||
6c74ba9f8f | |||
f0b559d2ef | |||
08ce7e26b7 | |||
fe4fbc0bdf | |||
927e95f387 | |||
778a69cd | 0d804cba1f | ||
87208369b2 | |||
dc3b93ffb5 | |||
7fa452d9e3 | |||
94f186ce50 | |||
3e0324d36e | |||
2933ee0679 | |||
96129d2339 | |||
60185e80e3 | |||
778a69cd | 4284e8e635 | ||
67d0ee282d | |||
d7e425dce5 | |||
d47b69d6ca | |||
d5a6df9940 | |||
6da0dba0fd | |||
643a5b5921 | |||
6858bcbbda | |||
94182aed2d | |||
6bd8034fe8 | |||
a7c8cc0b9f | |||
d73bafec97 | |||
9249661225 | |||
67407dae62 | |||
3eddd5fc8f | |||
39da840da9 | |||
964e479aaf | |||
9fe2f658e2 | |||
0b3d7d5b3c | |||
c4d60194a6 | |||
7defbe6bc9 | |||
26e22edfc0 | |||
ba3a52cd4b | |||
3309850d72 | |||
johndoe4 | 08764fff72 | ||
johndoe4 | a50b9128fe | ||
174e3fdbf9 | |||
43eab9e680 | |||
34f853159e | |||
johndoe4 | be8a206b0d | ||
johndoe4 | 04e7a2e02d | ||
johndoe4 | 96871853d1 | ||
johndoe4 | 6bcff6d1ae | ||
778a69cd | 7127b0da28 | ||
778a69cd | cba53e2e9d | ||
778a69cd | 1fac112942 | ||
johndoe4 | c5ecaf5d7c | ||
4a8ea1738b | |||
ecd2be4f82 | |||
1bee1506db | |||
5563052795 | |||
c420bbccc9 | |||
1c1d0d47d7 | |||
73eb4603b1 | |||
274476ed4d | |||
8ea00e7c18 | |||
aa20f69911 | |||
b5f106b0a8 | |||
dbe2da79c3 | |||
79b48da222 | |||
d2ba732b8b | |||
d2b48563f6 | |||
f0cc5ffb8f | |||
8dcb76c30d | |||
1e1342de12 | |||
2a57340a82 | |||
3d21a06789 | |||
b586f59288 | |||
6faafd6393 | |||
c7b90cd60a | |||
a22a5e3cb9 | |||
1ea5342fca | |||
johndoe4 | 974c73e071 | ||
johndoe4 | 2072402e98 | ||
b423caf6c3 | |||
summersamara | 67765a645e | ||
summersamara | f887455d66 | ||
778a69cd | d52dc72f9a | ||
778a69cd | 0b73264392 | ||
778a69cd | cc89e9f98e | ||
778a69cd | 609512a9ed | ||
summersamara | 773ddd22a7 | ||
778a69cd | 8e00c78a11 | ||
778a69cd | d830597206 | ||
778a69cd | 438f1dde3e | ||
778a69cd | d2af20dd59 | ||
778a69cd | aa043d8793 | ||
778a69cd | 23378a378a | ||
778a69cd | 0824694c56 | ||
778a69cd | dcd097bf6d | ||
778a69cd | d53f7b93e9 | ||
778a69cd | 1b9aafa855 | ||
7b796a1d3a | |||
4bcf572c54 | |||
94a51fbcab | |||
d969c6648f | |||
8543204bd9 | |||
7def6739e1 | |||
a4e7ee37be | |||
4375438dc9 | |||
4bb062528f | |||
2c28312fc9 | |||
2043c98717 | |||
09d705a1d0 | |||
63c9ed62de | |||
35b07dceaa | |||
cfd10ea960 | |||
7872100af3 | |||
487f4069b1 | |||
96d397baf1 | |||
f3a443138a | |||
5ae2e2254a | |||
ddba7d6fc5 | |||
52c4337c4f | |||
23b5e5930c | |||
eda2761032 | |||
8141bb0acb | |||
6b560b9f04 | |||
bcf6fd893c | |||
3f5e39cd7a | |||
09874bb049 | |||
7753e4991d | |||
36a7f819a3 | |||
ada4805961 | |||
df7edc9fa9 | |||
07ea127780 | |||
5ce558b94a | |||
e1b4337a36 | |||
845e8aa754 | |||
43bcfe1a90 | |||
4a9121825f | |||
d7df0f5dea | |||
961cb21d14 | |||
9748b1b70b | |||
c19f82e346 | |||
ca5b379b28 | |||
53782bf6e6 | |||
609d4a3b29 | |||
ef1b0ed2dd | |||
3d29bfaf2e | |||
0a7504d332 | |||
4fcd44a1aa | |||
49579466f7 | |||
24b74b1d8f | |||
a948579858 | |||
3a9188e081 | |||
3b7dbcd71f | |||
85d643d0ec | |||
50ab531156 | |||
a73e5a085e | |||
1a14959594 | |||
5c91b16337 | |||
e0ee9c143b | |||
d881703386 | |||
96695524eb | |||
b18e8fd37c | |||
5a5adc8a8f | |||
b4ae4fce30 | |||
1a82cb95bc | |||
c4fd0e062a | |||
c07ba3a5d1 | |||
c4e9f88e85 | |||
986ae45f52 | |||
a28ce5e6b6 | |||
647efad982 | |||
59944603b7 | |||
8984bd7636 | |||
b6875f6a4b | |||
2ee329ff7b | |||
39768693c5 | |||
cfa1754ab5 | |||
61441efbf7 | |||
fbddee22f9 | |||
0b3ae6fcf1 | |||
b138e4aec4 | |||
abc0f294f8 | |||
c9d8bb10d9 | |||
53e01ddcae | |||
c9f68dc709 | |||
b42056b915 | |||
8e6dc30198 | |||
7546ced4a1 | |||
d140d799fa | |||
5c9a5eb0ac | |||
437f03315f | |||
b4bdedee02 | |||
ffda6bdc99 | |||
aee8e07c7c | |||
668869a837 | |||
f3ec2a8d90 | |||
b8a3d9bd26 | |||
c53f3f1dc9 | |||
ad2328bf53 | |||
67cef7891a | |||
c7da4a15d0 | |||
2219cdcd47 | |||
9aaaedb5df | |||
0cfe6c89d9 | |||
0ed4434d66 | |||
78969d66d0 | |||
317a3434b2 | |||
1db5c4ae2d | |||
5c43713d85 | |||
4cea61bd9f | |||
8eb949af60 | |||
f531c39b7e | |||
92d7de51d0 | |||
0775814e19 | |||
31fd99bd37 | |||
1a03c49fa4 | |||
8ce1f596cc | |||
41267bc9a6 | |||
dabcae7f98 | |||
f48e2c3e9c | |||
e42167d22e | |||
941d79bfa5 | |||
283581b97f | |||
483593194a | |||
e409d11f7b | |||
a7b9ef36ab | |||
823bdea2ff | |||
91d2fea73e | |||
a78cd16afe | |||
7e49a80df9 | |||
01bf7261cd | |||
ae05832cb5 | |||
402820bae0 | |||
0fa25ccc5b | |||
5ea7f07400 | |||
b0889da38b | |||
d435c35af2 | |||
4593aa6a1f | |||
67dcd7813e | |||
b9264b8da3 | |||
8ec1d5d31f | |||
1e8b7647ad | |||
81e26114ed | |||
1f8c16cb0b | |||
8cae6d30c0 |
|
@ -22,7 +22,7 @@
|
|||
# In the latter case `**/*.{ex,exs}` will be used.
|
||||
#
|
||||
included: ["lib/", "src/", "test/"],
|
||||
excluded: [~r"/_build/", ~r"/deps/", ~r"/js/"]
|
||||
excluded: [~r"/_build/", ~r"/deps/", ~r"/src/"]
|
||||
},
|
||||
#
|
||||
# If you create your own checks, you must specify the source files for
|
||||
|
@ -33,12 +33,15 @@
|
|||
# If you want to enforce a style guide and need a more traditional linting
|
||||
# experience, you can change `strict` to `true` below:
|
||||
#
|
||||
strict: false,
|
||||
strict: true,
|
||||
#
|
||||
# If you want to use uncolored output by default, you can change `color`
|
||||
# to `false` below:
|
||||
#
|
||||
color: true,
|
||||
plugins: [
|
||||
{CredoCodeClimate, []}
|
||||
],
|
||||
#
|
||||
# You can customize the parameters of any check by adding a second element
|
||||
# to the tuple.
|
||||
|
@ -157,6 +160,7 @@
|
|||
#
|
||||
{Credo.Check.Warning.LazyLogging, false},
|
||||
{Credo.Check.Refactor.MapInto, false},
|
||||
{Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, false}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,44 +1,46 @@
|
|||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/elixir-phoenix-postgres
|
||||
{
|
||||
"name": "Elixir, Phoenix, Node.js & PostgresSQL (Community)",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "elixir",
|
||||
"workspaceFolder": "/workspace",
|
||||
"name": "Elixir, Phoenix, Node.js & PostgresSQL (Community)",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "elixir",
|
||||
"workspaceFolder": "/workspace",
|
||||
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"sqltools.connections": [{
|
||||
"name": "Container database",
|
||||
"driver": "PostgreSQL",
|
||||
"previewLimit": 50,
|
||||
"server": "localhost",
|
||||
"port": 5432,
|
||||
"database": "postgres",
|
||||
"username": "postgres",
|
||||
"password": "postgres"
|
||||
}]
|
||||
},
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"sqltools.connections": [
|
||||
{
|
||||
"name": "Container database",
|
||||
"driver": "PostgreSQL",
|
||||
"previewLimit": 50,
|
||||
"server": "localhost",
|
||||
"port": 5432,
|
||||
"database": "postgres",
|
||||
"username": "postgres",
|
||||
"password": "postgres"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"jakebecker.elixir-ls",
|
||||
"mtxr.sqltools",
|
||||
"mtxr.sqltools-driver-pg"
|
||||
],
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"jakebecker.elixir-ls",
|
||||
"mtxr.sqltools",
|
||||
"mtxr.sqltools-driver-pg"
|
||||
],
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [4000, 4001, 5432],
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
"forwardPorts": [4000, 4001, 5432],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "mix deps.get",
|
||||
// "runArgs": ["--userns=keep-id", "--privileged"],
|
||||
// "containerUser": "vscode",
|
||||
// "containerEnv": {
|
||||
// "HOME": "/home/vscode",
|
||||
// },
|
||||
// "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,Z",
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "mix deps.get",
|
||||
// "runArgs": ["--userns=keep-id", "--privileged"],
|
||||
// "containerUser": "vscode",
|
||||
// "containerEnv": {
|
||||
// "HOME": "/home/vscode",
|
||||
// },
|
||||
// "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,Z",
|
||||
|
||||
// Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "vscode"
|
||||
// Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
_build
|
||||
CONTRIBUTING.md
|
||||
coverage
|
||||
demo
|
||||
deps
|
||||
doc
|
||||
docs
|
||||
docker-compose.yml
|
||||
Dockerfile
|
||||
.elixir_ls
|
||||
|
@ -15,5 +19,8 @@ Makefile
|
|||
README.md
|
||||
SECURITY.md
|
||||
ssh_match_hostname
|
||||
.js/package-lock.json
|
||||
js/node_modules
|
||||
package-lock.json
|
||||
node_modules
|
||||
playwright-report
|
||||
test
|
||||
tests
|
||||
|
|
|
@ -22,3 +22,6 @@ MOBILIZON_SMTP_PORT=25
|
|||
MOBILIZON_SMTP_USERNAME=noreply@mobilizon.lan
|
||||
MOBILIZON_SMTP_PASSWORD=password
|
||||
MOBILIZON_SMTP_SSL=false
|
||||
|
||||
# When using docker for development, VITE_HOST must be set to 0.0.0.0
|
||||
VITE_HOST=localhost
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
[
|
||||
import_deps: [:ecto, :ecto_sql, :phoenix],
|
||||
plugins: [Phoenix.LiveView.HTMLFormatter],
|
||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test,priv}/**/*.{ex,exs,heex}"]
|
||||
]
|
||||
|
|
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -46,6 +46,16 @@ release/
|
|||
.weblate
|
||||
docker/production/.env
|
||||
test-junit-report.xml
|
||||
js/junit.xml
|
||||
junit.xml
|
||||
.env
|
||||
demo/
|
||||
codeclimate.json
|
||||
|
||||
node_modules
|
||||
stats.html
|
||||
/coverage
|
||||
/playwright-report/
|
||||
.histoire
|
||||
|
||||
# Nix out-links
|
||||
result*
|
||||
|
|
203
.gitlab-ci.yml
203
.gitlab-ci.yml
|
@ -6,14 +6,12 @@ stages:
|
|||
- build-js
|
||||
- sentry
|
||||
- test
|
||||
- docker
|
||||
- package
|
||||
- build
|
||||
- upload
|
||||
- deploy
|
||||
|
||||
variables:
|
||||
MIX_ENV: "test"
|
||||
YARN_CACHE_FOLDER: "js/.yarn"
|
||||
# DB Variables for Postgres / Postgis
|
||||
POSTGRES_DB: mobilizon_test
|
||||
POSTGRES_USER: postgres
|
||||
|
@ -32,17 +30,14 @@ variables:
|
|||
EXPORT_FORMATS: "csv,ods,pdf"
|
||||
APP_VERSION: "${CI_COMMIT_REF_NAME}"
|
||||
APP_ASSET: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${ARCH}.tar.gz"
|
||||
CYPRESS_INSTALL_BINARY: 0
|
||||
|
||||
cache:
|
||||
key: "${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- ~/.cache/Cypress
|
||||
- cache/Cypress
|
||||
- deps/
|
||||
- _build/
|
||||
- js/node_modules
|
||||
- js/.yarn
|
||||
- node_modules
|
||||
- .npm
|
||||
|
||||
# Installed dependencies are cached across the pipeline
|
||||
# So there is no need to reinstall them all the time
|
||||
|
@ -50,7 +45,7 @@ cache:
|
|||
install:
|
||||
stage: install
|
||||
script:
|
||||
- yarn --cwd "js" install --frozen-lockfile
|
||||
- npm ci
|
||||
- mix deps.get
|
||||
- mix compile
|
||||
|
||||
|
@ -67,27 +62,30 @@ lint-elixir:
|
|||
- mix credo diff --from-git-merge-base $TARGET_SHA1 --strict -a || export EXITVALUE=1
|
||||
- mix sobelow --config || export EXITVALUE=1
|
||||
- exit $EXITVALUE
|
||||
artifacts:
|
||||
reports:
|
||||
codequality: codeclimate.json
|
||||
|
||||
lint-front:
|
||||
image: node:16
|
||||
image: node:20
|
||||
stage: check
|
||||
before_script:
|
||||
- export EXITVALUE=0
|
||||
- yarn --cwd "js" install --frozen-lockfile
|
||||
- npm ci
|
||||
script:
|
||||
- yarn --cwd "js" run lint || export EXITVALUE=1
|
||||
- yarn --cwd "js" run prettier -c . || export EXITVALUE=1
|
||||
- npm run lint || export EXITVALUE=1
|
||||
- npx prettier -c . || export EXITVALUE=1
|
||||
- exit $EXITVALUE
|
||||
|
||||
build-frontend:
|
||||
stage: build-js
|
||||
image: node:16
|
||||
image: node:20
|
||||
before_script:
|
||||
- apt update
|
||||
- apt install -y --no-install-recommends python build-essential webp imagemagick gifsicle jpegoptim optipng pngquant
|
||||
- apt install -y --no-install-recommends python3 build-essential webp imagemagick gifsicle jpegoptim optipng pngquant
|
||||
script:
|
||||
- yarn --cwd "js" install --frozen-lockfile
|
||||
- yarn --cwd "js" run build
|
||||
- npm install --frozen-lockfile
|
||||
- npm run build
|
||||
artifacts:
|
||||
expire_in: 5 days
|
||||
paths:
|
||||
|
@ -117,7 +115,7 @@ deps:
|
|||
script:
|
||||
- export EXITVALUE=0
|
||||
- mix hex.outdated || export EXITVALUE=1
|
||||
- yarn --cwd "js" outdated || export EXITVALUE=1
|
||||
- npm outdated || export EXITVALUE=1
|
||||
- exit $EXITVALUE
|
||||
allow_failure: true
|
||||
needs:
|
||||
|
@ -126,12 +124,14 @@ deps:
|
|||
exunit:
|
||||
stage: test
|
||||
services:
|
||||
- name: postgis/postgis:14-3.2
|
||||
- name: postgis/postgis:16-3.4
|
||||
alias: postgres
|
||||
variables:
|
||||
MIX_ENV: test
|
||||
before_script:
|
||||
- mix deps.get && mix tz_world.update
|
||||
- mix deps.get
|
||||
- mix compile
|
||||
- mix tz_world.update
|
||||
- mix ecto.create
|
||||
- mix ecto.migrate
|
||||
script:
|
||||
|
@ -148,22 +148,22 @@ vitest:
|
|||
needs:
|
||||
- lint-front
|
||||
before_script:
|
||||
- yarn --cwd "js" install --frozen-lockfile
|
||||
- npm install --frozen-lockfile
|
||||
script:
|
||||
- yarn --cwd "js" run coverage --reporter=default --reporter=junit --outputFile.junit=./junit.xml
|
||||
- npm run coverage --reporter=default --reporter=junit --outputFile.junit=./junit.xml
|
||||
artifacts:
|
||||
when: always
|
||||
paths:
|
||||
- js/coverage
|
||||
- coverage
|
||||
reports:
|
||||
junit:
|
||||
- js/junit.xml
|
||||
- junit.xml
|
||||
expire_in: 30 days
|
||||
|
||||
e2e:
|
||||
stage: test
|
||||
services:
|
||||
- name: postgis/postgis:14-3.2
|
||||
- name: postgis/postgis:16-3.4
|
||||
alias: postgres
|
||||
variables:
|
||||
MIX_ENV: "e2e"
|
||||
|
@ -172,31 +172,31 @@ e2e:
|
|||
- mix ecto.create
|
||||
- mix ecto.migrate
|
||||
- mix run priv/repo/e2e.seed.exs
|
||||
- cd js && yarn install && yarn run build && npx playwright install && cd ../
|
||||
- npm install && npm run build && npx playwright install
|
||||
- mix phx.digest
|
||||
script:
|
||||
- mix phx.server &
|
||||
- cd js
|
||||
- npx wait-on http://localhost:4000
|
||||
- npx playwright test --project $BROWSER
|
||||
parallel:
|
||||
matrix:
|
||||
- BROWSER: ['firefox', 'chromium']
|
||||
- BROWSER: ["firefox", "chromium"]
|
||||
artifacts:
|
||||
expire_in: 2 days
|
||||
paths:
|
||||
- js/playwright-report/
|
||||
- js/test-results/
|
||||
- playwright-report/
|
||||
- test-results/
|
||||
|
||||
pages:
|
||||
stage: deploy
|
||||
script:
|
||||
- mv public public-mbz
|
||||
- mkdir public
|
||||
- mix deps.get
|
||||
- mix docs
|
||||
- mv doc public/backend
|
||||
# #- yarn run --cwd "js" styleguide:build
|
||||
# #- mv js/styleguide public/frontend
|
||||
# #- npm run styleguide:build
|
||||
# #- mv styleguide public/frontend
|
||||
rules:
|
||||
- if: '$CI_COMMIT_BRANCH == "main"'
|
||||
artifacts:
|
||||
|
@ -205,22 +205,23 @@ pages:
|
|||
- public
|
||||
|
||||
.docker: &docker
|
||||
stage: docker
|
||||
image: docker:20.10.18
|
||||
stage: build
|
||||
image: docker:24
|
||||
variables:
|
||||
DOCKER_TLS_CERTDIR: "/certs"
|
||||
DOCKER_HOST: tcp://docker:2376
|
||||
DOCKER_TLS_VERIFY: 1
|
||||
DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
|
||||
DOCKER_DRIVER: overlay2
|
||||
DOCKER_CLI_EXPERIMENTAL: enabled
|
||||
services:
|
||||
- docker:20.10.18-dind
|
||||
- docker:24-dind
|
||||
cache: {}
|
||||
before_script:
|
||||
# Install buildx
|
||||
- wget https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.linux-amd64
|
||||
- wget https://github.com/docker/buildx/releases/download/v0.11.2/buildx-v0.11.2.linux-amd64
|
||||
- mkdir -p ~/.docker/cli-plugins/
|
||||
- mv buildx-v0.9.1.linux-amd64 ~/.docker/cli-plugins/docker-buildx
|
||||
- mv buildx-v0.11.2.linux-amd64 ~/.docker/cli-plugins/docker-buildx
|
||||
- chmod a+x ~/.docker/cli-plugins/docker-buildx
|
||||
# Create env
|
||||
- docker context create tls-environment
|
||||
|
@ -228,6 +229,8 @@ pages:
|
|||
# Install qemu/binfmt
|
||||
- docker pull tonistiigi/binfmt:latest
|
||||
- docker run --rm --privileged tonistiigi/binfmt:latest --install all
|
||||
# Install jq
|
||||
- apk --no-cache add jq
|
||||
# Login to DockerHub
|
||||
- mkdir -p ~/.docker
|
||||
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$CI_REGISTRY_AUTH\",\"email\":\"$CI_REGISTRY_EMAIL\"}}}" > ~/.docker/config.json
|
||||
|
@ -241,46 +244,70 @@ build-docker-main:
|
|||
when: never
|
||||
- if: '$CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_TRIGGERED == "true"'
|
||||
script:
|
||||
- docker buildx build --push --platform linux/amd64 -t framasoft/mobilizon:main -f docker/production/Dockerfile .
|
||||
- docker buildx build --platform linux/amd64 -t framasoft/mobilizon:main -f docker/production/Dockerfile .
|
||||
|
||||
# Don't push to latest when building beta/rc tags
|
||||
build-and-push-to-latest-docker-tag:
|
||||
build-docker-tag:
|
||||
<<: *docker
|
||||
rules: &release-tag-rules
|
||||
- if: '$CI_PROJECT_NAMESPACE != "framasoft"'
|
||||
when: never
|
||||
- if: $CI_COMMIT_TAG != null
|
||||
when: on_success
|
||||
timeout: 3 hours
|
||||
script:
|
||||
- >
|
||||
docker buildx build
|
||||
--push
|
||||
--platform linux/${ARCH}
|
||||
--provenance=false
|
||||
--build-arg="${ERL_FLAGS}"
|
||||
-t framasoft/mobilizon:${CI_COMMIT_TAG}-${ARCH}
|
||||
-f docker/production/Dockerfile .
|
||||
parallel:
|
||||
matrix:
|
||||
- ARCH: ["amd64"]
|
||||
ERL_FLAGS: ["ERL_FLAGS="]
|
||||
- ARCH: ["arm64"]
|
||||
ERL_FLAGS: ["ERL_FLAGS=+JMsingle true"]
|
||||
|
||||
# Create manifest and push
|
||||
docker-manifest-push:
|
||||
<<: *docker
|
||||
needs: ["build-docker-tag"]
|
||||
rules: &release-tag-rules
|
||||
- if: '$CI_PROJECT_NAMESPACE != "framasoft"'
|
||||
when: never
|
||||
- if: $CI_COMMIT_TAG != null
|
||||
when: on_success
|
||||
script:
|
||||
- >
|
||||
docker manifest create framasoft/mobilizon:${CI_COMMIT_TAG}
|
||||
--amend framasoft/mobilizon:${CI_COMMIT_TAG}-amd64
|
||||
--amend framasoft/mobilizon:${CI_COMMIT_TAG}-arm64
|
||||
- docker manifest push --purge framasoft/mobilizon:${CI_COMMIT_TAG}
|
||||
|
||||
###
|
||||
# Simply creating an alias to the tag doesn't work:
|
||||
# « xxx is a manifest list »
|
||||
# https://joonas.fi/2021/02/docker-multi-arch-image-tooling-buildx/
|
||||
###
|
||||
docker-latest:
|
||||
<<: *docker
|
||||
needs: ["docker-manifest-push"]
|
||||
rules: &release-tag-rules
|
||||
- if: '$CI_PROJECT_NAMESPACE != "framasoft"'
|
||||
when: never
|
||||
- if: $CI_COMMIT_TAG != null && $CI_COMMIT_TAG !~ /alpha|beta|rc/
|
||||
when: on_success
|
||||
timeout: 3 hours
|
||||
script:
|
||||
- >
|
||||
docker buildx build
|
||||
--push
|
||||
--platform linux/amd64,linux/arm
|
||||
-t framasoft/mobilizon:$CI_COMMIT_TAG
|
||||
-t framasoft/mobilizon:latest
|
||||
-f docker/production/Dockerfile .
|
||||
|
||||
build-and-push-docker-tag:
|
||||
<<: *docker
|
||||
rules: &pre-release-tag-rules
|
||||
- if: '$CI_PROJECT_NAMESPACE != "framasoft"'
|
||||
when: never
|
||||
- if: $CI_COMMIT_TAG =~ /alpha|beta|rc/
|
||||
when: on_success
|
||||
timeout: 3 hours
|
||||
script:
|
||||
- >
|
||||
docker buildx build
|
||||
--push
|
||||
--platform linux/amd64,linux/arm
|
||||
-t framasoft/mobilizon:$CI_COMMIT_TAG
|
||||
-f docker/production/Dockerfile .
|
||||
- echo docker manifest create framasoft/mobilizon:latest $(docker manifest inspect framasoft/mobilizon:$CI_COMMIT_TAG | jq '.manifests[] | .digest' | xargs -I {} echo framasoft/mobilizon@{})
|
||||
- docker manifest create framasoft/mobilizon:latest $(docker manifest inspect framasoft/mobilizon:$CI_COMMIT_TAG | jq -r '.manifests[] | .digest' | xargs -I {} echo framasoft/mobilizon@{})
|
||||
- docker manifest push --purge framasoft/mobilizon:latest
|
||||
|
||||
# Packaging app for amd64
|
||||
package-app:
|
||||
image: mobilizon/buildpack:1.14.1-erlang-25.1.1-debian-buster
|
||||
stage: package
|
||||
image: mobilizon/buildpack:1.15.7-erlang-26.1.2-${SYSTEM}
|
||||
stage: build
|
||||
variables: &release-variables
|
||||
MIX_ENV: "prod"
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
|
@ -304,9 +331,22 @@ package-app:
|
|||
expire_in: 2 days
|
||||
paths:
|
||||
- ${APP_ASSET}
|
||||
parallel:
|
||||
matrix:
|
||||
- SYSTEM:
|
||||
[
|
||||
"debian-bookworm",
|
||||
"debian-bullseye",
|
||||
"debian-buster",
|
||||
"ubuntu-jammy",
|
||||
"ubuntu-focal",
|
||||
"ubuntu-bionic",
|
||||
"fedora-38",
|
||||
"fedora-39",
|
||||
]
|
||||
|
||||
package-app-dev:
|
||||
stage: package
|
||||
stage: build
|
||||
variables: *release-variables
|
||||
script: *release-script
|
||||
except:
|
||||
|
@ -317,9 +357,9 @@ package-app-dev:
|
|||
- ${APP_ASSET}
|
||||
|
||||
# Packaging app for multi-arch
|
||||
multi-arch-release:
|
||||
stage: package
|
||||
image: docker:20.10.21
|
||||
package-multi-arch-release:
|
||||
stage: build
|
||||
image: docker:24
|
||||
variables:
|
||||
DOCKER_TLS_CERTDIR: "/certs"
|
||||
DOCKER_HOST: tcp://docker:2376
|
||||
|
@ -329,13 +369,13 @@ multi-arch-release:
|
|||
APP_ASSET: "${CI_PROJECT_NAME}_${CI_COMMIT_REF_NAME}_${ARCH}.tar.gz"
|
||||
OS: debian-buster
|
||||
services:
|
||||
- docker:20.10.21-dind
|
||||
- docker:24-dind
|
||||
cache: {}
|
||||
before_script:
|
||||
# Install buildx
|
||||
- wget https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.linux-amd64
|
||||
- wget https://github.com/docker/buildx/releases/download/v0.11.2/buildx-v0.11.2.linux-amd64
|
||||
- mkdir -p ~/.docker/cli-plugins/
|
||||
- mv buildx-v0.9.1.linux-amd64 ~/.docker/cli-plugins/docker-buildx
|
||||
- mv buildx-v0.11.2.linux-amd64 ~/.docker/cli-plugins/docker-buildx
|
||||
- chmod a+x ~/.docker/cli-plugins/docker-buildx
|
||||
# Create env
|
||||
- docker context create tls-environment
|
||||
|
@ -344,7 +384,7 @@ multi-arch-release:
|
|||
- docker pull tonistiigi/binfmt:latest
|
||||
- docker run --rm --privileged tonistiigi/binfmt:latest --install all
|
||||
script:
|
||||
- docker buildx build --platform linux/${ARCH} --output type=local,dest=releases --build-arg APP_ASSET=${APP_ASSET} -f docker/multiarch/Dockerfile .
|
||||
- docker buildx build --platform linux/${ARCH} --output type=local,dest=releases --build-arg="ERL_FLAGS=+JMsingle true" --build-arg APP_ASSET=${APP_ASSET} -f docker/multiarch/Dockerfile .
|
||||
- ls -alh releases/mobilizon/
|
||||
- du -sh releases/mobilizon/${APP_ASSET}
|
||||
- mv releases/mobilizon/${APP_ASSET} .
|
||||
|
@ -357,7 +397,20 @@ multi-arch-release:
|
|||
- erl_crash.dump # if there's a memory issue
|
||||
parallel:
|
||||
matrix:
|
||||
- ARCH: ["arm", "arm64"]
|
||||
- ARCH: ["arm64"]
|
||||
## Currently not used as the hexpm base images do not have support for other architectures than amd64
|
||||
# SYSTEM:
|
||||
# [
|
||||
# "debian-bookworm",
|
||||
# "debian-bullseye",
|
||||
# "ubuntu-jammy",
|
||||
# "ubuntu-focal",
|
||||
# "ubuntu-bionic",
|
||||
# "alpine-3.17.5",
|
||||
# "alpine-3.18.4",
|
||||
# "fedora-38",
|
||||
# "fedora-39",
|
||||
# ]
|
||||
rules:
|
||||
- if: '$CI_COMMIT_TAG != null || $CI_PIPELINE_SOURCE == "schedule" || $CI_PIPELINE_TRIGGERED == "true"'
|
||||
timeout: 3h
|
||||
|
|
3
.husky/pre-commit
Executable file
3
.husky/pre-commit
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
npm run pre-commit
|
4
.prettierignore
Normal file
4
.prettierignore
Normal file
|
@ -0,0 +1,4 @@
|
|||
src/i18n/*.json
|
||||
coverage/
|
||||
**/*.md
|
||||
test/fixtures
|
|
@ -1,16 +1,20 @@
|
|||
|
||||
02CE4963DFD1B0D6D5C567357CAFFE97
|
||||
155A1FB53DE39EC8EFCFD7FB94EA823D
|
||||
2262742E5C8944D5BF6698EC61F5DE50
|
||||
25BEE162A99754480967216281E9EF33
|
||||
2A6F71CD6F1246F0B152C2376E2E398A
|
||||
30552A09D485A6AA73401C1D54F63C21
|
||||
52900CE4EE3598F6F178A651FB256770
|
||||
6151F44368FC19F2394274F513C29151
|
||||
765526195D4C6D770EAF4DC944A8CBF4
|
||||
B2FF1A12F13B873507C85091688C1D6D
|
||||
B9AF8A342CD7FF39E10CC10A408C28E1
|
||||
C042E87389F7BDCFF4E076E95731AE69
|
||||
C42BFAEF7100F57BED75998B217C857A
|
||||
D11958E86F1B6D37EF656B63405CA8A4
|
||||
F16F054F2628609A726B9FF2F089D484
|
||||
1C29EE70E90ECED01AF28EC58D2575B5
|
||||
26ED12A8E03D044BEDC08749BAA5E357
|
||||
2BB1D36656B423758A470021718FCB09
|
||||
31CE26BC979C57B9E3CC97B40C290CE5
|
||||
3529E7A4CECC24D02678820E6F521162
|
||||
3644C4E850300482AA409471EFE1EFB3
|
||||
4E7C044C59E0BCB76AA826789998F624
|
||||
53CBBEB6243FAF5C37249CBA17DE6F4C
|
||||
5BCE3651A03711295046DE48BDFE007E
|
||||
5C4CED447689F00D9D1ACEB9B895ED29
|
||||
94ACF7B17C3FF42F64E57DD1DA936BD8
|
||||
A32E125003F1EDFAD95C487C6A969725
|
||||
ACF6272A1DBB3A2ABD96C0C120B5CA69
|
||||
C46C4893B2F702ACADC4CAA5683FE370
|
||||
CDF2CCE0CF10F49CDFAE22FE26208155
|
||||
E720CB13C50FF3ADEE7C522531E11217
|
||||
F3D5851D3FB050939841ED2F14307A27
|
||||
FD1C9756370A195B74E95CE504C45E9E
|
|
@ -1,2 +1,2 @@
|
|||
elixir 1.14.1-otp-25
|
||||
erlang 25.1.1
|
||||
elixir 1.15.5-otp-26
|
||||
erlang 26.0.2
|
||||
|
|
616
CHANGELOG.md
616
CHANGELOG.md
|
@ -5,13 +5,623 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 4.0.2 (2023-12-07)
|
||||
|
||||
## 3.0.4 - 2023-03-20
|
||||
### Security issues
|
||||
|
||||
This release fixes different security issues reported by the potsda.mn collective. Please make sure to upgrade as soon as possible.
|
||||
|
||||
### Fixed
|
||||
|
||||
- LDAP connector with Erlang 24.3+
|
||||
- Participant export configuration not being loaded
|
||||
- Fixes XSS issues in notifier and participant and event contacts list formatting
|
||||
|
||||
|
||||
* fix(front-end): add more security fixes for formatted lists and notifier ([1af8e37](https://framagit.org/framasoft/mobilizon/commits/1af8e37))
|
||||
|
||||
|
||||
## 4.0.1 (2023-12-07)
|
||||
|
||||
### Security issues
|
||||
|
||||
This release fixes different security issues reported by the potsda.mn collective. Please make sure to upgrade as soon as possible.
|
||||
|
||||
### Added
|
||||
|
||||
- Added a CLI task to test if emails configuration works properly
|
||||
|
||||
### Fixed
|
||||
- Fixes XSS issues in groups descriptions, report contents, messages from anonymous participations and resources descriptions
|
||||
- Fixes Docker configuration that prevented the image to launch
|
||||
|
||||
### Changed
|
||||
|
||||
- Added back Debian Buster builds
|
||||
|
||||
### Complete changelog
|
||||
|
||||
* build(packages): add back Debian Buster as it seems people are still using it ([795ef24](https://framagit.org/framasoft/mobilizon/commits/795ef24))
|
||||
* build(packages): remove alpine packages as there's no demand for it ([0caaf2b](https://framagit.org/framasoft/mobilizon/commits/0caaf2b))
|
||||
* Translated using Weblate (Croatian) ([9c88fae](https://framagit.org/framasoft/mobilizon/commits/9c88fae))
|
||||
* Translated using Weblate (Croatian) ([623f4ee](https://framagit.org/framasoft/mobilizon/commits/623f4ee))
|
||||
* Translated using Weblate (Croatian) ([1162dd0](https://framagit.org/framasoft/mobilizon/commits/1162dd0))
|
||||
* Translated using Weblate (Galician) ([97c53bb](https://framagit.org/framasoft/mobilizon/commits/97c53bb))
|
||||
* Translated using Weblate (Galician) ([e08b057](https://framagit.org/framasoft/mobilizon/commits/e08b057))
|
||||
* Translated using Weblate (Galician) ([ec5e436](https://framagit.org/framasoft/mobilizon/commits/ec5e436))
|
||||
* Translated using Weblate (Korean) ([1a1ad52](https://framagit.org/framasoft/mobilizon/commits/1a1ad52))
|
||||
* Translated using Weblate (Korean) ([7b4c31d](https://framagit.org/framasoft/mobilizon/commits/7b4c31d))
|
||||
* fix: always consider report content as text ([ffff379](https://framagit.org/framasoft/mobilizon/commits/ffff379))
|
||||
* fix: sanitize descriptions from resources ([dc6647f](https://framagit.org/framasoft/mobilizon/commits/dc6647f))
|
||||
* fix(config): fix setting path for Mobilizon.Service.SiteMap ([7d725bd](https://framagit.org/framasoft/mobilizon/commits/7d725bd))
|
||||
* fix(docker): fix getting configuration value from env MOBILIZON_SMTP_TLS ([28063bd](https://framagit.org/framasoft/mobilizon/commits/28063bd)), closes [#1381](https://framagit.org/framasoft/mobilizon/issues/1381)
|
||||
* fix(docker): fix getting default value for MOBILIZON_SMTP_SSL env ([126727b](https://framagit.org/framasoft/mobilizon/commits/126727b))
|
||||
* fix(docker): use separate env for tzdata dir path ([9907f88](https://framagit.org/framasoft/mobilizon/commits/9907f88))
|
||||
* fix(emails): use tls_certificate_check to add tls config for mailer ([db38550](https://framagit.org/framasoft/mobilizon/commits/db38550))
|
||||
* fix(front): anonymous participant text is plain text, avoid using v-html ([2c12fbf](https://framagit.org/framasoft/mobilizon/commits/2c12fbf))
|
||||
* fix(front): fix editing group ([935799f](https://framagit.org/framasoft/mobilizon/commits/935799f))
|
||||
* fix(front): fix XSS because of bad operations when setting the group's summary ([ded59be](https://framagit.org/framasoft/mobilizon/commits/ded59be))
|
||||
* fix(front): put correct value for CONVERSATION_LIST enum value ([94bf2e5](https://framagit.org/framasoft/mobilizon/commits/94bf2e5))
|
||||
* fix(graphql): set default value for resource type parameter ([09f4132](https://framagit.org/framasoft/mobilizon/commits/09f4132))
|
||||
* feat(cli): add command to test emails send correctly ([7210f86](https://framagit.org/framasoft/mobilizon/commits/7210f86))
|
||||
* feat(docker): allow to configure loglevel at runtime through env variable ([4855af8](https://framagit.org/framasoft/mobilizon/commits/4855af8))
|
||||
* test: add new tests for XSS in actors summary ([58e50e3](https://framagit.org/framasoft/mobilizon/commits/58e50e3))
|
||||
* style: linting front-end ([41227d9](https://framagit.org/framasoft/mobilizon/commits/41227d9))
|
||||
* refactor(activitypub): handle failure finding public key in actor keys ([5b337f9](https://framagit.org/framasoft/mobilizon/commits/5b337f9))
|
||||
|
||||
|
||||
## 4.0.0 (2023-12-05)
|
||||
|
||||
### Breaking changes
|
||||
|
||||
#### Release (binary package) installations
|
||||
|
||||
- We now produce packages for different distributions targets (Debian Bookworm, Debian Bullseye, Ubuntu Jammy, Ubuntu Focal, Ubuntu Bionic, Fedora 38 and Fedora 39). Be sure to pick the right one for your system, as there can be issues with OpenSSL versions differing from inside the Mobilizon package and on your system.
|
||||
- The `https://joinmobilizon.org/latest-package` URL now links to the latest package builded against Debian Bookworm. Make sure to follow the documentation if you're not using this.
|
||||
- There's also an `arm64` package build on Debian Bullseye for now.
|
||||
|
||||
#### Source installations
|
||||
- Elixir 15 is now required
|
||||
- The content of the `js` directory is now at the root of the repository, so you don't need to `cd js` anymore
|
||||
- No need for `yarn` anymore, simply use `npm` instead for `npm i` and `npm run build`
|
||||
|
||||
### New features
|
||||
|
||||
- Event organizers and groups can be contacted through private messages (including PMs from 3rd-party micro-blogging fediverse services)
|
||||
- Event organizers can send private announcements to event participants (approved or not)
|
||||
|
||||
|
||||
### Improvements
|
||||
- Anonymous participation e-mails now contain links to cancel your participation
|
||||
- ActivityPub improvements for compatibility with https://event-federation.eu
|
||||
- ICS export fixes for descriptions and adding event status
|
||||
|
||||
### Changes since 4.0.0-rc.1
|
||||
|
||||
* refactor: to lower cyclomatic complexity ([147096c](https://framagit.org/framasoft/mobilizon/commits/147096c))
|
||||
* fix(activitypub): compact ical:status in activitystream data ([5e8f9af](https://framagit.org/framasoft/mobilizon/commits/5e8f9af)), closes [#1378](https://framagit.org/framasoft/mobilizon/issues/1378)
|
||||
* fix(activitypub): fix receiving comments ([f1084c1](https://framagit.org/framasoft/mobilizon/commits/f1084c1))
|
||||
* fix(backend): handle ecto errors when fetching and create entities ([89d1ee4](https://framagit.org/framasoft/mobilizon/commits/89d1ee4))
|
||||
* fix(front): fix tag loading ([f81472e](https://framagit.org/framasoft/mobilizon/commits/f81472e))
|
||||
* fix(front): make recipient field placeholder translatable ([10ce812](https://framagit.org/framasoft/mobilizon/commits/10ce812))
|
||||
* fix(front): only show participants & announcements menu items to organizers ([c4d2ec6](https://framagit.org/framasoft/mobilizon/commits/c4d2ec6))
|
||||
* Translated using Weblate (Croatian) ([a26ff98](https://framagit.org/framasoft/mobilizon/commits/a26ff98))
|
||||
* Translated using Weblate (Croatian) ([1683f01](https://framagit.org/framasoft/mobilizon/commits/1683f01))
|
||||
* Translated using Weblate (Croatian) ([aa7f870](https://framagit.org/framasoft/mobilizon/commits/aa7f870))
|
||||
* Translated using Weblate (Croatian) ([1ce34ea](https://framagit.org/framasoft/mobilizon/commits/1ce34ea))
|
||||
* Translated using Weblate (Croatian) ([5e7edc0](https://framagit.org/framasoft/mobilizon/commits/5e7edc0))
|
||||
* Translated using Weblate (Croatian) ([d777d88](https://framagit.org/framasoft/mobilizon/commits/d777d88))
|
||||
* Translated using Weblate (Croatian) ([0118d97](https://framagit.org/framasoft/mobilizon/commits/0118d97))
|
||||
* Translated using Weblate (Croatian) ([805e931](https://framagit.org/framasoft/mobilizon/commits/805e931))
|
||||
|
||||
## 4.0.0-rc.1 (2023-12-04)
|
||||
|
||||
* fix: prevent sending group physical address if it's empty and allow empty text for timezone ([32caebb](https://framagit.org/framasoft/mobilizon/commits/32caebb)), closes [#1357](https://framagit.org/framasoft/mobilizon/issues/1357)
|
||||
* fix(activitypub): add missing externalParticipationUrl context ([8795576](https://framagit.org/framasoft/mobilizon/commits/8795576)), closes [#1376](https://framagit.org/framasoft/mobilizon/issues/1376)
|
||||
* fix(backend): only send suspension notification emails when actor's suspended and not just deleted ([9e41bc1](https://framagit.org/framasoft/mobilizon/commits/9e41bc1))
|
||||
* docs(nginx): improve nginx configuration ([6c992ca](https://framagit.org/framasoft/mobilizon/commits/6c992ca))
|
||||
|
||||
## 4.0.0-beta.2 (2023-12-01)
|
||||
|
||||
* test: fix tests using verified routes ([5fcf3d5](https://framagit.org/framasoft/mobilizon/commits/5fcf3d5))
|
||||
* feat: add links to cancel anonymous participations in emails ([9e6b232](https://framagit.org/framasoft/mobilizon/commits/9e6b232))
|
||||
* feat(background): add a job to refresh participant stats ([11e42d6](https://framagit.org/framasoft/mobilizon/commits/11e42d6))
|
||||
* feat(front): add dedicated page and route for event announcements ([d831dff](https://framagit.org/framasoft/mobilizon/commits/d831dff))
|
||||
* chore(i18n): update backend translations ([6df16ef](https://framagit.org/framasoft/mobilizon/commits/6df16ef))
|
||||
* fix: fix creating participant stats ([3f2a88f](https://framagit.org/framasoft/mobilizon/commits/3f2a88f))
|
||||
* refactor: use Phoenix verified routes ([b315e1d](https://framagit.org/framasoft/mobilizon/commits/b315e1d))
|
||||
|
||||
## 4.0.0-beta.1 (2023-11-30)
|
||||
|
||||
* fix: add a final fallback if we have default_language: nil in instance config ([cd53062](https://framagit.org/framasoft/mobilizon/commits/cd53062))
|
||||
* fix: build pictures at correct location and fix Plug.Static ([3c288c5](https://framagit.org/framasoft/mobilizon/commits/3c288c5))
|
||||
* fix: don't show passed/finished events in related events section ([69e4a5c](https://framagit.org/framasoft/mobilizon/commits/69e4a5c))
|
||||
* fix: fix Dockerfile copying assets path ([16cd377](https://framagit.org/framasoft/mobilizon/commits/16cd377))
|
||||
* fix: normalize suggested username ([4960387](https://framagit.org/framasoft/mobilizon/commits/4960387))
|
||||
* fix: set correct watcher config for E2E tests ([f47889b](https://framagit.org/framasoft/mobilizon/commits/f47889b))
|
||||
* fix: various fixes ([b635937](https://framagit.org/framasoft/mobilizon/commits/b635937))
|
||||
* fix(announcements): load group announcements ([7ef85fe](https://framagit.org/framasoft/mobilizon/commits/7ef85fe))
|
||||
* fix(api): allow localhost as a valid uri host for applications ([49b070d](https://framagit.org/framasoft/mobilizon/commits/49b070d))
|
||||
* fix(api): fix allowing posting event private announcement ([1831495](https://framagit.org/framasoft/mobilizon/commits/1831495))
|
||||
* fix(docker): add sitemap folder ([bd38449](https://framagit.org/framasoft/mobilizon/commits/bd38449))
|
||||
* fix(docker): allow to configure SMTP TLS ([2ecdf05](https://framagit.org/framasoft/mobilizon/commits/2ecdf05))
|
||||
* fix(docker): convert smtp tls sni to char list ([b3be7c6](https://framagit.org/framasoft/mobilizon/commits/b3be7c6))
|
||||
* fix(export): fix iCalendar export description HTML conversion ([d7daafc](https://framagit.org/framasoft/mobilizon/commits/d7daafc)), closes [#888](https://framagit.org/framasoft/mobilizon/issues/888)
|
||||
* fix(front): hide all categories card if we don't have even one ([5e86ef1](https://framagit.org/framasoft/mobilizon/commits/5e86ef1))
|
||||
* fix(histoire): fix URL to Framapiaf avatars ([0613f7f](https://framagit.org/framasoft/mobilizon/commits/0613f7f))
|
||||
* fix(i18n): fix typos in translation sources ([2ecd55d](https://framagit.org/framasoft/mobilizon/commits/2ecd55d))
|
||||
* fix(i18n): update spanish translations ([cfebc35](https://framagit.org/framasoft/mobilizon/commits/cfebc35))
|
||||
* add simplified Chinese mapping ([02af9a4](https://framagit.org/framasoft/mobilizon/commits/02af9a4))
|
||||
* Added translation using Weblate (Korean) ([a11fab6](https://framagit.org/framasoft/mobilizon/commits/a11fab6))
|
||||
* Added translation using Weblate (Korean) ([c529a83](https://framagit.org/framasoft/mobilizon/commits/c529a83))
|
||||
* Added translation using Weblate (Tatar) ([cefdaf8](https://framagit.org/framasoft/mobilizon/commits/cefdaf8))
|
||||
* Fix docker development: ([9705978](https://framagit.org/framasoft/mobilizon/commits/9705978))
|
||||
* fix fullAddressAutocomplete component not loading results ([83da88c](https://framagit.org/framasoft/mobilizon/commits/83da88c))
|
||||
* Fix typo in ctl help text ([495d163](https://framagit.org/framasoft/mobilizon/commits/495d163))
|
||||
* Fix typos ([66e89b9](https://framagit.org/framasoft/mobilizon/commits/66e89b9))
|
||||
* introduce VITE_HOST env var and pass it to the node watcher vite --host ([bfb7e3c](https://framagit.org/framasoft/mobilizon/commits/bfb7e3c))
|
||||
* remove unnecessary function ([8a1b122](https://framagit.org/framasoft/mobilizon/commits/8a1b122))
|
||||
* resolve result promise in a shorter way ([f81804d](https://framagit.org/framasoft/mobilizon/commits/f81804d))
|
||||
* Translated using Weblate (Croatian) ([e510e09](https://framagit.org/framasoft/mobilizon/commits/e510e09))
|
||||
* Translated using Weblate (Czech) ([d702ca2](https://framagit.org/framasoft/mobilizon/commits/d702ca2))
|
||||
* Translated using Weblate (Czech) ([9224f89](https://framagit.org/framasoft/mobilizon/commits/9224f89))
|
||||
* Translated using Weblate (Czech) ([c14dffb](https://framagit.org/framasoft/mobilizon/commits/c14dffb))
|
||||
* Translated using Weblate (Czech) ([a7d70d5](https://framagit.org/framasoft/mobilizon/commits/a7d70d5))
|
||||
* Translated using Weblate (French) ([c7ba003](https://framagit.org/framasoft/mobilizon/commits/c7ba003))
|
||||
* Translated using Weblate (German) ([7732f87](https://framagit.org/framasoft/mobilizon/commits/7732f87))
|
||||
* Translated using Weblate (Indonesian) ([d065193](https://framagit.org/framasoft/mobilizon/commits/d065193))
|
||||
* Translated using Weblate (Italian) ([b5e9f62](https://framagit.org/framasoft/mobilizon/commits/b5e9f62))
|
||||
* Translated using Weblate (Italian) ([e8e1a62](https://framagit.org/framasoft/mobilizon/commits/e8e1a62))
|
||||
* Translated using Weblate (Italian) ([84fc175](https://framagit.org/framasoft/mobilizon/commits/84fc175))
|
||||
* Translated using Weblate (Italian) ([5b64388](https://framagit.org/framasoft/mobilizon/commits/5b64388))
|
||||
* Translated using Weblate (Italian) ([5e3dedb](https://framagit.org/framasoft/mobilizon/commits/5e3dedb))
|
||||
* Translated using Weblate (Italian) ([afe4dd2](https://framagit.org/framasoft/mobilizon/commits/afe4dd2))
|
||||
* Translated using Weblate (Italian) ([fa0ae83](https://framagit.org/framasoft/mobilizon/commits/fa0ae83))
|
||||
* Translated using Weblate (Italian) ([181a5a7](https://framagit.org/framasoft/mobilizon/commits/181a5a7))
|
||||
* Translated using Weblate (Italian) ([827caa3](https://framagit.org/framasoft/mobilizon/commits/827caa3))
|
||||
* Translated using Weblate (Italian) ([d08d350](https://framagit.org/framasoft/mobilizon/commits/d08d350))
|
||||
* Translated using Weblate (Italian) ([e9d38c2](https://framagit.org/framasoft/mobilizon/commits/e9d38c2))
|
||||
* Translated using Weblate (Italian) ([a4578f3](https://framagit.org/framasoft/mobilizon/commits/a4578f3))
|
||||
* Translated using Weblate (Polish) ([d62c31e](https://framagit.org/framasoft/mobilizon/commits/d62c31e))
|
||||
* Translated using Weblate (Polish) ([a8ea217](https://framagit.org/framasoft/mobilizon/commits/a8ea217))
|
||||
* Translated using Weblate (Polish) ([42537af](https://framagit.org/framasoft/mobilizon/commits/42537af))
|
||||
* Translated using Weblate (Polish) ([fb0a74e](https://framagit.org/framasoft/mobilizon/commits/fb0a74e))
|
||||
* Translated using Weblate (Polish) ([2458076](https://framagit.org/framasoft/mobilizon/commits/2458076))
|
||||
* Translated using Weblate (Polish) ([46ffc8c](https://framagit.org/framasoft/mobilizon/commits/46ffc8c))
|
||||
* Translated using Weblate (Polish) ([f0d7807](https://framagit.org/framasoft/mobilizon/commits/f0d7807))
|
||||
* Translated using Weblate (Portuguese (Brazil)) ([9f78c73](https://framagit.org/framasoft/mobilizon/commits/9f78c73))
|
||||
* Translated using Weblate (Portuguese (Brazil)) ([802ab78](https://framagit.org/framasoft/mobilizon/commits/802ab78))
|
||||
* Translated using Weblate (Spanish) ([ee5ee8d](https://framagit.org/framasoft/mobilizon/commits/ee5ee8d))
|
||||
* Translated using Weblate (Spanish) ([66c49e4](https://framagit.org/framasoft/mobilizon/commits/66c49e4))
|
||||
* Translated using Weblate (Tatar) ([ba5f8f8](https://framagit.org/framasoft/mobilizon/commits/ba5f8f8))
|
||||
* Update translation files ([9aa9cd2](https://framagit.org/framasoft/mobilizon/commits/9aa9cd2))
|
||||
* WIP ([b5672ce](https://framagit.org/framasoft/mobilizon/commits/b5672ce))
|
||||
* build: downgrade Sentry since it doesn't want to compile ([b2bacbf](https://framagit.org/framasoft/mobilizon/commits/b2bacbf))
|
||||
* build: only run ecto create & migrate & tz_world update on prepare_test task, not main test one ([8d11073](https://framagit.org/framasoft/mobilizon/commits/8d11073))
|
||||
* build: replace @pluralsh/socket with @framasoft/socket ([435bd9d](https://framagit.org/framasoft/mobilizon/commits/435bd9d))
|
||||
* build: replace @vueuse/head with @unhead/vue ([5602164](https://framagit.org/framasoft/mobilizon/commits/5602164))
|
||||
* build: switch from yarn to npm to manage js dependencies and move js contents to root ([2e72f6f](https://framagit.org/framasoft/mobilizon/commits/2e72f6f))
|
||||
* build(deps): replace absinthe socket library with fork ([ec397aa](https://framagit.org/framasoft/mobilizon/commits/ec397aa))
|
||||
* build(docker): optimize image size ([f34099d](https://framagit.org/framasoft/mobilizon/commits/f34099d)), closes [#1012](https://framagit.org/framasoft/mobilizon/issues/1012)
|
||||
* ci: bump node version in CI ([3205512](https://framagit.org/framasoft/mobilizon/commits/3205512))
|
||||
* ci: fix handling pages deploy with existing public folder ([1228ec1](https://framagit.org/framasoft/mobilizon/commits/1228ec1))
|
||||
* ci: install python3 instead of python ([5d65981](https://framagit.org/framasoft/mobilizon/commits/5d65981))
|
||||
* ci: Release on multiple distributions & fix Docker multiple-step build ([262d1fc](https://framagit.org/framasoft/mobilizon/commits/262d1fc))
|
||||
* test: fix ActivityPub headers test ([f248660](https://framagit.org/framasoft/mobilizon/commits/f248660))
|
||||
* test: fix front-end tests ([105d3b5](https://framagit.org/framasoft/mobilizon/commits/105d3b5))
|
||||
* test: fix histoire configuration ([bfbc299](https://framagit.org/framasoft/mobilizon/commits/bfbc299))
|
||||
* test: fix tests ([c731f0f](https://framagit.org/framasoft/mobilizon/commits/c731f0f))
|
||||
* test: fix unit backend tests ([e051df1](https://framagit.org/framasoft/mobilizon/commits/e051df1))
|
||||
* chore: fix prettier configuration and run it ([c255cea](https://framagit.org/framasoft/mobilizon/commits/c255cea))
|
||||
* chore: update Sobelow security ignores ([1d0398d](https://framagit.org/framasoft/mobilizon/commits/1d0398d))
|
||||
* chore: upgrade deps ([99c80c6](https://framagit.org/framasoft/mobilizon/commits/99c80c6))
|
||||
* chore(deps): update geo_postgis to 3.5.0 for Elixir 1.15 compat ([3936eb4](https://framagit.org/framasoft/mobilizon/commits/3936eb4))
|
||||
* chore(deps): upgrade dependencies ([3d9beaa](https://framagit.org/framasoft/mobilizon/commits/3d9beaa))
|
||||
* chore(i18n): add missing translation key ([6ecfa48](https://framagit.org/framasoft/mobilizon/commits/6ecfa48))
|
||||
* chore(i18n): update gettext dependency and regenerate translation files ([d7ad934](https://framagit.org/framasoft/mobilizon/commits/d7ad934))
|
||||
* chore(i18n): update translation templates ([70e9ce0](https://framagit.org/framasoft/mobilizon/commits/70e9ce0))
|
||||
* refactor: use dedicated email for event announcements ([b97f1c9](https://framagit.org/framasoft/mobilizon/commits/b97f1c9))
|
||||
* docs(dev.md): keep some info about structure ([d130b15](https://framagit.org/framasoft/mobilizon/commits/d130b15))
|
||||
* feat(export): add event status in iCalendar exports ([7a1bfca](https://framagit.org/framasoft/mobilizon/commits/7a1bfca))
|
||||
* feat(federation): expose public activities as announcements in relay outbx & rfrsh profile aftr fllw ([85e4715](https://framagit.org/framasoft/mobilizon/commits/85e4715))
|
||||
|
||||
|
||||
## 3.2.0 (2023-09-07)
|
||||
|
||||
### Features
|
||||
|
||||
* **cli:** allow the mobilizon.users.delete command to delete multiple users by email domain or ip ([bc50ab6](https://framagit.org/framasoft/mobilizon/commit/bc50ab66f3a44df220a7daa3cb1d917bd02487ba))
|
||||
* **export:** add date of participant creation in participant exports ([fef60ed](https://framagit.org/framasoft/mobilizon/commit/fef60ed0f92fc4e09ee261ff03f1139aff2449c3)), closes [#1343](https://framagit.org/framasoft/mobilizon/issues/1343)
|
||||
* **notifications:** add missing notifications when an user registers to an event ([da532c7](https://framagit.org/framasoft/mobilizon/commit/da532c7059bea5fcd47e2f42210e8ba842a11d63)), closes [#1344](https://framagit.org/framasoft/mobilizon/issues/1344)
|
||||
* **reports:** allow reports to hold multiple events ([f2ac3e2](https://framagit.org/framasoft/mobilizon/commit/f2ac3e2e5d28f4257a5e2d4870d339fecf3a5f1b))
|
||||
* **reports:** allow to suspend a profile or a user account directly from the report view ([69588db](https://framagit.org/framasoft/mobilizon/commit/69588dbf4ce2f80cc5829a841135042fa73eb4fe))
|
||||
* **reports:** improve reportview and allow removing content + resolve report automatically ([b105c50](https://framagit.org/framasoft/mobilizon/commit/b105c508c03ce3cb96dd8342f96d3291aa197e22))
|
||||
* **reports:** show suspended status next to reported profile ([b9a165a](https://framagit.org/framasoft/mobilizon/commit/b9a165a7fc565dc583cca81dd9c54570f73b4ca3))
|
||||
* Add option to link an external registration provider for events ([2de6937](https://framagit.org/framasoft/mobilizon/commit/2de6937407743100daba1d397db4da32d4cb606b))
|
||||
* **back:** add admin setting to disable external event feature ([f6611e8](https://framagit.org/framasoft/mobilizon/commit/f6611e8eb5a7e12dc0dc0c216b598e04144e07c6))
|
||||
* improve group creation view [3f601748](https://framagit.org/framasoft/mobilizon/-/commit/3f60174877bbe05773b1d1b2ceb91749adec7ed7)
|
||||
* **auth:** pre-initialize registration fields with information from 3rd-party provider ([7e49345](https://framagit.org/framasoft/mobilizon/commit/7e4934513a0ca4a5f95e8c8e4a600459911899d5)), closes [#1105](https://framagit.org/framasoft/mobilizon/issues/1105)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add inets and ssl to extra_applications in test env ([af46bea](https://framagit.org/framasoft/mobilizon/commit/af46bea7f730f4479bb31518a9fa53de7302049a))
|
||||
* **apps:** add missing app scopes ([7e98097](https://framagit.org/framasoft/mobilizon/commit/7e98097c710663609274200564fca9eff1ea4d20))
|
||||
* **apps:** make sure we can set status for an application token ([1a6095d](https://framagit.org/framasoft/mobilizon/commit/1a6095d27aeb440379d27c3894c302f831214822))
|
||||
* **backend:** fix config cache not being used everytime ([ed3cd58](https://framagit.org/framasoft/mobilizon/commit/ed3cd5858cd27a90d4724a95ee660bbc08e92e80))
|
||||
* **backend:** handle email not being sent when resending registration instructions ([b2492a3](https://framagit.org/framasoft/mobilizon/commit/b2492a387086528598da36f11e53569c5bdb164c))
|
||||
* create event time/date allignment ([3de90a3](https://framagit.org/framasoft/mobilizon/commit/3de90a3c73414105becdcb24899016178b1c6f02))
|
||||
* **docker:** fix Qemu segfaulting on arm64 ([8e3f90f](https://framagit.org/framasoft/mobilizon/commit/8e3f90f7135e2a8a8ac46464420c9d57b2e02534)), closes [#1241](https://framagit.org/framasoft/mobilizon/issues/1241) [#1249](https://framagit.org/framasoft/mobilizon/issues/1249)
|
||||
* **federation:** fix getting pictures from Gruppe actors ([7c5f8b2](https://framagit.org/framasoft/mobilizon/commit/7c5f8b24311253ef89c7e47cd7ce22ebe6cf2ec9))
|
||||
* fix Elixir 1.15 depreciations ([da70427](https://framagit.org/framasoft/mobilizon/commit/da70427e3292be8943167bbad73d5a782a98c6b5))
|
||||
* fix some typescript issues with pwa ([e351d3c](https://framagit.org/framasoft/mobilizon/commit/e351d3cb2f8183bb4335b3b21e154f46d9237a76))
|
||||
* **front:** avoid crashing if we don't have configuration data in time when in guard ([7916261](https://framagit.org/framasoft/mobilizon/commit/7916261c5c8c680d064fba106619d733575bc39c))
|
||||
* **front:** fix alignment of some input elements on event edition form ([50695fc](https://framagit.org/framasoft/mobilizon/commit/50695fcfd5e0dc6fd55185f4399d45ed1852f880))
|
||||
* **front:** fix changing language not being saved to the user's settings ([010a5e4](https://framagit.org/framasoft/mobilizon/commit/010a5e426def0a0b7f2658234f3c9d6eec46a68e))
|
||||
* **front:** fix comment not showing up when replying in a discussion ([cc8f02d](https://framagit.org/framasoft/mobilizon/commit/cc8f02d0a6354c49437e7ff1780912a71bed03f4))
|
||||
* **front:** fix confirm anonymous participation ([f99267c](https://framagit.org/framasoft/mobilizon/commit/f99267c6115601fce6eadd6ee54893fde0d6fd84))
|
||||
* **front:** fix discussion edition panel always showing up ([fee0e38](https://framagit.org/framasoft/mobilizon/commit/fee0e388af798f14d4da8cbd9f037137f6be9f85))
|
||||
* **front:** fix display of participants list ([c6b83c4](https://framagit.org/framasoft/mobilizon/commit/c6b83c42d6fbb2e6a93175479ef1620913c6532f))
|
||||
* **front:** fix map ([8f84ba1](https://framagit.org/framasoft/mobilizon/commit/8f84ba1d08ce8d2d266010ee3166106eed66116d)), closes [#1314](https://framagit.org/framasoft/mobilizon/issues/1314)
|
||||
* **front:** fix missing type causing eslint error ([c76dba3](https://framagit.org/framasoft/mobilizon/commit/c76dba3dbfe4fb0ab9ed24f71a6f64681c643fca))
|
||||
* **front:** fix selecting all participants in participant view ([beef3ff](https://framagit.org/framasoft/mobilizon/commit/beef3ff16d12f5d5710e302b739dd724ad4b0cb5))
|
||||
* **front:** fix showing error message when app to approve doesn't exist ([12cbff1](https://framagit.org/framasoft/mobilizon/commit/12cbff154ae5cdd72a1a7e882cb99e943010222b))
|
||||
* **front:** fix some alignment of some UI elements in mobile event view ([8c313b5](https://framagit.org/framasoft/mobilizon/commit/8c313b53977493792c113b5191443515f8aeae78))
|
||||
* **front:** properly handle error when approving app ([086d208](https://framagit.org/framasoft/mobilizon/commit/086d208ee50ae1f9ecb30196e758fdc7687714ae))
|
||||
* **front:** properly handle post not found ([8db31c9](https://framagit.org/framasoft/mobilizon/commit/8db31c99df668389db4c6651fa71a8c1420484cf))
|
||||
* **front:** reduce horizontal padding on main element ([f3c218f](https://framagit.org/framasoft/mobilizon/commit/f3c218f841292a28ec6d1284a205e2c7fd7d8f6e))
|
||||
* **lint:** fix lint after upgrades ([60aceb4](https://framagit.org/framasoft/mobilizon/commit/60aceb442ae49458e31a1f38d277eca7af248a36))
|
||||
* **mail:** fix sending mail on OTP26 ([f54fff5](https://framagit.org/framasoft/mobilizon/commit/f54fff56fc5c94408b1fd16b1eb9dd0f91bc2dfd)), closes [#1341](https://framagit.org/framasoft/mobilizon/issues/1341)
|
||||
* **push:** fix push subscriptions registration ([fdf87ea](https://framagit.org/framasoft/mobilizon/commit/fdf87ea991b1d406b28dbd0c8807908939070c8b))
|
||||
* **pwa:** improvements to the PWA configuration ([04c5ac1](https://framagit.org/framasoft/mobilizon/commit/04c5ac11636a4ffb5d3ac0c510b028edfb7fc057))
|
||||
* **reports:** make front-end handle nullified reported_id and reported_id ([afd2ffe](https://framagit.org/framasoft/mobilizon/commit/afd2ffe72294baedc9dd15dc89d57301831545cc))
|
||||
* **reports:** remove on delete cascade for reports ([4f530ca](https://framagit.org/framasoft/mobilizon/commit/4f530cabcf1bcadc09399a728975d329f3c9fdbf))
|
||||
* **front:** fix behavior of local toggle for profiles & groups view depending on domain value ([84f62cd](https://framagit.org/framasoft/mobilizon/commit/84f62cd043d5cf5d186fea6f24a1a9dff5fc64ce))
|
||||
* **i18n:** add missing translations ([af670f3](https://framagit.org/framasoft/mobilizon/commit/af670f39478b11465205fbea9b9268bab401bbb6))
|
||||
* **back:** allow any other type of actor to be suspended ([92b222b](https://framagit.org/framasoft/mobilizon/commit/92b222b091cf6248969b0206e7c052b725a1286b))
|
||||
* **back:** only try to insert activities for groups ([cfc9843](https://framagit.org/framasoft/mobilizon/commit/cfc984345e90b2960077956858606395f37ef9b9))
|
||||
* **front:** don't return promise if result is not finished loading for tags ([8c14ba4](https://framagit.org/framasoft/mobilizon/commit/8c14ba441c6f0fadb3c59f80ff4e3abb2e625752))
|
||||
* **front:** fix getting result from interactable object in InteractView ([31b2d06](https://framagit.org/framasoft/mobilizon/commit/31b2d065a904453580731133cd3dfd545a5816fa))
|
||||
* **docker:** make Docker entrypoint port configurable via $MOBILIZON_DATABASE_PORT ([13099e0](https://framagit.org/framasoft/mobilizon/commit/13099e0f118b727a1472282c6419ef9b1842c191))
|
||||
* **front:** fix fetching and rendering profile mentions and fetching tags ([895378a](https://framagit.org/framasoft/mobilizon/commit/895378a96bf8a6c7662ed02509c37b8d8a95db0b))
|
||||
* **sitemap:** save generated sitemaps in configurable directory ([f28109a](https://framagit.org/framasoft/mobilizon/commit/f28109ad50d85143e38c8e9f5d09c28f80566462)), closes [#1321](https://framagit.org/framasoft/mobilizon/issues/1321)
|
||||
* **auth:** small front fixes in 3rd-party auth provider callback ([bde7206](https://framagit.org/framasoft/mobilizon/commit/bde7206a1ca44fdf96d817921bb1efc497dcae40))
|
||||
* **config:** rollback Mailer tls setting to :never by default ([3d63c12](https://framagit.org/framasoft/mobilizon/commit/3d63c12e88ca31f582489f126d1ef5677af79721))
|
||||
* **docker:** fix entrypoint PostgreSQL extensions creations not using MOBILIZON_DATABASE_PORT ([9b49918](https://framagit.org/framasoft/mobilizon/commit/9b4991844ecaf7c1f1287ae62d1dfd463c2ea26b)), closes [#1321](https://framagit.org/framasoft/mobilizon/issues/1321) [#1321](https://framagit.org/framasoft/mobilizon/issues/1321)
|
||||
* **front:** fixes in EditIdentity view ([7e13e2b](https://framagit.org/framasoft/mobilizon/commit/7e13e2baa7690d5dfc4a8b12097a4ed85ea825d7))
|
||||
|
||||
|
||||
## 3.2.0-beta.5 (2023-09-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docker:** make Docker entrypoint port configurable via $MOBILIZON_DATABASE_PORT ([13099e0](https://framagit.org/framasoft/mobilizon/commit/13099e0f118b727a1472282c6419ef9b1842c191))
|
||||
* **front:** fix fetching and rendering profile mentions and fetching tags ([895378a](https://framagit.org/framasoft/mobilizon/commit/895378a96bf8a6c7662ed02509c37b8d8a95db0b))
|
||||
* **sitemap:** save generated sitemaps in configurable directory ([f28109a](https://framagit.org/framasoft/mobilizon/commit/f28109ad50d85143e38c8e9f5d09c28f80566462)), closes [#1321](https://framagit.org/framasoft/mobilizon/issues/1321)
|
||||
|
||||
|
||||
## 3.2.0-beta.4 (2023-09-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **back:** allow any other type of actor to be suspended ([92b222b](https://framagit.org/framasoft/mobilizon/commit/92b222b091cf6248969b0206e7c052b725a1286b))
|
||||
* **back:** only try to insert activities for groups ([cfc9843](https://framagit.org/framasoft/mobilizon/commit/cfc984345e90b2960077956858606395f37ef9b9))
|
||||
* **front:** don't return promise if result is not finished loading for tags ([8c14ba4](https://framagit.org/framasoft/mobilizon/commit/8c14ba441c6f0fadb3c59f80ff4e3abb2e625752))
|
||||
* **front:** fix getting result from interactable object in InteractView ([31b2d06](https://framagit.org/framasoft/mobilizon/commit/31b2d065a904453580731133cd3dfd545a5816fa))
|
||||
|
||||
|
||||
## 3.2.0-beta.3 (2023-09-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **i18n:** add missing translations ([af670f3](https://framagit.org/framasoft/mobilizon/commit/af670f39478b11465205fbea9b9268bab401bbb6))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add option to link an external registration provider for events ([2de6937](https://framagit.org/framasoft/mobilizon/commit/2de6937407743100daba1d397db4da32d4cb606b))
|
||||
* **back:** add admin setting to disable external event feature ([f6611e8](https://framagit.org/framasoft/mobilizon/commit/f6611e8eb5a7e12dc0dc0c216b598e04144e07c6))
|
||||
* improve group creation view [3f601748](https://framagit.org/framasoft/mobilizon/-/commit/3f60174877bbe05773b1d1b2ceb91749adec7ed7)
|
||||
|
||||
|
||||
## 3.2.0-beta.2 (2023-09-01)
|
||||
|
||||
Fixes a CI issue that prevented 3.2.0-beta.2 being released.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **front:** fix behavior of local toggle for profiles & groups view depending on domain value ([84f62cd](https://framagit.org/framasoft/mobilizon/commit/84f62cd043d5cf5d186fea6f24a1a9dff5fc64ce))
|
||||
|
||||
|
||||
## 3.2.0-beta.1 (2023-09-01)
|
||||
|
||||
### Features
|
||||
|
||||
* **cli:** allow the mobilizon.users.delete command to delete multiple users by email domain or ip ([bc50ab6](https://framagit.org/framasoft/mobilizon/commit/bc50ab66f3a44df220a7daa3cb1d917bd02487ba))
|
||||
* **export:** add date of participant creation in participant exports ([fef60ed](https://framagit.org/framasoft/mobilizon/commit/fef60ed0f92fc4e09ee261ff03f1139aff2449c3)), closes [#1343](https://framagit.org/framasoft/mobilizon/issues/1343)
|
||||
* **notifications:** add missing notifications when an user registers to an event ([da532c7](https://framagit.org/framasoft/mobilizon/commit/da532c7059bea5fcd47e2f42210e8ba842a11d63)), closes [#1344](https://framagit.org/framasoft/mobilizon/issues/1344)
|
||||
* **reports:** allow reports to hold multiple events ([f2ac3e2](https://framagit.org/framasoft/mobilizon/commit/f2ac3e2e5d28f4257a5e2d4870d339fecf3a5f1b))
|
||||
* **reports:** allow to suspend a profile or a user account directly from the report view ([69588db](https://framagit.org/framasoft/mobilizon/commit/69588dbf4ce2f80cc5829a841135042fa73eb4fe))
|
||||
* **reports:** improve reportview and allow removing content + resolve report automatically ([b105c50](https://framagit.org/framasoft/mobilizon/commit/b105c508c03ce3cb96dd8342f96d3291aa197e22))
|
||||
* **reports:** show suspended status next to reported profile ([b9a165a](https://framagit.org/framasoft/mobilizon/commit/b9a165a7fc565dc583cca81dd9c54570f73b4ca3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add inets and ssl to extra_applications in test env ([af46bea](https://framagit.org/framasoft/mobilizon/commit/af46bea7f730f4479bb31518a9fa53de7302049a))
|
||||
* **apps:** add missing app scopes ([7e98097](https://framagit.org/framasoft/mobilizon/commit/7e98097c710663609274200564fca9eff1ea4d20))
|
||||
* **apps:** make sure we can set status for an application token ([1a6095d](https://framagit.org/framasoft/mobilizon/commit/1a6095d27aeb440379d27c3894c302f831214822))
|
||||
* **backend:** fix config cache not being used everytime ([ed3cd58](https://framagit.org/framasoft/mobilizon/commit/ed3cd5858cd27a90d4724a95ee660bbc08e92e80))
|
||||
* **backend:** handle email not being sent when resending registration instructions ([b2492a3](https://framagit.org/framasoft/mobilizon/commit/b2492a387086528598da36f11e53569c5bdb164c))
|
||||
* create event time/date allignment ([3de90a3](https://framagit.org/framasoft/mobilizon/commit/3de90a3c73414105becdcb24899016178b1c6f02))
|
||||
* **docker:** fix Qemu segfaulting on arm64 ([8e3f90f](https://framagit.org/framasoft/mobilizon/commit/8e3f90f7135e2a8a8ac46464420c9d57b2e02534)), closes [#1241](https://framagit.org/framasoft/mobilizon/issues/1241) [#1249](https://framagit.org/framasoft/mobilizon/issues/1249)
|
||||
* **federation:** fix getting pictures from Gruppe actors ([7c5f8b2](https://framagit.org/framasoft/mobilizon/commit/7c5f8b24311253ef89c7e47cd7ce22ebe6cf2ec9))
|
||||
* fix Elixir 1.15 depreciations ([da70427](https://framagit.org/framasoft/mobilizon/commit/da70427e3292be8943167bbad73d5a782a98c6b5))
|
||||
* fix some typescript issues with pwa ([e351d3c](https://framagit.org/framasoft/mobilizon/commit/e351d3cb2f8183bb4335b3b21e154f46d9237a76))
|
||||
* **front:** avoid crashing if we don't have configuration data in time when in guard ([7916261](https://framagit.org/framasoft/mobilizon/commit/7916261c5c8c680d064fba106619d733575bc39c))
|
||||
* **front:** fix alignment of some input elements on event edition form ([50695fc](https://framagit.org/framasoft/mobilizon/commit/50695fcfd5e0dc6fd55185f4399d45ed1852f880))
|
||||
* **front:** fix changing language not being saved to the user's settings ([010a5e4](https://framagit.org/framasoft/mobilizon/commit/010a5e426def0a0b7f2658234f3c9d6eec46a68e))
|
||||
* **front:** fix comment not showing up when replying in a discussion ([cc8f02d](https://framagit.org/framasoft/mobilizon/commit/cc8f02d0a6354c49437e7ff1780912a71bed03f4))
|
||||
* **front:** fix confirm anonymous participation ([f99267c](https://framagit.org/framasoft/mobilizon/commit/f99267c6115601fce6eadd6ee54893fde0d6fd84))
|
||||
* **front:** fix discussion edition panel always showing up ([fee0e38](https://framagit.org/framasoft/mobilizon/commit/fee0e388af798f14d4da8cbd9f037137f6be9f85))
|
||||
* **front:** fix display of participants list ([c6b83c4](https://framagit.org/framasoft/mobilizon/commit/c6b83c42d6fbb2e6a93175479ef1620913c6532f))
|
||||
* **front:** fix map ([8f84ba1](https://framagit.org/framasoft/mobilizon/commit/8f84ba1d08ce8d2d266010ee3166106eed66116d)), closes [#1314](https://framagit.org/framasoft/mobilizon/issues/1314)
|
||||
* **front:** fix missing type causing eslint error ([c76dba3](https://framagit.org/framasoft/mobilizon/commit/c76dba3dbfe4fb0ab9ed24f71a6f64681c643fca))
|
||||
* **front:** fix selecting all participants in participant view ([beef3ff](https://framagit.org/framasoft/mobilizon/commit/beef3ff16d12f5d5710e302b739dd724ad4b0cb5))
|
||||
* **front:** fix showing error message when app to approve doesn't exist ([12cbff1](https://framagit.org/framasoft/mobilizon/commit/12cbff154ae5cdd72a1a7e882cb99e943010222b))
|
||||
* **front:** fix some alignment of some UI elements in mobile event view ([8c313b5](https://framagit.org/framasoft/mobilizon/commit/8c313b53977493792c113b5191443515f8aeae78))
|
||||
* **front:** properly handle error when approving app ([086d208](https://framagit.org/framasoft/mobilizon/commit/086d208ee50ae1f9ecb30196e758fdc7687714ae))
|
||||
* **front:** properly handle post not found ([8db31c9](https://framagit.org/framasoft/mobilizon/commit/8db31c99df668389db4c6651fa71a8c1420484cf))
|
||||
* **front:** reduce horizontal padding on main element ([f3c218f](https://framagit.org/framasoft/mobilizon/commit/f3c218f841292a28ec6d1284a205e2c7fd7d8f6e))
|
||||
* **lint:** fix lint after upgrades ([60aceb4](https://framagit.org/framasoft/mobilizon/commit/60aceb442ae49458e31a1f38d277eca7af248a36))
|
||||
* **mail:** fix sending mail on OTP26 ([f54fff5](https://framagit.org/framasoft/mobilizon/commit/f54fff56fc5c94408b1fd16b1eb9dd0f91bc2dfd)), closes [#1341](https://framagit.org/framasoft/mobilizon/issues/1341)
|
||||
* **push:** fix push subscriptions registration ([fdf87ea](https://framagit.org/framasoft/mobilizon/commit/fdf87ea991b1d406b28dbd0c8807908939070c8b))
|
||||
* **pwa:** improvements to the PWA configuration ([04c5ac1](https://framagit.org/framasoft/mobilizon/commit/04c5ac11636a4ffb5d3ac0c510b028edfb7fc057))
|
||||
* **reports:** make front-end handle nullified reported_id and reported_id ([afd2ffe](https://framagit.org/framasoft/mobilizon/commit/afd2ffe72294baedc9dd15dc89d57301831545cc))
|
||||
* **reports:** remove on delete cascade for reports ([4f530ca](https://framagit.org/framasoft/mobilizon/commit/4f530cabcf1bcadc09399a728975d329f3c9fdbf))
|
||||
|
||||
|
||||
## 3.1.3 (2023-06-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **groups:** fix unauthenticated access to groups because of missing read:group:members permission ([3714925](https://framagit.org/framasoft/mobilizon/commit/3714925896ad0415496352b9901ebec199afa0f2)), closes [#1311](https://framagit.org/framasoft/mobilizon/issues/1311)
|
||||
|
||||
|
||||
## 3.1.2 (2023-06-21)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **activity settings:** fix saving activity settings ([6c1e1e9](https://framagit.org/framasoft/mobilizon/commit/6c1e1e98d81c7469f41beed17cfa1d4b718b5d13)), closes [#1251](https://framagit.org/framasoft/mobilizon/issues/1251)
|
||||
* **apps:** fix pruning old application device activations ([dd00620](https://framagit.org/framasoft/mobilizon/commit/dd00620b9a54b2b1356855d280e03c82befe15e4))
|
||||
* **backend:** filter out nil tags before starting looking for existing ones ([f04d2b9](https://framagit.org/framasoft/mobilizon/commit/f04d2b9225b80333f03a3cc9366df4a05af88a73))
|
||||
* **deps:** fix compatibility with elixir-plug/mime 2.0.5 ([d63999c](https://framagit.org/framasoft/mobilizon/commit/d63999c081bcbb5923af17b71edbfd13a3720d7d))
|
||||
* **discussions:** handle changeset errors when updating discussion ([ca06ec3](https://framagit.org/framasoft/mobilizon/commit/ca06ec397fbd6848e340dfae12c635736069a9f3))
|
||||
* **exports:** properly handle export format not being handled ([a76b1ca](https://framagit.org/framasoft/mobilizon/commit/a76b1ca66d776fbe4566d7f23b38b087ae32530b))
|
||||
* **federation:** allow federated usernames with capitals ([d502164](https://framagit.org/framasoft/mobilizon/commit/d5021647d753e6457e459b1f992da60876292428))
|
||||
* **federation:** handle fetch_actor with a map ([552ab4c](https://framagit.org/framasoft/mobilizon/commit/552ab4c80b2f99095028ab3685c71ff9efdb94eb))
|
||||
* **federation:** handle string values for tags when constructing mentions ([2729d5e](https://framagit.org/framasoft/mobilizon/commit/2729d5ed7acef7c20a4388f019152e80a9db163c))
|
||||
* **federation:** ignore mentions from everything that's not a AP Person ([56f341e](https://framagit.org/framasoft/mobilizon/commit/56f341e960b7ae0a5fe78d7174f0e05d14add3f2))
|
||||
* **federation:** only refresh instances once a day ([6745590](https://framagit.org/framasoft/mobilizon/commit/6745590e54dce236dc7a2319f9c49c4aa6858306))
|
||||
* **federation:** prevent fetching own relay actor ([b981f91](https://framagit.org/framasoft/mobilizon/commit/b981f91cf748079847ae7a71b68f98b6914c951f))
|
||||
* **federation:** restrict fetch_group first arg to binaries ([e8d34b4](https://framagit.org/framasoft/mobilizon/commit/e8d34b4ea9f06d16a5982da8e5ff5140852c985d))
|
||||
* **federation:** rotate relay keys on startup if missing private keys ([5381eaa](https://framagit.org/framasoft/mobilizon/commit/5381eaae22248cdc6585d19c10be7fe2b7f5709f))
|
||||
* **front:** add missing title to Participants View page ([a5a86a5](https://framagit.org/framasoft/mobilizon/commit/a5a86a5e1be08cf9123ee7ad0979974bc2be1cb4))
|
||||
* **front:** fix displaying user activity settings checkboxes ([8e21c30](https://framagit.org/framasoft/mobilizon/commit/8e21c30f92f47dcb742d8f7df2aed59191158d80)), closes [#1251](https://framagit.org/framasoft/mobilizon/issues/1251)
|
||||
* **front:** fix wrong key name for dialog.confirm() option ([c8f49e1](https://framagit.org/framasoft/mobilizon/commit/c8f49e1837d719cd737c3e1ae976f14b20345e2b))
|
||||
* **front:** fix wrong value for timezone when it has no prefix ([2dd0e13](https://framagit.org/framasoft/mobilizon/commit/2dd0e13eba8bb5c04af45bae0de059deb93c2efa)), closes [#1275](https://framagit.org/framasoft/mobilizon/issues/1275)
|
||||
* **group:** fix getting group members count ([f749518](https://framagit.org/framasoft/mobilizon/commit/f749518bf7a29a86da559bfe6aba6d7485e7cfeb)), closes [#1303](https://framagit.org/framasoft/mobilizon/issues/1303)
|
||||
* **participant exports:** fix participants by returning the export type as well as the file path ([49b04c9](https://framagit.org/framasoft/mobilizon/commit/49b04c9b19517daa0a07656779d53001b39ab803))
|
||||
* **participant:** handle re-confirming participation ([5cc5c99](https://framagit.org/framasoft/mobilizon/commit/5cc5c9943cbc9a53246dda98958e99d004f0dfa9))
|
||||
|
||||
### Features
|
||||
|
||||
* **graphql:** validate timezone id as a GraphQL Scalar ([845bb6a](https://framagit.org/framasoft/mobilizon/commit/845bb6ac90081ef8cb4cff8d6ec3d11bfc19857c)), closes [#1299](https://framagit.org/framasoft/mobilizon/issues/1299)
|
||||
|
||||
|
||||
## 3.1.1 (2023-06-02)
|
||||
|
||||
### Features
|
||||
|
||||
* **anti-spam:** allow to only scan for spam in profiles or events ([c971287](https://framagit.org/framasoft/mobilizon/-/commit/c971287624913c8555fe288af0df1175701e6209))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **front:** fix group settings getting unresponsive because of reactive bug ([f1e119c](https://framagit.org/framasoft/mobilizon/-/commit/f1e119cb7ad580dfab73de3083f20a7303822888)), closes [#1298](https://framagit.org/framasoft/mobilizon/-/issues/1298)
|
||||
* **search:** fix global search sorting ([39e24c3](https://framagit.org/framasoft/mobilizon/-/commit/39e24c328a21f7058e4d2526e13eae85e39bae86)), closes [#1297](https://framagit.org/framasoft/mobilizon/-/issues/1297)
|
||||
|
||||
## 3.1.0 (2023-05-31)
|
||||
|
||||
### Features
|
||||
|
||||
* **API:** Allow to create apps, with permissions and both Authorization Code Flow and Device Flow
|
||||
* **addresses:** Allow to enter manual addresses ([85d643d](https://framagit.org/framasoft/mobilizon/-/commit/85d643d0ecd5e7504f32953b9ed1509697b915e2))
|
||||
* **docker:** Specify the folder where tzdata downloads data so that it can be used in a volume ([4bb0625](https://framagit.org/framasoft/mobilizon/-/commit/4bb062528f12be530a3754ca23c1bc6dbc862e5a)), closes [#1280](https://framagit.org/framasoft/mobilizon/-/issues/1280)
|
||||
* **spam:** Introduce checking new accounts, events & comments for spam with the help of Akismet ([317a343](https://framagit.org/framasoft/mobilizon/-/commit/317a3434b221a1a91b66d8443984269404863a8e))
|
||||
* **rate-limiting:** Introduce rate-limiting on some endpoints ([c07ba3a5](https://framagit.org/framasoft/mobilizon/-/commit/c07ba3a5d19c419ef8aaf3ea9ca6e7f48e4f4487))
|
||||
* **front:** improve padding on event tags ([7fa452d](https://framagit.org/framasoft/mobilizon/-/commit/7fa452d9e3f9bb2443e571c9a32eaed51e32480a))
|
||||
* **front:** make admin profile view linkable directly with parameters ([08ce7e2](https://framagit.org/framasoft/mobilizon/-/commit/08ce7e26b73045279261ab87a14cb4f3dab5df1e))
|
||||
* **front:** make profile members link to profile on group admin view and the reverse ([96129d2](https://framagit.org/framasoft/mobilizon/-/commit/96129d2339133027220d3b5fcb1c52f84bcc5cbb))
|
||||
* **front:** make profiles and group admin views default to local ([3e0324d](https://framagit.org/framasoft/mobilizon/-/commit/3e0324d36ec5a8aa388e6b5d598a6f9a0c596797))
|
||||
* **front:** redirect user to homepage on disconnect when currently on private page ([d5a6df9](https://framagit.org/framasoft/mobilizon/-/commit/d5a6df9940fb458c5dbaee149015c02ebc370c6b)), closes [#1278](https://framagit.org/framasoft/mobilizon/-/issues/1278)
|
||||
* **front:** show skeleton content on event view until the event is loaded ([dc3b93f](https://framagit.org/framasoft/mobilizon/-/commit/dc3b93ffb5a4b072aec792533fd6e4b58ed7a893))
|
||||
* **i18n:** activate croatian language ([94182ae](https://framagit.org/framasoft/mobilizon/-/commit/94182aed2d8a22d00534f6376dfda2658bc8ba7e))
|
||||
* **i18n:** activate japanese language ([6bd8034](https://framagit.org/framasoft/mobilizon/-/commit/6bd8034fe816a432c3547de6d1ad8a18e73dc314)), closes [#1293](https://framagit.org/framasoft/mobilizon/-/issues/1293)
|
||||
* **post:** show post visibily in PostListItem component ([ec7ca4d](https://framagit.org/framasoft/mobilizon/-/commit/ec7ca4ddf18a38cf6f51d38b540eecc9858f3c98))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **global-search:** Add option values in debug log before calling global search service ([8141bb0](https://framagit.org/framasoft/mobilizon/-/commit/8141bb0acbc4eb02a917c5bc18712d0d954c4ee5))
|
||||
* **apps:** Fix cleaning application data background job ([aa20f69](https://framagit.org/framasoft/mobilizon/-/commit/aa20f6991127ddee546fc0b867298c1342dbcb4d))
|
||||
* **apps:** Show message when the user doesn't have approved apps yet ([e0ee9c1](https://framagit.org/framasoft/mobilizon/-/commit/e0ee9c143b0335753db5dfae19e324781d55bd4e))
|
||||
* **auth:** Handle logging-in with disabled auth provider ([a22a5e3](https://framagit.org/framasoft/mobilizon/-/commit/a22a5e3cb924869e32cb9ed71dab3e03d91c018f))
|
||||
* **backend:** Fix Mobilizon.Events.list_participations_for_user_query/1 ([bcf6fd8](https://framagit.org/framasoft/mobilizon/-/commit/bcf6fd893c762c12b63d7e02da43cd5c05db509b))
|
||||
* **backend:** Handle CLDR data having no standard property for a language ([dbe2da7](https://framagit.org/framasoft/mobilizon/-/commit/dbe2da79c3aa1543b87dce61b5fd90195fb53afe))
|
||||
* **backend:** Ignore group mentions for now ([b5f106b](https://framagit.org/framasoft/mobilizon/-/commit/b5f106b0a81fefba3203f8ec5855e834a2078222))
|
||||
* **back:** Improve error message when requesting reset passwords and new instructions ([1c1d0d4](https://framagit.org/framasoft/mobilizon/-/commit/1c1d0d47d70cf19abe5be42e7ec3a73656a8172b))
|
||||
* **back:** Replace NaiveDateTime uses with DateTime for consistency ([8ea00e7](https://framagit.org/framasoft/mobilizon/-/commit/8ea00e7c1827ce3056ae51968a62fb3dc03ac6eb))
|
||||
* **back:** Various small fixes in backend ([2a57340](https://framagit.org/framasoft/mobilizon/-/commit/2a57340a82e414e69924ad89e8db9fc326742cc7))
|
||||
* bind pagination current prop ([4bcf572](https://framagit.org/framasoft/mobilizon/-/commit/4bcf572c54d904587d0409e2eb68b4ca6cf48fec))
|
||||
* **federation:** Account suspension should use actor in question as author and not relay actor ([79b48da](https://framagit.org/framasoft/mobilizon/-/commit/79b48da22209a8b2f1b234b8b8e121543a39b22b))
|
||||
* **feeds:** Only provide future events in ICS/Atom feeds ([f3a4431](https://framagit.org/framasoft/mobilizon/-/commit/f3a443138a0e1e6cf34fc593f5c174d56c21e904)), closes [#1246](https://framagit.org/framasoft/mobilizon/-/issues/1246)
|
||||
* Fix type of variable in navbar ([50ab531](https://framagit.org/framasoft/mobilizon/-/commit/50ab531156214f883cb03f785ccf65e3f19ef50e))
|
||||
* **follow-instances:** Show correct error message when trying to follow already following actor ([d969c66](https://framagit.org/framasoft/mobilizon/-/commit/d969c6648f15e1ed280169a4c55d612bb002f03f))
|
||||
* **front:** Fix about sections titles ([487f406](https://framagit.org/framasoft/mobilizon/-/commit/487f4069b14fde6304c9a42cec5b1c1af79814c5))
|
||||
* **front:** Fix autocomplete attribute in o-inputitems after Oruga new version BC ([d2ba732](https://framagit.org/framasoft/mobilizon/-/commit/d2ba732b8b51986b739f6fbe3d74fa68e4b74ba0))
|
||||
* **front:** Fix behaviour when deleting an event from event list ([cfd10ea](https://framagit.org/framasoft/mobilizon/-/commit/cfd10ea96078f03ad3b4f5682e37078ffae16ee4))
|
||||
* **front:** Fix event list month order ([63c9ed6](https://framagit.org/framasoft/mobilizon/-/commit/63c9ed62de94d6d150798c949bad3d8a2dd4db23)), closes [#1244](https://framagit.org/framasoft/mobilizon/-/issues/1244)
|
||||
* **front:** Fix instances list pagination ([8543204](https://framagit.org/framasoft/mobilizon/-/commit/8543204bd95de886d8d35bd491f23ecbc0a6ef8d)), closes [#1277](https://framagit.org/framasoft/mobilizon/-/issues/1277)
|
||||
* **front:** Fix pagination display on dark mode ([4375438](https://framagit.org/framasoft/mobilizon/-/commit/4375438dc9fd2f1c5c9d7ed6670dde04f2da520f))
|
||||
* **front:** Fix style of My Events participations ([35b07dc](https://framagit.org/framasoft/mobilizon/-/commit/35b07dceaa41c74c28ea49655b755e341f56df32))
|
||||
* **front:** Focus report comment input in report modal ([2c28312](https://framagit.org/framasoft/mobilizon/-/commit/2c28312fc957901b86c2f3d1db8fc3376f505d37)), closes [#1236](https://framagit.org/framasoft/mobilizon/-/issues/1236)
|
||||
* **front:** Handle "Failed to fetch dynamically imported module" errors by refreshing the page ([3d21a06](https://framagit.org/framasoft/mobilizon/-/commit/3d21a067897e4aa24f6404686ca6896044584796))
|
||||
* **front:** Improve Delete account modal UI ([c420bbc](https://framagit.org/framasoft/mobilizon/-/commit/c420bbccc9bd1c348e41904e826dc49c71d7eeb4))
|
||||
* **front:** Improve resend inscription instructions view and show error when appropriate ([5563052](https://framagit.org/framasoft/mobilizon/-/commit/55630527957d4f6a2e1e6845e64a92bc4794efc8))
|
||||
* **front:** No cache-only for config ([8dcb76c](https://framagit.org/framasoft/mobilizon/-/commit/8dcb76c30d4fa835837fd3b3833f83682fbae615))
|
||||
* **front:** Small UI fixes on identity pickers ([6faafd6](https://framagit.org/framasoft/mobilizon/-/commit/6faafd639303e4b57ed81db2ffb5db4ad598b904))
|
||||
* **i18n:** Update translations ([3b7dbcd](https://framagit.org/framasoft/mobilizon/-/commit/3b7dbcd71f0d19d5e723a03c56ca0b1abbd16f5d))
|
||||
* **map:** Fix style of the map marker ([c7b90cd](https://framagit.org/framasoft/mobilizon/-/commit/c7b90cd60a14abea7aebab7e1d87f37a44371f7c))
|
||||
* **map:** Only show map details when needed ([23b5e59](https://framagit.org/framasoft/mobilizon/-/commit/23b5e5930cb9bdb57b1d7fa3ec899d7e4d3571be))
|
||||
* **map:** Only show marker if we have it's position ([f0cc5ff](https://framagit.org/framasoft/mobilizon/-/commit/f0cc5ffb8feb2f4d70416792a8ab2f4f44bfba85))
|
||||
* **password-reset:** Lower time before being available to reset password or resend instructions ([73eb460](https://framagit.org/framasoft/mobilizon/-/commit/73eb4603b185c341b63481ed934f66e19aa0784f))
|
||||
* **search:** Fix event search order ([a4e7ee3](https://framagit.org/framasoft/mobilizon/-/commit/a4e7ee37bedc63b2193a401c801b3b1298f566d2))
|
||||
* **typespec:** Fix missing return type in typespec ([2043c98](https://framagit.org/framasoft/mobilizon/-/commit/2043c98717e8621b3953d347be0b4a35f494af98))
|
||||
* Change the way preferredUsername is synced ([a73e5a08](https://framagit.org/framasoft/mobilizon/-/commit/a73e5a085ef48a88dbb8f9c407df0430ca89fe1f))
|
||||
* datetimepicker: change colors for day & time selectors on dark mode ([b18e8fd3](https://framagit.org/framasoft/mobilizon/-/commit/b18e8fd37c76190ca7f6db82e408cdb005d1810a))
|
||||
* Save IP and login date from directly registered accounts ([1db5c4ae](https://framagit.org/framasoft/mobilizon/-/commit/1db5c4ae2d49d5adbda2c0825ee0320322b525d6))
|
||||
* Make sure every cache is properly cleared when managing an event ([f531c39b](https://framagit.org/framasoft/mobilizon/-/commit/f531c39b7e8829a5e3ff68f624b04e12266f2148))
|
||||
* Add page title for Categories view ([0775814e](https://framagit.org/framasoft/mobilizon/-/commit/0775814e19e6f6ddde564f7a29ae80fab2175d3f))
|
||||
* Fix notifications settings not working ([31fd99bd](https://framagit.org/framasoft/mobilizon/-/commit/31fd99bd3760872e452351b33765d25b2b9720f2))
|
||||
* **discussionlistitem:** remove unecessary parameter in vue router target ([779812c](https://framagit.org/framasoft/mobilizon/-/commit/779812c746cf722dd86bcc0ad3bc58e558c13223))
|
||||
* **emails:** make sure group notification emails are only sent once per email ([927e95f](https://framagit.org/framasoft/mobilizon/-/commit/927e95f387653c7d620e9051c30843ba49c2d65c))
|
||||
* **frontend:** event edition UI improvements ([0e14a36](https://framagit.org/framasoft/mobilizon/-/commit/0e14a36c6d30ebe386b2136d29539f3b3e914efc))
|
||||
* **frontend:** only show map on event edition when we have an address or we want to put in details ([02867e6](https://framagit.org/framasoft/mobilizon/-/commit/02867e6e1482ac8770f94fd2bd00174bb31fbdc7))
|
||||
* **front:** fix showing current group avatar & banners ([20b4aaa](https://framagit.org/framasoft/mobilizon/-/commit/20b4aaabc97080e85cb68fd03393379c7ef82d95))
|
||||
* **front:** fix showing current identity avatar & banners ([d0f4721](https://framagit.org/framasoft/mobilizon/-/commit/d0f4721925d0c50340d6db8a4e9f4d3e4ca01457))
|
||||
* **front:** improve UI of the glossary page ([d47b69d](https://framagit.org/framasoft/mobilizon/-/commit/d47b69d6caa7c4405ab2e573ba407f9b2450c3bb))
|
||||
* **front:** increase padding next to arrow down in `<select>` elements ([94f186c](https://framagit.org/framasoft/mobilizon/-/commit/94f186ce5080316cd633e0344651b0050c2f14d4))
|
||||
* **front:** remove cache-only for ABOUT GraphQL details on homepage ([6858bcb](https://framagit.org/framasoft/mobilizon/-/commit/6858bcbbda6d8527bd15b9138e7bb30c5ead72d7))
|
||||
* **front:** remove leftover console.logs ([6da0dba](https://framagit.org/framasoft/mobilizon/-/commit/6da0dba0fd6d071ce5978802104538d0c2ef7dae))
|
||||
* **front:** reset page number to 1 when search criteria changes ([d73bafe](https://framagit.org/framasoft/mobilizon/-/commit/d73bafec97cd7d8eda887d21870427262befab0f)), closes [#1272](https://framagit.org/framasoft/mobilizon/-/issues/1272)
|
||||
* **front:** various UI improvements for group page ([b097567](https://framagit.org/framasoft/mobilizon/-/commit/b0975672c1c06ace364cf47bfcfa39db9c3b712b))
|
||||
* **graphql:** fix calling GET_GROUP ([2933ee0](https://framagit.org/framasoft/mobilizon/-/commit/2933ee06791a24dbf8c8b2a2eabc67f71e56f361))
|
||||
* **group:** rephrase "Public Page" to "Announcements", as all posts are not necessary public ([b0a564f](https://framagit.org/framasoft/mobilizon/-/commit/b0a564f64f72f40b6bb9560f9bc0fbea5d099fd7)), closes [#900](https://framagit.org/framasoft/mobilizon/-/issues/900)
|
||||
* **i18n:** fix Swedish translations error that prevented Participate button from showing up ([643a5b5](https://framagit.org/framasoft/mobilizon/-/commit/643a5b5921f91fed6a9f674c0ab3a36bf2d05835)), closes [#1281](https://framagit.org/framasoft/mobilizon/-/issues/1281)
|
||||
* **rich media:** fix error handling when resource preview URL leads to empty parsed data ([850b4e2](https://framagit.org/framasoft/mobilizon/-/commit/850b4e2a735e335c4737caa8b60e190613e778ef)), closes [#1279](https://framagit.org/framasoft/mobilizon/-/issues/1279)
|
||||
* **sharepostmodal:** only show the share warning message if the post is accessible by link ([8e626dc](https://framagit.org/framasoft/mobilizon/-/commit/8e626dce7807640a89770e50ca2621d34d6a5d97))
|
||||
* **apps:** fix device flow authorization process ([9a457fb](https://framagit.org/framasoft/mobilizon/-/commit/9a457fb011b77b27dc465f1bc7327a08f554ccfb))
|
||||
* **apps:** fix typo in redirect_uri parameter ([5664625](https://framagit.org/framasoft/mobilizon/-/commit/5664625c1c57ccba947400475414c1301d4bf955))
|
||||
* **apps:** show scope from device activation in authorize device view ([c9d2074](https://framagit.org/framasoft/mobilizon/-/commit/c9d20748a4dd3e0687515f4776335d0ec9bdfcdc))
|
||||
* **front:** fix homepage event and groups cards snapping ([8809db5](https://framagit.org/framasoft/mobilizon/-/commit/8809db582ccf45fcd477f46dcf70e106720626a8))
|
||||
* **front:** fix selecting addresses in autocomplete ([e0488dd](https://framagit.org/framasoft/mobilizon/-/commit/e0488dd87ffc0184162a2ff67a13717e6263d56d))
|
||||
* include user role in moderator role ([c4d6019](https://framagit.org/framasoft/mobilizon/-/commit/c4d60194a6900a3f9430355c5fbb346d910e4df6))
|
||||
|
||||
|
||||
## 3.1.0-rc.2 (2023-05-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **apps:** fix device flow authorization process ([9a457fb](https://framagit.org/framasoft/mobilizon/-/commit/9a457fb011b77b27dc465f1bc7327a08f554ccfb))
|
||||
* **apps:** fix typo in redirect_uri parameter ([5664625](https://framagit.org/framasoft/mobilizon/-/commit/5664625c1c57ccba947400475414c1301d4bf955))
|
||||
* **apps:** show scope from device activation in authorize device view ([c9d2074](https://framagit.org/framasoft/mobilizon/-/commit/c9d20748a4dd3e0687515f4776335d0ec9bdfcdc))
|
||||
* **front:** fix homepage event and groups cards snapping ([8809db5](https://framagit.org/framasoft/mobilizon/-/commit/8809db582ccf45fcd477f46dcf70e106720626a8))
|
||||
* **front:** fix selecting addresses in autocomplete ([e0488dd](https://framagit.org/framasoft/mobilizon/-/commit/e0488dd87ffc0184162a2ff67a13717e6263d56d))
|
||||
|
||||
|
||||
## 3.1.0-rc.1 (2023-05-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **discussionlistitem:** remove unecessary parameter in vue router target ([779812c](https://framagit.org/framasoft/mobilizon/-/commit/779812c746cf722dd86bcc0ad3bc58e558c13223))
|
||||
* **emails:** make sure group notification emails are only sent once per email ([927e95f](https://framagit.org/framasoft/mobilizon/-/commit/927e95f387653c7d620e9051c30843ba49c2d65c))
|
||||
* **frontend:** event edition UI improvements ([0e14a36](https://framagit.org/framasoft/mobilizon/-/commit/0e14a36c6d30ebe386b2136d29539f3b3e914efc))
|
||||
* **frontend:** only show map on event edition when we have an address or we want to put in details ([02867e6](https://framagit.org/framasoft/mobilizon/-/commit/02867e6e1482ac8770f94fd2bd00174bb31fbdc7))
|
||||
* **front:** fix showing current group avatar & banners ([20b4aaa](https://framagit.org/framasoft/mobilizon/-/commit/20b4aaabc97080e85cb68fd03393379c7ef82d95))
|
||||
* **front:** fix showing current identity avatar & banners ([d0f4721](https://framagit.org/framasoft/mobilizon/-/commit/d0f4721925d0c50340d6db8a4e9f4d3e4ca01457))
|
||||
* **front:** improve UI of the glossary page ([d47b69d](https://framagit.org/framasoft/mobilizon/-/commit/d47b69d6caa7c4405ab2e573ba407f9b2450c3bb))
|
||||
* **front:** increase padding next to arrow down in `<select>` elements ([94f186c](https://framagit.org/framasoft/mobilizon/-/commit/94f186ce5080316cd633e0344651b0050c2f14d4))
|
||||
* **front:** remove cache-only for ABOUT GraphQL details on homepage ([6858bcb](https://framagit.org/framasoft/mobilizon/-/commit/6858bcbbda6d8527bd15b9138e7bb30c5ead72d7))
|
||||
* **front:** remove leftover console.logs ([6da0dba](https://framagit.org/framasoft/mobilizon/-/commit/6da0dba0fd6d071ce5978802104538d0c2ef7dae))
|
||||
* **front:** reset page number to 1 when search criteria changes ([d73bafe](https://framagit.org/framasoft/mobilizon/-/commit/d73bafec97cd7d8eda887d21870427262befab0f)), closes [#1272](https://framagit.org/framasoft/mobilizon/-/issues/1272)
|
||||
* **front:** various UI improvements for group page ([b097567](https://framagit.org/framasoft/mobilizon/-/commit/b0975672c1c06ace364cf47bfcfa39db9c3b712b))
|
||||
* **graphql:** fix calling GET_GROUP ([2933ee0](https://framagit.org/framasoft/mobilizon/-/commit/2933ee06791a24dbf8c8b2a2eabc67f71e56f361))
|
||||
* **group:** rephrase "Public Page" to "Announcements", as all posts are not necessary public ([b0a564f](https://framagit.org/framasoft/mobilizon/-/commit/b0a564f64f72f40b6bb9560f9bc0fbea5d099fd7)), closes [#900](https://framagit.org/framasoft/mobilizon/-/issues/900)
|
||||
* **i18n:** fix Swedish translations error that prevented Participate button from showing up ([643a5b5](https://framagit.org/framasoft/mobilizon/-/commit/643a5b5921f91fed6a9f674c0ab3a36bf2d05835)), closes [#1281](https://framagit.org/framasoft/mobilizon/-/issues/1281)
|
||||
* **rich media:** fix error handling when resource preview URL leads to empty parsed data ([850b4e2](https://framagit.org/framasoft/mobilizon/-/commit/850b4e2a735e335c4737caa8b60e190613e778ef)), closes [#1279](https://framagit.org/framasoft/mobilizon/-/issues/1279)
|
||||
* **sharepostmodal:** only show the share warning message if the post is accessible by link ([8e626dc](https://framagit.org/framasoft/mobilizon/-/commit/8e626dce7807640a89770e50ca2621d34d6a5d97))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **front:** improve padding on event tags ([7fa452d](https://framagit.org/framasoft/mobilizon/-/commit/7fa452d9e3f9bb2443e571c9a32eaed51e32480a))
|
||||
* **front:** make admin profile view linkable directly with parameters ([08ce7e2](https://framagit.org/framasoft/mobilizon/-/commit/08ce7e26b73045279261ab87a14cb4f3dab5df1e))
|
||||
* **front:** make profile members link to profile on group admin view and the reverse ([96129d2](https://framagit.org/framasoft/mobilizon/-/commit/96129d2339133027220d3b5fcb1c52f84bcc5cbb))
|
||||
* **front:** make profiles and group admin views default to local ([3e0324d](https://framagit.org/framasoft/mobilizon/-/commit/3e0324d36ec5a8aa388e6b5d598a6f9a0c596797))
|
||||
* **front:** redirect user to homepage on disconnect when currently on private page ([d5a6df9](https://framagit.org/framasoft/mobilizon/-/commit/d5a6df9940fb458c5dbaee149015c02ebc370c6b)), closes [#1278](https://framagit.org/framasoft/mobilizon/-/issues/1278)
|
||||
* **front:** show skeleton content on event view until the event is loaded ([dc3b93f](https://framagit.org/framasoft/mobilizon/-/commit/dc3b93ffb5a4b072aec792533fd6e4b58ed7a893))
|
||||
* **i18n:** activate croatian language ([94182ae](https://framagit.org/framasoft/mobilizon/-/commit/94182aed2d8a22d00534f6376dfda2658bc8ba7e))
|
||||
* **i18n:** activate japanese language ([6bd8034](https://framagit.org/framasoft/mobilizon/-/commit/6bd8034fe816a432c3547de6d1ad8a18e73dc314)), closes [#1293](https://framagit.org/framasoft/mobilizon/-/issues/1293)
|
||||
* **post:** show post visibily in PostListItem component ([ec7ca4d](https://framagit.org/framasoft/mobilizon/-/commit/ec7ca4ddf18a38cf6f51d38b540eecc9858f3c98))
|
||||
|
||||
|
||||
## 3.1.0-beta.2 (2023-05-23)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* include user role in moderator role ([c4d6019](https://framagit.org/framasoft/mobilizon/-/commit/c4d60194a6900a3f9430355c5fbb346d910e4df6))
|
||||
|
||||
|
||||
## 3.1.0-beta.1 (2023-05-17)
|
||||
|
||||
### Features
|
||||
|
||||
* **API:** Allow to create apps, with permissions and both Authorization Code Flow and Device Flow
|
||||
* **addresses:** Allow to enter manual addresses ([85d643d](https://framagit.org/framasoft/mobilizon/-/commit/85d643d0ecd5e7504f32953b9ed1509697b915e2))
|
||||
* **docker:** Specify the folder where tzdata downloads data so that it can be used in a volume ([4bb0625](https://framagit.org/framasoft/mobilizon/-/commit/4bb062528f12be530a3754ca23c1bc6dbc862e5a)), closes [#1280](https://framagit.org/framasoft/mobilizon/-/issues/1280)
|
||||
* **spam:** Introduce checking new accounts, events & comments for spam with the help of Akismet ([317a343](https://framagit.org/framasoft/mobilizon/-/commit/317a3434b221a1a91b66d8443984269404863a8e))
|
||||
* **rate-limiting:** Introduce rate-limiting on some endpoints ([c07ba3a5](https://framagit.org/framasoft/mobilizon/-/commit/c07ba3a5d19c419ef8aaf3ea9ca6e7f48e4f4487))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **global-search:** Add option values in debug log before calling global search service ([8141bb0](https://framagit.org/framasoft/mobilizon/-/commit/8141bb0acbc4eb02a917c5bc18712d0d954c4ee5))
|
||||
* **apps:** Fix cleaning application data background job ([aa20f69](https://framagit.org/framasoft/mobilizon/-/commit/aa20f6991127ddee546fc0b867298c1342dbcb4d))
|
||||
* **apps:** Show message when the user doesn't have approved apps yet ([e0ee9c1](https://framagit.org/framasoft/mobilizon/-/commit/e0ee9c143b0335753db5dfae19e324781d55bd4e))
|
||||
* **auth:** Handle logging-in with disabled auth provider ([a22a5e3](https://framagit.org/framasoft/mobilizon/-/commit/a22a5e3cb924869e32cb9ed71dab3e03d91c018f))
|
||||
* **backend:** Fix Mobilizon.Events.list_participations_for_user_query/1 ([bcf6fd8](https://framagit.org/framasoft/mobilizon/-/commit/bcf6fd893c762c12b63d7e02da43cd5c05db509b))
|
||||
* **backend:** Handle CLDR data having no standard property for a language ([dbe2da7](https://framagit.org/framasoft/mobilizon/-/commit/dbe2da79c3aa1543b87dce61b5fd90195fb53afe))
|
||||
* **backend:** Ignore group mentions for now ([b5f106b](https://framagit.org/framasoft/mobilizon/-/commit/b5f106b0a81fefba3203f8ec5855e834a2078222))
|
||||
* **back:** Improve error message when requesting reset passwords and new instructions ([1c1d0d4](https://framagit.org/framasoft/mobilizon/-/commit/1c1d0d47d70cf19abe5be42e7ec3a73656a8172b))
|
||||
* **back:** Replace NaiveDateTime uses with DateTime for consistency ([8ea00e7](https://framagit.org/framasoft/mobilizon/-/commit/8ea00e7c1827ce3056ae51968a62fb3dc03ac6eb))
|
||||
* **back:** Various small fixes in backend ([2a57340](https://framagit.org/framasoft/mobilizon/-/commit/2a57340a82e414e69924ad89e8db9fc326742cc7))
|
||||
* bind pagination current prop ([4bcf572](https://framagit.org/framasoft/mobilizon/-/commit/4bcf572c54d904587d0409e2eb68b4ca6cf48fec))
|
||||
* **federation:** Account suspension should use actor in question as author and not relay actor ([79b48da](https://framagit.org/framasoft/mobilizon/-/commit/79b48da22209a8b2f1b234b8b8e121543a39b22b))
|
||||
* **feeds:** Only provide future events in ICS/Atom feeds ([f3a4431](https://framagit.org/framasoft/mobilizon/-/commit/f3a443138a0e1e6cf34fc593f5c174d56c21e904)), closes [#1246](https://framagit.org/framasoft/mobilizon/-/issues/1246)
|
||||
* Fix type of variable in navbar ([50ab531](https://framagit.org/framasoft/mobilizon/-/commit/50ab531156214f883cb03f785ccf65e3f19ef50e))
|
||||
* **follow-instances:** Show correct error message when trying to follow already following actor ([d969c66](https://framagit.org/framasoft/mobilizon/-/commit/d969c6648f15e1ed280169a4c55d612bb002f03f))
|
||||
* **front:** Fix about sections titles ([487f406](https://framagit.org/framasoft/mobilizon/-/commit/487f4069b14fde6304c9a42cec5b1c1af79814c5))
|
||||
* **front:** Fix autocomplete attribute in o-inputitems after Oruga new version BC ([d2ba732](https://framagit.org/framasoft/mobilizon/-/commit/d2ba732b8b51986b739f6fbe3d74fa68e4b74ba0))
|
||||
* **front:** Fix behaviour when deleting an event from event list ([cfd10ea](https://framagit.org/framasoft/mobilizon/-/commit/cfd10ea96078f03ad3b4f5682e37078ffae16ee4))
|
||||
* **front:** Fix event list month order ([63c9ed6](https://framagit.org/framasoft/mobilizon/-/commit/63c9ed62de94d6d150798c949bad3d8a2dd4db23)), closes [#1244](https://framagit.org/framasoft/mobilizon/-/issues/1244)
|
||||
* **front:** Fix instances list pagination ([8543204](https://framagit.org/framasoft/mobilizon/-/commit/8543204bd95de886d8d35bd491f23ecbc0a6ef8d)), closes [#1277](https://framagit.org/framasoft/mobilizon/-/issues/1277)
|
||||
* **front:** Fix pagination display on dark mode ([4375438](https://framagit.org/framasoft/mobilizon/-/commit/4375438dc9fd2f1c5c9d7ed6670dde04f2da520f))
|
||||
* **front:** Fix style of My Events participations ([35b07dc](https://framagit.org/framasoft/mobilizon/-/commit/35b07dceaa41c74c28ea49655b755e341f56df32))
|
||||
* **front:** Focus report comment input in report modal ([2c28312](https://framagit.org/framasoft/mobilizon/-/commit/2c28312fc957901b86c2f3d1db8fc3376f505d37)), closes [#1236](https://framagit.org/framasoft/mobilizon/-/issues/1236)
|
||||
* **front:** Handle "Failed to fetch dynamically imported module" errors by refreshing the page ([3d21a06](https://framagit.org/framasoft/mobilizon/-/commit/3d21a067897e4aa24f6404686ca6896044584796))
|
||||
* **front:** Improve Delete account modal UI ([c420bbc](https://framagit.org/framasoft/mobilizon/-/commit/c420bbccc9bd1c348e41904e826dc49c71d7eeb4))
|
||||
* **front:** Improve resend inscription instructions view and show error when appropriate ([5563052](https://framagit.org/framasoft/mobilizon/-/commit/55630527957d4f6a2e1e6845e64a92bc4794efc8))
|
||||
* **front:** No cache-only for config ([8dcb76c](https://framagit.org/framasoft/mobilizon/-/commit/8dcb76c30d4fa835837fd3b3833f83682fbae615))
|
||||
* **front:** Small UI fixes on identity pickers ([6faafd6](https://framagit.org/framasoft/mobilizon/-/commit/6faafd639303e4b57ed81db2ffb5db4ad598b904))
|
||||
* **i18n:** Update translations ([3b7dbcd](https://framagit.org/framasoft/mobilizon/-/commit/3b7dbcd71f0d19d5e723a03c56ca0b1abbd16f5d))
|
||||
* **map:** Fix style of the map marker ([c7b90cd](https://framagit.org/framasoft/mobilizon/-/commit/c7b90cd60a14abea7aebab7e1d87f37a44371f7c))
|
||||
* **map:** Only show map details when needed ([23b5e59](https://framagit.org/framasoft/mobilizon/-/commit/23b5e5930cb9bdb57b1d7fa3ec899d7e4d3571be))
|
||||
* **map:** Only show marker if we have it's position ([f0cc5ff](https://framagit.org/framasoft/mobilizon/-/commit/f0cc5ffb8feb2f4d70416792a8ab2f4f44bfba85))
|
||||
* **password-reset:** Lower time before being available to reset password or resend instructions ([73eb460](https://framagit.org/framasoft/mobilizon/-/commit/73eb4603b185c341b63481ed934f66e19aa0784f))
|
||||
* **search:** Fix event search order ([a4e7ee3](https://framagit.org/framasoft/mobilizon/-/commit/a4e7ee37bedc63b2193a401c801b3b1298f566d2))
|
||||
* **typespec:** Fix missing return type in typespec ([2043c98](https://framagit.org/framasoft/mobilizon/-/commit/2043c98717e8621b3953d347be0b4a35f494af98))
|
||||
* Change the way preferredUsername is synced ([a73e5a08](https://framagit.org/framasoft/mobilizon/-/commit/a73e5a085ef48a88dbb8f9c407df0430ca89fe1f))
|
||||
* datetimepicker: change colors for day & time selectors on dark mode ([b18e8fd3](https://framagit.org/framasoft/mobilizon/-/commit/b18e8fd37c76190ca7f6db82e408cdb005d1810a))
|
||||
* Save IP and login date from directly registered accounts ([1db5c4ae](https://framagit.org/framasoft/mobilizon/-/commit/1db5c4ae2d49d5adbda2c0825ee0320322b525d6))
|
||||
* Make sure every cache is properly cleared when managing an event ([f531c39b](https://framagit.org/framasoft/mobilizon/-/commit/f531c39b7e8829a5e3ff68f624b04e12266f2148))
|
||||
* Add page title for Categories view ([0775814e](https://framagit.org/framasoft/mobilizon/-/commit/0775814e19e6f6ddde564f7a29ae80fab2175d3f))
|
||||
* Fix notifications settings not working ([31fd99bd](https://framagit.org/framasoft/mobilizon/-/commit/31fd99bd3760872e452351b33765d25b2b9720f2))
|
||||
|
||||
|
||||
## 3.0.3 - 2022-12-22
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
FROM elixir:alpine
|
||||
FROM elixir:1.15-alpine
|
||||
|
||||
RUN apk add --no-cache inotify-tools postgresql-client yarn file make gcc libc-dev argon2 imagemagick cmake build-base libwebp-tools bash ncurses git python3
|
||||
RUN apk add --no-cache inotify-tools postgresql-client file make gcc libc-dev argon2 imagemagick cmake build-base libwebp-tools bash ncurses git python3 npm
|
||||
|
||||
RUN mix local.hex --force && mix local.rebar --force
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
EXPOSE 4000
|
||||
EXPOSE 5173
|
||||
|
|
15
Makefile
15
Makefile
|
@ -4,24 +4,25 @@ init:
|
|||
|
||||
setup: stop
|
||||
@bash docker/message.sh "Compiling everything"
|
||||
docker-compose run --rm api bash -c 'mix deps.get; yarn --cwd "js"; yarn --cwd "js" build:pictures; mix ecto.create; mix ecto.migrate'
|
||||
docker compose run --rm api bash -c 'mix deps.get; npm ci; npm run build:pictures; mix ecto.create; mix ecto.migrate'
|
||||
migrate:
|
||||
docker-compose run --rm api mix ecto.migrate
|
||||
docker compose run --rm api mix ecto.migrate
|
||||
logs:
|
||||
docker-compose logs -f
|
||||
docker compose logs -f
|
||||
start: stop
|
||||
@bash docker/message.sh "Starting Mobilizon with Docker"
|
||||
docker-compose up -d api
|
||||
docker compose up -d api
|
||||
@bash docker/message.sh "Docker server started"
|
||||
stop:
|
||||
@bash docker/message.sh "Stopping Mobilizon"
|
||||
docker-compose down
|
||||
docker compose down
|
||||
@bash docker/message.sh "Mobilizon is stopped"
|
||||
test: stop
|
||||
@bash docker/message.sh "Running tests"
|
||||
docker-compose -f docker-compose.yml -f docker-compose.test.yml run api mix test $(only)
|
||||
docker compose -f docker compose.yml -f docker compose.test.yml run api mix prepare_test
|
||||
docker compose -f docker compose.yml -f docker compose.test.yml run api mix test $(only)
|
||||
@bash docker/message.sh "Done running tests"
|
||||
format:
|
||||
docker-compose run --rm api bash -c "mix format && mix credo --strict"
|
||||
docker compose run --rm api bash -c "mix format && mix credo --strict"
|
||||
@bash docker/message.sh "Code is now ready to commit :)"
|
||||
target: init
|
||||
|
|
17
README.md
17
README.md
|
@ -1,3 +1,5 @@
|
|||
*You can learn about [what we plan to do with this fork](https://framacolibri.org/t/using-mobilizon-for-regional-leftist-subculture-calendar-platforms/18772) in this post in the Mobilizon forum.*
|
||||
|
||||
<h1 align="center">
|
||||
<a href="https://joinmobilizon.org">
|
||||
<img src="https://lutim.cpy.re/qVYC86G9.png" alt="Mobilizon">
|
||||
|
@ -16,6 +18,20 @@ Mobilizon is your federated organization and mobilization platform. Gather peopl
|
|||
</a>
|
||||
</p>
|
||||
|
||||
## Notes about this fork
|
||||
|
||||
The currently deployed `main` branch can be tested at [https://rotes.potsda.mn/](https://rotes.potsda.mn/).
|
||||
|
||||
### Building with Nix
|
||||
|
||||
For building this locally, you can use Nix (with Flakes enabled):
|
||||
|
||||
```
|
||||
$ nix build git+ssh://git@git.potsda.mn/potsda.mn/mobilizon.git?ref=main#mobilizon
|
||||
```
|
||||
|
||||
The built package is then located in `result/`.
|
||||
|
||||
## Introduction
|
||||
|
||||
Mobilizon is a tool designed to create platforms for managing communities and events. Its purpose is to help as many people as possible to free themselves from Facebook groups and events, from Meetup, etc.
|
||||
|
@ -50,6 +66,7 @@ We appreciate any contribution to Mobilizon. Check [our contributing page](https
|
|||
* 🔢 Pick an instance [https://mobilizon.org](https://mobilizon.org)
|
||||
* 💻 Source: [https://framagit.org/framasoft/mobilizon](https://framagit.org/framasoft/mobilizon)
|
||||
* 📜 Documentation [https://docs.joinmobilizon.org](https://docs.joinmobilizon.org)
|
||||
* A summarized description of structure of sources is done in [`docs/dev.md`](./docs/dev.md)
|
||||
|
||||
### Discuss
|
||||
* 💬 Element/Matrix: [https://matrix.to/#/#Mobilizon:matrix.org](https://matrix.to/#/#Mobilizon:matrix.org)
|
||||
|
|
|
@ -7,6 +7,6 @@ module.exports = {
|
|||
localSchemaFile: "./schema.graphql",
|
||||
},
|
||||
// Files processed by the extension
|
||||
includes: ["js/src/**/*.vue", "js/src/**/*.js"],
|
||||
includes: ["src/**/*.vue", "src/**/*.js"],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -41,7 +41,10 @@ config :mobilizon, :instance,
|
|||
email_reply_to: "noreply@localhost"
|
||||
|
||||
config :mobilizon, :groups, enabled: true
|
||||
config :mobilizon, :events, creation: true
|
||||
|
||||
config :mobilizon, :events,
|
||||
creation: true,
|
||||
external: true
|
||||
|
||||
config :mobilizon, :restrictions, only_admin_can_create_groups: false
|
||||
config :mobilizon, :restrictions, only_groups_can_create_events: false
|
||||
|
@ -65,6 +68,10 @@ config :mime, :types, %{
|
|||
"application/xrd+xml" => ["xrd-xml"]
|
||||
}
|
||||
|
||||
config :mime, :extensions, %{
|
||||
"activity-json" => "application/activity+json"
|
||||
}
|
||||
|
||||
# Upload configuration
|
||||
config :mobilizon, Mobilizon.Web.Upload,
|
||||
uploader: Mobilizon.Web.Upload.Uploader.Local,
|
||||
|
@ -109,17 +116,14 @@ config :mobilizon, :media_proxy,
|
|||
config :mobilizon, Mobilizon.Web.Email.Mailer,
|
||||
adapter: Swoosh.Adapters.SMTP,
|
||||
relay: "localhost",
|
||||
# usually 25, 465 or 587
|
||||
port: 25,
|
||||
username: "",
|
||||
password: "",
|
||||
# can be `:always` or `:never`
|
||||
auth: :if_available,
|
||||
# can be `true`
|
||||
ssl: false,
|
||||
# ssl: false,
|
||||
# can be `:always` or `:never`
|
||||
tls: :if_available,
|
||||
allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"],
|
||||
tls: :never,
|
||||
retries: 1,
|
||||
# can be `true`
|
||||
no_mx_lookups: false
|
||||
|
@ -138,7 +142,7 @@ config :vite_phx,
|
|||
config :logger, :console,
|
||||
backends: [:console],
|
||||
format: "$time $metadata[$level] $message\n",
|
||||
metadata: [:request_id, :graphql_operation_name, :user_id, :actor_name]
|
||||
metadata: [:request_id, :graphql_operation_name, :user_id, :actor_name, :trace]
|
||||
|
||||
config :mobilizon, Mobilizon.Web.Auth.Guardian,
|
||||
issuer: "mobilizon",
|
||||
|
@ -148,13 +152,12 @@ config :mobilizon, Mobilizon.Web.Auth.Guardian,
|
|||
}
|
||||
|
||||
config :guardian, Guardian.DB,
|
||||
adapter: Guardian.DB.EctoAdapter,
|
||||
repo: Mobilizon.Storage.Repo,
|
||||
# default
|
||||
schema_name: "guardian_tokens",
|
||||
# store all token types if not set
|
||||
token_types: ["refresh"],
|
||||
# default: 60 minutes
|
||||
sweep_interval: 60
|
||||
token_types: ["refresh"]
|
||||
|
||||
config :elixir, :time_zone_database, Tzdata.TimeZoneDatabase
|
||||
|
||||
|
@ -307,16 +310,24 @@ config :mobilizon, Oban,
|
|||
crontab: [
|
||||
{"@hourly", Mobilizon.Service.Workers.BuildSiteMap, queue: :background},
|
||||
{"17 4 * * *", Mobilizon.Service.Workers.RefreshGroups, queue: :background},
|
||||
{"36 * * * *", Mobilizon.Service.Workers.RefreshInstances, queue: :background},
|
||||
{"36 3 * * *", Mobilizon.Service.Workers.RefreshInstances, queue: :background},
|
||||
{"@hourly", Mobilizon.Service.Workers.CleanOrphanMediaWorker, queue: :background},
|
||||
{"@hourly", Mobilizon.Service.Workers.CleanUnconfirmedUsersWorker, queue: :background},
|
||||
{"@hourly", Mobilizon.Service.Workers.ExportCleanerWorker, queue: :background},
|
||||
{"@hourly", Mobilizon.Service.Workers.SendActivityRecapWorker, queue: :notifications},
|
||||
{"@daily", Mobilizon.Service.Workers.CleanOldActivityWorker, queue: :background}
|
||||
{"@daily", Mobilizon.Service.Workers.CleanOldActivityWorker, queue: :background},
|
||||
{"@daily", Mobilizon.Service.Workers.RefreshParticipantStats, queue: :background},
|
||||
{"@hourly", Mobilizon.Service.Workers.CleanApplicationData,
|
||||
queue: :background, args: %{type: :application_token}},
|
||||
{"@hourly", Mobilizon.Service.Workers.CleanApplicationData,
|
||||
queue: :background, args: %{type: :application_device_activation}}
|
||||
]},
|
||||
{Oban.Plugins.Pruner, max_age: 300}
|
||||
]
|
||||
|
||||
config :hammer,
|
||||
backend: {Hammer.Backend.ETS, [expiry_ms: 60_000 * 60 * 4, cleanup_interval_ms: 60_000 * 10]}
|
||||
|
||||
config :mobilizon, :rich_media,
|
||||
parsers: [
|
||||
Mobilizon.Service.RichMedia.Parsers.OEmbed,
|
||||
|
@ -371,6 +382,10 @@ config :mobilizon, Mobilizon.Service.GlobalSearch.SearchMobilizon,
|
|||
img_src: ["search.joinmobilizon.org"]
|
||||
]
|
||||
|
||||
config :mobilizon, Mobilizon.Service.AntiSpam, service: Mobilizon.Service.AntiSpam.Akismet
|
||||
|
||||
config :mobilizon, Mobilizon.Service.SiteMap, path: "/var/lib/mobilizon/sitemap"
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{config_env()}.exs"
|
||||
|
|
|
@ -16,7 +16,8 @@ config :mobilizon, Mobilizon.Web.Endpoint,
|
|||
watchers: [
|
||||
node: [
|
||||
"node_modules/.bin/vite",
|
||||
cd: Path.expand("../js", __DIR__)
|
||||
"--host",
|
||||
System.get_env("VITE_HOST", "localhost")
|
||||
]
|
||||
]
|
||||
|
||||
|
@ -92,6 +93,9 @@ config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "uploads"
|
|||
|
||||
config :mobilizon, :exports, path: "uploads/exports"
|
||||
|
||||
config :mobilizon, Mobilizon.Service.SiteMap,
|
||||
path: System.get_env("MOBILIZON_SITEMAP_PATH", "priv/static")
|
||||
|
||||
config :tz_world, data_dir: "_build/dev/lib/tz_world/priv"
|
||||
|
||||
config :mobilizon, :anonymous,
|
||||
|
|
|
@ -2,6 +2,28 @@
|
|||
|
||||
import Config
|
||||
|
||||
{:ok, _} = Application.ensure_all_started(:tls_certificate_check)
|
||||
|
||||
loglevels = [
|
||||
:emergency,
|
||||
:alert,
|
||||
:critical,
|
||||
:error,
|
||||
:warning,
|
||||
:notice,
|
||||
:info,
|
||||
:debug
|
||||
]
|
||||
|
||||
loglevel_env = System.get_env("MOBILIZON_LOGLEVEL", "error")
|
||||
|
||||
loglevel =
|
||||
if loglevel_env in Enum.map(loglevels, &to_string/1) do
|
||||
String.to_existing_atom(loglevel_env)
|
||||
else
|
||||
:error
|
||||
end
|
||||
|
||||
listen_ip = System.get_env("MOBILIZON_INSTANCE_LISTEN_IP", "0.0.0.0")
|
||||
|
||||
listen_ip =
|
||||
|
@ -40,16 +62,20 @@ config :mobilizon, Mobilizon.Storage.Repo,
|
|||
database: System.get_env("MOBILIZON_DATABASE_DBNAME", "mobilizon"),
|
||||
hostname: System.get_env("MOBILIZON_DATABASE_HOST", "postgres"),
|
||||
port: System.get_env("MOBILIZON_DATABASE_PORT", "5432"),
|
||||
ssl: System.get_env("MOBILIZON_DATABASE_SSL", "false") == "true",
|
||||
pool_size: 10
|
||||
|
||||
config :logger, level: loglevel
|
||||
|
||||
config :mobilizon, Mobilizon.Web.Email.Mailer,
|
||||
adapter: Swoosh.Adapters.SMTP,
|
||||
relay: System.get_env("MOBILIZON_SMTP_SERVER", "localhost"),
|
||||
port: System.get_env("MOBILIZON_SMTP_PORT", "25"),
|
||||
username: System.get_env("MOBILIZON_SMTP_USERNAME", nil),
|
||||
password: System.get_env("MOBILIZON_SMTP_PASSWORD", nil),
|
||||
tls: :if_available,
|
||||
allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"],
|
||||
tls: System.get_env("MOBILIZON_SMTP_TLS", "if_available"),
|
||||
tls_options:
|
||||
:tls_certificate_check.options(System.get_env("MOBILIZON_SMTP_SERVER", "localhost")),
|
||||
ssl: System.get_env("MOBILIZON_SMTP_SSL", "false"),
|
||||
retries: 1,
|
||||
no_mx_lookups: false,
|
||||
|
@ -77,3 +103,10 @@ config :mobilizon, :exports,
|
|||
|
||||
config :tz_world,
|
||||
data_dir: System.get_env("MOBILIZON_TIMEZONES_DIR", "/var/lib/mobilizon/timezones")
|
||||
|
||||
config :tzdata, :data_dir, System.get_env("MOBILIZON_TZDATA_DIR", "/var/lib/mobilizon/tzdata")
|
||||
|
||||
config :web_push_encryption, :vapid_details,
|
||||
subject: System.get_env("MOBILIZON_WEB_PUSH_ENCRYPTION_SUBJECT", nil),
|
||||
public_key: System.get_env("MOBILIZON_WEB_PUSH_ENCRYPTION_PUBLIC_KEY", nil),
|
||||
private_key: System.get_env("MOBILIZON_WEB_PUSH_ENCRYPTION_PRIVATE_KEY", nil)
|
||||
|
|
|
@ -16,7 +16,9 @@ config :mobilizon, Mobilizon.Web.Endpoint,
|
|||
check_origin: false,
|
||||
# Somehow this can't be merged properly with the dev config so we got this…
|
||||
watchers: [
|
||||
yarn: [cd: Path.expand("../js", __DIR__)]
|
||||
node: [
|
||||
"node_modules/.bin/vite"
|
||||
]
|
||||
]
|
||||
|
||||
config :vite_phx,
|
||||
|
|
|
@ -30,6 +30,7 @@ config :mobilizon, :cldr,
|
|||
"fr",
|
||||
"gd",
|
||||
"gl",
|
||||
"hr",
|
||||
"hu",
|
||||
"id",
|
||||
"it",
|
||||
|
|
|
@ -54,6 +54,11 @@ config :mobilizon, :ldap,
|
|||
bind_uid: System.get_env("LDAP_BIND_UID"),
|
||||
bind_password: System.get_env("LDAP_BIND_PASSWORD")
|
||||
|
||||
# Faster runs in test environment
|
||||
config :argon2_elixir,
|
||||
t_cost: 1,
|
||||
m_cost: 8
|
||||
|
||||
config :mobilizon, Mobilizon.Web.Email.Mailer, adapter: Swoosh.Adapters.Test
|
||||
|
||||
config :mobilizon, Mobilizon.Web.Upload, filters: [], link_name: false
|
||||
|
@ -62,6 +67,9 @@ config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "test/uploads"
|
|||
|
||||
config :mobilizon, :exports, path: "test/uploads/exports"
|
||||
|
||||
config :mobilizon, Mobilizon.Service.SiteMap,
|
||||
path: System.get_env("MOBILIZON_SITEMAP_PATH", "test/sitemap")
|
||||
|
||||
config :tz_world, data_dir: "_build/test/lib/tz_world/priv"
|
||||
|
||||
config :tesla, Mobilizon.Service.HTTP.ActivityPub,
|
||||
|
@ -78,7 +86,7 @@ config :tesla, Mobilizon.Service.HTTP.HostMetaClient,
|
|||
|
||||
config :mobilizon, Mobilizon.Service.Geospatial, service: Mobilizon.Service.Geospatial.Mock
|
||||
|
||||
config :mobilizon, Oban, queues: false, plugins: false
|
||||
config :mobilizon, Oban, testing: :manual
|
||||
|
||||
config :mobilizon, Mobilizon.Web.Auth.Guardian, secret_key: "some secret"
|
||||
|
||||
|
@ -90,6 +98,8 @@ config :junit_formatter, report_dir: "."
|
|||
|
||||
config :mobilizon, :http_security, report_uri: "https://endpoint.com"
|
||||
|
||||
config :mobilizon, Mobilizon.Service.AntiSpam, service: Mobilizon.Service.AntiSpam.Mock
|
||||
|
||||
if System.get_env("DOCKER", "false") == "false" && File.exists?("./config/test.secret.exs") do
|
||||
import_config "test.secret.exs"
|
||||
end
|
||||
|
|
139
default.nix
Normal file
139
default.nix
Normal file
|
@ -0,0 +1,139 @@
|
|||
{ lib
|
||||
, beamPackages
|
||||
, fetchFromGitHub
|
||||
, git
|
||||
, cmake
|
||||
, nixosTests
|
||||
, src
|
||||
, src-config
|
||||
, mobilizon-js
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (beamPackages) mixRelease buildMix;
|
||||
in
|
||||
mixRelease rec {
|
||||
pname = "mobilizon";
|
||||
version = "4.0.2";
|
||||
|
||||
inherit src;
|
||||
|
||||
# See https://github.com/whitfin/cachex/issues/205
|
||||
# This circumvents a startup error for now
|
||||
stripDebug = false;
|
||||
|
||||
nativeBuildInputs = [ git cmake ];
|
||||
|
||||
mixNixDeps = import ./mix.nix {
|
||||
inherit beamPackages lib;
|
||||
overrides = (final: prev:
|
||||
(lib.mapAttrs
|
||||
(_: value: value.override {
|
||||
appConfigPath = src-config;
|
||||
})
|
||||
prev) // {
|
||||
fast_html = prev.fast_html.override {
|
||||
nativeBuildInputs = [ cmake ];
|
||||
};
|
||||
ex_cldr = prev.ex_cldr.overrideAttrs (old: {
|
||||
# We have to use the GitHub sources, as it otherwise tries to download
|
||||
# the locales at build time.
|
||||
src = fetchFromGitHub {
|
||||
owner = "elixir-cldr";
|
||||
repo = "cldr";
|
||||
rev = "v${old.version}";
|
||||
sha256 = assert old.version == "2.37.5";
|
||||
"sha256-T5Qvuo+xPwpgBsqHNZYnTCA4loToeBn1LKTMsDcCdYs=";
|
||||
};
|
||||
postInstall = ''
|
||||
cp $src/priv/cldr/locales/* $out/lib/erlang/lib/ex_cldr-${old.version}/priv/cldr/locales/
|
||||
'';
|
||||
});
|
||||
# Upstream issue: https://github.com/bryanjos/geo_postgis/pull/87
|
||||
geo_postgis = prev.geo_postgis.overrideAttrs (old: {
|
||||
propagatedBuildInputs = old.propagatedBuildInputs ++ [ final.ecto ];
|
||||
});
|
||||
|
||||
# The remainder are Git dependencies (and their deps) that are not supported by mix2nix currently.
|
||||
web_push_encryption = buildMix rec {
|
||||
name = "web_push_encryption";
|
||||
version = "0.3.1";
|
||||
src = fetchFromGitHub {
|
||||
owner = "danhper";
|
||||
repo = "elixir-web-push-encryption";
|
||||
rev = "70f00d06cbd88c9ac382e0ad2539e54448e9d8da";
|
||||
sha256 = "sha256-b4ZMrt/8n2sPUFtCDRTwXS1qWm5VlYdbx8qC0R0boOA=";
|
||||
};
|
||||
beamDeps = with final; [ httpoison jose ];
|
||||
};
|
||||
icalendar = buildMix rec {
|
||||
name = "icalendar";
|
||||
version = "unstable-2022-04-10";
|
||||
src = fetchFromGitHub {
|
||||
owner = "tcitworld";
|
||||
repo = name;
|
||||
rev = "1033d922c82a7223db0ec138e2316557b70ff49f";
|
||||
sha256 = "sha256-N3bJZznNazLewHS4c2B7LP1lgxd1wev+EWVlQ7rOwfU=";
|
||||
};
|
||||
beamDeps = with final; [ mix_test_watch ex_doc timex ];
|
||||
};
|
||||
rajska = buildMix rec {
|
||||
name = "rajska";
|
||||
version = "1.3.3";
|
||||
src = fetchFromGitHub {
|
||||
owner = "tcitworld";
|
||||
repo = name;
|
||||
rev = "0c036448e261e8be6a512581c592fadf48982d84";
|
||||
sha256 = "sha256-4pfply1vTAIT2Xvm3kONmrCK05xKfXFvcb8EKoSCXBE=";
|
||||
};
|
||||
beamDeps = with final; [ ex_doc credo absinthe excoveralls hammer mock ];
|
||||
};
|
||||
exkismet = buildMix rec {
|
||||
name = "exkismet";
|
||||
version = "0.0.3";
|
||||
src = fetchFromGitHub {
|
||||
owner = "tcitworld";
|
||||
repo = name;
|
||||
rev = "8b5485fde00fafbde20f315bec387a77f7358334";
|
||||
sha256 = "sha256-ttgCWoBKU7VTjZJBhZNtqVF4kN7psBr/qOeR65MbTqw=";
|
||||
};
|
||||
beamDeps = with final; [ httpoison ex_doc credo doctor dialyxir ];
|
||||
};
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
preConfigure = ''
|
||||
export LANG=C.UTF-8 # fix elixir locale warning
|
||||
'';
|
||||
|
||||
# Install the compiled js part
|
||||
preBuild =
|
||||
''
|
||||
cp -a "${mobilizon-js}/_napalm-install/priv/static" ./priv
|
||||
chmod 770 -R ./priv
|
||||
'';
|
||||
|
||||
postBuild = ''
|
||||
mix phx.digest --no-deps-check
|
||||
'';
|
||||
|
||||
# Just a hack to reduce path size by 60MB
|
||||
postInstall =
|
||||
let
|
||||
inherit (mixNixDeps) ex_cldr;
|
||||
in
|
||||
''
|
||||
rm -r $out/lib/ex_cldr-${ex_cldr.version}/priv/cldr/locales
|
||||
ln -s ${ex_cldr.src}/priv/cldr/locales $out/lib/ex_cldr-${ex_cldr.version}/priv/cldr/locales
|
||||
'';
|
||||
|
||||
passthru.elixirPackage = beamPackages.elixir;
|
||||
|
||||
meta = with lib; {
|
||||
description = "Mobilizon is an online tool to help manage your events, your profiles and your groups";
|
||||
homepage = "https://joinmobilizon.org/";
|
||||
license = licenses.agpl3Plus;
|
||||
maintainers = with maintainers; [ minijackson erictapen ];
|
||||
};
|
||||
}
|
|
@ -11,7 +11,7 @@ services:
|
|||
MIX_ENV: "test"
|
||||
MOBILIZON_DATABASE_DBNAME: mobilizon_test
|
||||
MOBILIZON_INSTANCE_HOST: mobilizon.test
|
||||
command: "mix test"
|
||||
command: "mix prepare_test && mix test"
|
||||
volumes:
|
||||
pgdata:
|
||||
.:
|
||||
|
|
|
@ -19,6 +19,7 @@ services:
|
|||
- ".:/app"
|
||||
ports:
|
||||
- 4000:4000
|
||||
- 5173:5173
|
||||
depends_on:
|
||||
- postgres
|
||||
environment:
|
||||
|
@ -35,6 +36,7 @@ services:
|
|||
MOBILIZON_DATABASE_DBNAME: ${POSTGRES_DB}
|
||||
MOBILIZON_DATABASE_HOST: postgres
|
||||
MOBILIZON_DATABASE_PORT: ${POSTGRES_PORT}
|
||||
VITE_HOST: ${VITE_HOST:-localhost}
|
||||
command: sh -c "mix phx.server"
|
||||
volumes:
|
||||
pgdata:
|
||||
|
|
|
@ -1,21 +1,27 @@
|
|||
FROM elixir as build
|
||||
ARG IMAGE="elixir:1.15"
|
||||
|
||||
FROM ${IMAGE} as build
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
ENV MIX_ENV prod
|
||||
# ENV LANG en_US.UTF-8
|
||||
ARG APP_ASSET
|
||||
|
||||
# Fix qemu segfault on arm64
|
||||
# See https://github.com/plausible/analytics/pull/2879 and https://github.com/erlang/otp/pull/6340
|
||||
ARG ERL_FLAGS=""
|
||||
ENV ERL_FLAGS=$ERL_FLAGS
|
||||
|
||||
# Set the right versions
|
||||
ENV ELIXIR_VERSION latest
|
||||
ENV ERLANG_VERSION latest
|
||||
ENV NODE_VERSION 16
|
||||
ENV NODE_VERSION 20
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update -yq && apt-get install -yq build-essential cmake postgresql-client git curl gnupg unzip exiftool webp imagemagick gifsicle
|
||||
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# # Install Node & yarn
|
||||
# # Install Node
|
||||
# RUN curl -sL https://deb.nodesource.com/setup_16.x | bash && apt-get install nodejs -yq
|
||||
# RUN npm install -g yarn
|
||||
|
||||
# Install build tools
|
||||
RUN source /root/.bashrc && \
|
||||
|
@ -27,8 +33,8 @@ COPY ./ /mobilizon
|
|||
WORKDIR /mobilizon
|
||||
|
||||
# # Build front-end
|
||||
# RUN yarn --cwd "js" install --frozen-lockfile
|
||||
# RUN yarn --cwd "js" run build
|
||||
# RUN npm install
|
||||
# RUN npm run build
|
||||
|
||||
# Elixir release
|
||||
RUN source /root/.bashrc && \
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
# First build the application assets
|
||||
FROM node:16-alpine as assets
|
||||
FROM node:20-alpine as assets
|
||||
|
||||
RUN apk add --no-cache python3 build-base libwebp-tools bash imagemagick ncurses
|
||||
WORKDIR /build
|
||||
COPY js .
|
||||
COPY . .
|
||||
|
||||
# Network timeout because it's slow when cross-compiling
|
||||
RUN yarn install --network-timeout 100000 \
|
||||
&& yarn run build
|
||||
RUN npm install && npm run build
|
||||
|
||||
# Then, build the application binary
|
||||
FROM elixir:1.14-alpine AS builder
|
||||
FROM elixir:1.15-alpine AS builder
|
||||
|
||||
# Fix qemu segfault on arm64
|
||||
# See https://github.com/plausible/analytics/pull/2879 and https://github.com/erlang/otp/pull/6340
|
||||
ARG ERL_FLAGS=""
|
||||
ENV ERL_FLAGS=$ERL_FLAGS
|
||||
|
||||
RUN apk add --no-cache build-base git cmake
|
||||
|
||||
|
@ -26,7 +30,7 @@ COPY config/config.exs config/prod.exs ./config/
|
|||
COPY config/docker.exs ./config/runtime.exs
|
||||
COPY rel ./rel
|
||||
COPY support ./support
|
||||
COPY --from=assets ./priv/static ./priv/static
|
||||
COPY --from=assets /build/priv/static ./priv/static
|
||||
|
||||
RUN mix phx.digest.clean --all && mix phx.digest && mix release
|
||||
|
||||
|
@ -46,17 +50,24 @@ LABEL org.opencontainers.image.title="mobilizon" \
|
|||
org.opencontainers.image.revision=$VCS_REF \
|
||||
org.opencontainers.image.created=$BUILD_DATE
|
||||
|
||||
RUN apk add --no-cache curl openssl ca-certificates ncurses-libs file postgresql-client libgcc libstdc++ imagemagick python3 py3-pip py3-pillow py3-cffi py3-brotli gcc g++ musl-dev python3-dev pango libxslt-dev ttf-cantarell openssl1.1-compat
|
||||
RUN pip install weasyprint pyexcel-ods3
|
||||
RUN apk add --no-cache curl openssl ca-certificates ncurses-libs file postgresql-client libgcc libstdc++ imagemagick python3 py3-pip py3-pillow py3-cffi py3-brotli gcc g++ musl-dev python3-dev pango libxslt-dev ttf-cantarell
|
||||
RUN pip --no-cache-dir install --break-system-packages weasyprint pyexcel-ods3
|
||||
|
||||
# Create every data directory
|
||||
RUN mkdir -p /var/lib/mobilizon/uploads && chown nobody:nobody /var/lib/mobilizon/uploads
|
||||
RUN mkdir -p /var/lib/mobilizon/timezones && chown nobody:nobody /var/lib/mobilizon/timezones
|
||||
RUN mkdir -p /var/lib/mobilizon/tzdata && chown nobody:nobody /var/lib/mobilizon/tzdata
|
||||
RUN mkdir -p /var/lib/mobilizon/sitemap && chown nobody:nobody /var/lib/mobilizon/sitemap
|
||||
RUN mkdir -p /var/lib/mobilizon/uploads/exports/{csv,pdf,ods} && chown -R nobody:nobody /var/lib/mobilizon/uploads/exports
|
||||
RUN mkdir -p /var/lib/mobilizon/timezones
|
||||
RUN curl -L 'https://packages.joinmobilizon.org/tz_world/timezones-geodata.dets' -o /var/lib/mobilizon/timezones/timezones-geodata.dets
|
||||
RUN chown nobody:nobody /var/lib/mobilizon/timezones
|
||||
|
||||
# Create configuration directory
|
||||
RUN mkdir -p /etc/mobilizon && chown nobody:nobody /etc/mobilizon
|
||||
|
||||
USER nobody
|
||||
|
||||
# Get timezone geodata
|
||||
RUN curl -L 'https://packages.joinmobilizon.org/tz_world/timezones-geodata.dets' -o /var/lib/mobilizon/timezones/timezones-geodata.dets
|
||||
|
||||
EXPOSE 4000
|
||||
|
||||
ENV MOBILIZON_DOCKER=true
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
set -e
|
||||
|
||||
echo "-- Waiting for database..."
|
||||
while ! pg_isready -U ${MOBILIZON_DATABASE_USERNAME} -d postgres://${MOBILIZON_DATABASE_HOST}:5432/${MOBILIZON_DATABASE_DBNAME} -t 1; do
|
||||
while ! pg_isready -U ${MOBILIZON_DATABASE_USERNAME} -d postgres://${MOBILIZON_DATABASE_HOST}:${MOBILIZON_DATABASE_PORT:-5432}/${MOBILIZON_DATABASE_DBNAME} -t 1; do
|
||||
sleep 1s
|
||||
done
|
||||
|
||||
PGPASSWORD=$MOBILIZON_DATABASE_PASSWORD psql -U $MOBILIZON_DATABASE_USERNAME -d $MOBILIZON_DATABASE_DBNAME -h $MOBILIZON_DATABASE_HOST -c 'CREATE EXTENSION IF NOT EXISTS pg_trgm;'
|
||||
PGPASSWORD=$MOBILIZON_DATABASE_PASSWORD psql -U $MOBILIZON_DATABASE_USERNAME -d $MOBILIZON_DATABASE_DBNAME -h $MOBILIZON_DATABASE_HOST -c 'CREATE EXTENSION IF NOT EXISTS unaccent;'
|
||||
PGPASSWORD=$MOBILIZON_DATABASE_PASSWORD psql -U $MOBILIZON_DATABASE_USERNAME -d $MOBILIZON_DATABASE_DBNAME -h $MOBILIZON_DATABASE_HOST -p ${MOBILIZON_DATABASE_PORT:-5432} -c 'CREATE EXTENSION IF NOT EXISTS pg_trgm;'
|
||||
PGPASSWORD=$MOBILIZON_DATABASE_PASSWORD psql -U $MOBILIZON_DATABASE_USERNAME -d $MOBILIZON_DATABASE_DBNAME -h $MOBILIZON_DATABASE_HOST -p ${MOBILIZON_DATABASE_PORT:-5432} -c 'CREATE EXTENSION IF NOT EXISTS unaccent;'
|
||||
|
||||
echo "-- Running migrations..."
|
||||
/bin/mobilizon_ctl migrate
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
FROM elixir:latest
|
||||
LABEL maintainer="Thomas Citharel <tcit@tcit.fr>"
|
||||
LABEL maintainer="Thomas Citharel <thomas.citharel@framasoft.org>"
|
||||
|
||||
ENV REFRESHED_AT=2022-09-20
|
||||
RUN apt-get update -yq && apt-get install -yq build-essential inotify-tools postgresql-client git curl gnupg xvfb libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 cmake exiftool python3-pip python3-setuptools
|
||||
RUN curl -sL https://deb.nodesource.com/setup_16.x | bash && apt-get install nodejs -yq
|
||||
RUN npm install -g yarn wait-on
|
||||
ENV REFRESHED_AT=2023-11-20
|
||||
RUN apt-get update -yq && apt-get install -yq ca-certificates build-essential inotify-tools postgresql-client git curl gnupg xvfb libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 cmake exiftool python3-pip python3-setuptools
|
||||
RUN mkdir -p /etc/apt/keyrings && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && apt-get update && apt-get install nodejs -yq
|
||||
RUN npm install -g wait-on
|
||||
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
RUN mix local.hex --force && mix local.rebar --force
|
||||
RUN pip3 install -Iv weasyprint pyexcel_ods3
|
||||
RUN pip3 --no-cache-dir install -Iv weasyprint pyexcel_ods3
|
||||
RUN curl https://dbip.mirror.framasoft.org/files/dbip-city-lite-latest.mmdb --output GeoLite2-City.mmdb -s && mkdir -p /usr/share/GeoIP && mv GeoLite2-City.mmdb /usr/share/GeoIP/
|
||||
|
|
30
docs/dev.md
Normal file
30
docs/dev.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Documentation for developpers
|
||||
|
||||
_This file is a summary of the documentation for developpers. As explained in [CONTRIBUTING.md](../CONTRIBUTING.md), the main documentation is available at <https://docs.joinmobilizon.org/contribute/>_
|
||||
|
||||
## Technologies
|
||||
|
||||
Mobilizon is an app that uses:
|
||||
* [Elixir](https://elixir-lang.org/) for backend,
|
||||
* [VueJS](https://vuejs.org/) for front-end
|
||||
* [GraphQL](https://graphql.org/) as it's API layer
|
||||
|
||||
[GraphQL](https://graphql.org/) is managed using:
|
||||
* [Absinthe](https://absinthe-graphql.org/) on the backend
|
||||
* [VueApollo](https://apollo.vuejs.org/) on the front-end.
|
||||
|
||||
[UI](https://en.wikipedia.org/wiki/User_interface) is handled with [Tailwind](https://tailwindcss.com/) and [Oruga](https://oruga.io/).
|
||||
|
||||
## Structure of sources
|
||||
|
||||
* `config` backend compile-time and runtime configuration
|
||||
* `docker` 🐳
|
||||
* `src` Front-end
|
||||
* `lib/federation` Handling all the federation stuff (sending and receving activities, converting activities, signatures, helpers…)
|
||||
* `lib/graphql/schema` The schema declarations for the GraphQL API
|
||||
* `lib/graphql/resolvers` The logic behind the GraphQL API
|
||||
* `lib/mix/tasks` CLI
|
||||
* `lib/mobilizon` model structures, database queries
|
||||
* `lib/service` various services
|
||||
* `lib/web` controllers, middlewares, auth-related stuff
|
||||
* `test` tests
|
0
js/env.d.ts → env.d.ts
vendored
0
js/env.d.ts → env.d.ts
vendored
77
flake.lock
77
flake.lock
|
@ -1,12 +1,66 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1701680307,
|
||||
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"napalm": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1703102458,
|
||||
"narHash": "sha256-3pOV731qi34Q2G8e2SqjUXqnftuFrbcq+NdagEZXISo=",
|
||||
"owner": "nix-community",
|
||||
"repo": "napalm",
|
||||
"rev": "edcb26c266ca37c9521f6a97f33234633cbec186",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "napalm",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-filter": {
|
||||
"locked": {
|
||||
"lastModified": 1705332318,
|
||||
"narHash": "sha256-kcw1yFeJe9N4PjQji9ZeX47jg0p9A0DuU4djKvg1a7I=",
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"rev": "3449dc925982ad46246cfc36469baf66e1b64f17",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "nix-filter",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1678819893,
|
||||
"narHash": "sha256-lfA6WGdxPsPkBK5Y19ltr5Sn7v7MlT+jpZ4nUgco0Xs=",
|
||||
"lastModified": 1706550542,
|
||||
"narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "7067edc68c035e21780259ed2d26e1f164addaa2",
|
||||
"rev": "97b17f32362e475016f942bbdfda4a4a72a8a652",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -18,8 +72,25 @@
|
|||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"napalm": "napalm",
|
||||
"nix-filter": "nix-filter",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
|
261
flake.nix
261
flake.nix
|
@ -1,28 +1,241 @@
|
|||
{
|
||||
description = "A very basic flake";
|
||||
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
outputs = { self, nixpkgs }: {
|
||||
|
||||
packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
|
||||
|
||||
packages.x86_64-linux.default = self.packages.x86_64-linux.hello;
|
||||
|
||||
devShells.x86_64-linux.default = let
|
||||
pkgs = import nixpkgs { system = "x86_64-linux"; };
|
||||
in pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
elixir
|
||||
rebar3
|
||||
cmake
|
||||
imagemagick
|
||||
|
||||
mix2nix
|
||||
yarn2nix
|
||||
yarn
|
||||
];
|
||||
};
|
||||
description = "Mobilizon fork for potsda.mn";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
nix-filter.url = "github:numtide/nix-filter";
|
||||
napalm.url = "github:nix-community/napalm";
|
||||
napalm.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, nix-filter, napalm }:
|
||||
let
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs
|
||||
[ "x86_64-linux" "aarch64-linux" ]
|
||||
(system: f system);
|
||||
nixpkgsFor = forAllSystems (
|
||||
system:
|
||||
import nixpkgs { inherit system; }
|
||||
);
|
||||
filter = nix-filter.lib;
|
||||
in
|
||||
{
|
||||
|
||||
packages = forAllSystems (system:
|
||||
let
|
||||
pkgs = nixpkgsFor.${system};
|
||||
# Directories that are neither needed for building the frontend nor the backend.
|
||||
# For better build caching.
|
||||
unrelatedDirs = [
|
||||
"flake.lock"
|
||||
(filter.matchExt "nix")
|
||||
"docs"
|
||||
"docker"
|
||||
"docker-compose.test.yml"
|
||||
"docker-compose.yml"
|
||||
"generate-test-data"
|
||||
];
|
||||
in
|
||||
{
|
||||
mobilizon = pkgs.callPackage ./. {
|
||||
src = filter {
|
||||
root = ./.;
|
||||
exclude = [
|
||||
"src"
|
||||
(filter.matchExt "js")
|
||||
(filter.matchExt "ts")
|
||||
(filter.matchExt "json")
|
||||
"tests"
|
||||
"scripts"
|
||||
"public"
|
||||
] ++ unrelatedDirs;
|
||||
};
|
||||
src-config = ./config;
|
||||
mobilizon-js = self.packages."${system}".mobilizon-frontend;
|
||||
};
|
||||
|
||||
mobilizon-frontend =
|
||||
let
|
||||
nodejs = pkgs.nodejs-18_x;
|
||||
in
|
||||
napalm.legacyPackages."${system}".buildPackage
|
||||
(filter {
|
||||
root = ./.;
|
||||
exclude = [
|
||||
"lib"
|
||||
"config"
|
||||
"test"
|
||||
"rel"
|
||||
"support"
|
||||
] ++ unrelatedDirs;
|
||||
})
|
||||
{
|
||||
inherit nodejs;
|
||||
nativeBuildInputs = [ pkgs.imagemagick ];
|
||||
npmCommands = [ "npm install" "npm run build" ];
|
||||
};
|
||||
|
||||
default = self.packages."${system}".mobilizon;
|
||||
|
||||
# Update local Mobilizon definition
|
||||
update =
|
||||
pkgs.writeShellScriptBin "update" ''
|
||||
set -eou pipefail
|
||||
|
||||
${pkgs.mix2nix}/bin/mix2nix ./mix.lock > mix.nix
|
||||
'';
|
||||
});
|
||||
|
||||
devShells = forAllSystems (system:
|
||||
let pkgs = nixpkgsFor.${system};
|
||||
in {
|
||||
default =
|
||||
pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
elixir
|
||||
mix2nix
|
||||
cmake
|
||||
imagemagick
|
||||
nodejs-18_x
|
||||
];
|
||||
};
|
||||
});
|
||||
|
||||
overlays.default = final: prev: {
|
||||
inherit (self.packages."${prev.system}") mobilizon;
|
||||
};
|
||||
|
||||
checks = forAllSystems (system: {
|
||||
inherit (self.packages.${system}) mobilizon update;
|
||||
nixosTest =
|
||||
let
|
||||
pkgsMobilizon = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [ self.overlays.default ];
|
||||
};
|
||||
certs = import "${nixpkgs}/nixos/tests/common/acme/server/snakeoil-certs.nix";
|
||||
test = import ./nixos-test.nix { inherit certs; };
|
||||
in
|
||||
pkgsMobilizon.nixosTest test;
|
||||
});
|
||||
|
||||
lib = {
|
||||
# Patch the logos in the source tree of a mobilizon-frontend package before building.
|
||||
# Can be used to construct the argument for .overrideAttrs on mobilizon-frontend.
|
||||
mobilizonLogosOverride = icons:
|
||||
let
|
||||
inherit (icons) logo favicon;
|
||||
in
|
||||
old: {
|
||||
postPatch = ''
|
||||
cp '${logo}' src/assets/logo.svg
|
||||
|
||||
magick convert \
|
||||
-resize x16 \
|
||||
-gravity center \
|
||||
-crop 16x16+0+0 \
|
||||
-flatten \
|
||||
-colors 256 \
|
||||
'${favicon}' \
|
||||
public/img/icons/favicon-16x16.png
|
||||
|
||||
magick convert \
|
||||
-resize x32 \
|
||||
-gravity center \
|
||||
-crop 32x32+0+0 \
|
||||
-flatten \
|
||||
-colors 256 \
|
||||
'${favicon}' \
|
||||
public/img/icons/favicon-32x32.png
|
||||
|
||||
|
||||
magick convert \
|
||||
-resize x16 \
|
||||
-gravity center \
|
||||
-crop 16x16+0+0 \
|
||||
-flatten \
|
||||
-colors 256 \
|
||||
'${favicon}' \
|
||||
favicon-16x16.ico
|
||||
|
||||
magick convert \
|
||||
-resize x32 \
|
||||
-gravity center \
|
||||
-crop 32x32+0+0 \
|
||||
-flatten \
|
||||
-colors 256 \
|
||||
'${favicon}' \
|
||||
favicon-32x32.ico
|
||||
|
||||
magick convert \
|
||||
-resize x48 \
|
||||
-gravity center \
|
||||
-crop 48x48+0+0 \
|
||||
-flatten \
|
||||
-colors 256 \
|
||||
'${favicon}' \
|
||||
favicon-48x48.ico
|
||||
|
||||
magick convert \
|
||||
favicon-16x16.ico \
|
||||
favicon-32x32.ico \
|
||||
favicon-48x48.ico \
|
||||
public/favicon.ico
|
||||
|
||||
rm favicon-16x16.ico favicon-32x32.ico favicon-48x48.ico
|
||||
|
||||
cp '${favicon}' public/img/icons/favicon.svg
|
||||
cp '${favicon}' public/img/icons/safari-pinned-tab.svg
|
||||
|
||||
magick convert \
|
||||
'${favicon}' \
|
||||
-gravity center \
|
||||
-extent 630x350 \
|
||||
public/img/mobilizon_default_card.png
|
||||
|
||||
magick convert \
|
||||
-background '#e08c96' \
|
||||
'${logo}' \
|
||||
-resize 366x108 \
|
||||
public/img/mobilizon_logo.png
|
||||
|
||||
'' + nixpkgs.lib.concatMapStrings
|
||||
({ resize, filename }: ''
|
||||
magick convert \
|
||||
-resize x${resize} \
|
||||
'${favicon}' \
|
||||
public/img/icons/${filename}
|
||||
|
||||
'')
|
||||
[
|
||||
{ resize = "180"; filename = "apple-touch-icon.png"; }
|
||||
{ resize = "180"; filename = "apple-touch-icon-180x180.png"; }
|
||||
{ resize = "152"; filename = "apple-touch-icon-152x152.png"; }
|
||||
{ resize = "120"; filename = "apple-touch-icon-120x120.png"; }
|
||||
{ resize = "76"; filename = "apple-touch-icon-76x76.png"; }
|
||||
{ resize = "60"; filename = "apple-touch-icon-60x60.png"; }
|
||||
{ resize = "192"; filename = "android-chrome-192x192.png"; }
|
||||
{ resize = "512"; filename = "android-chrome-512x512.png"; }
|
||||
{ resize = "192"; filename = "android-chrome-maskable-192x192.png"; }
|
||||
{ resize = "512"; filename = "android-chrome-maskable-512x512.png"; }
|
||||
{ resize = "128"; filename = "badge-128x128.png"; }
|
||||
{ resize = "144"; filename = "icon-144x144.png"; }
|
||||
{ resize = "168"; filename = "icon-168x168.png"; }
|
||||
{ resize = "256"; filename = "icon-256x256.png"; }
|
||||
{ resize = "48"; filename = "icon-48x48.png"; }
|
||||
{ resize = "72"; filename = "icon-72x72.png"; }
|
||||
{ resize = "96"; filename = "icon-96x96.png"; }
|
||||
{ resize = "144"; filename = "msapplication-icon-144x144.png"; }
|
||||
{ resize = "150"; filename = "mstile-150x150.png"; }
|
||||
{ resize = "192"; filename = "android-chrome-192x192.png"; }
|
||||
{ resize = "512"; filename = "android-chrome-512x512.png"; }
|
||||
{ resize = "192"; filename = "android-chrome-maskable-192x192.png"; }
|
||||
{ resize = "512"; filename = "android-chrome-maskable-512x512.png"; }
|
||||
];
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ export default defineConfig({
|
|||
plugins: [HstVue()],
|
||||
setupFile: path.resolve(__dirname, "./src/histoire.setup.ts"),
|
||||
viteNodeInlineDeps: [/date-fns/],
|
||||
// viteIgnorePlugins: ['vite-plugin-pwa', 'vite-plugin-pwa:build', 'vite-plugin-pwa:info'],
|
||||
tree: {
|
||||
groups: [
|
||||
{
|
27
js/.gitignore
vendored
27
js/.gitignore
vendored
|
@ -1,27 +0,0 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
/coverage
|
||||
stats.html
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
|
@ -1,2 +0,0 @@
|
|||
src/i18n/*.json
|
||||
coverage/
|
|
@ -1,94 +0,0 @@
|
|||
<template>
|
||||
<div class="items">
|
||||
<button
|
||||
class="item"
|
||||
:class="{ 'is-selected': index === selectedIndex }"
|
||||
v-for="(item, index) in items"
|
||||
:key="index"
|
||||
@click="selectItem(index)"
|
||||
>
|
||||
<actor-inline :actor="item" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { usernameWithDomain } from "@/types/actor/actor.model";
|
||||
import { IPerson } from "@/types/actor";
|
||||
import ActorInline from "../../components/Account/ActorInline.vue";
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
items: IPerson[];
|
||||
command: ({ id }: { id: string }) => any;
|
||||
}>();
|
||||
|
||||
// @Prop({ type: Function, required: true }) command!: any;
|
||||
|
||||
const selectedIndex = ref(0);
|
||||
|
||||
watch(props.items, () => {
|
||||
selectedIndex.value = 0;
|
||||
});
|
||||
|
||||
// const onKeyDown = ({ event }: { event: KeyboardEvent }): boolean => {
|
||||
// if (event.key === "ArrowUp") {
|
||||
// upHandler();
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// if (event.key === "ArrowDown") {
|
||||
// downHandler();
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// if (event.key === "Enter") {
|
||||
// enterHandler();
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// return false;
|
||||
// };
|
||||
|
||||
// const upHandler = (): void => {
|
||||
// selectedIndex.value =
|
||||
// (selectedIndex.value + props.items.length - 1) % props.items.length;
|
||||
// };
|
||||
|
||||
// const downHandler = (): void => {
|
||||
// selectedIndex.value = (selectedIndex.value + 1) % props.items.length;
|
||||
// };
|
||||
|
||||
// const enterHandler = (): void => {
|
||||
// selectItem(selectedIndex.value);
|
||||
// };
|
||||
|
||||
const selectItem = (index: number): void => {
|
||||
const item = props.items[index];
|
||||
|
||||
if (item) {
|
||||
props.command({ id: usernameWithDomain(item) });
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.items {
|
||||
position: relative;
|
||||
border-radius: 0.25rem;
|
||||
background: white;
|
||||
color: rgba(black, 0.8);
|
||||
overflow: hidden;
|
||||
font-size: 0.9rem;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0px 10px 20px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
</style>
|
|
@ -1,385 +0,0 @@
|
|||
<template>
|
||||
<div class="address-autocomplete">
|
||||
<div class="">
|
||||
<o-field
|
||||
:label-for="id"
|
||||
:message="fieldErrors"
|
||||
:variant="fieldErrors ? 'danger' : ''"
|
||||
class="!-mt-2"
|
||||
:labelClass="labelClass"
|
||||
>
|
||||
<template #label>
|
||||
{{ actualLabel }}
|
||||
<span v-if="gettingLocation">{{ t("Getting location") }}</span>
|
||||
</template>
|
||||
<p class="control" v-if="canShowLocateMeButton">
|
||||
<o-loading
|
||||
:full-page="false"
|
||||
v-model:active="gettingLocation"
|
||||
:can-cancel="false"
|
||||
:container="mapMarker?.$el"
|
||||
/>
|
||||
<o-button
|
||||
ref="mapMarker"
|
||||
icon-right="map-marker"
|
||||
@click="locateMe"
|
||||
:title="t('Use my location')"
|
||||
/>
|
||||
</p>
|
||||
<o-autocomplete
|
||||
:data="addressData"
|
||||
v-model="queryText"
|
||||
:placeholder="placeholderWithDefault"
|
||||
:customFormatter="(elem: IAddress) => addressFullName(elem)"
|
||||
:debounceTyping="debounceDelay"
|
||||
@typing="asyncData"
|
||||
:icon="canShowLocateMeButton ? null : 'map-marker'"
|
||||
expanded
|
||||
@select="updateSelected"
|
||||
:id="id"
|
||||
:disabled="disabled"
|
||||
dir="auto"
|
||||
class="!mt-0"
|
||||
>
|
||||
<template #default="{ option }">
|
||||
<p class="flex gap-1">
|
||||
<o-icon :icon="addressToPoiInfos(option).poiIcon.icon" />
|
||||
<b>{{ addressToPoiInfos(option).name }}</b>
|
||||
</p>
|
||||
<small>{{ addressToPoiInfos(option).alternativeName }}</small>
|
||||
</template>
|
||||
<template #empty>
|
||||
<span v-if="isFetching">{{ t("Searching…") }}</span>
|
||||
<div v-else-if="queryText.length >= 3" class="enabled">
|
||||
<span>{{
|
||||
t('No results for "{queryText}"', { queryText })
|
||||
}}</span>
|
||||
<span>{{
|
||||
t(
|
||||
"You can try another search term or drag and drop the marker on the map",
|
||||
{
|
||||
queryText,
|
||||
}
|
||||
)
|
||||
}}</span>
|
||||
<!-- <p class="control" @click="openNewAddressModal">-->
|
||||
<!-- <button type="button" class="button is-primary">{{ t('Add') }}</button>-->
|
||||
<!-- </p>-->
|
||||
</div>
|
||||
</template>
|
||||
</o-autocomplete>
|
||||
<o-button
|
||||
:disabled="!queryText"
|
||||
@click="resetAddress"
|
||||
class="reset-area"
|
||||
icon-left="close"
|
||||
:title="t('Clear address field')"
|
||||
/>
|
||||
</o-field>
|
||||
<div
|
||||
class="mt-2 p-2 rounded-lg shadow-md dark:bg-violet-3"
|
||||
v-if="!hideSelected && (selected?.originId || selected?.url)"
|
||||
>
|
||||
<div class="">
|
||||
<address-info
|
||||
:address="selected"
|
||||
:show-icon="true"
|
||||
:show-timezone="true"
|
||||
:user-timezone="userTimezone"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="map" v-if="!hideMap && selected && selected.geom">
|
||||
<map-leaflet
|
||||
:coords="selected.geom"
|
||||
:marker="{
|
||||
text: [
|
||||
addressToPoiInfos(selected).name,
|
||||
addressToPoiInfos(selected).alternativeName,
|
||||
],
|
||||
icon: addressToPoiInfos(selected).poiIcon.icon,
|
||||
}"
|
||||
:updateDraggableMarkerCallback="reverseGeoCode"
|
||||
:options="{ zoom: mapDefaultZoom }"
|
||||
:readOnly="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { LatLng } from "leaflet";
|
||||
import {
|
||||
Address,
|
||||
IAddress,
|
||||
addressFullName,
|
||||
addressToPoiInfos,
|
||||
} from "../../types/address.model";
|
||||
import AddressInfo from "../../components/Address/AddressInfo.vue";
|
||||
import { computed, ref, watch, defineAsyncComponent } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useGeocodingAutocomplete } from "@/composition/apollo/config";
|
||||
import { ADDRESS } from "@/graphql/address";
|
||||
import { useReverseGeocode } from "@/composition/apollo/address";
|
||||
import { useLazyQuery } from "@vue/apollo-composable";
|
||||
import { AddressSearchType } from "@/types/enums";
|
||||
const MapLeaflet = defineAsyncComponent(
|
||||
() => import("@/components/LeafletMap.vue")
|
||||
);
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: IAddress | null;
|
||||
defaultText?: string | null;
|
||||
label?: string;
|
||||
labelClass?: string;
|
||||
userTimezone?: string;
|
||||
disabled?: boolean;
|
||||
hideMap?: boolean;
|
||||
hideSelected?: boolean;
|
||||
placeholder?: string;
|
||||
resultType?: AddressSearchType;
|
||||
}>(),
|
||||
{
|
||||
labelClass: "",
|
||||
defaultText: "",
|
||||
disabled: false,
|
||||
hideMap: false,
|
||||
hideSelected: false,
|
||||
}
|
||||
);
|
||||
|
||||
// const addressModalActive = ref(false);
|
||||
|
||||
const componentId = 0;
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
const gettingLocationError = ref<string | null>(null);
|
||||
const gettingLocation = ref(false);
|
||||
const mapDefaultZoom = ref(15);
|
||||
|
||||
const addressData = ref<IAddress[]>([]);
|
||||
|
||||
const selected = ref<IAddress | null>(null);
|
||||
|
||||
const isFetching = ref(false);
|
||||
|
||||
const mapMarker = ref();
|
||||
|
||||
const placeholderWithDefault = computed(
|
||||
() => props.placeholder ?? t("e.g. 10 Rue Jangot")
|
||||
);
|
||||
|
||||
// created(): void {
|
||||
// componentId += 1;
|
||||
// }
|
||||
|
||||
const id = computed((): string => {
|
||||
return `full-address-autocomplete-${componentId}`;
|
||||
});
|
||||
|
||||
const modelValue = computed(() => props.modelValue);
|
||||
|
||||
watch(modelValue, () => {
|
||||
if (!modelValue.value) return;
|
||||
selected.value = modelValue.value;
|
||||
});
|
||||
|
||||
const updateSelected = (option: IAddress): void => {
|
||||
if (option == null) return;
|
||||
selected.value = option;
|
||||
emit("update:modelValue", selected.value);
|
||||
};
|
||||
|
||||
// const resetPopup = (): void => {
|
||||
// selected.value = new Address();
|
||||
// };
|
||||
|
||||
// const openNewAddressModal = (): void => {
|
||||
// resetPopup();
|
||||
// addressModalActive.value = true;
|
||||
// };
|
||||
|
||||
const checkCurrentPosition = (e: LatLng): boolean => {
|
||||
if (!selected.value?.geom) return false;
|
||||
const lat = parseFloat(selected.value?.geom.split(";")[1]);
|
||||
const lon = parseFloat(selected.value?.geom.split(";")[0]);
|
||||
|
||||
return e.lat === lat && e.lng === lon;
|
||||
};
|
||||
|
||||
const { t, locale } = useI18n({ useScope: "global" });
|
||||
|
||||
const actualLabel = computed((): string => {
|
||||
return props.label ?? t("Find an address");
|
||||
});
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
const canShowLocateMeButton = computed((): boolean => {
|
||||
return window.isSecureContext;
|
||||
});
|
||||
|
||||
const { geocodingAutocomplete } = useGeocodingAutocomplete();
|
||||
|
||||
const debounceDelay = computed(() =>
|
||||
geocodingAutocomplete.value === true ? 200 : 2000
|
||||
);
|
||||
|
||||
const { onResult: onAddressSearchResult, load: searchAddress } = useLazyQuery<{
|
||||
searchAddress: IAddress[];
|
||||
}>(ADDRESS);
|
||||
|
||||
onAddressSearchResult((result) => {
|
||||
if (result.loading) return;
|
||||
const { data } = result;
|
||||
console.debug("onAddressSearchResult", data.searchAddress);
|
||||
addressData.value = data.searchAddress;
|
||||
isFetching.value = false;
|
||||
});
|
||||
|
||||
const searchQuery = ref("");
|
||||
|
||||
const asyncData = async (query: string): Promise<void> => {
|
||||
if (!query.length) {
|
||||
addressData.value = [];
|
||||
selected.value = new Address();
|
||||
return;
|
||||
}
|
||||
|
||||
if (query.length < 3) {
|
||||
addressData.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
isFetching.value = true;
|
||||
|
||||
searchQuery.value = query;
|
||||
|
||||
searchAddress(undefined, {
|
||||
query: searchQuery.value,
|
||||
locale: locale,
|
||||
type: props.resultType,
|
||||
});
|
||||
};
|
||||
|
||||
const queryText = computed({
|
||||
get() {
|
||||
return (
|
||||
(selected.value ? addressFullName(selected.value) : props.defaultText) ??
|
||||
""
|
||||
);
|
||||
},
|
||||
set(text) {
|
||||
if (text === "" && selected.value?.id) {
|
||||
console.debug("doing reset");
|
||||
resetAddress();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const resetAddress = (): void => {
|
||||
emit("update:modelValue", null);
|
||||
selected.value = new Address();
|
||||
};
|
||||
|
||||
const locateMe = async (): Promise<void> => {
|
||||
gettingLocation.value = true;
|
||||
gettingLocationError.value = null;
|
||||
try {
|
||||
const location = await getLocation();
|
||||
mapDefaultZoom.value = 12;
|
||||
reverseGeoCode(
|
||||
new LatLng(location.coords.latitude, location.coords.longitude),
|
||||
12
|
||||
);
|
||||
} catch (e: any) {
|
||||
gettingLocationError.value = e.message;
|
||||
}
|
||||
gettingLocation.value = false;
|
||||
};
|
||||
|
||||
const { onResult: onReverseGeocodeResult, load: loadReverseGeocode } =
|
||||
useReverseGeocode();
|
||||
|
||||
onReverseGeocodeResult((result) => {
|
||||
if (result.loading !== false) return;
|
||||
const { data } = result;
|
||||
addressData.value = data.reverseGeocode;
|
||||
|
||||
if (addressData.value.length > 0) {
|
||||
const defaultAddress = addressData.value[0];
|
||||
selected.value = defaultAddress;
|
||||
emit("update:modelValue", selected.value);
|
||||
}
|
||||
});
|
||||
|
||||
const reverseGeoCode = (e: LatLng, zoom: number) => {
|
||||
// If the position has been updated through autocomplete selection, no need to geocode it!
|
||||
if (checkCurrentPosition(e)) return;
|
||||
|
||||
loadReverseGeocode(undefined, {
|
||||
latitude: e.lat,
|
||||
longitude: e.lng,
|
||||
zoom,
|
||||
locale: locale as unknown as string,
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const getLocation = async (): Promise<GeolocationPosition> => {
|
||||
let errorMessage = t("Failed to get location.");
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!("geolocation" in navigator)) {
|
||||
reject(new Error(errorMessage as string));
|
||||
}
|
||||
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(pos) => {
|
||||
resolve(pos);
|
||||
},
|
||||
(err) => {
|
||||
switch (err.code) {
|
||||
case GeolocationPositionError.PERMISSION_DENIED:
|
||||
errorMessage = t("The geolocation prompt was denied.");
|
||||
break;
|
||||
case GeolocationPositionError.POSITION_UNAVAILABLE:
|
||||
errorMessage = t("Your position was not available.");
|
||||
break;
|
||||
case GeolocationPositionError.TIMEOUT:
|
||||
errorMessage = t("Geolocation was not determined in time.");
|
||||
break;
|
||||
default:
|
||||
errorMessage = err.message;
|
||||
}
|
||||
reject(new Error(errorMessage as string));
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const fieldErrors = computed(() => {
|
||||
return gettingLocationError.value;
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.autocomplete {
|
||||
.dropdown-menu {
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.dropdown-item.is-disabled {
|
||||
opacity: 1 !important;
|
||||
cursor: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.read-only {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.map {
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
|
@ -1,114 +0,0 @@
|
|||
<template>
|
||||
<div class="relative pt-10 px-2">
|
||||
<div class="mb-2">
|
||||
<div class="w-full flex flex-wrap gap-3 items-center">
|
||||
<h2
|
||||
class="text-xl font-bold tracking-tight text-gray-900 dark:text-gray-100 mt-0"
|
||||
>
|
||||
<slot name="title" />
|
||||
</h2>
|
||||
|
||||
<o-button
|
||||
:disabled="doingGeoloc"
|
||||
v-if="suggestGeoloc"
|
||||
class="inline-flex bg-primary rounded text-white flex-initial px-4 py-2 justify-center w-full md:w-min whitespace-nowrap"
|
||||
@click="emit('doGeoLoc')"
|
||||
>
|
||||
{{ t("Geolocate me") }}
|
||||
</o-button>
|
||||
</div>
|
||||
<slot name="subtitle" />
|
||||
</div>
|
||||
<div class="hidden sm:block" v-show="showScrollLeftButton">
|
||||
<button
|
||||
@click="scrollLeft"
|
||||
class="absolute inset-y-0 my-auto z-10 rounded-full bg-white dark:bg-transparent w-10 h-10 border border-shadowColor -left-5"
|
||||
>
|
||||
<span class=""><</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="overflow-hidden">
|
||||
<div
|
||||
class="relative w-full snap-x snap-always snap-mandatory overflow-x-auto flex pb-6 gap-x-5 gap-y-8 p-1"
|
||||
ref="scrollContainer"
|
||||
@scroll="scrollHandler"
|
||||
>
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden sm:block" v-show="showScrollRightButton">
|
||||
<button
|
||||
@click="scrollRight"
|
||||
class="absolute inset-y-0 my-auto z-10 rounded-full bg-white dark:bg-transparent w-10 h-10 border border-shadowColor -right-5"
|
||||
>
|
||||
<span class="">></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
suggestGeoloc?: boolean;
|
||||
doingGeoloc?: boolean;
|
||||
}>(),
|
||||
{ suggestGeoloc: true, doingGeoloc: false }
|
||||
);
|
||||
|
||||
const emit = defineEmits(["doGeoLoc"]);
|
||||
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
|
||||
const showScrollRightButton = ref(true);
|
||||
const showScrollLeftButton = ref(false);
|
||||
|
||||
const scrollContainer = ref<any>();
|
||||
|
||||
const scrollHandler = () => {
|
||||
if (scrollContainer.value) {
|
||||
showScrollRightButton.value =
|
||||
scrollContainer.value.scrollLeft <
|
||||
scrollContainer.value.scrollWidth - scrollContainer.value.clientWidth;
|
||||
showScrollLeftButton.value = scrollContainer.value.scrollLeft > 0;
|
||||
}
|
||||
};
|
||||
|
||||
const doScroll = (e: Event, left: number) => {
|
||||
e.preventDefault();
|
||||
if (scrollContainer.value) {
|
||||
scrollContainer.value.scrollBy({
|
||||
left,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const scrollLeft = (e: Event) => {
|
||||
doScroll(e, -300);
|
||||
};
|
||||
|
||||
const scrollRight = (e: Event) => {
|
||||
doScroll(e, 300);
|
||||
};
|
||||
|
||||
const scrollHorizontalToVertical = (evt: WheelEvent) => {
|
||||
evt.deltaY > 0 ? doScroll(evt, 300) : doScroll(evt, -300);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
scrollContainer.value.addEventListener("wheel", scrollHorizontalToVertical);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (scrollContainer.value) {
|
||||
scrollContainer.value.removeEventListener(
|
||||
"wheel",
|
||||
scrollHorizontalToVertical
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -1,42 +0,0 @@
|
|||
<template>
|
||||
<svg
|
||||
class="bg-white dark:bg-zinc-900 dark:fill-white"
|
||||
:class="{ 'bg-gray-900': invert }"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 248.16 46.78"
|
||||
>
|
||||
<title>Mobilizon Logo</title>
|
||||
<g data-name="header">
|
||||
<path
|
||||
d="M0 45.82l3.18-40.8a29.88 29.88 0 015.07-.36 27.74 27.74 0 014.95.36l4.86 17.16a92.19 92.19 0 012.34 10.08h.36a92.19 92.19 0 012.34-10.08L28 5.02a29.23 29.23 0 015-.36 29.23 29.23 0 015 .36l3.18 40.8a13.61 13.61 0 01-3.63.42 23.41 23.41 0 01-3.63-.24l-1.2-19.92q-.36-5.52-.48-12.84h-.44l-7.32 26.51a25.62 25.62 0 01-4 .3 23.36 23.36 0 01-3.84-.3L9.36 13.24H9q-.3 8.94-.48 12.84L7.26 46a22.47 22.47 0 01-3.6.24A13.75 13.75 0 010 45.82zM74 31.06q0 8-4.26 12.3a12.21 12.21 0 01-9 3.42 12.21 12.21 0 01-9-3.42q-4.26-4.26-4.26-12.3t4.24-12.31a12.21 12.21 0 019-3.42 12.21 12.21 0 019 3.42Q74 23.02 74 31.06zM60.75 20.98q-5.67 0-5.67 10.08t5.67 10.08q5.67 0 5.67-10.08t-5.67-10.08zM103.2 19.75q2.7 4.11 2.7 11.28T102 42.31a13.18 13.18 0 01-10 4.11 31.41 31.41 0 01-11.34-2V2.2l.4-.45h2.76A4 4 0 0187 2.83a5.38 5.38 0 01.93 3.57v11.94a12.08 12.08 0 017.56-2.7 8.71 8.71 0 017.71 4.11zm-9.72 2a7.28 7.28 0 00-5.58 2.82v16a15 15 0 004.08.54 5.25 5.25 0 004.68-2.67q1.68-2.67 1.68-7.59 0-9.03-4.86-9.1zM121 22v23.94a20.85 20.85 0 01-3.66.3 23 23 0 01-3.78-.3V24.75q0-3.24-2.7-3.24h-.72a9.32 9.32 0 01-.3-2.58 10.7 10.7 0 01.3-2.7 39.63 39.63 0 014.38-.24h1a5.19 5.19 0 014 1.62A6.27 6.27 0 01121 22z"
|
||||
/>
|
||||
<path
|
||||
d="M119.82.84a7.37 7.37 0 01.6 3 7.37 7.37 0 01-.6 3 7.46 7.46 0 01-3.87.84 6.49 6.49 0 01-3.69-.93 7.37 7.37 0 01-.6-3 7.37 7.37 0 01.6-3 8.09 8.09 0 013.87-.84 7.05 7.05 0 013.69.93z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M139.08 40.42h2a10.23 10.23 0 01.6 3.18 9.24 9.24 0 01-.18 2.1 38.47 38.47 0 01-5.64.54q-6.48 0-6.48-7v-37l.36-.42h2.88a3.94 3.94 0 013.12 1.05 5.52 5.52 0 01.9 3.57v31.31q-.02 2.67 2.44 2.67zM155.94 22v23.94a20.85 20.85 0 01-3.66.3 23 23 0 01-3.78-.3V24.75q0-3.24-2.7-3.24h-.72a9.32 9.32 0 01-.3-2.58 10.7 10.7 0 01.3-2.7 39.63 39.63 0 014.38-.24h1a5.19 5.19 0 014.05 1.62 6.27 6.27 0 011.43 4.39z"
|
||||
/>
|
||||
<path
|
||||
d="M154.8 2.84a7.37 7.37 0 01.6 3 7.37 7.37 0 01-.6 3 7.46 7.46 0 01-3.87.84 6.49 6.49 0 01-3.69-.93 7.37 7.37 0 01-.6-3 7.37 7.37 0 01.6-3 8.09 8.09 0 013.87-.84 7.05 7.05 0 013.69.93z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M163.08 39.22l8.76-11.82q1.32-1.8 4.8-5.7l-.18-.3a63.09 63.09 0 01-7.74.42H163a9.79 9.79 0 01-.24-2.34 15.8 15.8 0 01.42-3.3h20.4a16.31 16.31 0 011 4.26 4.1 4.1 0 01-.78 2.34L175 34.66a64.65 64.65 0 01-4.56 5.7l.18.24q3.12-.3 5.22-.3h2.58a15.35 15.35 0 006.12-.9 9.4 9.4 0 01.72 3.12q0 3.42-4.32 3.42h-18a14.27 14.27 0 01-.9-3.93 5.08 5.08 0 011.04-2.79zM215.88 31.06q0 8-4.26 12.3a13.63 13.63 0 01-18.06 0q-4.26-4.26-4.26-12.3t4.26-12.31a13.63 13.63 0 0118.06 0q4.26 4.27 4.26 12.31zm-13.29-10.08q-5.67 0-5.67 10.08t5.67 10.08q5.67 0 5.67-10.08t-5.67-10.08zM247 25.84v13.32a11 11 0 001.2 5.64 7 7 0 01-4.41 1.56q-2.43 0-3.33-1.14a5.69 5.69 0 01-.9-3.54V27.4a7.74 7.74 0 00-.72-3.87 2.78 2.78 0 00-2.58-1.17 8.62 8.62 0 00-6.3 3v20.58a20.85 20.85 0 01-3.66.3 23 23 0 01-3.78-.3v-29.7l.42-.36h2.76q3.42 0 4.08 3.6 4.38-3.84 8.73-3.84t6.42 2.82a12.17 12.17 0 012.07 7.38z"
|
||||
/>
|
||||
<path
|
||||
d="M57.26 10.75a7.37 7.37 0 01-.6-3 7.37 7.37 0 01.6-3 8.09 8.09 0 013.87-.84 7.05 7.05 0 013.69.84 7.37 7.37 0 01.6 3 7.37 7.37 0 01-.6 3 7.46 7.46 0 01-3.87.84 6.49 6.49 0 01-3.69-.84zM198.26 10.75a7.37 7.37 0 01-.6-3 7.37 7.37 0 01.6-3 8.09 8.09 0 013.87-.84 7.05 7.05 0 013.69.84 7.37 7.37 0 01.6 3 7.37 7.37 0 01-.6 3 7.46 7.46 0 01-3.87.84 6.49 6.49 0 01-3.69-.84z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
invert?: boolean;
|
||||
}>(),
|
||||
{ invert: false }
|
||||
);
|
||||
</script>
|
|
@ -1,144 +0,0 @@
|
|||
<template>
|
||||
<div class="flex items-center">
|
||||
<figure class="image" v-if="imageSrc && !imagePreviewLoadingError">
|
||||
<img :src="imageSrc" @error="showImageLoadingError" />
|
||||
</figure>
|
||||
<figure class="image is-128x128" v-else>
|
||||
<div
|
||||
class="image-placeholder"
|
||||
:class="{ error: imagePreviewLoadingError }"
|
||||
>
|
||||
<span class="has-text-centered" v-if="imagePreviewLoadingError">{{
|
||||
$t("Error while loading the preview")
|
||||
}}</span>
|
||||
<span class="has-text-centered" v-else>{{
|
||||
textFallbackWithDefault
|
||||
}}</span>
|
||||
</div>
|
||||
</figure>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<p v-if="modelValue" class="inline-flex">
|
||||
<span class="block truncate max-w-[200px]" :title="modelValue.name">{{
|
||||
modelValue.name
|
||||
}}</span>
|
||||
<span>({{ formatBytes(modelValue.size) }})</span>
|
||||
</p>
|
||||
<p v-if="pictureTooBig" class="text-mbz-danger">
|
||||
{{
|
||||
$t(
|
||||
"The selected picture is too heavy. You need to select a file smaller than {size}.",
|
||||
{ size: formatBytes(maxSize) }
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<o-field class="justify-center" variant="primary">
|
||||
<o-upload @update:modelValue="onFileChanged" :accept="accept" drag-drop>
|
||||
<span>
|
||||
<Upload />
|
||||
<span>{{ $t("Click to upload") }}</span>
|
||||
</span>
|
||||
</o-upload>
|
||||
</o-field>
|
||||
<o-button
|
||||
variant="text"
|
||||
v-if="imageSrc"
|
||||
@click="removeOrClearPicture"
|
||||
@keyup.enter="removeOrClearPicture"
|
||||
>
|
||||
{{ $t("Clear") }}
|
||||
</o-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use "@/styles/_mixins" as *;
|
||||
figure.image {
|
||||
// @include margin-right(30px);
|
||||
max-height: 200px;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image-placeholder {
|
||||
background-color: grey;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&.error {
|
||||
border: 2px solid red;
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
color: #eee;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { IMedia } from "@/types/media.model";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import Upload from "vue-material-design-icons/Upload.vue";
|
||||
import { formatBytes } from "@/utils/datetime";
|
||||
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue: File | null;
|
||||
defaultImage?: IMedia | null;
|
||||
accept?: string;
|
||||
textFallback?: string;
|
||||
maxSize?: number;
|
||||
}>(),
|
||||
{
|
||||
accept: "image/gif,image/png,image/jpeg,image/webp",
|
||||
maxSize: 10_485_760,
|
||||
}
|
||||
);
|
||||
|
||||
const textFallbackWithDefault = props.textFallback ?? t("Avatar");
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
|
||||
const imagePreviewLoadingError = ref(false);
|
||||
|
||||
const pictureTooBig = computed((): boolean => {
|
||||
return props.modelValue != null && props.modelValue?.size > props.maxSize;
|
||||
});
|
||||
|
||||
const imageSrc = computed((): string | null | undefined => {
|
||||
if (props.modelValue !== undefined) {
|
||||
if (props.modelValue === null) return null;
|
||||
try {
|
||||
return URL.createObjectURL(props.modelValue);
|
||||
} catch (e) {
|
||||
console.error(e, props.modelValue);
|
||||
}
|
||||
}
|
||||
return props.defaultImage?.url;
|
||||
});
|
||||
|
||||
const onFileChanged = (file: File | null): void => {
|
||||
emit("update:modelValue", file);
|
||||
};
|
||||
|
||||
const removeOrClearPicture = async (): Promise<void> => {
|
||||
onFileChanged(null);
|
||||
};
|
||||
|
||||
watch(imageSrc, () => {
|
||||
imagePreviewLoadingError.value = false;
|
||||
});
|
||||
|
||||
const showImageLoadingError = (): void => {
|
||||
imagePreviewLoadingError.value = true;
|
||||
};
|
||||
</script>
|
|
@ -1,18 +0,0 @@
|
|||
import { FILTER_TAGS } from "@/graphql/tags";
|
||||
import { ITag } from "@/types/tag.model";
|
||||
import { apolloClient } from "@/vue-apollo";
|
||||
import { provideApolloClient, useQuery } from "@vue/apollo-composable";
|
||||
|
||||
export function fetchTags(text: string): Promise<ITag[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { onResult, onError } = provideApolloClient(apolloClient)(() =>
|
||||
useQuery<{ tags: ITag[] }, { filter: string }>(FILTER_TAGS, {
|
||||
filter: text,
|
||||
})
|
||||
);
|
||||
|
||||
onResult(({ data }) => resolve(data.tags));
|
||||
|
||||
onError((error) => reject(error));
|
||||
});
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
/**
|
||||
* New Line to <br>
|
||||
*
|
||||
* @param {string} str Input text
|
||||
* @return {string} Filtered text
|
||||
*/
|
||||
export default function nl2br(str: string): string {
|
||||
return `${str}`.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, "$1<br>");
|
||||
}
|
1262
js/src/i18n/pl.json
1262
js/src/i18n/pl.json
File diff suppressed because it is too large
Load diff
1263
js/src/i18n/sv.json
1263
js/src/i18n/sv.json
File diff suppressed because it is too large
Load diff
5
js/src/typings/absinthe.d.ts
vendored
5
js/src/typings/absinthe.d.ts
vendored
|
@ -1,5 +0,0 @@
|
|||
declare module "@absinthe/socket";
|
||||
|
||||
declare module "@absinthe/socket-apollo-link";
|
||||
|
||||
declare module "apollo-absinthe-upload-link";
|
|
@ -1,12 +0,0 @@
|
|||
export function nl2br(text: string): string {
|
||||
return text.replace(/(?:\r\n|\r|\n)/g, "<br>");
|
||||
}
|
||||
|
||||
export function htmlToText(html: string) {
|
||||
const template = document.createElement("template");
|
||||
const trimmedHTML = html.trim();
|
||||
template.innerHTML = trimmedHTML;
|
||||
const text = template.content.textContent;
|
||||
template.remove();
|
||||
return text;
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
export function supportsWebPFormat(): boolean {
|
||||
const elem = document.createElement("canvas");
|
||||
|
||||
if (elem.getContext && elem.getContext("2d")) {
|
||||
// was able or not to get WebP representation
|
||||
return elem.toDataURL("image/webp").indexOf("data:image/webp") === 0;
|
||||
}
|
||||
// very old browser like IE 8, canvas not supported
|
||||
return false;
|
||||
}
|
|
@ -1,259 +0,0 @@
|
|||
<template>
|
||||
<div v-if="instance">
|
||||
<breadcrumbs-nav
|
||||
:links="[
|
||||
{ name: RouteName.ADMIN, text: $t('Admin') },
|
||||
{ name: RouteName.INSTANCES, text: $t('Instances') },
|
||||
{ text: instance.domain },
|
||||
]"
|
||||
/>
|
||||
<h1 class="text-2xl">{{ instance.domain }}</h1>
|
||||
<div
|
||||
class="grid md:grid-cols-2 xl:grid-cols-4 gap-2 content-center text-center mt-2"
|
||||
>
|
||||
<div class="bg-zinc-50 dark:bg-mbz-purple-500 rounded-xl p-8">
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.PROFILES,
|
||||
query: { domain: instance.domain },
|
||||
}"
|
||||
>
|
||||
<span class="mb-4 text-xl font-semibold block">{{
|
||||
instance.personCount
|
||||
}}</span>
|
||||
<span class="text-sm block">{{ $t("Profiles") }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="bg-gray-50 dark:bg-mbz-purple-500 rounded-xl p-8">
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.ADMIN_GROUPS,
|
||||
query: { domain: instance.domain },
|
||||
}"
|
||||
>
|
||||
<span class="mb-4 text-xl font-semibold block">{{
|
||||
instance.groupCount
|
||||
}}</span>
|
||||
<span class="text-sm block">{{ $t("Groups") }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="bg-zinc-50 dark:bg-mbz-purple-500 rounded-xl p-8">
|
||||
<span class="mb-4 text-xl font-semibold block">{{
|
||||
instance.followingsCount
|
||||
}}</span>
|
||||
<span class="text-sm block">{{ $t("Followings") }}</span>
|
||||
</div>
|
||||
<div class="bg-zinc-50 dark:bg-mbz-purple-500 rounded-xl p-8">
|
||||
<span class="mb-4 text-xl font-semibold block">{{
|
||||
instance.followersCount
|
||||
}}</span>
|
||||
<span class="text-sm block">{{ $t("Followers") }}</span>
|
||||
</div>
|
||||
<div class="bg-zinc-50 dark:bg-mbz-purple-500 rounded-xl p-8">
|
||||
<router-link
|
||||
:to="{ name: RouteName.REPORTS, query: { domain: instance.domain } }"
|
||||
>
|
||||
<span class="mb-4 text-xl font-semibold block">{{
|
||||
instance.reportsCount
|
||||
}}</span>
|
||||
<span class="text-sm block">{{ $t("Reports") }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="bg-zinc-50 dark:bg-mbz-purple-500 rounded-xl p-8">
|
||||
<span class="mb-4 font-semibold block">{{
|
||||
formatBytes(instance.mediaSize)
|
||||
}}</span>
|
||||
<span class="text-sm block">{{ $t("Uploaded media size") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 grid xl:grid-cols-2 gap-4">
|
||||
<div
|
||||
class="border bg-white dark:bg-mbz-purple-500 dark:border-mbz-purple-700 p-6 shadow-md rounded-md"
|
||||
v-if="instance.hasRelay"
|
||||
>
|
||||
<button
|
||||
@click="
|
||||
removeInstanceFollow({
|
||||
address: instance?.relayAddress,
|
||||
})
|
||||
"
|
||||
v-if="instance.followedStatus == InstanceFollowStatus.APPROVED"
|
||||
class="bg-primary hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-gray-50 text-white hover:text-white font-semibold h-12 px-6 rounded-lg w-full flex items-center justify-center sm:w-auto"
|
||||
>
|
||||
{{ $t("Stop following instance") }}
|
||||
</button>
|
||||
<button
|
||||
@click="
|
||||
removeInstanceFollow({
|
||||
address: instance?.relayAddress,
|
||||
})
|
||||
"
|
||||
v-else-if="instance.followedStatus == InstanceFollowStatus.PENDING"
|
||||
class="bg-primary hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-gray-50 text-white hover:text-white font-semibold h-12 px-6 rounded-lg w-full flex items-center justify-center sm:w-auto"
|
||||
>
|
||||
{{ $t("Cancel follow request") }}
|
||||
</button>
|
||||
<button
|
||||
@click="followInstance"
|
||||
v-else
|
||||
class="bg-primary hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-gray-50 text-white hover:text-white font-semibold h-12 px-6 rounded-lg w-full flex items-center justify-center sm:w-auto"
|
||||
>
|
||||
{{ $t("Follow instance") }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="md:h-48 py-16 text-center opacity-50">
|
||||
{{ $t("Only Mobilizon instances can be followed") }}
|
||||
</div>
|
||||
<div
|
||||
class="border bg-white dark:bg-mbz-purple-500 dark:border-mbz-purple-700 p-6 shadow-md rounded-md flex flex-col gap-2"
|
||||
>
|
||||
<button
|
||||
@click="
|
||||
acceptInstance({
|
||||
address: instance?.relayAddress,
|
||||
})
|
||||
"
|
||||
v-if="instance.followerStatus == InstanceFollowStatus.PENDING"
|
||||
class="bg-green-700 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-gray-50 text-white hover:text-white font-semibold h-12 px-6 rounded-lg w-full flex items-center justify-center sm:w-auto"
|
||||
>
|
||||
{{ $t("Accept follow") }}
|
||||
</button>
|
||||
<button
|
||||
@click="
|
||||
rejectInstance({
|
||||
address: instance?.relayAddress,
|
||||
})
|
||||
"
|
||||
v-if="instance.followerStatus != InstanceFollowStatus.NONE"
|
||||
class="bg-red-700 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-gray-50 text-white hover:text-white font-semibold h-12 px-6 rounded-lg w-full flex items-center justify-center sm:w-auto"
|
||||
>
|
||||
{{ $t("Reject follow") }}
|
||||
</button>
|
||||
<p v-if="instance.followerStatus == InstanceFollowStatus.NONE">
|
||||
{{ $t("This instance doesn't follow yours.") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
ACCEPT_RELAY,
|
||||
ADD_INSTANCE,
|
||||
INSTANCE,
|
||||
REJECT_RELAY,
|
||||
REMOVE_RELAY,
|
||||
} from "@/graphql/admin";
|
||||
import { formatBytes } from "@/utils/datetime";
|
||||
import RouteName from "@/router/name";
|
||||
import { IInstance } from "@/types/instance.model";
|
||||
import { ApolloCache, gql, Reference } from "@apollo/client/core";
|
||||
import { InstanceFollowStatus } from "@/types/enums";
|
||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||
import { computed, inject } from "vue";
|
||||
import { Notifier } from "@/plugins/notifier";
|
||||
|
||||
const props = defineProps<{ domain: string }>();
|
||||
|
||||
const { result: instanceResult } = useQuery<{ instance: IInstance }>(
|
||||
INSTANCE,
|
||||
() => ({ domain: props.domain })
|
||||
);
|
||||
|
||||
const instance = computed(() => instanceResult.value?.instance);
|
||||
|
||||
const notifier = inject<Notifier>("notifier");
|
||||
|
||||
const { mutate: acceptInstance, onError: onAcceptInstanceError } = useMutation(
|
||||
ACCEPT_RELAY,
|
||||
() => ({
|
||||
update(cache: ApolloCache<any>) {
|
||||
cache.writeFragment({
|
||||
id: cache.identify(instance as unknown as Reference),
|
||||
fragment: gql`
|
||||
fragment InstanceFollowerStatus on Instance {
|
||||
followerStatus
|
||||
}
|
||||
`,
|
||||
data: {
|
||||
followerStatus: InstanceFollowStatus.APPROVED,
|
||||
},
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
onAcceptInstanceError((error) => {
|
||||
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
|
||||
notifier?.error(error.graphQLErrors[0].message);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Reject instance follow
|
||||
*/
|
||||
const { mutate: rejectInstance, onError: onRejectInstanceError } = useMutation(
|
||||
REJECT_RELAY,
|
||||
() => ({
|
||||
update(cache: ApolloCache<any>) {
|
||||
cache.writeFragment({
|
||||
id: cache.identify(instance as unknown as Reference),
|
||||
fragment: gql`
|
||||
fragment InstanceFollowerStatus on Instance {
|
||||
followerStatus
|
||||
}
|
||||
`,
|
||||
data: {
|
||||
followerStatus: InstanceFollowStatus.NONE,
|
||||
},
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
onRejectInstanceError((error) => {
|
||||
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
|
||||
notifier?.error(error.graphQLErrors[0].message);
|
||||
}
|
||||
});
|
||||
|
||||
const { mutate: followInstanceMutation, onError: onFollowInstanceError } =
|
||||
useMutation<{ addInstance: IInstance }>(ADD_INSTANCE);
|
||||
|
||||
onFollowInstanceError((error) => {
|
||||
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
|
||||
notifier?.error(error.graphQLErrors[0].message);
|
||||
}
|
||||
});
|
||||
|
||||
const followInstance = async (e: Event): Promise<void> => {
|
||||
e.preventDefault();
|
||||
followInstanceMutation({ domain: props.domain });
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop following instance
|
||||
*/
|
||||
const { mutate: removeInstanceFollow, onError: onRemoveInstanceFollowError } =
|
||||
useMutation(REMOVE_RELAY, () => ({
|
||||
update(cache: ApolloCache<any>) {
|
||||
cache.writeFragment({
|
||||
id: cache.identify(instance.value as unknown as Reference),
|
||||
fragment: gql`
|
||||
fragment InstanceFollowedStatus on Instance {
|
||||
followedStatus
|
||||
}
|
||||
`,
|
||||
data: {
|
||||
followedStatus: InstanceFollowStatus.NONE,
|
||||
},
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
onRemoveInstanceFollowError((error) => {
|
||||
if (error.graphQLErrors && error.graphQLErrors.length > 0) {
|
||||
notifier?.error(error.graphQLErrors[0].message);
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -1,530 +0,0 @@
|
|||
<template>
|
||||
<breadcrumbs-nav
|
||||
v-if="report"
|
||||
:links="[
|
||||
{
|
||||
name: RouteName.MODERATION,
|
||||
text: t('Moderation'),
|
||||
},
|
||||
{
|
||||
name: RouteName.REPORTS,
|
||||
text: t('Reports'),
|
||||
},
|
||||
{
|
||||
name: RouteName.REPORT,
|
||||
params: { id: report.id },
|
||||
text: t('Report #{reportNumber}', { reportNumber: report.id }),
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<o-notification
|
||||
title="Error"
|
||||
variant="danger"
|
||||
v-for="error in errors"
|
||||
:key="error"
|
||||
>
|
||||
{{ error }}
|
||||
</o-notification>
|
||||
<div class="container mx-auto" v-if="report">
|
||||
<div class="flex flex-wrap gap-2 my-2">
|
||||
<o-button
|
||||
v-if="report.status !== ReportStatusEnum.RESOLVED"
|
||||
@click="updateReport(ReportStatusEnum.RESOLVED)"
|
||||
variant="primary"
|
||||
>{{ t("Mark as resolved") }}</o-button
|
||||
>
|
||||
<o-button
|
||||
v-if="report.status !== ReportStatusEnum.OPEN"
|
||||
@click="updateReport(ReportStatusEnum.OPEN)"
|
||||
variant="success"
|
||||
>{{ t("Reopen") }}</o-button
|
||||
>
|
||||
<o-button
|
||||
v-if="report.status !== ReportStatusEnum.CLOSED"
|
||||
@click="updateReport(ReportStatusEnum.CLOSED)"
|
||||
variant="danger"
|
||||
>{{ t("Close") }}</o-button
|
||||
>
|
||||
</div>
|
||||
<section class="w-full">
|
||||
<table class="table w-full">
|
||||
<tbody>
|
||||
<tr v-if="report.reported.type === ActorType.GROUP">
|
||||
<td>{{ t("Reported group") }}</td>
|
||||
<td>
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.ADMIN_GROUP_PROFILE,
|
||||
params: { id: report.reported.id },
|
||||
}"
|
||||
>
|
||||
<img
|
||||
v-if="report.reported.avatar"
|
||||
class="image"
|
||||
:src="report.reported.avatar.url"
|
||||
alt=""
|
||||
/>
|
||||
{{ displayNameAndUsername(report.reported) }}
|
||||
</router-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else>
|
||||
<td>
|
||||
{{ t("Reported identity") }}
|
||||
</td>
|
||||
<td>
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.ADMIN_PROFILE,
|
||||
params: { id: report.reported.id },
|
||||
}"
|
||||
>
|
||||
<img
|
||||
v-if="report.reported.avatar"
|
||||
class="image"
|
||||
:src="report.reported.avatar.url"
|
||||
alt=""
|
||||
/>
|
||||
{{ displayNameAndUsername(report.reported) }}
|
||||
</router-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ t("Reported by") }}</td>
|
||||
<td v-if="report.reporter.type === ActorType.APPLICATION">
|
||||
{{ report.reporter.domain }}
|
||||
</td>
|
||||
<td v-else>
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.ADMIN_PROFILE,
|
||||
params: { id: report.reporter.id },
|
||||
}"
|
||||
>
|
||||
<img
|
||||
v-if="report.reporter.avatar"
|
||||
class="image"
|
||||
:src="report.reporter.avatar.url"
|
||||
alt=""
|
||||
/>
|
||||
{{ displayNameAndUsername(report.reporter) }}
|
||||
</router-link>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ t("Reported") }}</td>
|
||||
<td>{{ formatDateTimeString(report.insertedAt) }}</td>
|
||||
</tr>
|
||||
<tr v-if="report.updatedAt !== report.insertedAt">
|
||||
<td>{{ t("Updated") }}</td>
|
||||
<td>{{ formatDateTimeString(report.updatedAt) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ t("Status") }}</td>
|
||||
<td>
|
||||
<span v-if="report.status === ReportStatusEnum.OPEN">{{
|
||||
t("Open")
|
||||
}}</span>
|
||||
<span v-else-if="report.status === ReportStatusEnum.CLOSED">
|
||||
{{ t("Closed") }}
|
||||
</span>
|
||||
<span v-else-if="report.status === ReportStatusEnum.RESOLVED">
|
||||
{{ t("Resolved") }}
|
||||
</span>
|
||||
<span v-else>{{ t("Unknown") }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="report.event && report.comments.length > 0">
|
||||
<td>{{ t("Event") }}</td>
|
||||
<td class="flex gap-2 items-center">
|
||||
<router-link
|
||||
class="underline"
|
||||
:to="{
|
||||
name: RouteName.EVENT,
|
||||
params: { uuid: report.event.uuid },
|
||||
}"
|
||||
>
|
||||
{{ report.event.title }}
|
||||
</router-link>
|
||||
<o-button
|
||||
variant="danger"
|
||||
@click="confirmEventDelete()"
|
||||
icon-left="delete"
|
||||
>{{ t("Delete") }}</o-button
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="bg-white dark:bg-zinc-700 rounded px-2 pt-1 pb-2 my-3">
|
||||
<h2 class="mb-1">{{ t("Report reason") }}</h2>
|
||||
<div class="">
|
||||
<div class="flex gap-1">
|
||||
<figure class="" v-if="report.reported.avatar">
|
||||
<img
|
||||
alt=""
|
||||
:src="report.reported.avatar.url"
|
||||
class="rounded-full"
|
||||
width="36"
|
||||
height="36"
|
||||
/>
|
||||
</figure>
|
||||
<AccountCircle v-else :size="36" />
|
||||
<div class="">
|
||||
<p class="" v-if="report.reported.name">
|
||||
{{ report.reported.name }}
|
||||
</p>
|
||||
<p class="">@{{ usernameWithDomain(report.reported) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="prose dark:prose-invert"
|
||||
v-if="report.content"
|
||||
v-html="nl2br(report.content)"
|
||||
/>
|
||||
<p v-else>{{ t("No comment") }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="bg-white dark:bg-zinc-700 rounded px-2 pt-1 pb-2 my-3"
|
||||
v-if="report.event && report.comments.length === 0"
|
||||
>
|
||||
<h2 class="mb-1">{{ t("Reported content") }}</h2>
|
||||
<EventCard :event="report.event" mode="row" class="my-2 max-w-4xl" />
|
||||
<o-button
|
||||
variant="danger"
|
||||
@click="confirmEventDelete()"
|
||||
icon-left="delete"
|
||||
size="small"
|
||||
>{{ t("Delete") }}</o-button
|
||||
>
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="bg-white dark:bg-zinc-700 rounded px-2 pt-1 pb-2 my-3"
|
||||
v-if="report.comments.length > 0"
|
||||
>
|
||||
<h2 class="mb-1">{{ t("Reported content") }}</h2>
|
||||
<ul v-for="comment in report.comments" :key="comment.id">
|
||||
<li>
|
||||
<div class="" v-if="comment">
|
||||
<article>
|
||||
<div class="flex gap-1">
|
||||
<figure class="" v-if="comment.actor?.avatar">
|
||||
<img
|
||||
alt=""
|
||||
:src="comment.actor.avatar?.url"
|
||||
class="rounded-full"
|
||||
width="36"
|
||||
height="36"
|
||||
/>
|
||||
</figure>
|
||||
<AccountCircle v-else :size="36" />
|
||||
<div>
|
||||
<div v-if="comment.actor">
|
||||
<p>{{ comment.actor.name }}</p>
|
||||
<p>@{{ comment.actor.preferredUsername }}</p>
|
||||
</div>
|
||||
<span v-else>{{ t("Unknown actor") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prose dark:prose-invert" v-html="comment.text" />
|
||||
<o-button
|
||||
variant="danger"
|
||||
@click="confirmCommentDelete(comment)"
|
||||
icon-left="delete"
|
||||
size="small"
|
||||
>{{ t("Delete") }}</o-button
|
||||
>
|
||||
</article>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="bg-white dark:bg-zinc-700 rounded px-2 pt-1 pb-2 my-3">
|
||||
<h2 class="mb-1">{{ t("Notes") }}</h2>
|
||||
<div
|
||||
class=""
|
||||
v-for="note in report.notes"
|
||||
:id="`note-${note.id}`"
|
||||
:key="note.id"
|
||||
>
|
||||
<p>{{ note.content }}</p>
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.ADMIN_PROFILE,
|
||||
params: { id: note.moderator.id },
|
||||
}"
|
||||
>
|
||||
<img
|
||||
alt=""
|
||||
class="rounded-full"
|
||||
:src="note.moderator.avatar.url"
|
||||
v-if="note.moderator.avatar"
|
||||
/>
|
||||
@{{ note.moderator.preferredUsername }}
|
||||
</router-link>
|
||||
<br />
|
||||
<small>
|
||||
<a :href="`#note-${note.id}`" v-if="note.insertedAt">
|
||||
{{ formatDateTimeString(note.insertedAt) }}
|
||||
</a>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<form
|
||||
@submit="
|
||||
createReportNoteMutation({
|
||||
reportId: report?.id,
|
||||
content: noteContent,
|
||||
})
|
||||
"
|
||||
>
|
||||
<o-field :label="t('New note')" label-for="newNoteInput">
|
||||
<o-input
|
||||
type="textarea"
|
||||
v-model="noteContent"
|
||||
id="newNoteInput"
|
||||
></o-input>
|
||||
</o-field>
|
||||
<o-button class="mt-2" type="submit">{{ t("Add a note") }}</o-button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { CREATE_REPORT_NOTE, REPORT, UPDATE_REPORT } from "@/graphql/report";
|
||||
import { IReport, IReportNote } from "@/types/report.model";
|
||||
import { displayNameAndUsername, usernameWithDomain } from "@/types/actor";
|
||||
import { DELETE_EVENT } from "@/graphql/event";
|
||||
import uniq from "lodash/uniq";
|
||||
import { nl2br } from "@/utils/html";
|
||||
import { DELETE_COMMENT } from "@/graphql/comment";
|
||||
import { IComment } from "@/types/comment.model";
|
||||
import { ActorType, ReportStatusEnum } from "@/types/enums";
|
||||
import RouteName from "@/router/name";
|
||||
import { GraphQLError } from "graphql";
|
||||
import { ApolloCache, FetchResult } from "@apollo/client/core";
|
||||
import { useMutation, useQuery } from "@vue/apollo-composable";
|
||||
import { useCurrentActorClient } from "@/composition/apollo/actor";
|
||||
import { useHead } from "@vueuse/head";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useRouter } from "vue-router";
|
||||
import { ref, computed, inject } from "vue";
|
||||
import { formatDateTimeString } from "@/filters/datetime";
|
||||
import AccountCircle from "vue-material-design-icons/AccountCircle.vue";
|
||||
import { Dialog } from "@/plugins/dialog";
|
||||
import { Notifier } from "@/plugins/notifier";
|
||||
import EventCard from "@/components/Event/EventCard.vue";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const props = defineProps<{ reportId: string }>();
|
||||
|
||||
const { currentActor } = useCurrentActorClient();
|
||||
|
||||
const { result: reportResult, onError: onReportQueryError } = useQuery<{
|
||||
report: IReport;
|
||||
}>(REPORT, () => ({
|
||||
id: props.reportId,
|
||||
}));
|
||||
|
||||
const report = computed(() => reportResult.value?.report);
|
||||
|
||||
onReportQueryError(({ graphQLErrors }) => {
|
||||
errors.value = uniq(
|
||||
graphQLErrors.map(({ message }: GraphQLError) => message)
|
||||
);
|
||||
});
|
||||
|
||||
const { t } = useI18n({ useScope: "global" });
|
||||
|
||||
useHead({
|
||||
title: computed(() => t("Report")),
|
||||
});
|
||||
|
||||
const notifier = inject<Notifier>("notifier");
|
||||
|
||||
const errors = ref<string[]>([]);
|
||||
|
||||
const noteContent = ref("");
|
||||
|
||||
const {
|
||||
mutate: createReportNoteMutation,
|
||||
onDone: createReportNoteMutationDone,
|
||||
onError: createReportNoteMutationError,
|
||||
} = useMutation<{
|
||||
createReportNote: IReportNote;
|
||||
}>(CREATE_REPORT_NOTE, () => ({
|
||||
update: (
|
||||
store: ApolloCache<{ createReportNote: IReportNote }>,
|
||||
{ data }: FetchResult
|
||||
) => {
|
||||
if (data == null) return;
|
||||
const cachedData = store.readQuery<{ report: IReport }>({
|
||||
query: REPORT,
|
||||
variables: { id: report.value?.id },
|
||||
});
|
||||
if (cachedData == null) return;
|
||||
const { report: cachedReport } = cachedData;
|
||||
if (cachedReport === null) {
|
||||
console.error("Cannot update event notes cache, because of null value.");
|
||||
return;
|
||||
}
|
||||
const note = data.createReportNote;
|
||||
note.moderator = currentActor.value;
|
||||
|
||||
cachedReport.notes = cachedReport.notes.concat([note]);
|
||||
|
||||
store.writeQuery({
|
||||
query: REPORT,
|
||||
variables: { id: report.value?.id },
|
||||
data: { report },
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
createReportNoteMutationDone(() => {
|
||||
noteContent.value = "";
|
||||
});
|
||||
|
||||
createReportNoteMutationError((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
const dialog = inject<Dialog>("dialog");
|
||||
|
||||
const confirmEventDelete = (): void => {
|
||||
dialog?.confirm({
|
||||
title: t("Deleting event"),
|
||||
message: t(
|
||||
"Are you sure you want to <b>delete</b> this event? This action cannot be undone. You may want to engage the discussion with the event creator or edit its event instead."
|
||||
),
|
||||
confirmText: t("Delete Event"),
|
||||
variant: "danger",
|
||||
hasIcon: true,
|
||||
onConfirm: () => deleteEvent(),
|
||||
});
|
||||
};
|
||||
|
||||
const confirmCommentDelete = (comment: IComment): void => {
|
||||
dialog?.confirm({
|
||||
title: t("Deleting comment"),
|
||||
message: t(
|
||||
"Are you sure you want to <b>delete</b> this comment? This action cannot be undone."
|
||||
),
|
||||
confirmText: t("Delete Comment"),
|
||||
variant: "danger",
|
||||
hasIcon: true,
|
||||
onConfirm: () => deleteCommentMutation({ commentId: comment.id }),
|
||||
});
|
||||
};
|
||||
|
||||
const {
|
||||
mutate: deleteEventMutation,
|
||||
onDone: deleteEventMutationDone,
|
||||
onError: deleteEventMutationError,
|
||||
} = useMutation<{ deleteEvent: { id: string } }>(DELETE_EVENT);
|
||||
|
||||
deleteEventMutationDone(() => {
|
||||
const eventTitle = report.value?.event?.title;
|
||||
notifier?.success(
|
||||
t("Event {eventTitle} deleted", {
|
||||
eventTitle,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
deleteEventMutationError((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
const deleteEvent = async (): Promise<void> => {
|
||||
if (!report.value?.event?.id) return;
|
||||
|
||||
deleteEventMutation({ eventId: report.value.event.id });
|
||||
};
|
||||
|
||||
const {
|
||||
mutate: deleteCommentMutation,
|
||||
onDone: deleteCommentMutationDone,
|
||||
onError: deleteCommentMutationError,
|
||||
} = useMutation<{ deleteComment: { id: string } }>(DELETE_COMMENT);
|
||||
|
||||
deleteCommentMutationDone(() => {
|
||||
notifier?.success(t("Comment deleted") as string);
|
||||
});
|
||||
|
||||
deleteCommentMutationError((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
const {
|
||||
mutate: updateReportMutation,
|
||||
onDone: onUpdateReportMutation,
|
||||
onError: onUpdateReportError,
|
||||
} = useMutation(UPDATE_REPORT, () => ({
|
||||
update: (
|
||||
store: ApolloCache<{ updateReportStatus: IReport }>,
|
||||
{ data }: FetchResult
|
||||
) => {
|
||||
if (data == null) return;
|
||||
const reportCachedData = store.readQuery<{ report: IReport }>({
|
||||
query: REPORT,
|
||||
variables: { id: report.value?.id },
|
||||
});
|
||||
if (reportCachedData == null) return;
|
||||
const { report: cachedReport } = reportCachedData;
|
||||
if (cachedReport === null) {
|
||||
console.error("Cannot update event notes cache, because of null value.");
|
||||
return;
|
||||
}
|
||||
const updatedReport = {
|
||||
...cachedReport,
|
||||
status: data.updateReportStatus.status,
|
||||
};
|
||||
|
||||
store.writeQuery({
|
||||
query: REPORT,
|
||||
variables: { id: report.value?.id },
|
||||
data: { report: updatedReport },
|
||||
});
|
||||
},
|
||||
}));
|
||||
|
||||
onUpdateReportMutation(() => {
|
||||
router.push({ name: RouteName.REPORTS });
|
||||
});
|
||||
|
||||
onUpdateReportError((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
const updateReport = async (status: ReportStatusEnum): Promise<void> => {
|
||||
updateReportMutation({
|
||||
reportId: report.value?.id,
|
||||
status,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
tbody td img.image,
|
||||
.note img.image {
|
||||
display: inline;
|
||||
height: 1.5em;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
.dialog .modal-card-foot {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.box a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
</style>
|
|
@ -1,16 +0,0 @@
|
|||
import "./specs/mocks/matchMedia";
|
||||
import { config } from "@vue/test-utils";
|
||||
import { createHead } from "@vueuse/head";
|
||||
import { createI18n } from "vue-i18n";
|
||||
import en_US from "@/i18n/en_US.json";
|
||||
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
messages: { en_US },
|
||||
locale: "en_US",
|
||||
});
|
||||
|
||||
const head = createHead();
|
||||
|
||||
config.global.plugins.push(head);
|
||||
config.global.plugins.push(i18n);
|
|
@ -1,67 +0,0 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`CommentTree > renders a comment tree with comments 1`] = `
|
||||
"<div data-v-5d0380ab=\\"\\">
|
||||
<form class=\\"\\" data-v-5d0380ab=\\"\\">
|
||||
<!--v-if-->
|
||||
<article class=\\"flex flex-wrap items-start gap-2\\" data-v-5d0380ab=\\"\\">
|
||||
<figure class=\\"\\" data-v-5d0380ab=\\"\\">
|
||||
<identity-picker-wrapper-stub modelvalue=\\"[object Object]\\" inline=\\"false\\" masked=\\"false\\" data-v-5d0380ab=\\"\\"></identity-picker-wrapper-stub>
|
||||
</figure>
|
||||
<div class=\\"flex-1\\" data-v-5d0380ab=\\"\\">
|
||||
<div class=\\"flex flex-col gap-2\\" data-v-5d0380ab=\\"\\">
|
||||
<div class=\\"editor-wrapper\\" data-v-5d0380ab=\\"\\">
|
||||
<editor-stub currentactor=\\"[object Object]\\" mode=\\"comment\\" modelvalue=\\"\\" aria-label=\\"Comment body\\" data-v-5d0380ab=\\"\\"></editor-stub>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
</div>
|
||||
<div class=\\"\\" data-v-5d0380ab=\\"\\">
|
||||
<o-button-stub variant=\\"primary\\" iconleft=\\"send\\" rounded=\\"false\\" outlined=\\"false\\" expanded=\\"false\\" inverted=\\"false\\" nativetype=\\"submit\\" tag=\\"button\\" disabled=\\"false\\" iconboth=\\"false\\" data-v-5d0380ab=\\"\\"></o-button-stub>
|
||||
</div>
|
||||
</article>
|
||||
</form>
|
||||
<transition-group-stub data-v-5d0380ab=\\"\\">
|
||||
<transition-group-stub data-v-5d0380ab=\\"\\">
|
||||
<comment-stub comment=\\"[object Object]\\" event=\\"[object Object]\\" currentactor=\\"[object Object]\\" rootcomment=\\"true\\" class=\\"root-comment\\" data-v-5d0380ab=\\"\\"></comment-stub>
|
||||
<comment-stub comment=\\"[object Object]\\" event=\\"[object Object]\\" currentactor=\\"[object Object]\\" rootcomment=\\"true\\" class=\\"root-comment\\" data-v-5d0380ab=\\"\\"></comment-stub>
|
||||
</transition-group-stub>
|
||||
</transition-group-stub>
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`CommentTree > renders a loading comment tree 1`] = `
|
||||
"<div data-v-5d0380ab=\\"\\">
|
||||
<!--v-if-->
|
||||
<p class=\\"text-center\\" data-v-5d0380ab=\\"\\">Loading comments…</p>
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`CommentTree > renders an empty comment tree 1`] = `
|
||||
"<div data-v-5d0380ab=\\"\\">
|
||||
<form class=\\"\\" data-v-5d0380ab=\\"\\">
|
||||
<!--v-if-->
|
||||
<article class=\\"flex flex-wrap items-start gap-2\\" data-v-5d0380ab=\\"\\">
|
||||
<figure class=\\"\\" data-v-5d0380ab=\\"\\">
|
||||
<identity-picker-wrapper-stub modelvalue=\\"[object Object]\\" inline=\\"false\\" masked=\\"false\\" data-v-5d0380ab=\\"\\"></identity-picker-wrapper-stub>
|
||||
</figure>
|
||||
<div class=\\"flex-1\\" data-v-5d0380ab=\\"\\">
|
||||
<div class=\\"flex flex-col gap-2\\" data-v-5d0380ab=\\"\\">
|
||||
<div class=\\"editor-wrapper\\" data-v-5d0380ab=\\"\\">
|
||||
<editor-stub currentactor=\\"[object Object]\\" mode=\\"comment\\" modelvalue=\\"\\" aria-label=\\"Comment body\\" data-v-5d0380ab=\\"\\"></editor-stub>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
</div>
|
||||
<div class=\\"\\" data-v-5d0380ab=\\"\\">
|
||||
<o-button-stub variant=\\"primary\\" iconleft=\\"send\\" rounded=\\"false\\" outlined=\\"false\\" expanded=\\"false\\" inverted=\\"false\\" nativetype=\\"submit\\" tag=\\"button\\" disabled=\\"false\\" iconboth=\\"false\\" data-v-5d0380ab=\\"\\"></o-button-stub>
|
||||
</div>
|
||||
</article>
|
||||
</form>
|
||||
<transition-group-stub data-v-5d0380ab=\\"\\">
|
||||
<empty-content-stub icon=\\"comment\\" descriptionclasses=\\"\\" inline=\\"true\\" center=\\"false\\" data-v-5d0380ab=\\"\\"></empty-content-stub>
|
||||
</transition-group-stub>
|
||||
</div>"
|
||||
`;
|
|
@ -1,37 +0,0 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`PostListItem > renders post list item with basic informations 1`] = `
|
||||
"<a href=\\"/p/my-blog-post-some-uuid\\" class=\\"block md:flex bg-white dark:bg-violet-2 dark:text-white dark:hover:text-white rounded-lg shadow-md\\" dir=\\"auto\\" data-v-6ca7cc69=\\"\\">
|
||||
<!--v-if-->
|
||||
<div class=\\"flex flex-col gap-1 bg-inherit p-2 rounded-lg flex-1\\" data-v-6ca7cc69=\\"\\">
|
||||
<h3 class=\\"text-xl color-violet-3 line-clamp-3 mb-2 font-bold\\" lang=\\"en\\" data-v-6ca7cc69=\\"\\">My Blog Post</h3>
|
||||
<p class=\\"flex gap-2\\" data-v-6ca7cc69=\\"\\"><span aria-hidden=\\"true\\" class=\\"material-design-icon clock-icon\\" role=\\"img\\" data-v-6ca7cc69=\\"\\"><svg fill=\\"currentColor\\" class=\\"material-design-icon__svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\"><path d=\\"M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M16.2,16.2L11,13V7H12.5V12.2L17,14.9L16.2,16.2Z\\"><!--v-if--></path></svg></span><span dir=\\"auto\\" class=\\"\\" data-v-6ca7cc69=\\"\\">Dec 2, 2020</span></p>
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</div>
|
||||
</a>"
|
||||
`;
|
||||
|
||||
exports[`PostListItem > renders post list item with publisher name 1`] = `
|
||||
"<a href=\\"/p/my-blog-post-some-uuid\\" class=\\"block md:flex bg-white dark:bg-violet-2 dark:text-white dark:hover:text-white rounded-lg shadow-md\\" dir=\\"auto\\" data-v-6ca7cc69=\\"\\">
|
||||
<!--v-if-->
|
||||
<div class=\\"flex flex-col gap-1 bg-inherit p-2 rounded-lg flex-1\\" data-v-6ca7cc69=\\"\\">
|
||||
<h3 class=\\"text-xl color-violet-3 line-clamp-3 mb-2 font-bold\\" lang=\\"en\\" data-v-6ca7cc69=\\"\\">My Blog Post</h3>
|
||||
<p class=\\"flex gap-2\\" data-v-6ca7cc69=\\"\\"><span aria-hidden=\\"true\\" class=\\"material-design-icon clock-icon\\" role=\\"img\\" data-v-6ca7cc69=\\"\\"><svg fill=\\"currentColor\\" class=\\"material-design-icon__svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\"><path d=\\"M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M16.2,16.2L11,13V7H12.5V12.2L17,14.9L16.2,16.2Z\\"><!--v-if--></path></svg></span><span dir=\\"auto\\" class=\\"\\" data-v-6ca7cc69=\\"\\">Dec 2, 2020</span></p>
|
||||
<!--v-if-->
|
||||
<p class=\\"flex gap-1\\" data-v-6ca7cc69=\\"\\"><span aria-hidden=\\"true\\" class=\\"material-design-icon account-edit-icon\\" role=\\"img\\" data-v-6ca7cc69=\\"\\"><svg fill=\\"currentColor\\" class=\\"material-design-icon__svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\"><path d=\\"M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.09 20.21,11.09 20.42,11.3L21.7,12.58C21.91,12.79 21.91,13.14 21.7,13.35M12,18.94L18.06,12.88L20.11,14.93L14.06,21H12V18.94M12,14C7.58,14 4,15.79 4,18V20H10V18.11L14,14.11C13.34,14.03 12.67,14 12,14M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4Z\\"><!--v-if--></path></svg></span>Published by <b class=\\"\\" data-v-6ca7cc69=\\"\\">An author</b></p>
|
||||
</div>
|
||||
</a>"
|
||||
`;
|
||||
|
||||
exports[`PostListItem > renders post list item with tags 1`] = `
|
||||
"<a href=\\"/p/my-blog-post-some-uuid\\" class=\\"block md:flex bg-white dark:bg-violet-2 dark:text-white dark:hover:text-white rounded-lg shadow-md\\" dir=\\"auto\\" data-v-6ca7cc69=\\"\\">
|
||||
<!--v-if-->
|
||||
<div class=\\"flex flex-col gap-1 bg-inherit p-2 rounded-lg flex-1\\" data-v-6ca7cc69=\\"\\">
|
||||
<h3 class=\\"text-xl color-violet-3 line-clamp-3 mb-2 font-bold\\" lang=\\"en\\" data-v-6ca7cc69=\\"\\">My Blog Post</h3>
|
||||
<p class=\\"flex gap-2\\" data-v-6ca7cc69=\\"\\"><span aria-hidden=\\"true\\" class=\\"material-design-icon clock-icon\\" role=\\"img\\" data-v-6ca7cc69=\\"\\"><svg fill=\\"currentColor\\" class=\\"material-design-icon__svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\"><path d=\\"M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M16.2,16.2L11,13V7H12.5V12.2L17,14.9L16.2,16.2Z\\"><!--v-if--></path></svg></span><span dir=\\"auto\\" class=\\"\\" data-v-6ca7cc69=\\"\\">Dec 2, 2020</span></p>
|
||||
<div class=\\"flex flex-wrap gap-y-0 gap-x-2\\" data-v-6ca7cc69=\\"\\"><span aria-hidden=\\"true\\" class=\\"material-design-icon tag-icon\\" role=\\"img\\" data-v-6ca7cc69=\\"\\"><svg fill=\\"currentColor\\" class=\\"material-design-icon__svg\\" width=\\"24\\" height=\\"24\\" viewBox=\\"0 0 24 24\\"><path d=\\"M5.5,7A1.5,1.5 0 0,1 4,5.5A1.5,1.5 0 0,1 5.5,4A1.5,1.5 0 0,1 7,5.5A1.5,1.5 0 0,1 5.5,7M21.41,11.58L12.41,2.58C12.05,2.22 11.55,2 11,2H4C2.89,2 2,2.89 2,4V11C2,11.55 2.22,12.05 2.59,12.41L11.58,21.41C11.95,21.77 12.45,22 13,22C13.55,22 14.05,21.77 14.41,21.41L21.41,14.41C21.78,14.05 22,13.55 22,13C22,12.44 21.77,11.94 21.41,11.58Z\\"><!--v-if--></path></svg></span><span class=\\"rounded-md truncate text-sm text-violet-title px-2 py-1 bg-purple-3 dark:text-violet-3\\" data-v-6955ca87=\\"\\" data-v-6ca7cc69=\\"\\">A tag</span></div>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
</a>"
|
||||
`;
|
|
@ -1,29 +0,0 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`ReportModal > renders report modal with basic informations and submits it 1`] = `
|
||||
"<div class=\\"p-2\\" data-v-e0cceef3=\\"\\">
|
||||
<!--v-if-->
|
||||
<section data-v-e0cceef3=\\"\\">
|
||||
<div class=\\"flex gap-1 flex-row mb-3\\" data-v-e0cceef3=\\"\\"><span class=\\"o-icon o-icon--warning hidden md:block flex-1\\" data-v-e0cceef3=\\"\\"><i class=\\"mdi mdi-alert 48\\"></i></span>
|
||||
<p data-v-e0cceef3=\\"\\">The report will be sent to the moderators of your instance. You can explain why you report this content below.</p>
|
||||
</div>
|
||||
<div class=\\"\\" data-v-e0cceef3=\\"\\">
|
||||
<!--v-if-->
|
||||
<div class=\\"o-field o-field--filled\\" data-v-e0cceef3=\\"\\"><label for=\\"additional-comments\\" class=\\"o-field__label\\">Additional comments</label>
|
||||
<div class=\\"o-ctrl-input\\" data-v-e0cceef3=\\"\\"><textarea id=\\"additional-comments\\" class=\\"o-input o-input__textarea\\"></textarea>
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<!--v-if-->
|
||||
</div>
|
||||
</section>
|
||||
<footer class=\\"flex gap-2 py-3\\" data-v-e0cceef3=\\"\\"><button type=\\"button\\" class=\\"o-btn o-btn--outlined\\" data-v-e0cceef3=\\"\\"><span class=\\"o-btn__wrapper\\"><!--v-if--><span class=\\"o-btn__label\\">Cancel</span>
|
||||
<!--v-if--></span>
|
||||
</button><button type=\\"button\\" class=\\"o-btn o-btn--primary\\" data-v-e0cceef3=\\"\\"><span class=\\"o-btn__wrapper\\"><!--v-if--><span class=\\"o-btn__label\\">Send the report</span>
|
||||
<!--v-if--></span>
|
||||
</button></footer>
|
||||
</div>"
|
||||
`;
|
|
@ -1,196 +0,0 @@
|
|||
// Vitest Snapshot v1
|
||||
|
||||
exports[`App component > renders a Vue component 1`] = `
|
||||
"<nav class=\\"bg-white border-gray-200 px-2 sm:px-4 py-2.5 dark:bg-zinc-900\\" id=\\"navbar\\">
|
||||
<div class=\\"container mx-auto flex flex-wrap items-center mx-auto gap-4\\">
|
||||
<router-link to=\\"[object Object]\\" class=\\"flex items-center\\">
|
||||
<mobilizon-logo-stub invert=\\"false\\" class=\\"w-40\\"></mobilizon-logo-stub>
|
||||
</router-link>
|
||||
<!--v-if--><button type=\\"button\\" class=\\"inline-flex items-center p-2 ml-1 text-sm text-zinc-500 rounded-lg md:hidden hover:bg-zinc-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-zinc-400 dark:hover:bg-zinc-700 dark:focus:ring-gray-600\\" aria-controls=\\"mobile-menu-2\\" aria-expanded=\\"false\\"><span class=\\"sr-only\\">Open main menu</span><svg class=\\"w-6 h-6\\" aria-hidden=\\"true\\" fill=\\"currentColor\\" viewBox=\\"0 0 20 20\\" xmlns=\\"http://www.w3.org/2000/svg\\">
|
||||
<path fill-rule=\\"evenodd\\" d=\\"M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z\\" clip-rule=\\"evenodd\\"></path>
|
||||
</svg></button>
|
||||
<div class=\\"justify-between items-center w-full md:flex md:w-auto md:order-1 hidden\\" id=\\"mobile-menu-2\\">
|
||||
<ul class=\\"flex flex-col md:flex-row md:space-x-8 mt-2 md:mt-0 md:font-lightbold\\">
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
<li>
|
||||
<router-link to=\\"[object Object]\\" class=\\"block py-2 pr-4 pl-3 text-zinc-700 border-b border-gray-100 hover:bg-zinc-50 md:hover:bg-transparent md:border-0 md:hover:text-mbz-purple-700 md:p-0 dark:text-zinc-400 md:dark:hover:text-white dark:hover:bg-zinc-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700\\">Login</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link to=\\"[object Object]\\" class=\\"block py-2 pr-4 pl-3 text-zinc-700 border-b border-gray-100 hover:bg-zinc-50 md:hover:bg-transparent md:border-0 md:hover:text-mbz-purple-700 md:p-0 dark:text-zinc-400 md:dark:hover:text-white dark:hover:bg-zinc-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700\\">Register</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- <o-navbar
|
||||
id=\\"navbar\\"
|
||||
type=\\"is-secondary\\"
|
||||
wrapper-class=\\"container mx-auto\\"
|
||||
v-model:active=\\"mobileNavbarActive\\"
|
||||
>
|
||||
<template #brand>
|
||||
<o-navbar-item
|
||||
tag=\\"router-link\\"
|
||||
:to=\\"{ name: RouteName.HOME }\\"
|
||||
:aria-label=\\"$t('Home')\\"
|
||||
>
|
||||
<logo />
|
||||
</o-navbar-item>
|
||||
</template>
|
||||
<template #start>
|
||||
<o-navbar-item tag=\\"router-link\\" :to=\\"{ name: RouteName.SEARCH }\\">{{
|
||||
$t(\\"Explore\\")
|
||||
}}</o-navbar-item>
|
||||
<o-navbar-item
|
||||
v-if=\\"currentActor.id && currentUser?.isLoggedIn\\"
|
||||
tag=\\"router-link\\"
|
||||
:to=\\"{ name: RouteName.MY_EVENTS }\\"
|
||||
>{{ $t(\\"My events\\") }}</o-navbar-item
|
||||
>
|
||||
<o-navbar-item
|
||||
tag=\\"router-link\\"
|
||||
:to=\\"{ name: RouteName.MY_GROUPS }\\"
|
||||
v-if=\\"
|
||||
config &&
|
||||
config.features.groups &&
|
||||
currentActor.id &&
|
||||
currentUser?.isLoggedIn
|
||||
\\"
|
||||
>{{ $t(\\"My groups\\") }}</o-navbar-item
|
||||
>
|
||||
<o-navbar-item
|
||||
tag=\\"span\\"
|
||||
v-if=\\"
|
||||
config &&
|
||||
config.features.eventCreation &&
|
||||
currentActor.id &&
|
||||
currentUser?.isLoggedIn
|
||||
\\"
|
||||
>
|
||||
<o-button
|
||||
v-if=\\"!hideCreateEventsButton\\"
|
||||
tag=\\"router-link\\"
|
||||
:to=\\"{ name: RouteName.CREATE_EVENT }\\"
|
||||
variant=\\"primary\\"
|
||||
>{{ $t(\\"Create\\") }}</o-button
|
||||
>
|
||||
</o-navbar-item>
|
||||
</template>
|
||||
<template #end>
|
||||
<o-navbar-item tag=\\"div\\">
|
||||
<search-field @navbar-search=\\"mobileNavbarActive = false\\" />
|
||||
</o-navbar-item>
|
||||
|
||||
<o-navbar-dropdown
|
||||
v-if=\\"currentActor.id && currentUser?.isLoggedIn\\"
|
||||
right
|
||||
collapsible
|
||||
ref=\\"user-dropdown\\"
|
||||
tabindex=\\"0\\"
|
||||
tag=\\"span\\"
|
||||
@keyup.enter=\\"toggleMenu\\"
|
||||
>
|
||||
<template #label v-if=\\"currentActor\\">
|
||||
<div class=\\"identity-wrapper\\">
|
||||
<div>
|
||||
<figure class=\\"image is-32x32\\" v-if=\\"currentActor.avatar\\">
|
||||
<img
|
||||
class=\\"is-rounded\\"
|
||||
alt=\\"avatarUrl\\"
|
||||
:src=\\"currentActor.avatar.url\\"
|
||||
/>
|
||||
</figure>
|
||||
<o-icon v-else icon=\\"account-circle\\" />
|
||||
</div>
|
||||
<div class=\\"media-content is-hidden-desktop\\">
|
||||
<span>{{ displayName(currentActor) }}</span>
|
||||
<span class=\\"has-text-grey-dark\\" v-if=\\"currentActor.name\\"
|
||||
>@{{ currentActor.preferredUsername }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
No identities dropdown if no identities
|
||||
<span v-if=\\"identities.length <= 1\\"></span>
|
||||
<o-navbar-item
|
||||
tag=\\"span\\"
|
||||
v-for=\\"identity in identities\\"
|
||||
v-else
|
||||
:active=\\"identity.id === currentActor.id\\"
|
||||
:key=\\"identity.id\\"
|
||||
tabindex=\\"0\\"
|
||||
@click=\\"setIdentity({
|
||||
preferredUsername: identity.preferredUsername,
|
||||
})\\"
|
||||
@keyup.enter=\\"setIdentity({
|
||||
preferredUsername: identity.preferredUsername,
|
||||
})\\"
|
||||
>
|
||||
<span>
|
||||
<div class=\\"media-left\\">
|
||||
<figure class=\\"image is-32x32\\" v-if=\\"identity.avatar\\">
|
||||
<img
|
||||
class=\\"is-rounded\\"
|
||||
loading=\\"lazy\\"
|
||||
:src=\\"identity.avatar.url\\"
|
||||
alt
|
||||
/>
|
||||
</figure>
|
||||
<o-icon v-else size=\\"is-medium\\" icon=\\"account-circle\\" />
|
||||
</div>
|
||||
|
||||
<div class=\\"media-content\\">
|
||||
<span>{{ displayName(identity) }}</span>
|
||||
<span class=\\"has-text-grey-dark\\" v-if=\\"identity.name\\"
|
||||
>@{{ identity.preferredUsername }}</span
|
||||
>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<hr class=\\"navbar-divider\\" role=\\"presentation\\" />
|
||||
</o-navbar-item>
|
||||
|
||||
<o-navbar-item
|
||||
tag=\\"router-link\\"
|
||||
:to=\\"{ name: RouteName.UPDATE_IDENTITY }\\"
|
||||
>{{ $t(\\"My account\\") }}</o-navbar-item
|
||||
>
|
||||
<o-navbar-item
|
||||
v-if=\\"currentUser.role === ICurrentUserRole.ADMINISTRATOR\\"
|
||||
tag=\\"router-link\\"
|
||||
:to=\\"{ name: RouteName.ADMIN_DASHBOARD }\\"
|
||||
>{{ $t(\\"Administration\\") }}</o-navbar-item
|
||||
>
|
||||
|
||||
<o-navbar-item
|
||||
tag=\\"span\\"
|
||||
tabindex=\\"0\\"
|
||||
@click=\\"logout\\"
|
||||
@keyup.enter=\\"logout\\"
|
||||
>
|
||||
<span>{{ $t(\\"Log out\\") }}</span>
|
||||
</o-navbar-item>
|
||||
</o-navbar-dropdown>
|
||||
|
||||
<o-navbar-item v-else tag=\\"div\\">
|
||||
<div class=\\"buttons\\">
|
||||
<router-link
|
||||
class=\\"button is-primary\\"
|
||||
v-if=\\"config && config.registrationsOpen\\"
|
||||
:to=\\"{ name: RouteName.REGISTER }\\"
|
||||
>
|
||||
<strong>{{ $t(\\"Sign up\\") }}</strong>
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
class=\\"button is-light\\"
|
||||
:to=\\"{ name: RouteName.LOGIN }\\"
|
||||
>{{ $t(\\"Log in\\") }}</router-link
|
||||
>
|
||||
</div>
|
||||
</o-navbar-item>
|
||||
</template>
|
||||
</o-navbar> -->"
|
||||
`;
|
|
@ -1,40 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"importHelpers": true,
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"types": ["webpack-env", "jest", "vite/client"],
|
||||
"typeRoots": ["./@types", "./node_modules/@types"],
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"es2017.intl",
|
||||
"dom.iterable",
|
||||
"scripthost",
|
||||
"webworker"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue",
|
||||
"tests/**/*.ts",
|
||||
"tests/**/*.tsx",
|
||||
"env.d.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
import vue from "@vitejs/plugin-vue";
|
||||
import { defineConfig } from "vite";
|
||||
import path from "path";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
import { visualizer } from "rollup-plugin-visualizer";
|
||||
|
||||
export default defineConfig(({ command }) => {
|
||||
const isDev = command !== "build";
|
||||
if (isDev) {
|
||||
// Terminate the watcher when Phoenix quits
|
||||
process.stdin.on("close", () => {
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.stdin.resume();
|
||||
}
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
vue(),
|
||||
VitePWA({
|
||||
// registerType: "autoUpdate",
|
||||
strategies: "injectManifest",
|
||||
srcDir: "src",
|
||||
filename: "service-worker.ts",
|
||||
// injectRegister: "auto",
|
||||
// devOptions: {
|
||||
// enabled: true,
|
||||
// },
|
||||
}),
|
||||
visualizer(),
|
||||
],
|
||||
build: {
|
||||
manifest: true,
|
||||
outDir: path.resolve(__dirname, "../priv/static"),
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
// overwrite default .html entry
|
||||
input: {
|
||||
main: "src/main.ts",
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
sassOptions: {
|
||||
quietDeps: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
coverage: {
|
||||
reporter: ["text", "json", "html"],
|
||||
},
|
||||
setupFiles: path.resolve(__dirname, "./tests/unit/setup.ts"),
|
||||
include: [path.resolve(__dirname, "./tests/unit/specs/**/*.spec.ts")],
|
||||
},
|
||||
};
|
||||
});
|
6791
js/yarn.lock
6791
js/yarn.lock
File diff suppressed because it is too large
Load diff
|
@ -82,6 +82,11 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Accept do
|
|||
)
|
||||
|
||||
Scheduler.trigger_notifications_for_participant(participant)
|
||||
|
||||
Mobilizon.Service.Activity.Participant.insert_activity(participant,
|
||||
subject: "event_new_participation"
|
||||
)
|
||||
|
||||
participant_as_data = Convertible.model_to_as(participant)
|
||||
audience = Audience.get_audience(participant)
|
||||
|
||||
|
|
|
@ -14,7 +14,15 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Create do
|
|||
]
|
||||
|
||||
@type create_entities ::
|
||||
:event | :comment | :discussion | :actor | :todo_list | :todo | :resource | :post
|
||||
:event
|
||||
| :comment
|
||||
| :discussion
|
||||
| :conversation
|
||||
| :actor
|
||||
| :todo_list
|
||||
| :todo
|
||||
| :resource
|
||||
| :post
|
||||
|
||||
@doc """
|
||||
Create an activity of type `Create`
|
||||
|
@ -50,18 +58,27 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Create do
|
|||
end
|
||||
end
|
||||
|
||||
@map_types %{
|
||||
:event => Types.Events,
|
||||
:comment => Types.Comments,
|
||||
:discussion => Types.Discussions,
|
||||
:conversation => Types.Conversations,
|
||||
:actor => Types.Actors,
|
||||
:todo_list => Types.TodoLists,
|
||||
:todo => Types.Todos,
|
||||
:resource => Types.Resources,
|
||||
:post => Types.Posts
|
||||
}
|
||||
|
||||
@spec do_create(create_entities(), map(), map()) ::
|
||||
{:ok, Entity.t(), Activity.t()} | {:error, Ecto.Changeset.t() | atom()}
|
||||
defp do_create(type, args, additional) do
|
||||
case type do
|
||||
:event -> Types.Events.create(args, additional)
|
||||
:comment -> Types.Comments.create(args, additional)
|
||||
:discussion -> Types.Discussions.create(args, additional)
|
||||
:actor -> Types.Actors.create(args, additional)
|
||||
:todo_list -> Types.TodoLists.create(args, additional)
|
||||
:todo -> Types.Todos.create(args, additional)
|
||||
:resource -> Types.Resources.create(args, additional)
|
||||
:post -> Types.Posts.create(args, additional)
|
||||
mod = Map.get(@map_types, type)
|
||||
|
||||
if is_nil(mod) do
|
||||
{:error, :type_not_supported}
|
||||
else
|
||||
mod.create(args, additional)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Invite do
|
|||
) do
|
||||
Logger.debug("Handling #{actor_url} invite to #{group_url} sent to #{target_actor_url}")
|
||||
|
||||
if is_able_to_invite?(actor, group) do
|
||||
if able_to_invite?(actor, group) do
|
||||
with {:ok, %Member{url: member_url} = member} <-
|
||||
Actors.create_member(%{
|
||||
parent_id: group_id,
|
||||
|
@ -64,8 +64,8 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Invite do
|
|||
end
|
||||
end
|
||||
|
||||
@spec is_able_to_invite?(Actor.t(), Actor.t()) :: boolean
|
||||
defp is_able_to_invite?(%Actor{domain: actor_domain, id: actor_id}, %Actor{
|
||||
@spec able_to_invite?(Actor.t(), Actor.t()) :: boolean
|
||||
defp able_to_invite?(%Actor{domain: actor_domain, id: actor_id}, %Actor{
|
||||
domain: group_domain,
|
||||
id: group_id
|
||||
}) do
|
||||
|
@ -76,7 +76,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Invite do
|
|||
# If local group, we'll send the invite
|
||||
case Actors.get_member(actor_id, group_id) do
|
||||
{:ok, %Member{} = admin_member} ->
|
||||
Member.is_administrator(admin_member)
|
||||
Member.administrator?(admin_member)
|
||||
|
||||
_ ->
|
||||
false
|
||||
|
|
|
@ -34,7 +34,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Leave do
|
|||
local,
|
||||
additional
|
||||
) do
|
||||
if Participant.is_not_only_organizer(event_id, actor_id) do
|
||||
if Participant.not_only_organizer?(event_id, actor_id) do
|
||||
{:error, :is_only_organizer}
|
||||
else
|
||||
case Mobilizon.Events.get_participant(
|
||||
|
@ -83,7 +83,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Leave do
|
|||
case Actors.get_member(actor_id, group_id) do
|
||||
{:ok, %Member{id: member_id} = member} ->
|
||||
if Map.get(additional, :force_member_removal, false) || group_domain != actor_domain ||
|
||||
!Actors.is_only_administrator?(member_id, group_id) do
|
||||
!Actors.only_administrator?(member_id, group_id) do
|
||||
with {:ok, %Member{} = member} <- Actors.delete_member(member) do
|
||||
Mobilizon.Service.Activity.Member.insert_activity(member, subject: "member_quit")
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actions.Update do
|
|||
{:ok, activity, entity}
|
||||
|
||||
{:error, err} ->
|
||||
Logger.error("Something went wrong while creating an activity", err: inspect(err))
|
||||
Logger.error("Something went wrong while creating an activity: #{inspect(err)}")
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -70,7 +70,7 @@ defmodule Mobilizon.Federation.ActivityPub do
|
|||
handle_existing_entity(url, entity, options)
|
||||
|
||||
{:error, e} ->
|
||||
Logger.warn("Something failed while fetching url #{url} #{inspect(e)}")
|
||||
Logger.warning("Something failed while fetching url #{url} #{inspect(e)}")
|
||||
{:error, e}
|
||||
end
|
||||
else
|
||||
|
@ -135,7 +135,7 @@ defmodule Mobilizon.Federation.ActivityPub do
|
|||
|
||||
%Page{total: total_events, elements: events} =
|
||||
if actor_id == relay_actor_id do
|
||||
Events.list_public_local_events(page, limit)
|
||||
Events.list_public_local_events(page, limit, :publish_at, :desc)
|
||||
else
|
||||
Events.list_public_events_for_actor(actor, page, limit)
|
||||
end
|
||||
|
|
|
@ -102,7 +102,8 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
|
|||
end
|
||||
|
||||
@spec find_or_make_group_from_nickname(nick :: String.t()) ::
|
||||
{:error, make_actor_errors | WebFinger.finger_errors()}
|
||||
{:ok, Mobilizon.Actors.Actor.t()}
|
||||
| {:error, make_actor_errors | WebFinger.finger_errors()}
|
||||
def find_or_make_group_from_nickname(nick), do: find_or_make_actor_from_nickname(nick, :Group)
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -5,6 +5,7 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
|
|||
|
||||
alias Mobilizon.{Actors, Discussions, Events, Share}
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Conversations.Conversation
|
||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||
alias Mobilizon.Events.{Event, Participant}
|
||||
alias Mobilizon.Federation.ActivityPub.Types.Entity
|
||||
|
@ -38,6 +39,10 @@ defmodule Mobilizon.Federation.ActivityPub.Audience do
|
|||
%{"to" => maybe_add_group_members([], actor), "cc" => []}
|
||||
end
|
||||
|
||||
def get_audience(%Conversation{participants: participants}) do
|
||||
%{"to" => Enum.map(participants, & &1.url), "cc" => []}
|
||||
end
|
||||
|
||||
# Deleted comments are just like tombstones
|
||||
def get_audience(%Comment{deleted_at: deleted_at}) when not is_nil(deleted_at) do
|
||||
%{"to" => [@ap_public], "cc" => []}
|
||||
|
|
|
@ -22,7 +22,9 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
|
|||
| {:error,
|
||||
:invalid_url | :http_gone | :http_error | :http_not_found | :content_not_json}
|
||||
def fetch(url, options \\ []) do
|
||||
on_behalf_of = Keyword.get(options, :on_behalf_of, Relay.get_actor())
|
||||
Logger.debug("Fetching #{url} with AP Fetcher")
|
||||
on_behalf_of = Keyword.get(options, :on_behalf_of, actor_relay())
|
||||
Logger.debug("Fetching on behalf of #{inspect(on_behalf_of.url)}")
|
||||
date = Signature.generate_date_header()
|
||||
|
||||
headers =
|
||||
|
@ -32,59 +34,22 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
|
|||
|
||||
client = ActivityPubClient.client(headers: headers)
|
||||
|
||||
if address_valid?(url) do
|
||||
case ActivityPubClient.get(client, url) do
|
||||
{:ok, %Tesla.Env{body: data, status: code}} when code in 200..299 and is_map(data) ->
|
||||
{:ok, data}
|
||||
|
||||
{:ok, %Tesla.Env{status: 410}} ->
|
||||
Logger.debug("Resource at #{url} is 410 Gone")
|
||||
{:error, :http_gone}
|
||||
|
||||
{:ok, %Tesla.Env{status: 404}} ->
|
||||
Logger.debug("Resource at #{url} is 404 Gone")
|
||||
{:error, :http_not_found}
|
||||
|
||||
{:ok, %Tesla.Env{body: data}} when is_binary(data) ->
|
||||
{:error, :content_not_json}
|
||||
|
||||
{:ok, %Tesla.Env{} = res} ->
|
||||
Logger.debug("Resource returned bad HTTP code #{inspect(res)}")
|
||||
{:error, :http_error}
|
||||
|
||||
{:error, err} ->
|
||||
{:error, err}
|
||||
end
|
||||
if address_valid?(url) and url != on_behalf_of.url do
|
||||
do_fetch(url, client)
|
||||
else
|
||||
{:error, :invalid_url}
|
||||
end
|
||||
end
|
||||
|
||||
@spec fetch_and_create(String.t(), Keyword.t()) ::
|
||||
{:ok, map(), struct()} | {:error, atom()} | :error
|
||||
{:ok, map(), struct()} | {:error, atom()} | {:error, Ecto.Changeset.t()} | :error
|
||||
def fetch_and_create(url, options \\ []) do
|
||||
case fetch(url, options) do
|
||||
{:ok, data} when is_map(data) ->
|
||||
if origin_check?(url, data) do
|
||||
case Transmogrifier.handle_incoming(%{
|
||||
"type" => "Create",
|
||||
"to" => data["to"],
|
||||
"cc" => data["cc"],
|
||||
"actor" => data["actor"] || data["attributedTo"],
|
||||
"attributedTo" => data["attributedTo"] || data["actor"],
|
||||
"object" => data
|
||||
}) do
|
||||
{:ok, entity, structure} ->
|
||||
{:ok, entity, structure}
|
||||
|
||||
{:error, error} when is_atom(error) ->
|
||||
{:error, error}
|
||||
|
||||
:error ->
|
||||
{:error, :transmogrifier_error}
|
||||
end
|
||||
pass_to_transmogrifier(data)
|
||||
else
|
||||
Logger.warn("Object origin check failed")
|
||||
Logger.warning("Object origin check failed")
|
||||
{:error, :object_origin_check_failed}
|
||||
end
|
||||
|
||||
|
@ -108,7 +73,7 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
|
|||
"object" => data
|
||||
})
|
||||
else
|
||||
Logger.warn("Object origin check failed")
|
||||
Logger.warning("Object origin check failed")
|
||||
{:error, :object_origin_check_failed}
|
||||
end
|
||||
|
||||
|
@ -117,6 +82,34 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
|
|||
end
|
||||
end
|
||||
|
||||
@spec pass_to_transmogrifier(map()) ::
|
||||
{:ok, map(), struct()}
|
||||
| {:error, atom()}
|
||||
| {:error, Ecto.Changeset.t()}
|
||||
| {:error, :transmogrifier_error}
|
||||
defp pass_to_transmogrifier(data) do
|
||||
case Transmogrifier.handle_incoming(%{
|
||||
"type" => "Create",
|
||||
"to" => data["to"],
|
||||
"cc" => data["cc"],
|
||||
"actor" => data["actor"] || data["attributedTo"],
|
||||
"attributedTo" => data["attributedTo"] || data["actor"],
|
||||
"object" => data
|
||||
}) do
|
||||
{:ok, entity, structure} ->
|
||||
{:ok, entity, structure}
|
||||
|
||||
{:error, error} when is_atom(error) ->
|
||||
{:error, error}
|
||||
|
||||
{:error, %Ecto.Changeset{} = err} ->
|
||||
{:error, err}
|
||||
|
||||
:error ->
|
||||
{:error, :transmogrifier_error}
|
||||
end
|
||||
end
|
||||
|
||||
@type fetch_actor_errors ::
|
||||
:json_decode_error | :actor_deleted | :http_error | :actor_not_allowed_type
|
||||
|
||||
|
@ -169,4 +162,38 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
|
|||
%URI{host: host, scheme: scheme} = URI.parse(address)
|
||||
is_valid_string(host) and is_valid_string(scheme)
|
||||
end
|
||||
|
||||
defp actor_relay do
|
||||
Relay.get_actor()
|
||||
end
|
||||
|
||||
@spec do_fetch(String.t(), Tesla.Client.t()) ::
|
||||
{:ok, map()}
|
||||
| {:error,
|
||||
:invalid_url | :http_gone | :http_error | :http_not_found | :content_not_json}
|
||||
defp do_fetch(url, client) do
|
||||
case ActivityPubClient.get(client, url) do
|
||||
{:ok, %Tesla.Env{body: data, status: code}} when code in 200..299 and is_map(data) ->
|
||||
Logger.debug("Found the following from ActivityPubClient fetch: #{inspect(data)}")
|
||||
{:ok, data}
|
||||
|
||||
{:ok, %Tesla.Env{status: 410}} ->
|
||||
Logger.debug("Resource at #{url} is 410 Gone")
|
||||
{:error, :http_gone}
|
||||
|
||||
{:ok, %Tesla.Env{status: 404}} ->
|
||||
Logger.debug("Resource at #{url} is 404 Gone")
|
||||
{:error, :http_not_found}
|
||||
|
||||
{:ok, %Tesla.Env{body: data}} when is_binary(data) ->
|
||||
{:error, :content_not_json}
|
||||
|
||||
{:ok, %Tesla.Env{} = res} ->
|
||||
Logger.debug("Resource returned bad HTTP code (#{res.status}) #{inspect(res)}")
|
||||
{:error, :http_error}
|
||||
|
||||
{:error, err} ->
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -44,13 +44,13 @@ defmodule Mobilizon.Federation.ActivityPub.Permission do
|
|||
) do
|
||||
case object |> Ownable.permissions() |> get_in([:create]) do
|
||||
:member ->
|
||||
Actors.is_member?(actor_id, group_id)
|
||||
Actors.member?(actor_id, group_id)
|
||||
|
||||
:moderator ->
|
||||
Actors.is_moderator?(actor_id, group_id)
|
||||
Actors.moderator?(actor_id, group_id)
|
||||
|
||||
:administrator ->
|
||||
Actors.is_administrator?(actor_id, group_id)
|
||||
Actors.administrator?(actor_id, group_id)
|
||||
|
||||
_ ->
|
||||
false
|
||||
|
@ -89,13 +89,13 @@ defmodule Mobilizon.Federation.ActivityPub.Permission do
|
|||
_ ->
|
||||
case permission do
|
||||
:access ->
|
||||
Logger.warn("Actor #{actor_url} can't access #{object.url}")
|
||||
Logger.warning("Actor #{actor_url} can't access #{object.url}")
|
||||
|
||||
:update ->
|
||||
Logger.warn("Actor #{actor_url} can't update #{object.url}")
|
||||
Logger.warning("Actor #{actor_url} can't update #{object.url}")
|
||||
|
||||
:delete ->
|
||||
Logger.warn("Actor #{actor_url} can't delete #{object.url}")
|
||||
Logger.warning("Actor #{actor_url} can't delete #{object.url}")
|
||||
end
|
||||
|
||||
false
|
||||
|
@ -122,21 +122,21 @@ defmodule Mobilizon.Federation.ActivityPub.Permission do
|
|||
"Checking if activity actor #{actor_url} is a moderator from group from #{object.url}"
|
||||
)
|
||||
|
||||
Actors.is_moderator?(actor_id, group_id)
|
||||
Actors.moderator?(actor_id, group_id)
|
||||
|
||||
:administrator ->
|
||||
Logger.debug(
|
||||
"Checking if activity actor #{actor_url} is an administrator from group from #{object.url}"
|
||||
)
|
||||
|
||||
Actors.is_administrator?(actor_id, group_id)
|
||||
Actors.administrator?(actor_id, group_id)
|
||||
|
||||
_ ->
|
||||
Logger.debug(
|
||||
"Checking if activity actor #{actor_url} is a member from group from #{object.url}"
|
||||
)
|
||||
|
||||
Actors.is_member?(actor_id, group_id)
|
||||
Actors.member?(actor_id, group_id)
|
||||
end
|
||||
|
||||
_ ->
|
||||
|
|
|
@ -21,10 +21,10 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
|
|||
Logger.debug("Publishing an activity")
|
||||
Logger.debug(inspect(activity, pretty: true))
|
||||
|
||||
public = Visibility.is_public?(activity)
|
||||
public = Visibility.public?(activity)
|
||||
Logger.debug("is public ? #{public}")
|
||||
|
||||
if public && is_create_activity?(activity) && Config.get([:instance, :allow_relay]) do
|
||||
if public && create_activity?(activity) && Config.get([:instance, :allow_relay]) do
|
||||
Logger.info(fn -> "Relaying #{activity.data["id"]} out" end)
|
||||
|
||||
Relay.publish(activity)
|
||||
|
@ -125,9 +125,9 @@ defmodule Mobilizon.Federation.ActivityPub.Publisher do
|
|||
end)
|
||||
end
|
||||
|
||||
@spec is_create_activity?(Activity.t()) :: boolean
|
||||
defp is_create_activity?(%Activity{data: %{"type" => "Create"}}), do: true
|
||||
defp is_create_activity?(_), do: false
|
||||
@spec create_activity?(Activity.t()) :: boolean
|
||||
defp create_activity?(%Activity{data: %{"type" => "Create"}}), do: true
|
||||
defp create_activity?(_), do: false
|
||||
|
||||
@spec convert_members_in_recipients(list(String.t())) :: {list(String.t()), list(Actor.t())}
|
||||
defp convert_members_in_recipients(recipients) do
|
||||
|
|
|
@ -55,7 +55,7 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
|
|||
@type fetch_actor_errors :: ActivityPubActor.make_actor_errors() | fetch_collection_errors()
|
||||
|
||||
@spec fetch_group(String.t(), Actor.t()) :: :ok | {:error, fetch_actor_errors}
|
||||
def fetch_group(group_url, %Actor{} = on_behalf_of) do
|
||||
def fetch_group(group_url, %Actor{} = on_behalf_of) when is_binary(group_url) do
|
||||
Logger.debug("Fetching group #{group_url}")
|
||||
|
||||
case ActivityPubActor.make_actor_from_url(group_url, on_behalf_of: on_behalf_of) do
|
||||
|
|
|
@ -13,17 +13,22 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
|
|||
|
||||
alias Mobilizon.Federation.ActivityPub.{Actions, Activity, Transmogrifier}
|
||||
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
||||
alias Mobilizon.Federation.WebFinger
|
||||
alias Mobilizon.Federation.{NodeInfo, WebFinger}
|
||||
alias Mobilizon.GraphQL.API.Follows
|
||||
alias Mobilizon.Service.Workers.Background
|
||||
import Mobilizon.Federation.ActivityPub.Utils, only: [create_full_domain_string: 1]
|
||||
|
||||
require Logger
|
||||
|
||||
def init do
|
||||
# Wait for everything to settle.
|
||||
Process.sleep(1000 * 5)
|
||||
get_actor()
|
||||
relay = get_actor()
|
||||
|
||||
unless Regex.match?(~r/BEGIN RSA PRIVATE KEY/, relay.keys) do
|
||||
{:ok, _relay} = Actors.actor_key_rotation(relay)
|
||||
end
|
||||
|
||||
relay
|
||||
end
|
||||
|
||||
@spec get_actor() :: Actor.t() | no_return
|
||||
|
@ -43,18 +48,25 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
|
|||
%Actor{} = local_actor = get_actor()
|
||||
|
||||
with {:ok, target_instance} <- fetch_actor(address),
|
||||
{:ok, %Actor{} = target_actor} <-
|
||||
{:ok, %Actor{id: target_actor_id} = target_actor} <-
|
||||
ActivityPubActor.get_or_fetch_actor_by_url(target_instance),
|
||||
{:ok, activity, follow} <- Follows.follow(local_actor, target_actor) do
|
||||
Logger.info("Relay: followed instance #{target_instance}; id=#{activity.data["id"]}")
|
||||
|
||||
Background.enqueue("refresh_profile", %{
|
||||
"actor_id" => target_actor_id
|
||||
})
|
||||
|
||||
Logger.info("Relay: schedule refreshing instance #{target_instance} after follow")
|
||||
|
||||
{:ok, activity, follow}
|
||||
else
|
||||
{:error, :person_no_follow} ->
|
||||
Logger.warn("Only group and instances can be followed")
|
||||
Logger.warning("Only group and instances can be followed")
|
||||
{:error, :person_no_follow}
|
||||
|
||||
{:error, e} ->
|
||||
Logger.warn("Error while following remote instance: #{inspect(e)}")
|
||||
Logger.warning("Error while following remote instance: #{inspect(e)}")
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
@ -72,7 +84,7 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
|
|||
{:ok, activity, follow}
|
||||
else
|
||||
{:error, e} ->
|
||||
Logger.warn("Error while unfollowing remote instance: #{inspect(e)}")
|
||||
Logger.warning("Error while unfollowing remote instance: #{inspect(e)}")
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
@ -90,7 +102,7 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
|
|||
{:ok, activity, follow}
|
||||
else
|
||||
{:error, e} ->
|
||||
Logger.warn("Error while accepting remote instance follow: #{inspect(e)}")
|
||||
Logger.warning("Error while accepting remote instance follow: #{inspect(e)}")
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
@ -108,7 +120,7 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
|
|||
{:ok, activity, follow}
|
||||
else
|
||||
{:error, e} ->
|
||||
Logger.warn("Error while rejecting remote instance follow: #{inspect(e)}")
|
||||
Logger.warning("Error while rejecting remote instance follow: #{inspect(e)}")
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
@ -131,7 +143,7 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
|
|||
})
|
||||
else
|
||||
{:error, e} ->
|
||||
Logger.warn("Error while refreshing remote instance: #{inspect(e)}")
|
||||
Logger.warning("Error while refreshing remote instance: #{inspect(e)}")
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
@ -172,17 +184,17 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
|
|||
defp fetch_actor("http://" <> address), do: fetch_actor(address)
|
||||
|
||||
defp fetch_actor(address) do
|
||||
%URI{host: host} = uri = URI.parse("http://" <> address)
|
||||
%URI{host: host} = URI.parse("http://" <> address)
|
||||
|
||||
cond do
|
||||
String.contains?(address, "@") ->
|
||||
check_actor(address)
|
||||
|
||||
!is_nil(host) ->
|
||||
uri
|
||||
|> create_full_domain_string()
|
||||
|> then(&Kernel.<>("relay@", &1))
|
||||
|> check_actor()
|
||||
case NodeInfo.application_actor(host) do
|
||||
nil -> check_actor("relay@#{host}")
|
||||
actor_url when is_binary(actor_url) -> {:ok, actor_url}
|
||||
end
|
||||
|
||||
true ->
|
||||
{:error, :bad_url}
|
||||
|
|
|
@ -68,24 +68,24 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object}) do
|
||||
Logger.info("Handle incoming to create notes")
|
||||
|
||||
case Converter.Comment.as_to_model_data(object) do
|
||||
%{visibility: visibility, event_id: event_id}
|
||||
when visibility != :public and event_id != nil ->
|
||||
Logger.info("Tried to reply to an event with a private comment - ignore")
|
||||
:error
|
||||
case Discussions.get_comment_from_url_with_preload(object["id"]) do
|
||||
{:error, :comment_not_found} ->
|
||||
case Converter.Comment.as_to_model_data(object) do
|
||||
%{visibility: visibility, attributed_to_id: attributed_to_id} = object_data
|
||||
when visibility === :private and is_nil(attributed_to_id) ->
|
||||
Actions.Create.create(:conversation, object_data, false)
|
||||
|
||||
object_data when is_map(object_data) ->
|
||||
case Discussions.get_comment_from_url_with_preload(object_data.url) do
|
||||
{:error, :comment_not_found} ->
|
||||
object_data
|
||||
|> transform_object_data_for_discussion()
|
||||
|> save_comment_or_discussion()
|
||||
object_data when is_map(object_data) ->
|
||||
handle_comment_or_discussion(object_data)
|
||||
|
||||
{:ok, %Comment{} = comment} ->
|
||||
# Object already exists
|
||||
{:ok, nil, comment}
|
||||
{:error, err} ->
|
||||
{:error, err}
|
||||
end
|
||||
|
||||
{:ok, %Comment{} = comment} ->
|
||||
# Object already exists
|
||||
{:ok, nil, comment}
|
||||
|
||||
{:error, err} ->
|
||||
{:error, err}
|
||||
end
|
||||
|
@ -203,7 +203,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
Actions.Delete.delete(entity, Relay.get_actor(), false)
|
||||
|
||||
{:error, err} ->
|
||||
Logger.warn("Error while fetching object from URL", error: inspect(err))
|
||||
Logger.warning("Error while fetching object from URL: #{inspect(err)}")
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
@ -219,11 +219,11 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
{:ok, activity, object}
|
||||
else
|
||||
{:error, :person_no_follow} ->
|
||||
Logger.warn("Only group and instances can be followed")
|
||||
Logger.warning("Only group and instances can be followed")
|
||||
:error
|
||||
|
||||
{:error, e} ->
|
||||
Logger.warn("Unable to handle Follow activity #{inspect(e)}")
|
||||
Logger.warning("Unable to handle Follow activity #{inspect(e)}")
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
@ -285,7 +285,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
object_data when is_map(object_data) <-
|
||||
object |> Converter.Resource.as_to_model_data(),
|
||||
{:member, true} <-
|
||||
{:member, Actors.is_member?(object_data.creator_id, object_data.actor_id)},
|
||||
{:member, Actors.member?(object_data.creator_id, object_data.actor_id)},
|
||||
{:ok, %Activity{} = activity, %Resource{} = resource} <-
|
||||
Actions.Create.create(:resource, object_data, false) do
|
||||
{:ok, activity, resource}
|
||||
|
@ -325,14 +325,14 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
:error
|
||||
|
||||
{:object_not_found, nil} ->
|
||||
Logger.warn(
|
||||
Logger.warning(
|
||||
"Unable to process Accept activity #{inspect(id)}. Object #{inspect(accepted_object)} wasn't found."
|
||||
)
|
||||
|
||||
:error
|
||||
|
||||
e ->
|
||||
Logger.warn(
|
||||
Logger.warning(
|
||||
"Unable to process Accept activity #{inspect(id)} for object #{inspect(accepted_object)} only returned #{inspect(e)}"
|
||||
)
|
||||
|
||||
|
@ -353,14 +353,14 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
{:ok, activity, object}
|
||||
else
|
||||
{:object_not_found, nil} ->
|
||||
Logger.warn(
|
||||
Logger.warning(
|
||||
"Unable to process Reject activity #{inspect(id)}. Object #{inspect(rejected_object)} wasn't found."
|
||||
)
|
||||
|
||||
:error
|
||||
|
||||
e ->
|
||||
Logger.warn(
|
||||
Logger.warning(
|
||||
"Unable to process Reject activity #{inspect(id)} for object #{inspect(rejected_object)} only returned #{inspect(e)}"
|
||||
)
|
||||
|
||||
|
@ -405,8 +405,8 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
{:ok, activity, new_actor}
|
||||
else
|
||||
{:error, :update_not_allowed} ->
|
||||
Logger.warn("Activity tried to update an actor that's local or not a group",
|
||||
activity: params
|
||||
Logger.warning(
|
||||
"Activity tried to update an actor that's local or not a group: #{inspect(params)}"
|
||||
)
|
||||
|
||||
:error
|
||||
|
@ -485,7 +485,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
{:ok, activity, new_post}
|
||||
else
|
||||
{:origin_check, _} ->
|
||||
Logger.warn("Actor tried to update a post but doesn't has the required role")
|
||||
Logger.warning("Actor tried to update a post but doesn't has the required role")
|
||||
:error
|
||||
|
||||
_e ->
|
||||
|
@ -623,25 +623,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
if remote_actor_is_being_deleted(data) do
|
||||
Actions.Delete.delete(actor, actor, false)
|
||||
else
|
||||
case is_group_object_gone(object_id) do
|
||||
# The group object is no longer there, we can remove the element
|
||||
{:ok, entity} ->
|
||||
if Utils.origin_check_from_id?(actor_url, object_id) ||
|
||||
Permission.can_delete_group_object?(actor, entity) do
|
||||
Actions.Delete.delete(entity, actor, false)
|
||||
else
|
||||
Logger.warn("Object origin check failed")
|
||||
:error
|
||||
end
|
||||
|
||||
{:error, err} ->
|
||||
Logger.debug(inspect(err))
|
||||
{:error, err}
|
||||
|
||||
{:error, :http_not_found, err} ->
|
||||
Logger.debug(inspect(err))
|
||||
{:error, err}
|
||||
end
|
||||
handle_group_being_gone(actor, actor_url, object_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -710,7 +692,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
{:ok, activity, object}
|
||||
else
|
||||
{:only_organizer, true} ->
|
||||
Logger.warn(
|
||||
Logger.warning(
|
||||
"Actor #{inspect(actor)} tried to leave event #{inspect(object)} but it was the only organizer so we didn't detach it"
|
||||
)
|
||||
|
||||
|
@ -760,14 +742,14 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
Actions.Remove.remove(member, group, moderator, false)
|
||||
else
|
||||
{:is_admin, {:ok, %Member{}}} ->
|
||||
Logger.warn(
|
||||
Logger.warning(
|
||||
"Person #{inspect(actor)} is not an admin from #{inspect(origin)} and can't remove member #{inspect(object)}"
|
||||
)
|
||||
|
||||
{:error, "Member already removed"}
|
||||
|
||||
{:is_member, {:ok, %Member{role: :rejected}}} ->
|
||||
Logger.warn("Member #{inspect(object)} already removed from #{inspect(origin)}")
|
||||
Logger.warning("Member #{inspect(object)} already removed from #{inspect(origin)}")
|
||||
{:error, "Member already removed"}
|
||||
end
|
||||
end
|
||||
|
@ -967,7 +949,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
{:ok, activity, participant}
|
||||
else
|
||||
{:join_event, {:ok, _activity, %Participant{role: :rejected}}} ->
|
||||
Logger.warn(
|
||||
Logger.warning(
|
||||
"Tried to handle an Reject activity on a Join activity with a event object but the participant is already rejected"
|
||||
)
|
||||
|
||||
|
@ -997,19 +979,19 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
end
|
||||
|
||||
# If the object has been announced by a group let's use one of our members to fetch it
|
||||
@spec fetch_object_optionnally_authenticated(String.t(), Actor.t() | any()) ::
|
||||
@spec fetch_object_optionally_authenticated(String.t(), Actor.t() | any()) ::
|
||||
{:ok, struct()} | {:error, any()}
|
||||
defp fetch_object_optionnally_authenticated(url, %Actor{type: :Group, id: group_id}) do
|
||||
defp fetch_object_optionally_authenticated(url, %Actor{type: :Group, id: group_id}) do
|
||||
case Actors.get_single_group_member_actor(group_id) do
|
||||
%Actor{} = actor ->
|
||||
ActivityPub.fetch_object_from_url(url, on_behalf_of: actor, force: true)
|
||||
|
||||
_err ->
|
||||
fetch_object_optionnally_authenticated(url, nil)
|
||||
fetch_object_optionally_authenticated(url, nil)
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch_object_optionnally_authenticated(url, _),
|
||||
defp fetch_object_optionally_authenticated(url, _),
|
||||
do: ActivityPub.fetch_object_from_url(url, force: true)
|
||||
|
||||
defp eventually_create_share(object, entity, actor_id) do
|
||||
|
@ -1023,23 +1005,36 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
end
|
||||
|
||||
# Comment initiates a whole discussion only if it has full title
|
||||
@spec is_data_for_comment_or_discussion?(map()) :: boolean()
|
||||
defp is_data_for_comment_or_discussion?(object_data) do
|
||||
is_data_a_discussion_initialization?(object_data) and
|
||||
@spec data_for_comment_or_discussion?(map()) :: boolean()
|
||||
defp data_for_comment_or_discussion?(object_data) do
|
||||
data_a_discussion_initialization?(object_data) and
|
||||
is_nil(object_data.discussion_id)
|
||||
end
|
||||
|
||||
# Comment initiates a whole discussion only if it has full title
|
||||
defp is_data_a_discussion_initialization?(object_data) do
|
||||
defp data_a_discussion_initialization?(object_data) do
|
||||
not Map.has_key?(object_data, :title) or
|
||||
is_nil(object_data.title) or object_data.title == ""
|
||||
end
|
||||
|
||||
defp handle_comment_or_discussion(object_data) do
|
||||
case Discussions.get_comment_from_url_with_preload(object_data.url) do
|
||||
{:error, :comment_not_found} ->
|
||||
object_data
|
||||
|> transform_object_data_for_discussion()
|
||||
|> save_comment_or_discussion()
|
||||
|
||||
{:ok, %Comment{} = comment} ->
|
||||
# Object already exists
|
||||
{:ok, nil, comment}
|
||||
end
|
||||
end
|
||||
|
||||
# Comment and conversations have different attributes for actor and groups
|
||||
@spec transform_object_data_for_discussion(map()) :: map()
|
||||
defp transform_object_data_for_discussion(object_data) do
|
||||
# Basic comment
|
||||
if is_data_a_discussion_initialization?(object_data) do
|
||||
if data_a_discussion_initialization?(object_data) do
|
||||
object_data
|
||||
else
|
||||
# Conversation
|
||||
|
@ -1118,7 +1113,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
{:ok, object}
|
||||
|
||||
err ->
|
||||
Logger.warn("Error while fetching #{inspect(object)}")
|
||||
Logger.warning("Error while fetching #{inspect(object)}")
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
|
@ -1139,12 +1134,12 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
if Utils.are_same_origin?(url, Endpoint.url()) do
|
||||
ActivityPub.fetch_object_from_url(url, force: false)
|
||||
else
|
||||
fetch_object_optionnally_authenticated(url, actor)
|
||||
fetch_object_optionally_authenticated(url, actor)
|
||||
end
|
||||
end
|
||||
|
||||
defp is_group_object_gone(object_id) do
|
||||
Logger.debug("is_group_object_gone #{object_id}")
|
||||
defp group_object_gone_check(object_id) do
|
||||
Logger.debug("Checking if group object #{object_id} is gone")
|
||||
|
||||
case ActivityPub.fetch_object_from_url(object_id, force: true) do
|
||||
# comments are just emptied
|
||||
|
@ -1168,14 +1163,10 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
# Before 1.0.4 the object of a "Remove" activity was an actor's URL
|
||||
# instead of the member's URL.
|
||||
# TODO: Remove in 1.2
|
||||
@spec get_remove_object(map() | String.t()) :: {:ok, integer()}
|
||||
defp get_remove_object(object) do
|
||||
case object |> Utils.get_url() |> ActivityPub.fetch_object_from_url() do
|
||||
{:ok, %Member{actor: %Actor{id: person_id}}} -> {:ok, person_id}
|
||||
{:ok, %Actor{id: person_id}} -> {:ok, person_id}
|
||||
_ -> {:error, :remove_object_not_found}
|
||||
end
|
||||
end
|
||||
|
@ -1201,7 +1192,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
@spec create_comment_or_discussion(map()) ::
|
||||
{:ok, Activity.t(), struct()} | {:error, atom() | Ecto.Changeset.t()}
|
||||
defp create_comment_or_discussion(object_data) do
|
||||
if is_data_for_comment_or_discussion?(object_data) do
|
||||
if data_for_comment_or_discussion?(object_data) do
|
||||
Logger.debug("Chosing to create a regular comment")
|
||||
Actions.Create.create(:comment, object_data, false)
|
||||
else
|
||||
|
@ -1247,8 +1238,30 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
|||
:error
|
||||
|
||||
{:error, err} ->
|
||||
Logger.debug("Generic error when saving external comment", err: inspect(err))
|
||||
Logger.debug("Generic error when saving external comment: #{inspect(err)}")
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_group_being_gone(actor, actor_url, object_id) do
|
||||
case group_object_gone_check(object_id) do
|
||||
# The group object is no longer there, we can remove the element
|
||||
{:ok, entity} ->
|
||||
if Utils.origin_check_from_id?(actor_url, object_id) ||
|
||||
Permission.can_delete_group_object?(actor, entity) do
|
||||
Actions.Delete.delete(entity, actor, false)
|
||||
else
|
||||
Logger.warning("Object origin check failed")
|
||||
:error
|
||||
end
|
||||
|
||||
{:error, err} ->
|
||||
Logger.debug(inspect(err))
|
||||
{:error, err}
|
||||
|
||||
{:error, :http_not_found, err} ->
|
||||
Logger.debug(inspect(err))
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -301,7 +301,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
|
|||
) do
|
||||
%Actor{id: relay_id} = Relay.get_actor()
|
||||
|
||||
unless follower.target_actor.manually_approves_followers or
|
||||
unless follower.target_actor.manually_approves_followers == true or
|
||||
follower.target_actor.id == relay_id do
|
||||
require Logger
|
||||
Logger.debug("Target doesn't manually approves followers, we can accept right away")
|
||||
|
|
219
lib/federation/activity_pub/types/conversation.ex
Normal file
219
lib/federation/activity_pub/types/conversation.ex
Normal file
|
@ -0,0 +1,219 @@
|
|||
defmodule Mobilizon.Federation.ActivityPub.Types.Conversations do
|
||||
@moduledoc false
|
||||
|
||||
# alias Mobilizon.Conversations.ConversationParticipant
|
||||
alias Mobilizon.{Actors, Conversations, Discussions}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Conversations.Conversation
|
||||
alias Mobilizon.Discussions.Comment
|
||||
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
||||
alias Mobilizon.Federation.ActivityPub.{Audience, Permission}
|
||||
alias Mobilizon.Federation.ActivityPub.Types.Entity
|
||||
alias Mobilizon.Federation.ActivityStream
|
||||
alias Mobilizon.Federation.ActivityStream.Converter.Utils, as: ConverterUtils
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
alias Mobilizon.GraphQL.API.Utils, as: APIUtils
|
||||
alias Mobilizon.Service.Activity.Conversation, as: ConversationActivity
|
||||
alias Mobilizon.Web.Endpoint
|
||||
import Mobilizon.Federation.ActivityPub.Utils, only: [make_create_data: 2, make_update_data: 2]
|
||||
require Logger
|
||||
|
||||
@behaviour Entity
|
||||
|
||||
@impl Entity
|
||||
@spec create(map(), map()) ::
|
||||
{:ok, Conversation.t(), ActivityStream.t()}
|
||||
| {:error,
|
||||
:conversation_not_found
|
||||
| :last_comment_not_found
|
||||
| :empty_participants
|
||||
| Ecto.Changeset.t()}
|
||||
def create(%{conversation_id: conversation_id} = args, additional)
|
||||
when not is_nil(conversation_id) do
|
||||
Logger.debug("Creating a reply to a conversation #{inspect(args, pretty: true)}")
|
||||
args = prepare_args(args)
|
||||
Logger.debug("Creating a reply to a conversation #{inspect(args, pretty: true)}")
|
||||
|
||||
with args when is_map(args) <- prepare_args(args) do
|
||||
case Conversations.get_conversation(conversation_id) do
|
||||
%Conversation{} = conversation ->
|
||||
case Conversations.reply_to_conversation(conversation, args) do
|
||||
{:ok, %Conversation{last_comment_id: last_comment_id} = conversation} ->
|
||||
ConversationActivity.insert_activity(conversation, subject: "conversation_replied")
|
||||
maybe_publish_graphql_subscription(conversation)
|
||||
|
||||
case Discussions.get_comment_with_preload(last_comment_id) do
|
||||
%Comment{} = last_comment ->
|
||||
comment_as_data = Convertible.model_to_as(last_comment)
|
||||
audience = Audience.get_audience(conversation)
|
||||
create_data = make_create_data(comment_as_data, Map.merge(audience, additional))
|
||||
{:ok, conversation, create_data}
|
||||
|
||||
nil ->
|
||||
{:error, :last_comment_not_found}
|
||||
end
|
||||
|
||||
{:error, _, %Ecto.Changeset{} = err, _} ->
|
||||
{:error, err}
|
||||
end
|
||||
|
||||
nil ->
|
||||
{:error, :discussion_not_found}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@impl Entity
|
||||
def create(args, additional) do
|
||||
with args when is_map(args) <- prepare_args(args) do
|
||||
case Conversations.create_conversation(args) do
|
||||
{:ok, %Conversation{} = conversation} ->
|
||||
ConversationActivity.insert_activity(conversation, subject: "conversation_created")
|
||||
conversation_as_data = Convertible.model_to_as(conversation)
|
||||
audience = Audience.get_audience(conversation)
|
||||
create_data = make_create_data(conversation_as_data, Map.merge(audience, additional))
|
||||
{:ok, conversation, create_data}
|
||||
|
||||
{:error, _, %Ecto.Changeset{} = err, _} ->
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@impl Entity
|
||||
@spec update(Conversation.t(), map(), map()) ::
|
||||
{:ok, Conversation.t(), ActivityStream.t()} | {:error, Ecto.Changeset.t()}
|
||||
def update(%Conversation{} = old_conversation, args, additional) do
|
||||
case Conversations.update_conversation(old_conversation, args) do
|
||||
{:ok, %Conversation{} = new_conversation} ->
|
||||
# ConversationActivity.insert_activity(new_conversation,
|
||||
# subject: "conversation_renamed",
|
||||
# old_conversation: old_conversation
|
||||
# )
|
||||
|
||||
conversation_as_data = Convertible.model_to_as(new_conversation)
|
||||
audience = Audience.get_audience(new_conversation)
|
||||
update_data = make_update_data(conversation_as_data, Map.merge(audience, additional))
|
||||
{:ok, new_conversation, update_data}
|
||||
|
||||
{:error, %Ecto.Changeset{} = err} ->
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
|
||||
@impl Entity
|
||||
@spec delete(Conversation.t(), Actor.t(), boolean, map()) ::
|
||||
{:error, Ecto.Changeset.t()} | {:ok, ActivityStream.t(), Actor.t(), Conversation.t()}
|
||||
def delete(
|
||||
%Conversation{} = _conversation,
|
||||
%Actor{} = _actor,
|
||||
_local,
|
||||
_additionnal
|
||||
) do
|
||||
{:error, :not_applicable}
|
||||
end
|
||||
|
||||
# @spec actor(Conversation.t()) :: Actor.t() | nil
|
||||
# def actor(%ConversationParticipant{actor_id: actor_id}), do: Actors.get_actor(actor_id)
|
||||
|
||||
# @spec group_actor(Conversation.t()) :: Actor.t() | nil
|
||||
# def group_actor(%Conversation{actor_id: actor_id}), do: Actors.get_actor(actor_id)
|
||||
|
||||
@spec permissions(Conversation.t()) :: Permission.t()
|
||||
def permissions(%Conversation{}) do
|
||||
%Permission{access: :member, create: :member, update: :moderator, delete: :moderator}
|
||||
end
|
||||
|
||||
@spec maybe_publish_graphql_subscription(Conversation.t()) :: :ok
|
||||
defp maybe_publish_graphql_subscription(%Conversation{} = conversation) do
|
||||
Absinthe.Subscription.publish(Endpoint, conversation,
|
||||
conversation_comment_changed: conversation.id
|
||||
)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@spec prepare_args(map) :: map | {:error, :empty_participants}
|
||||
defp prepare_args(args) do
|
||||
{text, mentions, _tags} =
|
||||
APIUtils.make_content_html(
|
||||
args |> Map.get(:text, "") |> String.trim(),
|
||||
# Can't put additional tags on a comment
|
||||
[],
|
||||
"text/html"
|
||||
)
|
||||
|
||||
mentions =
|
||||
(args |> Map.get(:mentions, []) |> prepare_mentions()) ++
|
||||
ConverterUtils.fetch_mentions(mentions)
|
||||
|
||||
# Can't create a conversation with just ourselves
|
||||
mentions =
|
||||
Enum.filter(mentions, fn %{actor_id: actor_id} ->
|
||||
to_string(actor_id) != to_string(args.actor_id)
|
||||
end)
|
||||
|
||||
if Enum.empty?(mentions) do
|
||||
{:error, :empty_participants}
|
||||
else
|
||||
event = Map.get(args, :event, get_event(Map.get(args, :event_id)))
|
||||
|
||||
participants =
|
||||
(mentions ++
|
||||
[
|
||||
%{actor_id: args.actor_id},
|
||||
%{
|
||||
actor_id:
|
||||
if(is_nil(event),
|
||||
do: nil,
|
||||
else: event.attributed_to_id || event.organizer_actor_id
|
||||
)
|
||||
}
|
||||
])
|
||||
|> Enum.reduce(
|
||||
[],
|
||||
fn %{actor_id: actor_id}, acc ->
|
||||
case Actors.get_actor(actor_id) do
|
||||
nil -> acc
|
||||
actor -> acc ++ [actor]
|
||||
end
|
||||
end
|
||||
)
|
||||
|> Enum.uniq_by(& &1.id)
|
||||
|
||||
args
|
||||
|> Map.put(:text, text)
|
||||
|> Map.put(:mentions, mentions)
|
||||
|> Map.put(:participants, participants)
|
||||
end
|
||||
end
|
||||
|
||||
@spec prepare_mentions(list(String.t())) :: list(%{actor_id: String.t()})
|
||||
defp prepare_mentions(mentions) do
|
||||
Enum.reduce(mentions, [], &prepare_mention/2)
|
||||
end
|
||||
|
||||
@spec prepare_mention(String.t() | map(), list()) :: list(%{actor_id: String.t()})
|
||||
defp prepare_mention(%{actor_id: _} = mention, mentions) do
|
||||
mentions ++ [mention]
|
||||
end
|
||||
|
||||
defp prepare_mention(mention, mentions) do
|
||||
case ActivityPubActor.find_or_make_actor_from_nickname(mention) do
|
||||
{:ok, %Actor{id: actor_id}} ->
|
||||
mentions ++ [%{actor_id: actor_id}]
|
||||
|
||||
{:error, _} ->
|
||||
mentions
|
||||
end
|
||||
end
|
||||
|
||||
defp get_event(nil), do: nil
|
||||
|
||||
defp get_event(event_id) do
|
||||
case Mobilizon.Events.get_event(event_id) do
|
||||
{:ok, event} -> event
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
|
@ -32,6 +32,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
|
|||
case EventsManager.create_event(args) do
|
||||
{:ok, %Event{uuid: event_uuid, begins_on: begins_on} = event} ->
|
||||
EventActivity.insert_activity(event, subject: "event_created")
|
||||
clear_caches(event)
|
||||
|
||||
%{action: :notify_of_new_event, event_uuid: event_uuid}
|
||||
|> EventDelayedNotificationWorker.new(
|
||||
|
@ -61,7 +62,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
|
|||
case EventsManager.update_event(old_event, args) do
|
||||
{:ok, %Event{} = new_event} ->
|
||||
EventActivity.insert_activity(new_event, subject: "event_updated")
|
||||
Cachex.del(:activity_pub, "event_#{new_event.uuid}")
|
||||
clear_caches(new_event)
|
||||
event_as_data = Convertible.model_to_as(new_event)
|
||||
audience = Audience.get_audience(new_event)
|
||||
update_data = make_update_data(event_as_data, Map.merge(audience, additional))
|
||||
|
@ -99,7 +100,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
|
|||
case Tombstone.create_tombstone(%{uri: event.url, actor_id: actor.id}) do
|
||||
{:ok, %Tombstone{} = _tombstone} ->
|
||||
EventActivity.insert_activity(event, subject: "event_deleted")
|
||||
Cachex.del(:activity_pub, "event_#{event.uuid}")
|
||||
clear_caches(event)
|
||||
Share.delete_all_by_uri(event.url)
|
||||
{:ok, Map.merge(activity_data, audience), actor, event}
|
||||
|
||||
|
@ -223,6 +224,10 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
|
|||
cond do
|
||||
Mobilizon.Events.get_default_participant_role(event) == :participant &&
|
||||
role == :participant ->
|
||||
Mobilizon.Service.Activity.Participant.insert_activity(participant,
|
||||
subject: "event_new_participation"
|
||||
)
|
||||
|
||||
{:accept,
|
||||
Actions.Accept.accept(
|
||||
:join,
|
||||
|
@ -314,4 +319,29 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
|
|||
@spec tag_to_string(%{title: String.t()} | String.t()) :: String.t()
|
||||
defp tag_to_string(%{title: tag}), do: tag
|
||||
defp tag_to_string(tag), do: tag
|
||||
|
||||
defp clear_caches(event) do
|
||||
clear_ap_cache(event)
|
||||
clear_feeds_cache(event)
|
||||
clear_category_statistics_cache()
|
||||
end
|
||||
|
||||
defp clear_ap_cache(event), do: Cachex.del(:activity_pub, "event_#{event.uuid}")
|
||||
|
||||
defp clear_category_statistics_cache, do: Cachex.del(:statistics, :categories)
|
||||
|
||||
defp clear_feeds_cache(event) do
|
||||
if event.attributed_to do
|
||||
clear_actor_feeds_cache(event.attributed_to)
|
||||
end
|
||||
|
||||
clear_event_ics_cache(event)
|
||||
end
|
||||
|
||||
defp clear_actor_feeds_cache(actor) do
|
||||
Cachex.del(:ics, "actor_#{actor.preferred_username}")
|
||||
Cachex.del(:feed, "actor_#{actor.preferred_username}")
|
||||
end
|
||||
|
||||
defp clear_event_ics_cache(event), do: Cachex.del(:ics, "event_#{event.uuid}")
|
||||
end
|
||||
|
|
|
@ -93,7 +93,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Members do
|
|||
atom()
|
||||
) :: boolean
|
||||
defp check_admins_left?(member_id, group_id, current_role, updated_role) do
|
||||
Actors.is_only_administrator?(member_id, group_id) && current_role == :administrator &&
|
||||
Actors.only_administrator?(member_id, group_id) && current_role == :administrator &&
|
||||
updated_role != :administrator
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,6 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Reports do
|
|||
@moduledoc false
|
||||
alias Mobilizon.{Actors, Discussions, Events, Reports}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Federation.ActivityStream
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
alias Mobilizon.Reports.Report
|
||||
|
@ -26,15 +25,19 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Reports do
|
|||
%Actor{} = reported_actor = Actors.get_actor!(args.reported_id)
|
||||
content = HTML.strip_tags(args.content)
|
||||
|
||||
event_id = Map.get(args, :event_id)
|
||||
|
||||
event =
|
||||
if is_nil(event_id) do
|
||||
nil
|
||||
else
|
||||
{:ok, %Event{} = event} = Events.get_event(event_id)
|
||||
event
|
||||
end
|
||||
events =
|
||||
args
|
||||
|> Map.get(:events_ids, [])
|
||||
|> Enum.map(fn event_id ->
|
||||
case Events.get_event(event_id) do
|
||||
{:ok, event} -> event
|
||||
{:error, :event_not_found} -> nil
|
||||
end
|
||||
end)
|
||||
|> Enum.filter(fn event ->
|
||||
is_struct(event) and
|
||||
Enum.member?([event.organizer_actor_id, event.attributed_to_id], reported_actor.id)
|
||||
end)
|
||||
|
||||
comments =
|
||||
Discussions.list_comments_by_actor_and_ids(
|
||||
|
@ -46,7 +49,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Reports do
|
|||
reporter: reporter_actor,
|
||||
reported: reported_actor,
|
||||
content: content,
|
||||
event: event,
|
||||
events: events,
|
||||
comments: comments
|
||||
})
|
||||
end
|
||||
|
|
|
@ -8,6 +8,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Resources do
|
|||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
alias Mobilizon.Resources.Resource
|
||||
alias Mobilizon.Service.Activity.Resource, as: ResourceActivity
|
||||
alias Mobilizon.Service.Formatter.HTML
|
||||
alias Mobilizon.Service.RichMedia.Parser
|
||||
require Logger
|
||||
|
||||
|
@ -20,21 +21,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Resources do
|
|||
@spec create(map(), map()) ::
|
||||
{:ok, Resource.t(), ActivityStream.t()}
|
||||
| {:error, Ecto.Changeset.t() | :creator_not_found | :group_not_found}
|
||||
def create(%{type: type} = args, additional) do
|
||||
args =
|
||||
case type do
|
||||
:folder ->
|
||||
args
|
||||
|
||||
_ ->
|
||||
case Parser.parse(Map.get(args, :resource_url)) do
|
||||
{:ok, metadata} ->
|
||||
Map.put(args, :metadata, metadata)
|
||||
|
||||
_ ->
|
||||
args
|
||||
end
|
||||
end
|
||||
def create(args, additional) do
|
||||
args = prepare_args(args)
|
||||
|
||||
with {:ok,
|
||||
%Resource{actor_id: group_id, creator_id: creator_id, parent_id: parent_id} = resource} <-
|
||||
|
@ -76,7 +64,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Resources do
|
|||
additional
|
||||
)
|
||||
when old_parent_id != parent_id do
|
||||
move(old_resource, args, additional)
|
||||
move(old_resource, prepare_args(args), additional)
|
||||
end
|
||||
|
||||
# Simple rename
|
||||
|
@ -218,4 +206,23 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Resources do
|
|||
defp parents(old_parent_id, new_parent_id) do
|
||||
{:ok, Resources.get_resource(old_parent_id), Resources.get_resource(new_parent_id)}
|
||||
end
|
||||
|
||||
defp prepare_args(args) do
|
||||
args =
|
||||
case Map.get(args, :type, :link) do
|
||||
:folder ->
|
||||
args
|
||||
|
||||
_ ->
|
||||
case Parser.parse(Map.get(args, :resource_url)) do
|
||||
{:ok, metadata} ->
|
||||
Map.put(args, :metadata, metadata)
|
||||
|
||||
_ ->
|
||||
args
|
||||
end
|
||||
end
|
||||
|
||||
Map.update(args, :description, nil, &HTML.strip_tags/1)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,6 +22,7 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
|||
|
||||
@actor_types ["Group", "Person", "Application"]
|
||||
@all_actor_types @actor_types ++ ["Organization", "Service"]
|
||||
@ap_public_audience "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
# Wraps an object into an activity
|
||||
@spec create_activity(map(), boolean()) :: {:ok, Activity.t()}
|
||||
|
@ -125,6 +126,10 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
|||
"@type" => "sc:Boolean",
|
||||
"@id" => "mz:isOnline"
|
||||
},
|
||||
"externalParticipationUrl" => %{
|
||||
"@id" => "mz:externalParticipationUrl",
|
||||
"@type" => "sc:URL"
|
||||
},
|
||||
"PropertyValue" => "sc:PropertyValue",
|
||||
"value" => "sc:value",
|
||||
"propertyID" => "sc:propertyID",
|
||||
|
@ -160,6 +165,10 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
|||
"todos" => %{
|
||||
"@id" => "mz:todos",
|
||||
"@type" => "@id"
|
||||
},
|
||||
"status" => %{
|
||||
"@id" => "ical:status",
|
||||
"@type" => "ical:status"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -491,8 +500,7 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
|||
if public do
|
||||
Logger.debug("Making announce data for a public object")
|
||||
|
||||
{[actor.followers_url, object_actor_url],
|
||||
["https://www.w3.org/ns/activitystreams#Public"]}
|
||||
{[actor.followers_url, object_actor_url], [@ap_public_audience]}
|
||||
else
|
||||
Logger.debug("Making announce data for a private object")
|
||||
|
||||
|
@ -539,7 +547,7 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
|||
"actor" => url,
|
||||
"object" => activity,
|
||||
"to" => [actor.followers_url, actor.url],
|
||||
"cc" => ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
"cc" => [@ap_public_audience]
|
||||
}
|
||||
|
||||
if activity_id, do: Map.put(data, "id", activity_id), else: data
|
||||
|
@ -672,19 +680,22 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
|||
@doc """
|
||||
Converts PEM encoded keys to a public key representation
|
||||
"""
|
||||
@spec pem_to_public_key_pem(String.t()) :: String.t()
|
||||
@spec pem_to_public_key_pem(String.t()) :: String.t() | {:error, :no_publickey_found}
|
||||
def pem_to_public_key_pem(pem) do
|
||||
public_key = pem_to_public_key(pem)
|
||||
public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
|
||||
:public_key.pem_encode([public_key])
|
||||
case :public_key.pem_decode(pem) do
|
||||
[key_code] ->
|
||||
public_key = pem_to_public_key(key_code)
|
||||
public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
|
||||
:public_key.pem_encode([public_key])
|
||||
|
||||
_ ->
|
||||
{:error, :no_publickey_found}
|
||||
end
|
||||
end
|
||||
|
||||
@spec pem_to_public_key(String.t()) :: {:RSAPublicKey, any(), any()}
|
||||
defp pem_to_public_key(pem) do
|
||||
[key_code] = :public_key.pem_decode(pem)
|
||||
key = :public_key.pem_entry_decode(key_code)
|
||||
|
||||
case key do
|
||||
defp pem_to_public_key(key_code) do
|
||||
case :public_key.pem_entry_decode(key_code) do
|
||||
{:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} ->
|
||||
{:RSAPublicKey, modulus, exponent}
|
||||
|
||||
|
|
|
@ -14,17 +14,17 @@ defmodule Mobilizon.Federation.ActivityPub.Visibility do
|
|||
|
||||
@public "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
@spec is_public?(Activity.t() | map()) :: boolean()
|
||||
def is_public?(%{data: %{"type" => "Tombstone"}}), do: false
|
||||
def is_public?(%{data: data}), do: is_public?(data)
|
||||
def is_public?(%Activity{data: data}), do: is_public?(data)
|
||||
@spec public?(Activity.t() | map()) :: boolean()
|
||||
def public?(%{data: %{"type" => "Tombstone"}}), do: false
|
||||
def public?(%{data: data}), do: public?(data)
|
||||
def public?(%Activity{data: data}), do: public?(data)
|
||||
|
||||
def is_public?(data) when is_map(data) do
|
||||
def public?(data) when is_map(data) do
|
||||
@public in make_list(Map.get(data, "to", []))
|
||||
end
|
||||
|
||||
def is_public?(%Comment{deleted_at: deleted_at}), do: !is_nil(deleted_at)
|
||||
def is_public?(err), do: raise(ArgumentError, message: "Invalid argument #{inspect(err)}")
|
||||
def public?(%Comment{deleted_at: deleted_at}), do: !is_nil(deleted_at)
|
||||
def public?(err), do: raise(ArgumentError, message: "Invalid argument #{inspect(err)}")
|
||||
|
||||
defp make_list(data) when is_list(data), do: data
|
||||
defp make_list(data), do: [data]
|
||||
|
|
|
@ -36,10 +36,18 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
|
|||
@spec as_to_model_data(map()) :: map() | {:error, :actor_not_allowed_type}
|
||||
def as_to_model_data(%{"type" => type} = data) when type in @allowed_types do
|
||||
avatar =
|
||||
download_picture(get_in(data, ["icon", "url"]), get_in(data, ["icon", "name"]), "avatar")
|
||||
download_picture(
|
||||
get_picture(data, ["icon", "url"]),
|
||||
get_picture(data, ["icon", "name"]),
|
||||
"avatar"
|
||||
)
|
||||
|
||||
banner =
|
||||
download_picture(get_in(data, ["image", "url"]), get_in(data, ["image", "name"]), "banner")
|
||||
download_picture(
|
||||
get_picture(data, ["image", "url"]),
|
||||
get_picture(data, ["image", "name"]),
|
||||
"banner"
|
||||
)
|
||||
|
||||
address = get_address(data["location"])
|
||||
|
||||
|
@ -68,14 +76,13 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
|
|||
def as_to_model_data(_), do: {:error, :actor_not_allowed_type}
|
||||
|
||||
defp add_endpoints_to_model(actor, data) do
|
||||
# TODO: Remove fallbacks in 3.0
|
||||
endpoints = %{
|
||||
members_url: get_in(data, ["endpoints", "members"]) || data["members"],
|
||||
resources_url: get_in(data, ["endpoints", "resources"]) || data["resources"],
|
||||
todos_url: get_in(data, ["endpoints", "todos"]) || data["todos"],
|
||||
events_url: get_in(data, ["endpoints", "events"]) || data["events"],
|
||||
posts_url: get_in(data, ["endpoints", "posts"]) || data["posts"],
|
||||
discussions_url: get_in(data, ["endpoints", "discussions"]) || data["discussions"],
|
||||
members_url: get_in(data, ["endpoints", "members"]),
|
||||
resources_url: get_in(data, ["endpoints", "resources"]),
|
||||
todos_url: get_in(data, ["endpoints", "todos"]),
|
||||
events_url: get_in(data, ["endpoints", "events"]),
|
||||
posts_url: get_in(data, ["endpoints", "posts"]),
|
||||
discussions_url: get_in(data, ["endpoints", "discussions"]),
|
||||
shared_inbox_url: data["endpoints"]["sharedInbox"]
|
||||
}
|
||||
|
||||
|
@ -104,19 +111,11 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
|
|||
},
|
||||
"discoverable" => actor.visibility == :public,
|
||||
"openness" => actor.openness,
|
||||
"manuallyApprovesFollowers" => actor.manually_approves_followers,
|
||||
"publicKey" => %{
|
||||
"id" => "#{actor.url}#main-key",
|
||||
"owner" => actor.url,
|
||||
"publicKeyPem" =>
|
||||
if(is_nil(actor.domain) and not is_nil(actor.keys),
|
||||
do: Utils.pem_to_public_key_pem(actor.keys),
|
||||
else: actor.keys
|
||||
)
|
||||
}
|
||||
"manuallyApprovesFollowers" => actor.manually_approves_followers
|
||||
}
|
||||
|
||||
actor_data
|
||||
|> add_keys(actor)
|
||||
|> add_endpoints(actor)
|
||||
|> maybe_add_members(actor)
|
||||
|> maybe_add_avatar_picture(actor)
|
||||
|
@ -124,6 +123,28 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
|
|||
|> maybe_add_physical_address(actor)
|
||||
end
|
||||
|
||||
@spec add_keys(map(), ActorModel.t()) :: map()
|
||||
defp add_keys(actor_data, %ActorModel{} = actor) do
|
||||
keys =
|
||||
if is_nil(actor.domain) and not is_nil(actor.keys) do
|
||||
case Utils.pem_to_public_key_pem(actor.keys) do
|
||||
{:error, :no_publickey_found} ->
|
||||
raise "No publickey found in private keys"
|
||||
|
||||
public_key when is_binary(public_key) ->
|
||||
public_key
|
||||
end
|
||||
else
|
||||
actor.keys
|
||||
end
|
||||
|
||||
Map.put(actor_data, "publicKey", %{
|
||||
"id" => "#{actor.url}#main-key",
|
||||
"owner" => actor.url,
|
||||
"publicKeyPem" => keys
|
||||
})
|
||||
end
|
||||
|
||||
defp add_endpoints(%{"endpoints" => endpoints} = actor_data, %ActorModel{} = actor) do
|
||||
new_endpoints = %{
|
||||
"members" => actor.members_url,
|
||||
|
@ -193,4 +214,10 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Actor do
|
|||
end
|
||||
|
||||
defp maybe_add_physical_address(res, _), do: res
|
||||
|
||||
defp get_picture(nil, _keys), do: nil
|
||||
defp get_picture(url, _keys) when is_binary(url), do: url
|
||||
|
||||
defp get_picture(data, [key | rest] = keys) when is_map(data) and is_list(keys),
|
||||
do: get_picture(data[key], rest)
|
||||
end
|
||||
|
|
|
@ -24,16 +24,23 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Address do
|
|||
}
|
||||
|
||||
res =
|
||||
if is_nil(object["address"]) or not is_map(object["address"]) do
|
||||
res
|
||||
else
|
||||
Map.merge(res, %{
|
||||
"country" => object["address"]["addressCountry"],
|
||||
"postal_code" => object["address"]["postalCode"],
|
||||
"region" => object["address"]["addressRegion"],
|
||||
"street" => object["address"]["streetAddress"],
|
||||
"locality" => object["address"]["addressLocality"]
|
||||
})
|
||||
cond do
|
||||
is_binary(object["address"]) ->
|
||||
Map.merge(res, %{
|
||||
"street" => object["address"]
|
||||
})
|
||||
|
||||
is_map(object["address"]) ->
|
||||
Map.merge(res, %{
|
||||
"country" => object["address"]["addressCountry"],
|
||||
"postal_code" => object["address"]["postalCode"],
|
||||
"region" => object["address"]["addressRegion"],
|
||||
"street" => object["address"]["streetAddress"],
|
||||
"locality" => object["address"]["addressLocality"]
|
||||
})
|
||||
|
||||
is_nil(object["address"]) ->
|
||||
res
|
||||
end
|
||||
|
||||
latitude = Map.get(object, "latitude")
|
||||
|
|
|
@ -47,9 +47,6 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Comment do
|
|||
|
||||
case maybe_fetch_actor_and_attributed_to_id(object) do
|
||||
{:ok, %Actor{id: actor_id, domain: actor_domain}, attributed_to} ->
|
||||
Logger.debug("Inserting full comment")
|
||||
Logger.debug(inspect(object))
|
||||
|
||||
data = %{
|
||||
text: object["content"],
|
||||
url: object["id"],
|
||||
|
@ -65,19 +62,12 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Comment do
|
|||
tags: fetch_tags(tag_object),
|
||||
mentions: fetch_mentions(tag_object),
|
||||
local: is_nil(actor_domain),
|
||||
visibility: if(Visibility.is_public?(object), do: :public, else: :private),
|
||||
visibility: if(Visibility.public?(object), do: :public, else: :private),
|
||||
published_at: object["published"],
|
||||
is_announcement: Map.get(object, "isAnnouncement", false)
|
||||
}
|
||||
|
||||
Logger.debug("Converted object before fetching parents")
|
||||
Logger.debug(inspect(data))
|
||||
|
||||
data = maybe_fetch_parent_object(object, data)
|
||||
|
||||
Logger.debug("Converted object after fetching parents")
|
||||
Logger.debug(inspect(data))
|
||||
data
|
||||
maybe_fetch_parent_object(object, data)
|
||||
|
||||
{:error, err} ->
|
||||
{:error, err}
|
||||
|
@ -147,19 +137,22 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Comment do
|
|||
end
|
||||
|
||||
@spec determine_to(CommentModel.t()) :: [String.t()]
|
||||
defp determine_to(%CommentModel{} = comment) do
|
||||
cond do
|
||||
not is_nil(comment.attributed_to) ->
|
||||
[comment.attributed_to.url]
|
||||
defp determine_to(%CommentModel{visibility: :private, mentions: mentions} = _comment) do
|
||||
Enum.map(mentions, fn mention -> mention.actor.url end)
|
||||
end
|
||||
|
||||
comment.visibility == :public ->
|
||||
["https://www.w3.org/ns/activitystreams#Public"]
|
||||
|
||||
true ->
|
||||
[comment.actor.followers_url]
|
||||
defp determine_to(%CommentModel{visibility: :public} = comment) do
|
||||
if is_nil(comment.attributed_to) do
|
||||
["https://www.w3.org/ns/activitystreams#Public"]
|
||||
else
|
||||
[comment.attributed_to.url]
|
||||
end
|
||||
end
|
||||
|
||||
defp determine_to(%CommentModel{} = comment) do
|
||||
[comment.actor.followers_url]
|
||||
end
|
||||
|
||||
defp maybe_fetch_parent_object(object, data) do
|
||||
# We fetch the parent object
|
||||
Logger.debug("We're fetching the parent object")
|
||||
|
@ -170,9 +163,12 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Comment do
|
|||
|
||||
case ActivityPub.fetch_object_from_url(object["inReplyTo"]) do
|
||||
# Reply to an event (Event)
|
||||
{:ok, %Event{id: id}} ->
|
||||
{:ok, %Event{id: id} = event} ->
|
||||
Logger.debug("Parent object is an event")
|
||||
data |> Map.put(:event_id, id)
|
||||
|
||||
data
|
||||
|> Map.put(:event_id, id)
|
||||
|> Map.put(:event, event)
|
||||
|
||||
# Reply to a comment (Comment)
|
||||
{:ok, %CommentModel{id: id} = comment} ->
|
||||
|
@ -182,6 +178,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Comment do
|
|||
|> Map.put(:in_reply_to_comment_id, id)
|
||||
|> Map.put(:origin_comment_id, comment |> CommentModel.get_thread_id())
|
||||
|> Map.put(:event_id, comment.event_id)
|
||||
|> Map.put(:conversation_id, comment.conversation_id)
|
||||
|
||||
# Reply to a discucssion (Discussion)
|
||||
{:ok,
|
||||
|
@ -202,7 +199,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Comment do
|
|||
|
||||
# Anything else is kind of a MP
|
||||
{:error, parent} ->
|
||||
Logger.warn("Parent object is something we don't handle")
|
||||
Logger.warning("Parent object is something we don't handle")
|
||||
Logger.debug(inspect(parent))
|
||||
data
|
||||
end
|
||||
|
|
68
lib/federation/activity_stream/converter/conversation.ex
Normal file
68
lib/federation/activity_stream/converter/conversation.ex
Normal file
|
@ -0,0 +1,68 @@
|
|||
defmodule Mobilizon.Federation.ActivityStream.Converter.Conversation do
|
||||
@moduledoc """
|
||||
Comment converter.
|
||||
|
||||
This module allows to convert conversations from ActivityStream format to our own
|
||||
internal one, and back.
|
||||
"""
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Conversations.Conversation
|
||||
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
||||
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
|
||||
alias Mobilizon.Federation.ActivityStream.Converter.Conversation, as: ConversationConverter
|
||||
alias Mobilizon.Storage.Repo
|
||||
import Mobilizon.Service.Guards, only: [is_valid_string: 1]
|
||||
|
||||
require Logger
|
||||
|
||||
@behaviour Converter
|
||||
|
||||
defimpl Convertible, for: Conversation do
|
||||
defdelegate model_to_as(comment), to: ConversationConverter
|
||||
end
|
||||
|
||||
@doc """
|
||||
Make an AS comment object from an existing `conversation` structure.
|
||||
"""
|
||||
@impl Converter
|
||||
@spec model_to_as(Conversation.t()) :: map
|
||||
def model_to_as(%Conversation{} = conversation) do
|
||||
conversation = Repo.preload(conversation, [:participants, last_comment: [:actor]])
|
||||
|
||||
%{
|
||||
"type" => "Note",
|
||||
"to" => Enum.map(conversation.participants, & &1.url),
|
||||
"cc" => [],
|
||||
"content" => conversation.last_comment.text,
|
||||
"mediaType" => "text/html",
|
||||
"actor" => conversation.last_comment.actor.url,
|
||||
"id" => conversation.last_comment.url,
|
||||
"publishedAt" => conversation.inserted_at
|
||||
}
|
||||
end
|
||||
|
||||
@impl Converter
|
||||
@spec as_to_model_data(map) :: map() | {:error, atom()}
|
||||
def as_to_model_data(%{"type" => "Note", "name" => name} = object) when is_valid_string(name) do
|
||||
with %{actor_id: actor_id, creator_id: creator_id} <- extract_actors(object) do
|
||||
%{actor_id: actor_id, creator_id: creator_id, title: name, url: object["id"]}
|
||||
end
|
||||
end
|
||||
|
||||
@spec extract_actors(map()) ::
|
||||
%{actor_id: String.t(), creator_id: String.t()} | {:error, atom()}
|
||||
defp extract_actors(%{"actor" => creator_url, "attributedTo" => actor_url} = _object)
|
||||
when is_valid_string(creator_url) and is_valid_string(actor_url) do
|
||||
with {:ok, %Actor{id: creator_id, suspended: false}} <-
|
||||
ActivityPubActor.get_or_fetch_actor_by_url(creator_url),
|
||||
{:ok, %Actor{id: actor_id, suspended: false}} <-
|
||||
ActivityPubActor.get_or_fetch_actor_by_url(actor_url) do
|
||||
%{actor_id: actor_id, creator_id: creator_id}
|
||||
else
|
||||
{:error, error} -> {:error, error}
|
||||
{:ok, %Actor{url: ^creator_url}} -> {:error, :creator_suspended}
|
||||
{:ok, %Actor{url: ^actor_url}} -> {:error, :actor_suspended}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -77,10 +77,15 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
|||
category: Categories.get_category(object["category"]),
|
||||
visibility: visibility,
|
||||
join_options: Map.get(object, "joinMode", "free"),
|
||||
local: is_local?(object["id"]),
|
||||
local: local?(object["id"]),
|
||||
external_participation_url: object["externalParticipationUrl"],
|
||||
options: options,
|
||||
metadata: metadata,
|
||||
status: object |> Map.get("ical:status", "CONFIRMED") |> String.downcase(),
|
||||
# Remove fallback in MBZ 5.x
|
||||
status:
|
||||
object
|
||||
|> Map.get("status", Map.get(object, "ical:status", "CONFIRMED"))
|
||||
|> String.downcase(),
|
||||
online_address: object |> Map.get("attachment", []) |> get_online_address(),
|
||||
phone_address: object["phoneAddress"],
|
||||
draft: object["draft"] == true,
|
||||
|
@ -129,6 +134,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
|||
"mediaType" => "text/html",
|
||||
"startTime" => event.begins_on |> shift_tz(event.options.timezone) |> date_to_string(),
|
||||
"joinMode" => to_string(event.join_options),
|
||||
"externalParticipationUrl" => event.external_participation_url,
|
||||
"endTime" => event.ends_on |> shift_tz(event.options.timezone) |> date_to_string(),
|
||||
"tag" => event.tags |> build_tags(),
|
||||
"maximumAttendeeCapacity" => event.options.maximum_attendee_capacity,
|
||||
|
@ -140,7 +146,9 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
|||
"anonymousParticipationEnabled" => event.options.anonymous_participation,
|
||||
"attachment" => Enum.map(event.metadata, &EventMetadataConverter.metadata_to_as/1),
|
||||
"draft" => event.draft,
|
||||
# Remove me in MBZ 5.x
|
||||
"ical:status" => event.status |> to_string |> String.upcase(),
|
||||
"status" => event.status |> to_string |> String.upcase(),
|
||||
"id" => event.url,
|
||||
"url" => event.url,
|
||||
"inLanguage" => event.language,
|
||||
|
@ -190,6 +198,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
|||
|
||||
defp calculate_timezone(_object, nil), do: nil
|
||||
|
||||
defp calculate_timezone(_object, %Address{geom: nil}), do: nil
|
||||
|
||||
defp calculate_timezone(_object, %Address{geom: geom}) do
|
||||
TimezoneDetector.detect(
|
||||
nil,
|
||||
|
@ -295,8 +305,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
|||
)
|
||||
end
|
||||
|
||||
@spec is_local?(String.t()) :: boolean()
|
||||
defp is_local?(url) do
|
||||
@spec local?(String.t()) :: boolean()
|
||||
defp local?(url) do
|
||||
%URI{host: url_domain} = URI.parse(url)
|
||||
%URI{host: local_domain} = URI.parse(Endpoint.url())
|
||||
url_domain == local_domain
|
||||
|
|
|
@ -9,11 +9,11 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Flag do
|
|||
"""
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Discussions
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Discussions.Comment
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Reports.Report
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
||||
alias Mobilizon.Federation.ActivityPub.Relay
|
||||
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
|
||||
|
@ -38,7 +38,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Flag do
|
|||
"uri" => params["uri"],
|
||||
"content" => params["content"],
|
||||
"reported_id" => params["reported"].id,
|
||||
"event_id" => (!is_nil(params["event"]) && params["event"].id) || nil,
|
||||
"events" => params["events"],
|
||||
"comments" => params["comments"]
|
||||
}
|
||||
end
|
||||
|
@ -50,15 +50,17 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Flag do
|
|||
@impl Converter
|
||||
@spec model_to_as(Report.t()) :: map
|
||||
def model_to_as(%Report{} = report) do
|
||||
object = [report.reported.url] ++ Enum.map(report.comments, fn comment -> comment.url end)
|
||||
|
||||
object = if report.event, do: object ++ [report.event.url], else: object
|
||||
object =
|
||||
[report.reported.url] ++
|
||||
Enum.map(report.comments, fn comment -> comment.url end) ++
|
||||
Enum.map(report.events, & &1.url)
|
||||
|
||||
%{
|
||||
"type" => "Flag",
|
||||
"actor" => Relay.get_actor().url,
|
||||
"id" => report.url,
|
||||
"content" => report.content,
|
||||
"mediaType" => "text/plain",
|
||||
"object" => object
|
||||
}
|
||||
end
|
||||
|
@ -68,14 +70,13 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Flag do
|
|||
with {:ok, %Actor{} = reporter} <-
|
||||
ActivityPubActor.get_or_fetch_actor_by_url(object["actor"]),
|
||||
%Actor{} = reported <- find_reported(objects),
|
||||
event <- find_event(objects),
|
||||
comments <- find_comments(objects, reported, event) do
|
||||
%{events: events, comments: comments} <- find_events_and_comments(objects) do
|
||||
%{
|
||||
"reporter" => reporter,
|
||||
"uri" => object["id"],
|
||||
"content" => object["content"],
|
||||
"reported" => reported,
|
||||
"event" => event,
|
||||
"events" => events,
|
||||
"comments" => comments
|
||||
}
|
||||
end
|
||||
|
@ -94,26 +95,19 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Flag do
|
|||
end)
|
||||
end
|
||||
|
||||
# Remove the reported actor and the event from the object list.
|
||||
@spec find_comments(list(String.t()), Actor.t() | nil, Event.t() | nil) :: list(Comment.t())
|
||||
defp find_comments(objects, reported, event) do
|
||||
defp find_events_and_comments(objects) do
|
||||
objects
|
||||
|> Enum.filter(fn url ->
|
||||
!((!is_nil(reported) && url == reported.url) || (!is_nil(event) && event.url == url))
|
||||
end)
|
||||
|> Enum.map(&Discussions.get_comment_from_url/1)
|
||||
|> Enum.filter(& &1)
|
||||
end
|
||||
|> Enum.map(&ActivityPub.fetch_object_from_url/1)
|
||||
|> Enum.reduce(%{comments: [], events: []}, fn res, acc ->
|
||||
case res do
|
||||
{:ok, %Event{} = event} ->
|
||||
Map.put(acc, :events, [event | acc.events])
|
||||
|
||||
@spec find_event(list(String.t())) :: Event.t() | nil
|
||||
defp find_event(objects) do
|
||||
Enum.reduce_while(objects, nil, fn url, _ ->
|
||||
case Events.get_event_by_url(url) do
|
||||
%Event{} = event ->
|
||||
{:halt, event}
|
||||
{:ok, %Comment{} = comment} ->
|
||||
Map.put(acc, :comments, [comment | acc.comments])
|
||||
|
||||
_ ->
|
||||
{:cont, nil}
|
||||
acc
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
|
|
@ -26,7 +26,11 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
|
|||
Logger.debug("fetching tags")
|
||||
Logger.debug(inspect(tags))
|
||||
|
||||
tags |> Enum.flat_map(&fetch_tag/1) |> Enum.uniq() |> Enum.map(&existing_tag_or_data/1)
|
||||
tags
|
||||
|> Enum.flat_map(&fetch_tag/1)
|
||||
|> Enum.uniq()
|
||||
|> Enum.filter(& &1)
|
||||
|> Enum.map(&existing_tag_or_data/1)
|
||||
end
|
||||
|
||||
def fetch_tags(_), do: []
|
||||
|
@ -122,6 +126,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
|
|||
create_mention(mention, acc)
|
||||
end
|
||||
|
||||
defp create_mention(_, acc), do: acc
|
||||
|
||||
@spec maybe_fetch_actor_and_attributed_to_id(map()) ::
|
||||
{:ok, Actor.t(), Actor.t() | nil} | {:error, atom()}
|
||||
def maybe_fetch_actor_and_attributed_to_id(%{
|
||||
|
@ -179,8 +185,10 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
|
|||
|
||||
def maybe_fetch_actor_and_attributed_to_id(_), do: {:error, :no_actor_found}
|
||||
|
||||
@spec fetch_actor(String.t()) :: {:ok, Actor.t()} | {:error, atom()}
|
||||
def fetch_actor(actor_url) do
|
||||
@spec fetch_actor(String.t() | map()) :: {:ok, Actor.t()} | {:error, atom()}
|
||||
def fetch_actor(%{"id" => actor_url}) when is_binary(actor_url), do: fetch_actor(actor_url)
|
||||
|
||||
def fetch_actor(actor_url) when is_binary(actor_url) do
|
||||
case ActivityPubActor.get_or_fetch_actor_by_url(actor_url) do
|
||||
{:ok, %Actor{suspended: false} = actor} ->
|
||||
{:ok, actor}
|
||||
|
@ -193,6 +201,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
|
|||
end
|
||||
end
|
||||
|
||||
def fetch_actor(_), do: {:error, :no_actor_found}
|
||||
|
||||
@spec process_pictures(map(), integer()) :: Keyword.t()
|
||||
def process_pictures(object, actor_id) do
|
||||
attachements = Map.get(object, "attachment", [])
|
||||
|
@ -268,8 +278,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
|
|||
end
|
||||
|
||||
@spec get_address(map | binary | nil) :: Address.t() | nil
|
||||
def get_address(address_url) when is_binary(address_url) do
|
||||
get_address(%{"id" => address_url})
|
||||
def get_address(text_address) when is_binary(text_address) do
|
||||
get_address(%{"type" => "Place", "name" => text_address})
|
||||
end
|
||||
|
||||
def get_address(%{"id" => url} = map) when is_map(map) and is_binary(url) do
|
||||
|
|
|
@ -12,6 +12,7 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
|
|||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
||||
alias Mobilizon.Federation.ActivityPub.Relay
|
||||
|
||||
require Logger
|
||||
|
||||
|
@ -94,13 +95,19 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
|
|||
%{"keyId" => kid} = HTTPSignatures.signature_for_conn(conn)
|
||||
actor_url = key_id_to_actor_url(kid)
|
||||
Logger.debug("Refetching public key for #{actor_url}")
|
||||
relay = Relay.get_actor()
|
||||
|
||||
# In this specific case we don't sign object fetches because
|
||||
# this would cause infinite recursion when servers both need
|
||||
# to fetch each other's keys
|
||||
with {:ok, %Actor{} = actor} <-
|
||||
ActivityPubActor.make_actor_from_url(actor_url, ignore_sign_object_fetches: true) do
|
||||
get_actor_public_key(actor)
|
||||
if actor_url == relay.url do
|
||||
# Special case if ever it's our own actor fetching ourselves
|
||||
get_actor_public_key(relay)
|
||||
else
|
||||
# In this specific case we don't sign object fetches because
|
||||
# this would cause infinite recursion when servers both need
|
||||
# to fetch each other's keys
|
||||
with {:ok, %Actor{} = actor} <-
|
||||
ActivityPubActor.make_actor_from_url(actor_url, ignore_sign_object_fetches: true) do
|
||||
get_actor_public_key(actor)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
123
lib/federation/node_info.ex
Normal file
123
lib/federation/node_info.ex
Normal file
|
@ -0,0 +1,123 @@
|
|||
defmodule Mobilizon.Federation.NodeInfo do
|
||||
@moduledoc """
|
||||
Performs NodeInfo requests
|
||||
"""
|
||||
|
||||
alias Mobilizon.Service.HTTP.WebfingerClient
|
||||
require Logger
|
||||
import Mobilizon.Service.HTTP.Utils, only: [content_type_matches?: 2]
|
||||
|
||||
@application_uri "https://www.w3.org/ns/activitystreams#Application"
|
||||
@nodeinfo_rel_2_0 "http://nodeinfo.diaspora.software/ns/schema/2.0"
|
||||
@nodeinfo_rel_2_1 "http://nodeinfo.diaspora.software/ns/schema/2.1"
|
||||
|
||||
@env Application.compile_env(:mobilizon, :env)
|
||||
|
||||
@spec application_actor(String.t()) :: String.t() | nil
|
||||
def application_actor(host) do
|
||||
Logger.debug("Fetching application actor from NodeInfo data for domain #{host}")
|
||||
|
||||
case fetch_nodeinfo_endpoint(host) do
|
||||
{:ok, body} ->
|
||||
extract_application_actor(body)
|
||||
|
||||
{:error, _err} ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@spec nodeinfo(String.t()) :: {:ok, map()} | {:error, atom()}
|
||||
def nodeinfo(host) do
|
||||
Logger.debug("Fetching NodeInfo details for domain #{host}")
|
||||
|
||||
with {:ok, endpoint} when is_binary(endpoint) <- fetch_nodeinfo_details(host),
|
||||
:ok <- Logger.debug("Going to get NodeInfo information from URL #{endpoint}"),
|
||||
{:ok, %{body: body, status: code, headers: headers}} when code in 200..299 <-
|
||||
WebfingerClient.get(endpoint),
|
||||
{:ok, body} <- validate_json_response(body, headers) do
|
||||
Logger.debug("Found nodeinfo information for domain #{host}")
|
||||
{:ok, body}
|
||||
else
|
||||
{:error, err} ->
|
||||
{:error, err}
|
||||
|
||||
err ->
|
||||
Logger.debug("Failed to fetch NodeInfo data from endpoint #{inspect(err)}")
|
||||
{:error, :node_info_endpoint_http_error}
|
||||
end
|
||||
end
|
||||
|
||||
defp extract_application_actor(body) do
|
||||
body
|
||||
|> Map.get("links", [])
|
||||
|> Enum.find(%{"rel" => @application_uri, "href" => nil}, fn %{"rel" => rel, "href" => href} ->
|
||||
rel == @application_uri and is_binary(href)
|
||||
end)
|
||||
|> Map.get("href")
|
||||
end
|
||||
|
||||
@spec fetch_nodeinfo_endpoint(String.t()) :: {:ok, map()} | {:error, atom()}
|
||||
defp fetch_nodeinfo_endpoint(host) do
|
||||
prefix = if @env !== :dev, do: "https", else: "http"
|
||||
|
||||
case WebfingerClient.get("#{prefix}://#{host}/.well-known/nodeinfo") do
|
||||
{:ok, %{body: body, status: code, headers: headers}} when code in 200..299 ->
|
||||
validate_json_response(body, headers)
|
||||
|
||||
err ->
|
||||
Logger.debug("Failed to fetch NodeInfo data #{inspect(err)}")
|
||||
{:error, :node_info_meta_http_error}
|
||||
end
|
||||
end
|
||||
|
||||
@spec fetch_nodeinfo_details(String.t()) :: {:ok, String.t()} | {:error, atom()}
|
||||
defp fetch_nodeinfo_details(host) do
|
||||
with {:ok, body} <- fetch_nodeinfo_endpoint(host) do
|
||||
extract_nodeinfo_endpoint(body)
|
||||
end
|
||||
end
|
||||
|
||||
@spec extract_nodeinfo_endpoint(map()) ::
|
||||
{:ok, String.t()}
|
||||
| {:error, :no_node_info_endpoint_found | :no_valid_node_info_endpoint_found}
|
||||
defp extract_nodeinfo_endpoint(body) do
|
||||
links = Map.get(body, "links", [])
|
||||
|
||||
relation =
|
||||
find_nodeinfo_relation(links, @nodeinfo_rel_2_1) ||
|
||||
find_nodeinfo_relation(links, @nodeinfo_rel_2_0)
|
||||
|
||||
if is_nil(relation) do
|
||||
{:error, :no_node_info_endpoint_found}
|
||||
else
|
||||
endpoint = Map.get(relation, "href")
|
||||
|
||||
if is_nil(endpoint) do
|
||||
{:error, :no_valid_node_info_endpoint_found}
|
||||
else
|
||||
{:ok, endpoint}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp find_nodeinfo_relation(links, relation) do
|
||||
Enum.find(links, fn %{"rel" => rel, "href" => href} ->
|
||||
rel == relation and is_binary(href)
|
||||
end)
|
||||
end
|
||||
|
||||
@spec validate_json_response(map() | String.t(), list()) ::
|
||||
{:ok, String.t()} | {:error, :bad_content_type | :body_not_json}
|
||||
defp validate_json_response(body, headers) do
|
||||
cond do
|
||||
!content_type_matches?(headers, "application/json") ->
|
||||
{:error, :bad_content_type}
|
||||
|
||||
!is_map(body) ->
|
||||
{:error, :body_not_json}
|
||||
|
||||
true ->
|
||||
{:ok, body}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14,7 +14,7 @@ defmodule Mobilizon.Federation.WebFinger do
|
|||
alias Mobilizon.Federation.WebFinger.XmlBuilder
|
||||
alias Mobilizon.Service.HTTP.{HostMetaClient, WebfingerClient}
|
||||
alias Mobilizon.Web.Endpoint
|
||||
alias Mobilizon.Web.Router.Helpers, as: Routes
|
||||
use Mobilizon.Web, :verified_routes
|
||||
require Jason
|
||||
require Logger
|
||||
import SweetXml
|
||||
|
@ -85,7 +85,7 @@ defmodule Mobilizon.Federation.WebFinger do
|
|||
%{"rel" => "self", "type" => "application/activity+json", "href" => actor.url},
|
||||
%{
|
||||
"rel" => "http://ostatus.org/schema/1.0/subscribe",
|
||||
"template" => "#{Routes.page_url(Endpoint, :interact, uri: nil)}{uri}"
|
||||
"template" => "#{url(~p"/interact?#{[uri: nil]}")}{uri}"
|
||||
}
|
||||
]
|
||||
|> maybe_add_avatar(actor)
|
||||
|
@ -242,12 +242,15 @@ defmodule Mobilizon.Federation.WebFinger do
|
|||
@spec domain_from_federated_actor(String.t()) :: {:ok, String.t()} | {:error, :host_not_found}
|
||||
defp domain_from_federated_actor(actor) do
|
||||
case String.split(actor, "@") do
|
||||
[_name, ""] ->
|
||||
{:error, :host_not_found}
|
||||
|
||||
[_name, domain] ->
|
||||
{:ok, domain}
|
||||
|
||||
_e ->
|
||||
host = URI.parse(actor).host
|
||||
if is_nil(host), do: {:error, :host_not_found}, else: {:ok, host}
|
||||
if is_nil(host) or host == "", do: {:error, :host_not_found}, else: {:ok, host}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -259,24 +262,7 @@ defmodule Mobilizon.Federation.WebFinger do
|
|||
subject = Map.get(doc, "subject")
|
||||
|
||||
if !is_nil(links) && !is_nil(subject) do
|
||||
data =
|
||||
Enum.reduce(links, %{"subject" => subject}, fn link, data ->
|
||||
case {link["type"], link["rel"]} do
|
||||
{"application/activity+json", "self"} ->
|
||||
Map.put(data, "url", link["href"])
|
||||
|
||||
{nil, _rel} ->
|
||||
Logger.debug("No type declared for the following link #{inspect(link)}")
|
||||
data
|
||||
|
||||
_ ->
|
||||
Logger.debug(fn ->
|
||||
"Unhandled type to finger: #{inspect(link)}"
|
||||
end)
|
||||
|
||||
data
|
||||
end
|
||||
end)
|
||||
data = Enum.reduce(links, %{"subject" => subject}, &handle_link/2)
|
||||
|
||||
{:ok, data}
|
||||
else
|
||||
|
@ -286,6 +272,25 @@ defmodule Mobilizon.Federation.WebFinger do
|
|||
|
||||
defp webfinger_from_json(_doc), do: {:error, :webfinger_information_not_json}
|
||||
|
||||
@spec handle_link(map(), map()) :: map()
|
||||
defp handle_link(link, data) do
|
||||
case {link["type"], link["rel"]} do
|
||||
{"application/activity+json", "self"} ->
|
||||
Map.put(data, "url", link["href"])
|
||||
|
||||
{nil, _rel} ->
|
||||
Logger.debug("No type declared for the following link #{inspect(link)}")
|
||||
data
|
||||
|
||||
_ ->
|
||||
Logger.debug(fn ->
|
||||
"Unhandled type to finger: #{inspect(link)}"
|
||||
end)
|
||||
|
||||
data
|
||||
end
|
||||
end
|
||||
|
||||
@spec find_link_from_template(String.t()) :: String.t() | {:error, :link_not_found}
|
||||
defp find_link_from_template(doc) do
|
||||
with res when res in [nil, ""] <-
|
||||
|
|
|
@ -4,7 +4,8 @@ defmodule Mobilizon.GraphQL.API.Comments do
|
|||
"""
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Discussions.Comment
|
||||
alias Mobilizon.Conversations.Conversation
|
||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||
alias Mobilizon.Federation.ActivityPub.{Actions, Activity}
|
||||
alias Mobilizon.GraphQL.API.Utils
|
||||
|
||||
|
@ -53,6 +54,22 @@ defmodule Mobilizon.GraphQL.API.Comments do
|
|||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a conversation (or reply to a conversation)
|
||||
"""
|
||||
@spec create_conversation(map()) ::
|
||||
{:ok, Activity.t(), Conversation.t()}
|
||||
| {:error, :entity_tombstoned | atom | Ecto.Changeset.t()}
|
||||
def create_conversation(args) do
|
||||
args = extract_pictures_from_comment_body(args)
|
||||
|
||||
Actions.Create.create(
|
||||
:conversation,
|
||||
args,
|
||||
true
|
||||
)
|
||||
end
|
||||
|
||||
@spec extract_pictures_from_comment_body(map()) :: map()
|
||||
defp extract_pictures_from_comment_body(%{text: text, actor_id: actor_id} = args) do
|
||||
pictures = Utils.extract_pictures_from_body(text, actor_id)
|
||||
|
|
|
@ -4,8 +4,8 @@ defmodule Mobilizon.GraphQL.API.Events do
|
|||
"""
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Discussions.Comment
|
||||
alias Mobilizon.Events.Event
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub.{Actions, Activity, Utils}
|
||||
alias Mobilizon.GraphQL.API.Utils, as: APIUtils
|
||||
|
||||
|
@ -36,6 +36,12 @@ defmodule Mobilizon.GraphQL.API.Events do
|
|||
Actions.Delete.delete(event, actor, true)
|
||||
end
|
||||
|
||||
@spec send_private_message_to_participants(map()) ::
|
||||
{:ok, Activity.t(), Comment.t()} | {:error, atom() | Ecto.Changeset.t()}
|
||||
def send_private_message_to_participants(args) do
|
||||
Actions.Create.create(:comment, args, true)
|
||||
end
|
||||
|
||||
@spec prepare_args(map) :: map
|
||||
defp prepare_args(args) do
|
||||
organizer_actor = Map.get(args, :organizer_actor)
|
||||
|
|
|
@ -5,11 +5,13 @@ defmodule Mobilizon.GraphQL.API.Reports do
|
|||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.{Admin, Users}
|
||||
alias Mobilizon.Federation.ActivityPub.{Actions, Activity}
|
||||
alias Mobilizon.Reports, as: ReportsAction
|
||||
alias Mobilizon.Reports.{Note, Report, ReportStatus}
|
||||
alias Mobilizon.Service.AntiSpam.Akismet
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
alias Mobilizon.Federation.ActivityPub.{Actions, Activity}
|
||||
import Mobilizon.Web.Gettext, only: [dgettext: 2]
|
||||
require Logger
|
||||
|
||||
@doc """
|
||||
Create a report/flag on an actor, and optionally on an event or on comments.
|
||||
|
@ -20,18 +22,48 @@ defmodule Mobilizon.GraphQL.API.Reports do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Update the state of a report
|
||||
Update the status of a report
|
||||
"""
|
||||
@spec update_report_status(Actor.t(), Report.t(), atom()) ::
|
||||
@spec update_report_status(Actor.t(), Report.t(), atom(), atom() | nil) ::
|
||||
{:ok, Report.t()} | {:error, Ecto.Changeset.t() | String.t()}
|
||||
def update_report_status(%Actor{} = actor, %Report{} = report, state) do
|
||||
if ReportStatus.valid_value?(state) do
|
||||
with {:ok, %Report{} = report} <- ReportsAction.update_report(report, %{"status" => state}) do
|
||||
Admin.log_action(actor, "update", report)
|
||||
def update_report_status(
|
||||
%Actor{} = actor,
|
||||
%Report{status: old_status} = report,
|
||||
status,
|
||||
antispam_feedback \\ nil
|
||||
) do
|
||||
if ReportStatus.valid_value?(status) do
|
||||
with {:ok, %Report{} = report} <-
|
||||
ReportsAction.update_report(report, %{
|
||||
"status" => status,
|
||||
"antispam_feedback" => antispam_feedback
|
||||
}) do
|
||||
if old_status != status do
|
||||
Admin.log_action(actor, "update", report)
|
||||
end
|
||||
|
||||
antispam_response =
|
||||
case antispam_feedback do
|
||||
:ham ->
|
||||
Logger.debug("Reporting a report details as ham")
|
||||
Akismet.report_ham(report)
|
||||
|
||||
:spam ->
|
||||
Logger.debug("Reporting a report details as spam")
|
||||
Akismet.report_spam(report)
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
if antispam_response != :ok do
|
||||
Logger.warning("Antispam response has been #{inspect(antispam_response)}")
|
||||
end
|
||||
|
||||
{:ok, report}
|
||||
end
|
||||
else
|
||||
{:error, "Unsupported state"}
|
||||
{:error, dgettext("errors", "Unsupported status for a report")}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -58,7 +90,11 @@ defmodule Mobilizon.GraphQL.API.Reports do
|
|||
{:ok, note}
|
||||
end
|
||||
else
|
||||
{:error, "You need to be a moderator or an administrator to create a note on a report"}
|
||||
{:error,
|
||||
dgettext(
|
||||
"errors",
|
||||
"You need to be a moderator or an administrator to create a note on a report"
|
||||
)}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -81,10 +117,14 @@ defmodule Mobilizon.GraphQL.API.Reports do
|
|||
{:ok, note}
|
||||
end
|
||||
else
|
||||
{:error, "You need to be a moderator or an administrator to create a note on a report"}
|
||||
{:error,
|
||||
dgettext(
|
||||
"errors",
|
||||
"You need to be a moderator or an administrator to create a note on a report"
|
||||
)}
|
||||
end
|
||||
else
|
||||
{:error, "You can only remove your own notes"}
|
||||
{:error, dgettext("errors", "You can only remove your own notes")}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,8 +25,8 @@ defmodule Mobilizon.GraphQL.API.Search do
|
|||
|
||||
cond do
|
||||
# Some URLs could be domain.tld/@username, so keep this condition above
|
||||
# the `is_handle` function
|
||||
is_url(term) ->
|
||||
# the `handle?` function
|
||||
url?(term) ->
|
||||
# skip, if it's not an actor
|
||||
case process_from_url(term) do
|
||||
%Page{total: _total, elements: [%Actor{} = _actor]} = page ->
|
||||
|
@ -36,11 +36,11 @@ defmodule Mobilizon.GraphQL.API.Search do
|
|||
{:ok, %{total: 0, elements: []}}
|
||||
end
|
||||
|
||||
is_handle(term) ->
|
||||
handle?(term) ->
|
||||
{:ok, process_from_username(term)}
|
||||
|
||||
true ->
|
||||
if is_global_search(args) do
|
||||
if global_search?(args) do
|
||||
service = GlobalSearch.service()
|
||||
|
||||
{:ok, service.search_groups(Keyword.new(args, fn {k, v} -> {k, v} end))}
|
||||
|
@ -75,7 +75,7 @@ defmodule Mobilizon.GraphQL.API.Search do
|
|||
def search_events(%{term: term} = args, page \\ 1, limit \\ 10) do
|
||||
term = String.trim(term)
|
||||
|
||||
if is_url(term) do
|
||||
if url?(term) do
|
||||
# skip, if it's not an event
|
||||
case process_from_url(term) do
|
||||
%Page{total: _total, elements: [%Event{} = event]} = page ->
|
||||
|
@ -89,7 +89,7 @@ defmodule Mobilizon.GraphQL.API.Search do
|
|||
{:ok, %{total: 0, elements: []}}
|
||||
end
|
||||
else
|
||||
if is_global_search(args) do
|
||||
if global_search?(args) do
|
||||
service = GlobalSearch.service()
|
||||
|
||||
{:ok, service.search_events(Keyword.new(args, fn {k, v} -> {k, v} end))}
|
||||
|
@ -116,13 +116,9 @@ defmodule Mobilizon.GraphQL.API.Search do
|
|||
@spec process_from_username(String.t()) :: Page.t(Actor.t())
|
||||
defp process_from_username(search) do
|
||||
case ActivityPubActor.find_or_make_actor_from_nickname(search) do
|
||||
{:ok, %Actor{type: :Group} = actor} ->
|
||||
{:ok, %Actor{} = actor} ->
|
||||
%Page{total: 1, elements: [actor]}
|
||||
|
||||
# Don't return anything else than groups
|
||||
{:ok, %Actor{}} ->
|
||||
%Page{total: 0, elements: []}
|
||||
|
||||
{:error, _err} ->
|
||||
Logger.debug(fn -> "Unable to find or make actor '#{search}'" end)
|
||||
|
||||
|
@ -144,17 +140,17 @@ defmodule Mobilizon.GraphQL.API.Search do
|
|||
end
|
||||
end
|
||||
|
||||
@spec is_url(String.t()) :: boolean
|
||||
defp is_url(search), do: String.starts_with?(search, ["http://", "https://"])
|
||||
@spec url?(String.t()) :: boolean
|
||||
defp url?(search), do: String.starts_with?(search, ["http://", "https://"])
|
||||
|
||||
@spec is_handle(String.t()) :: boolean
|
||||
defp is_handle(search), do: String.match?(search, ~r/@/)
|
||||
@spec handle?(String.t()) :: boolean
|
||||
defp handle?(search), do: String.match?(search, ~r/@/)
|
||||
|
||||
defp is_global_search(%{search_target: :global}) do
|
||||
defp global_search?(%{search_target: :global}) do
|
||||
global_search_enabled?()
|
||||
end
|
||||
|
||||
defp is_global_search(_), do: global_search_enabled?() && global_search_default?()
|
||||
defp global_search?(_), do: global_search_enabled?() && global_search_default?()
|
||||
|
||||
defp global_search_enabled? do
|
||||
Application.get_env(:mobilizon, :search) |> get_in([:global]) |> get_in([:is_enabled])
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue