Merge branch 'master' of github.com:Plume-org/Plume
This commit is contained in:
		
						commit
						b0e70a42a9
					
				
							
								
								
									
										137
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										137
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -71,14 +71,6 @@ dependencies = [ | ||||
|  "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "ansi_term" | ||||
| version = "0.11.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "antidote" | ||||
| version = "1.0.0" | ||||
| @ -97,16 +89,6 @@ dependencies = [ | ||||
|  "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "atty" | ||||
| version = "0.2.10" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "backtrace" | ||||
| version = "0.3.6" | ||||
| @ -231,20 +213,6 @@ dependencies = [ | ||||
|  "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "clap" | ||||
| version = "2.31.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "colored" | ||||
| version = "1.6.0" | ||||
| @ -253,22 +221,6 @@ dependencies = [ | ||||
|  "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "comrak" | ||||
| version = "0.2.12" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "pest 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "pest_derive 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "twoway 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "typed-arena 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "cookie" | ||||
| version = "0.11.0-dev" | ||||
| @ -400,11 +352,6 @@ dependencies = [ | ||||
|  "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "entities" | ||||
| version = "1.0.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "error-chain" | ||||
| version = "0.11.0" | ||||
| @ -1008,7 +955,6 @@ dependencies = [ | ||||
|  "bcrypt 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "comrak 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "diesel 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "dotenv 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| @ -1019,6 +965,7 @@ dependencies = [ | ||||
|  "hyper 0.11.25 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "openssl 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "reqwest 0.8.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "rocket 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)", | ||||
|  "rocket_codegen 0.4.0-dev (git+https://github.com/SergioBenitez/Rocket?rev=df7111143e466c18d1f56377a8d9530a5a306aba)", | ||||
| @ -1070,6 +1017,14 @@ dependencies = [ | ||||
|  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "pulldown-cmark" | ||||
| version = "0.1.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "quote" | ||||
| version = "0.3.15" | ||||
| @ -1134,14 +1089,6 @@ name = "redox_syscall" | ||||
| version = "0.1.37" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "redox_termios" | ||||
| version = "0.1.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "regex" | ||||
| version = "0.2.10" | ||||
| @ -1474,11 +1421,6 @@ name = "string_cache_shared" | ||||
| version = "0.3.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "strsim" | ||||
| version = "0.7.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "syn" | ||||
| version = "0.11.11" | ||||
| @ -1587,24 +1529,6 @@ dependencies = [ | ||||
|  "url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "termion" | ||||
| version = "1.5.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "textwrap" | ||||
| version = "0.9.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "thread_local" | ||||
| version = "0.3.5" | ||||
| @ -1786,24 +1710,11 @@ name = "traitobject" | ||||
| version = "0.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "twoway" | ||||
| version = "0.1.8" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "typeable" | ||||
| version = "0.1.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "typed-arena" | ||||
| version = "1.3.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "typenum" | ||||
| version = "1.10.0" | ||||
| @ -1848,11 +1759,6 @@ name = "unicode-segmentation" | ||||
| version = "1.2.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "unicode-width" | ||||
| version = "0.1.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "unicode-xid" | ||||
| version = "0.0.4" | ||||
| @ -1863,11 +1769,6 @@ name = "unicode-xid" | ||||
| version = "0.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "unicode_categories" | ||||
| version = "0.1.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "unidecode" | ||||
| version = "0.3.0" | ||||
| @ -1919,11 +1820,6 @@ name = "vcpkg" | ||||
| version = "0.2.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "vec_map" | ||||
| version = "0.8.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "version_check" | ||||
| version = "0.1.3" | ||||
| @ -1996,11 +1892,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| "checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45" | ||||
| "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" | ||||
| "checksum ammonia 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fd4c682378117e4186a492b2252b9537990e1617f44aed9788b9a1149de45477" | ||||
| "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" | ||||
| "checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" | ||||
| "checksum array_tool 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8f8cb5d814eb646a863c4f24978cff2880c4be96ad8cde2c0f0678732902e271" | ||||
| "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" | ||||
| "checksum atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2fc4a1aa4c24c0718a250f0681885c1af91419d242f29eb8f2ab28502d80dbd1" | ||||
| "checksum backtrace 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbe525f66f42d207968308ee86bc2dd60aa5fab535b22e616323a173d097d8e" | ||||
| "checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661" | ||||
| "checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9" | ||||
| @ -2017,9 +1911,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| "checksum cc 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8b9d2900f78631a5876dc5d6c9033ede027253efcd33dd36b1309fc6cab97ee0" | ||||
| "checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" | ||||
| "checksum chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1cce36c92cb605414e9b824f866f5babe0a0368e39ea07393b9b63cf3844c0e6" | ||||
| "checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536" | ||||
| "checksum colored 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b0aa3473e85a3161b59845d6096b289bb577874cafeaf75ea1b1beaa6572c7fc" | ||||
| "checksum comrak 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "053b26c8ce23b4c505a9479beace98f95899e0bf5c5255cf0219e9b0f48cf6ea" | ||||
| "checksum cookie 0.11.0-dev (git+https://github.com/alexcrichton/cookie-rs?rev=0365a18)" = "<none>" | ||||
| "checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67" | ||||
| "checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d" | ||||
| @ -2034,7 +1926,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| "checksum dotenv 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a70de3c590ce18df70743cace1cf12565637a0b26fd8b04ef10c7d33fdc66cdc" | ||||
| "checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" | ||||
| "checksum encoding_rs 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98fd0f24d1fb71a4a6b9330c8ca04cbd4e7cc5d846b54ca74ff376bc7c9f798d" | ||||
| "checksum entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" | ||||
| "checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" | ||||
| "checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82" | ||||
| "checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b" | ||||
| @ -2110,6 +2001,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| "checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0" | ||||
| "checksum proc-macro2 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "49b6a521dc81b643e9a51e0d1cf05df46d5a2f3c0280ea72bcb68276ba64a118" | ||||
| "checksum proc-macro2 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "effdb53b25cdad54f8f48843d67398f7ef2e14f12c1b4cb4effc549a6462a4d6" | ||||
| "checksum pulldown-cmark 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d6fdf85cda6cadfae5428a54661d431330b312bc767ddbc57adbedc24da66e32" | ||||
| "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" | ||||
| "checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408" | ||||
| "checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8" | ||||
| @ -2118,7 +2010,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" | ||||
| "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" | ||||
| "checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" | ||||
| "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" | ||||
| "checksum regex 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "aec3f58d903a7d2a9dc2bf0e41a746f4530e0cab6b615494e058f67a3ef947fb" | ||||
| "checksum regex-syntax 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bd90079345f4a4c3409214734ae220fd773c6f2e8a543d07370c6c1c369cfbfb" | ||||
| "checksum relay 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1576e382688d7e9deecea24417e350d3062d97e32e45d70b1cde65994ff1489a" | ||||
| @ -2155,7 +2046,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| "checksum string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25d70109977172b127fe834e5449e5ab1740b9ba49fa18a2020f509174f25423" | ||||
| "checksum string_cache_codegen 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "35293b05cf1494e8ddd042a7df6756bf18d07f42d234f32e71dce8a7aabb0191" | ||||
| "checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" | ||||
| "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" | ||||
| "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" | ||||
| "checksum syn 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)" = "c97c05b8ebc34ddd6b967994d5c6e9852fa92f8b82b3858c39451f97346dcce5" | ||||
| "checksum syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "91b52877572087400e83d24b9178488541e3d535259e04ff17a63df1e5ceff59" | ||||
| @ -2167,8 +2057,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" | ||||
| "checksum tendril 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9de21546595a0873061940d994bbbc5c35f024ae4fd61ec5c5b159115684f508" | ||||
| "checksum tera 0.11.7 (registry+https://github.com/rust-lang/crates.io-index)" = "e815b67d44c26feb06630011fb58b5b243f4e9585aac1ed0592c5795de64cd75" | ||||
| "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" | ||||
| "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" | ||||
| "checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" | ||||
| "checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098" | ||||
| "checksum tokio 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "be15ef40f675c9fe66e354d74c73f3ed012ca1aa14d65846a33ee48f1ae8d922" | ||||
| @ -2185,9 +2073,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| "checksum tokio-udp 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "137bda266504893ac4774e0ec4c2108f7ccdbcb7ac8dced6305fe9e4e0b5041a" | ||||
| "checksum toml 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a0263c6c02c4db6c8f7681f9fd35e90de799ebd4cfdeab77a38f4ff6b3d8c0d9" | ||||
| "checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" | ||||
| "checksum twoway 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" | ||||
| "checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" | ||||
| "checksum typed-arena 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5934776c3ac1bea4a9d56620d6bf2d483b20d394e49581db40f187e1118ff667" | ||||
| "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" | ||||
| "checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d" | ||||
| "checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" | ||||
| @ -2195,10 +2081,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" | ||||
| "checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" | ||||
| "checksum unicode-segmentation 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8083c594e02b8ae1654ae26f0ade5158b119bd88ad0e8227a5d8fcd72407946" | ||||
| "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" | ||||
| "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" | ||||
| "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" | ||||
| "checksum unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" | ||||
| "checksum unidecode 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "402bb19d8e03f1d1a7450e2bd613980869438e0666331be3e073089124aa1adc" | ||||
| "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" | ||||
| "checksum untrusted 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70afa43c8c5d23a53a3c39ec9b56232c5badc19f6bb5ad529c1d6448a7241365" | ||||
| @ -2207,7 +2091,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" | ||||
| "checksum uuid 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22" | ||||
| "checksum vcpkg 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ed0f6789c8a85ca41bbc1c9d175422116a9869bd1cf31bb08e1493ecce60380" | ||||
| "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" | ||||
| "checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d" | ||||
| "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" | ||||
| "checksum webfinger 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "27a4e6d1de7050af8beb026c02bcef5340ec1f3af6d4a02248b7990908baa3ff" | ||||
|  | ||||
| @ -9,7 +9,6 @@ array_tool = "1.0" | ||||
| base64 = "0.9" | ||||
| bcrypt = "0.2" | ||||
| colored = "1.6" | ||||
| comrak = "0.2" | ||||
| dotenv = "*" | ||||
| failure = "0.1" | ||||
| failure_derive = "0.1" | ||||
| @ -19,6 +18,7 @@ hex = "0.3" | ||||
| hyper = "*" | ||||
| lazy_static = "*" | ||||
| openssl = "0.10.6" | ||||
| pulldown-cmark = { version = "0.1.2", default-features = false } | ||||
| reqwest = "0.8" | ||||
| rpassword = "2.0" | ||||
| serde = "*" | ||||
|  | ||||
							
								
								
									
										2
									
								
								migrations/2018-06-20-175532_create_mentions/down.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								migrations/2018-06-20-175532_create_mentions/down.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| -- This file should undo anything in `up.sql` | ||||
| DROP TABLE mentions; | ||||
							
								
								
									
										7
									
								
								migrations/2018-06-20-175532_create_mentions/up.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								migrations/2018-06-20-175532_create_mentions/up.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| -- Your SQL goes here | ||||
| CREATE TABLE mentions ( | ||||
|     id SERIAL PRIMARY KEY, | ||||
|     mentioned_id INTEGER REFERENCES users(id) ON DELETE CASCADE NOT NULL, | ||||
|     post_id INTEGER REFERENCES posts(id) ON DELETE CASCADE, | ||||
|     comment_id INTEGER REFERENCES comments(id) ON DELETE CASCADE | ||||
| ) | ||||
| @ -0,0 +1,2 @@ | ||||
| -- This file should undo anything in `up.sql` | ||||
| ALTER TABLE mentions DROP COLUMN ap_url; | ||||
							
								
								
									
										2
									
								
								migrations/2018-06-20-194538_add_mentions_ap_url/up.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								migrations/2018-06-20-194538_add_mentions_ap_url/up.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,2 @@ | ||||
| -- Your SQL goes here | ||||
| ALTER TABLE mentions ADD COLUMN ap_url VARCHAR NOT NULL DEFAULT ''; | ||||
							
								
								
									
										3
									
								
								po/en.po
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								po/en.po
									
									
									
									
									
								
							| @ -280,3 +280,6 @@ msgstr "" | ||||
| 
 | ||||
| msgid "You are not author in this blog." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "{{ data }} mentioned you." | ||||
| msgstr "" | ||||
|  | ||||
							
								
								
									
										3
									
								
								po/fr.po
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								po/fr.po
									
									
									
									
									
								
							| @ -279,3 +279,6 @@ msgstr "Vous n'avez pas les droits." | ||||
| 
 | ||||
| msgid "You are not author in this blog." | ||||
| msgstr "Vous n'êtes pas auteur dans ce blog." | ||||
| 
 | ||||
| msgid "{{ data }} mentioned you." | ||||
| msgstr "" | ||||
|  | ||||
							
								
								
									
										4
									
								
								po/pl.po
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								po/pl.po
									
									
									
									
									
								
							| @ -285,5 +285,9 @@ msgstr "" | ||||
| msgid "You are not author in this blog." | ||||
| msgstr "" | ||||
| 
 | ||||
| #, fuzzy | ||||
| msgid "{{ data }} mentioned you." | ||||
| msgstr "{{ data }} skomentował Twój artykuł" | ||||
| 
 | ||||
| #~ msgid "Logowanie" | ||||
| #~ msgstr "Zaloguj się" | ||||
|  | ||||
| @ -275,3 +275,6 @@ msgstr "" | ||||
| 
 | ||||
| msgid "You are not author in this blog." | ||||
| msgstr "" | ||||
| 
 | ||||
| msgid "{{ data }} mentioned you." | ||||
| msgstr "" | ||||
|  | ||||
| @ -40,8 +40,8 @@ pub trait FromActivity<T: Object>: Sized { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub trait Notify<T: Object> { | ||||
|     fn notify(conn: &PgConnection, act: T, actor: Id); | ||||
| pub trait Notify { | ||||
|     fn notify(&self, conn: &PgConnection); | ||||
| } | ||||
| 
 | ||||
| pub trait Deletable { | ||||
|  | ||||
| @ -1,10 +1,12 @@ | ||||
| use diesel::{ | ||||
|     pg::PgConnection, | ||||
|     r2d2::{ConnectionManager, Pool, PooledConnection} | ||||
|     r2d2::{ConnectionManager, PooledConnection} | ||||
| }; | ||||
| use rocket::{Request, State, Outcome, http::Status, request::{self, FromRequest}}; | ||||
| use std::ops::Deref; | ||||
| 
 | ||||
| use setup::PgPool; | ||||
| 
 | ||||
| // From rocket documentation
 | ||||
| 
 | ||||
| // Connection request guard type: a wrapper around an r2d2 pooled connection.
 | ||||
| @ -17,7 +19,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for DbConn { | ||||
|     type Error = (); | ||||
| 
 | ||||
|     fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> { | ||||
|         let pool = request.guard::<State<Pool<ConnectionManager<PgConnection>>>>()?; | ||||
|         let pool = request.guard::<State<PgPool>>()?; | ||||
|         match pool.get() { | ||||
|             Ok(conn) => Outcome::Success(DbConn(conn)), | ||||
|             Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())) | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| #![feature(plugin, custom_derive, decl_macro, iterator_find_map)] | ||||
| #![feature(plugin, custom_derive, decl_macro, iterator_find_map, iterator_flatten)] | ||||
| #![plugin(rocket_codegen)] | ||||
| 
 | ||||
| extern crate activitypub; | ||||
| @ -8,7 +8,6 @@ extern crate base64; | ||||
| extern crate bcrypt; | ||||
| extern crate chrono; | ||||
| extern crate colored; | ||||
| extern crate comrak; | ||||
| extern crate failure; | ||||
| #[macro_use] | ||||
| extern crate failure_derive; | ||||
| @ -23,6 +22,7 @@ extern crate dotenv; | ||||
| #[macro_use] | ||||
| extern crate lazy_static; | ||||
| extern crate openssl; | ||||
| extern crate pulldown_cmark; | ||||
| extern crate reqwest; | ||||
| extern crate rocket; | ||||
| extern crate rocket_contrib; | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| use activitypub::{ | ||||
|     activity::Create, | ||||
|     object::Note | ||||
|     link, | ||||
|     object::{Note, properties::ObjectProperties} | ||||
| }; | ||||
| use chrono; | ||||
| use diesel::{self, PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods, dsl::any}; | ||||
| @ -14,6 +15,7 @@ use activity_pub::{ | ||||
| use models::{ | ||||
|     get_next_id, | ||||
|     instance::Instance, | ||||
|     mentions::Mention, | ||||
|     notifications::*, | ||||
|     posts::Post, | ||||
|     users::User | ||||
| @ -84,6 +86,16 @@ impl FromActivity<Note> for Comment { | ||||
|     fn from_activity(conn: &PgConnection, note: Note, actor: Id) -> Comment { | ||||
|         let previous_url = note.object_props.in_reply_to.clone().unwrap().as_str().unwrap().to_string(); | ||||
|         let previous_comment = Comment::find_by_ap_url(conn, previous_url.clone()); | ||||
| 
 | ||||
|         // save mentions
 | ||||
|         if let Some(serde_json::Value::Array(tags)) = note.object_props.tag.clone() { | ||||
|             for tag in tags.into_iter() { | ||||
|                 serde_json::from_value::<link::Mention>(tag) | ||||
|                     .map(|m| Mention::from_activity(conn, m, Id::new(note.clone().object_props.clone().url_string().unwrap_or(String::from(""))))) | ||||
|                     .ok(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         let comm = Comment::insert(conn, NewComment { | ||||
|             content: SafeString::new(¬e.object_props.content_string().unwrap()), | ||||
|             spoiler_text: note.object_props.summary_string().unwrap_or(String::from("")), | ||||
| @ -95,28 +107,22 @@ impl FromActivity<Note> for Comment { | ||||
|             author_id: User::from_url(conn, actor.clone().into()).unwrap().id, | ||||
|             sensitive: false // "sensitive" is not a standard property, we need to think about how to support it with the activitypub crate
 | ||||
|         }); | ||||
|         Comment::notify(conn, note, actor); | ||||
|         comm.notify(conn); | ||||
|         comm | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Notify<Note> for Comment { | ||||
|     fn notify(conn: &PgConnection, note: Note, _actor: Id) { | ||||
|         match Comment::find_by_ap_url(conn, note.object_props.id_string().unwrap()) { | ||||
|             Some(comment) => { | ||||
|                 for author in comment.clone().get_post(conn).get_authors(conn) { | ||||
|                     let comment = comment.clone(); | ||||
|                     Notification::insert(conn, NewNotification { | ||||
|                         title: "{{ data }} commented your article".to_string(), | ||||
|                         data: Some(comment.get_author(conn).display_name.clone()), | ||||
|                         content: Some(comment.get_post(conn).title), | ||||
|                         link: comment.ap_url, | ||||
|                         user_id: author.id | ||||
|                     }); | ||||
|                 } | ||||
|             }, | ||||
|             None => println!("Couldn't find comment by AP id, to create a new notification") | ||||
|         }; | ||||
| impl Notify for Comment { | ||||
|     fn notify(&self, conn: &PgConnection) { | ||||
|         for author in self.get_post(conn).get_authors(conn) { | ||||
|             Notification::insert(conn, NewNotification { | ||||
|                 title: "{{ data }} commented your article".to_string(), | ||||
|                 data: Some(self.get_author(conn).display_name.clone()), | ||||
|                 content: Some(self.get_post(conn).title), | ||||
|                 link: self.ap_url.clone(), | ||||
|                 user_id: author.id | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -62,15 +62,15 @@ impl FromActivity<FollowAct> for Follow { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Notify<FollowAct> for Follow { | ||||
|     fn notify(conn: &PgConnection, follow: FollowAct, actor: Id) { | ||||
|         let follower = User::from_url(conn, actor.into()).unwrap(); | ||||
| impl Notify for Follow { | ||||
|     fn notify(&self, conn: &PgConnection) { | ||||
|         let follower = User::get(conn, self.follower_id).unwrap(); | ||||
|         Notification::insert(conn, NewNotification { | ||||
|             title: "{{ data }} started following you".to_string(), | ||||
|             data: Some(follower.display_name.clone()), | ||||
|             content: None, | ||||
|             link: Some(follower.ap_url), | ||||
|             user_id: User::from_url(conn, follow.follow_props.object_link::<Id>().unwrap().into()).unwrap().id | ||||
|             user_id: self.following_id | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -77,7 +77,7 @@ impl Like { | ||||
| } | ||||
| 
 | ||||
| impl FromActivity<activity::Like> for Like { | ||||
|     fn from_activity(conn: &PgConnection, like: activity::Like, actor: Id) -> Like { | ||||
|     fn from_activity(conn: &PgConnection, like: activity::Like, _actor: Id) -> Like { | ||||
|         let liker = User::from_url(conn, like.like_props.actor.as_str().unwrap().to_string()); | ||||
|         let post = Post::find_by_ap_url(conn, like.like_props.object.as_str().unwrap().to_string()); | ||||
|         let res = Like::insert(conn, NewLike { | ||||
| @ -85,15 +85,15 @@ impl FromActivity<activity::Like> for Like { | ||||
|             user_id: liker.unwrap().id, | ||||
|             ap_url: like.object_props.id_string().unwrap_or(String::from("")) | ||||
|         }); | ||||
|         Like::notify(conn, like, actor); | ||||
|         res.notify(conn); | ||||
|         res | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Notify<activity::Like> for Like { | ||||
|     fn notify(conn: &PgConnection, like: activity::Like, actor: Id) { | ||||
|         let liker = User::from_url(conn, actor.into()).unwrap(); | ||||
|         let post = Post::find_by_ap_url(conn, like.like_props.object_link::<Id>().unwrap().into()).unwrap(); | ||||
| impl Notify for Like { | ||||
|     fn notify(&self, conn: &PgConnection) { | ||||
|         let liker = User::get(conn, self.user_id).unwrap(); | ||||
|         let post = Post::get(conn, self.post_id).unwrap(); | ||||
|         for author in post.get_authors(conn) { | ||||
|             let post = post.clone(); | ||||
|             Notification::insert(conn, NewNotification { | ||||
|  | ||||
							
								
								
									
										113
									
								
								src/models/mentions.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/models/mentions.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,113 @@ | ||||
| use activitypub::link; | ||||
| use diesel::{self, PgConnection, QueryDsl, RunQueryDsl, ExpressionMethods}; | ||||
| 
 | ||||
| use activity_pub::{Id, inbox::Notify}; | ||||
| use models::{ | ||||
|     comments::Comment, | ||||
|     notifications::*, | ||||
|     posts::Post, | ||||
|     users::User | ||||
| }; | ||||
| use schema::mentions; | ||||
| 
 | ||||
| #[derive(Queryable, Identifiable)] | ||||
| pub struct Mention { | ||||
|     pub id: i32, | ||||
|     pub mentioned_id: i32, | ||||
|     pub post_id: Option<i32>, | ||||
|     pub comment_id: Option<i32>, | ||||
|     pub ap_url: String | ||||
| } | ||||
| 
 | ||||
| #[derive(Insertable)] | ||||
| #[table_name = "mentions"] | ||||
| pub struct NewMention { | ||||
|     pub mentioned_id: i32, | ||||
|     pub post_id: Option<i32>, | ||||
|     pub comment_id: Option<i32>, | ||||
|     pub ap_url: String | ||||
| } | ||||
| 
 | ||||
| impl Mention { | ||||
|     insert!(mentions, NewMention); | ||||
|     get!(mentions); | ||||
|     find_by!(mentions, find_by_ap_url, ap_url as String); | ||||
|     list_by!(mentions, list_for_user, mentioned_id as i32); | ||||
|     list_by!(mentions, list_for_post, post_id as i32); | ||||
| 
 | ||||
|     pub fn get_mentioned(&self, conn: &PgConnection) -> Option<User> { | ||||
|         User::get(conn, self.mentioned_id) | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_post(&self, conn: &PgConnection) -> Option<Post> { | ||||
|         self.post_id.and_then(|id| Post::get(conn, id)) | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_comment(&self, conn: &PgConnection) -> Option<Comment> { | ||||
|         self.post_id.and_then(|id| Comment::get(conn, id)) | ||||
|     } | ||||
| 
 | ||||
|     pub fn build_activity(conn: &PgConnection, ment: String) -> link::Mention { | ||||
|         let user = User::find_by_fqn(conn, ment.clone()); | ||||
|         println!("building act : {} -> {:?}", ment, user); | ||||
|         let mut mention = link::Mention::default(); | ||||
|         mention.link_props.set_href_string(user.clone().map(|u| u.ap_url).unwrap_or(String::new())).expect("Error setting mention's href"); | ||||
|         mention.link_props.set_name_string(format!("@{}", ment)).expect("Error setting mention's name"); | ||||
|         mention | ||||
|     } | ||||
| 
 | ||||
|     pub fn to_activity(&self, conn: &PgConnection) -> link::Mention { | ||||
|         let user = self.get_mentioned(conn); | ||||
|         let mut mention = link::Mention::default(); | ||||
|         mention.link_props.set_href_string(user.clone().map(|u| u.ap_url).unwrap_or(String::new())).expect("Error setting mention's href"); | ||||
|         mention.link_props.set_name_string(user.map(|u| format!("@{}", u.get_fqn(conn))).unwrap_or(String::new())).expect("Error setting mention's name"); | ||||
|         mention | ||||
|     } | ||||
| 
 | ||||
|     pub fn from_activity(conn: &PgConnection, ment: link::Mention, inside: Id) -> Option<Self> { | ||||
|         let ap_url = ment.link_props.href_string().unwrap(); | ||||
|         let mentioned = User::find_by_ap_url(conn, ap_url).unwrap(); | ||||
| 
 | ||||
|         if let Some(post) = Post::find_by_ap_url(conn, inside.clone().into()) { | ||||
|             let res = Mention::insert(conn, NewMention { | ||||
|                 mentioned_id: mentioned.id, | ||||
|                 post_id: Some(post.id), | ||||
|                 comment_id: None, | ||||
|                 ap_url: ment.link_props.href_string().unwrap_or(String::new()) | ||||
|             }); | ||||
|             res.notify(conn); | ||||
|             Some(res) | ||||
|         } else { | ||||
|             if let Some(comment) = Comment::find_by_ap_url(conn, inside.into()) { | ||||
|                 let res = Mention::insert(conn, NewMention { | ||||
|                     mentioned_id: mentioned.id, | ||||
|                     post_id: None, | ||||
|                     comment_id: Some(comment.id), | ||||
|                     ap_url: ment.link_props.href_string().unwrap_or(String::new()) | ||||
|                 }); | ||||
|                 res.notify(conn); | ||||
|                 Some(res) | ||||
|             } else { | ||||
|                 None | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Notify for Mention { | ||||
|     fn notify(&self, conn: &PgConnection) { | ||||
|         let author = self.get_comment(conn) | ||||
|             .map(|c| c.get_author(conn).display_name.clone()) | ||||
|             .unwrap_or(self.get_post(conn).unwrap().get_authors(conn)[0].display_name.clone()); | ||||
| 
 | ||||
|         self.get_mentioned(conn).map(|m| { | ||||
|             Notification::insert(conn, NewNotification { | ||||
|                 title: "{{ data }} mentioned you.".to_string(), | ||||
|                 data: Some(author), | ||||
|                 content: None, | ||||
|                 link: Some(self.get_post(conn).map(|p| p.ap_url).unwrap_or_else(|| self.get_comment(conn).unwrap().ap_url.unwrap_or(String::new()))), | ||||
|                 user_id: m.id | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @ -14,6 +14,18 @@ macro_rules! find_by { | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| macro_rules! list_by { | ||||
|     ($table:ident, $fn:ident, $($col:ident as $type:ident),+) => { | ||||
|         /// Try to find a $table with a given $col
 | ||||
|         pub fn $fn(conn: &PgConnection, $($col: $type),+) -> Vec<Self> { | ||||
|             $table::table | ||||
|                 $(.filter($table::$col.eq($col)))+ | ||||
|                 .load::<Self>(conn) | ||||
|                 .expect("Error loading $table by $col") | ||||
|         } | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| macro_rules! get { | ||||
|     ($table:ident) => { | ||||
|         pub fn get(conn: &PgConnection, id: i32) -> Option<Self> { | ||||
| @ -53,6 +65,7 @@ pub mod comments; | ||||
| pub mod follows; | ||||
| pub mod instance; | ||||
| pub mod likes; | ||||
| pub mod mentions; | ||||
| pub mod notifications; | ||||
| pub mod post_authors; | ||||
| pub mod posts; | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| use activitypub::{ | ||||
|     activity::Create, | ||||
|     link, | ||||
|     object::{Article, properties::ObjectProperties} | ||||
| }; | ||||
| use chrono::NaiveDateTime; | ||||
| @ -9,13 +10,13 @@ use serde_json; | ||||
| use BASE_URL; | ||||
| use activity_pub::{ | ||||
|     PUBLIC_VISIBILTY, ap_url, Id, IntoId, | ||||
|     actor::Actor, | ||||
|     inbox::FromActivity | ||||
| }; | ||||
| use models::{ | ||||
|     blogs::Blog, | ||||
|     instance::Instance, | ||||
|     likes::Like, | ||||
|     mentions::Mention, | ||||
|     post_authors::PostAuthor, | ||||
|     reshares::Reshare, | ||||
|     users::User | ||||
| @ -144,6 +145,8 @@ impl Post { | ||||
|         let mut to = self.get_receivers_urls(conn); | ||||
|         to.push(PUBLIC_VISIBILTY.to_string()); | ||||
| 
 | ||||
|         let mentions = Mention::list_for_post(conn, self.id).into_iter().map(|m| m.to_activity(conn)).collect::<Vec<link::Mention>>(); | ||||
| 
 | ||||
|         let mut article = Article::default(); | ||||
|         article.object_props = ObjectProperties { | ||||
|             name: Some(serde_json::to_value(self.title.clone()).unwrap()), | ||||
| @ -151,11 +154,11 @@ impl Post { | ||||
|             attributed_to: Some(serde_json::to_value(self.get_authors(conn).into_iter().map(|x| x.ap_url).collect::<Vec<String>>()).unwrap()), | ||||
|             content: Some(serde_json::to_value(self.content.clone()).unwrap()), | ||||
|             published: Some(serde_json::to_value(self.creation_date).unwrap()), | ||||
|             tag: Some(serde_json::to_value(Vec::<serde_json::Value>::new()).unwrap()), | ||||
|             tag: Some(serde_json::to_value(mentions).unwrap()), | ||||
|             url: Some(serde_json::to_value(self.compute_id(conn)).unwrap()), | ||||
|             to: Some(serde_json::to_value(to).unwrap()), | ||||
|             cc: Some(serde_json::to_value(Vec::<serde_json::Value>::new()).unwrap()), | ||||
|             ..ObjectProperties::default()                
 | ||||
|             ..ObjectProperties::default() | ||||
|         }; | ||||
|         article | ||||
|     } | ||||
| @ -184,6 +187,15 @@ impl Post { | ||||
| 
 | ||||
| impl FromActivity<Article> for Post { | ||||
|     fn from_activity(conn: &PgConnection, article: Article, _actor: Id) -> Post { | ||||
|         // save mentions
 | ||||
|         if let Some(serde_json::Value::Array(tags)) = article.object_props.tag.clone() { | ||||
|             for tag in tags.into_iter() { | ||||
|                 serde_json::from_value::<link::Mention>(tag) | ||||
|                     .map(|m| Mention::from_activity(conn, m, Id::new(article.clone().object_props.clone().url_string().unwrap_or(String::from(""))))) | ||||
|                     .ok(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         Post::insert(conn, NewPost { | ||||
|             blog_id: 0, // TODO
 | ||||
|             slug: String::from(""), // TODO
 | ||||
|  | ||||
| @ -73,7 +73,7 @@ impl Reshare { | ||||
| } | ||||
| 
 | ||||
| impl FromActivity<Announce> for Reshare { | ||||
|     fn from_activity(conn: &PgConnection, announce: Announce, actor: Id) -> Reshare { | ||||
|     fn from_activity(conn: &PgConnection, announce: Announce, _actor: Id) -> Reshare { | ||||
|         let user = User::from_url(conn, announce.announce_props.actor.as_str().unwrap().to_string()); | ||||
|         let post = Post::find_by_ap_url(conn, announce.announce_props.object.as_str().unwrap().to_string()); | ||||
|         let reshare = Reshare::insert(conn, NewReshare { | ||||
| @ -81,15 +81,15 @@ impl FromActivity<Announce> for Reshare { | ||||
|             user_id: user.unwrap().id, | ||||
|             ap_url: announce.object_props.id_string().unwrap_or(String::from("")) | ||||
|         }); | ||||
|         Reshare::notify(conn, announce, actor); | ||||
|         reshare.notify(conn); | ||||
|         reshare | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Notify<Announce> for Reshare { | ||||
|     fn notify(conn: &PgConnection, announce: Announce, actor: Id) { | ||||
|         let actor = User::from_url(conn, actor.into()).unwrap(); | ||||
|         let post = Post::find_by_ap_url(conn, announce.announce_props.object_link::<Id>().unwrap().into()).unwrap(); | ||||
| impl Notify for Reshare { | ||||
|     fn notify(&self, conn: &PgConnection) { | ||||
|         let actor = User::get(conn, self.user_id).unwrap(); | ||||
|         let post = self.get_post(conn).unwrap(); | ||||
|         for author in post.get_authors(conn) { | ||||
|             let post = post.clone(); | ||||
|             Notification::insert(conn, NewNotification { | ||||
|  | ||||
| @ -47,7 +47,7 @@ use safe_string::SafeString; | ||||
| 
 | ||||
| pub const AUTH_COOKIE: &'static str = "user_id"; | ||||
| 
 | ||||
| #[derive(Queryable, Identifiable, Serialize, Deserialize, Clone)] | ||||
| #[derive(Queryable, Identifiable, Serialize, Deserialize, Clone, Debug)] | ||||
| pub struct User { | ||||
|     pub id: i32, | ||||
|     pub username: String, | ||||
| @ -89,7 +89,7 @@ impl User { | ||||
|     get!(users); | ||||
|     find_by!(users, find_by_email, email as String); | ||||
|     find_by!(users, find_by_name, username as String, instance_id as i32); | ||||
| 
 | ||||
|     find_by!(users, find_by_ap_url, ap_url as String); | ||||
| 
 | ||||
|     pub fn grant_admin_rights(&self, conn: &PgConnection) { | ||||
|         diesel::update(self) | ||||
| @ -419,23 +419,15 @@ impl APActor for User { | ||||
|     } | ||||
| 
 | ||||
|     fn from_url(conn: &PgConnection, url: String) -> Option<User> { | ||||
|         let in_db = users::table.filter(users::ap_url.eq(url.clone())) | ||||
|             .limit(1) | ||||
|             .load::<User>(conn) | ||||
|             .expect("Error loading user by AP url") | ||||
|             .into_iter().nth(0); | ||||
|         match in_db { | ||||
|             Some(u) => Some(u), | ||||
|             None => { | ||||
|                 // The requested user was not in the DB
 | ||||
|                 // We try to fetch it if it is remote
 | ||||
|                 if Url::parse(url.as_ref()).unwrap().host_str().unwrap() != BASE_URL.as_str() { | ||||
|                     Some(User::fetch_from_url(conn, url).unwrap()) | ||||
|                 } else { | ||||
|                     None | ||||
|                 } | ||||
|         User::find_by_ap_url(conn, url.clone()).or_else(|| { | ||||
|             // The requested user was not in the DB
 | ||||
|             // We try to fetch it if it is remote
 | ||||
|             if Url::parse(url.as_ref()).unwrap().host_str().unwrap() != BASE_URL.as_str() { | ||||
|                 Some(User::fetch_from_url(conn, url).unwrap()) | ||||
|             } else { | ||||
|                 None | ||||
|             } | ||||
|         } | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -5,7 +5,7 @@ use rocket::{ | ||||
| use rocket_contrib::Template; | ||||
| use serde_json; | ||||
| 
 | ||||
| use activity_pub::{broadcast, inbox::Inbox}; | ||||
| use activity_pub::{broadcast, inbox::Inbox, inbox::Notify}; | ||||
| use db_conn::DbConn; | ||||
| use models::{ | ||||
|     blogs::Blog, | ||||
| @ -55,6 +55,7 @@ fn create_response(blog_name: String, slug: String, query: Option<CommentQuery>, | ||||
|     let blog = Blog::find_by_fqn(&*conn, blog_name.clone()).unwrap(); | ||||
|     let post = Post::find_by_slug(&*conn, slug.clone(), blog.id).unwrap(); | ||||
|     let form = data.get(); | ||||
| <<<<<<< HEAD | ||||
| 
 | ||||
|     let (new_comment, id) = NewComment::build() | ||||
|         .content(form.content.clone()) | ||||
| @ -67,6 +68,20 @@ fn create_response(blog_name: String, slug: String, query: Option<CommentQuery>, | ||||
|     let instance = Instance::get_local(&*conn).unwrap(); | ||||
|     instance.received(&*conn, serde_json::to_value(new_comment.clone()).expect("JSON serialization error")); | ||||
|     broadcast(&*conn, &user, new_comment, user.get_followers(&*conn)); | ||||
| ======= | ||||
|     let comment = Comment::insert(&*conn, NewComment { | ||||
|         content: SafeString::new(&form.content.clone()), | ||||
|         in_response_to_id: query.responding_to, | ||||
|         post_id: post.id, | ||||
|         author_id: user.id, | ||||
|         ap_url: None, // TODO: set it
 | ||||
|         sensitive: false, | ||||
|         spoiler_text: "".to_string() | ||||
|     }); | ||||
|     comment.notify(&*conn); | ||||
| 
 | ||||
|     broadcast(&*conn, &user, comment.create_activity(&*conn), user.get_followers(&*conn)); | ||||
| >>>>>>> dbdcbe71049e181c1c7649169c0153b3c9d81ad8 | ||||
| 
 | ||||
|     Redirect::to(format!("/~/{}/{}/#comment-{}", blog_name, slug, id)) | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| use rocket::response::{Redirect, Flash}; | ||||
| 
 | ||||
| use activity_pub::{broadcast, IntoId, inbox::Notify}; | ||||
| use activity_pub::{broadcast, inbox::Notify}; | ||||
| use db_conn::DbConn; | ||||
| use models::{ | ||||
|     blogs::Blog, | ||||
| @ -23,8 +23,8 @@ fn create(blog: String, slug: String, user: User, conn: DbConn) -> Redirect { | ||||
|             ap_url: "".to_string() | ||||
|         }); | ||||
|         like.update_ap_url(&*conn); | ||||
|         like.notify(&*conn); | ||||
| 
 | ||||
|         likes::Like::notify(&*conn, like.into_activity(&*conn), user.clone().into_id()); | ||||
|         broadcast(&*conn, &user, like.into_activity(&*conn), user.get_followers(&*conn)); | ||||
|     } else { | ||||
|         let like = likes::Like::find_by_user_on_post(&*conn, user.id, post.id).unwrap(); | ||||
|  | ||||
| @ -1,21 +1,21 @@ | ||||
| use comrak::{markdown_to_html, ComrakOptions}; | ||||
| use heck::KebabCase; | ||||
| use rocket::request::Form; | ||||
| use rocket::response::{Redirect, Flash}; | ||||
| use rocket_contrib::Template; | ||||
| use serde_json; | ||||
| 
 | ||||
| use activity_pub::{broadcast, context, activity_pub, ActivityPub}; | ||||
| use activity_pub::{broadcast, context, activity_pub, ActivityPub, Id}; | ||||
| use db_conn::DbConn; | ||||
| use models::{ | ||||
|     blogs::*, | ||||
|     comments::Comment, | ||||
|     mentions::Mention, | ||||
|     post_authors::*, | ||||
|     posts::*, | ||||
|     users::User | ||||
| }; | ||||
| use utils; | ||||
| use safe_string::SafeString; | ||||
| use utils; | ||||
| 
 | ||||
| #[get("/~/<blog>/<slug>", rank = 4)] | ||||
| fn details(blog: String, slug: String, conn: DbConn, user: Option<User>) -> Template { | ||||
| @ -88,19 +88,7 @@ fn create(blog_name: String, data: Form<NewPostForm>, user: User, conn: DbConn) | ||||
|         if slug == "new" || Post::find_by_slug(&*conn, slug.clone(), blog.id).is_some() { | ||||
|             Redirect::to(uri!(new: blog = blog_name)) | ||||
|         } else { | ||||
|             let content = markdown_to_html(form.content.to_string().as_ref(), &ComrakOptions{ | ||||
|                 smart: true, | ||||
|                 safe: true, | ||||
|                 ext_strikethrough: true, | ||||
|                 ext_tagfilter: true, | ||||
|                 ext_table: true, | ||||
|                 ext_autolink: true, | ||||
|                 ext_tasklist: true, | ||||
|                 ext_superscript: true, | ||||
|                 ext_header_ids: Some("title".to_string()), | ||||
|                 ext_footnotes: true, | ||||
|                 ..ComrakOptions::default() | ||||
|             }); | ||||
|             let (content, mentions) = utils::md_to_html(form.content.to_string().as_ref()); | ||||
| 
 | ||||
|             let post = Post::insert(&*conn, NewPost { | ||||
|                 blog_id: blog.id, | ||||
| @ -117,6 +105,10 @@ fn create(blog_name: String, data: Form<NewPostForm>, user: User, conn: DbConn) | ||||
|                 author_id: user.id | ||||
|             }); | ||||
| 
 | ||||
|             for m in mentions.into_iter() { | ||||
|                 Mention::from_activity(&*conn, Mention::build_activity(&*conn, m), Id::new(post.compute_id(&*conn))); | ||||
|             } | ||||
| 
 | ||||
|             let act = post.create_activity(&*conn); | ||||
|             broadcast(&*conn, &user, act, user.get_followers(&*conn)); | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| use rocket::response::{Redirect, Flash}; | ||||
| 
 | ||||
| use activity_pub::{broadcast, IntoId, inbox::Notify}; | ||||
| use activity_pub::{broadcast, inbox::Notify}; | ||||
| use db_conn::DbConn; | ||||
| use models::{ | ||||
|     blogs::Blog, | ||||
| @ -23,8 +23,8 @@ fn create(blog: String, slug: String, user: User, conn: DbConn) -> Redirect { | ||||
|             ap_url: "".to_string() | ||||
|         }); | ||||
|         reshare.update_ap_url(&*conn); | ||||
|         reshare.notify(&*conn); | ||||
| 
 | ||||
|         Reshare::notify(&*conn, reshare.into_activity(&*conn), user.clone().into_id()); | ||||
|         broadcast(&*conn, &user, reshare.into_activity(&*conn), user.get_followers(&*conn)); | ||||
|     } else { | ||||
|         let reshare = Reshare::find_by_user_on_post(&*conn, user.id, post.id).unwrap(); | ||||
|  | ||||
| @ -71,16 +71,17 @@ fn dashboard_auth() -> Flash<Redirect> { | ||||
| #[get("/@/<name>/follow")] | ||||
| fn follow(name: String, conn: DbConn, user: User) -> Redirect { | ||||
|     let target = User::find_by_fqn(&*conn, name.clone()).unwrap(); | ||||
|     follows::Follow::insert(&*conn, follows::NewFollow { | ||||
|     let f = follows::Follow::insert(&*conn, follows::NewFollow { | ||||
|         follower_id: user.id, | ||||
|         following_id: target.id | ||||
|     }); | ||||
|     f.notify(&*conn); | ||||
| 
 | ||||
|     let mut act = Follow::default(); | ||||
|     act.follow_props.set_actor_link::<Id>(user.clone().into_id()).unwrap(); | ||||
|     act.follow_props.set_object_object(user.into_activity(&*conn)).unwrap(); | ||||
|     act.object_props.set_id_string(format!("{}/follow/{}", user.ap_url, target.ap_url)).unwrap(); | ||||
| 
 | ||||
|     follows::Follow::notify(&*conn, act.clone(), user.clone().into_id()); | ||||
|     broadcast(&*conn, &user, act, vec![target]); | ||||
|     Redirect::to(uri!(details: name = name)) | ||||
| } | ||||
|  | ||||
| @ -66,6 +66,16 @@ table! { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| table! { | ||||
|     mentions (id) { | ||||
|         id -> Int4, | ||||
|         mentioned_id -> Int4, | ||||
|         post_id -> Nullable<Int4>, | ||||
|         comment_id -> Nullable<Int4>, | ||||
|         ap_url -> Varchar, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| table! { | ||||
|     notifications (id) { | ||||
|         id -> Int4, | ||||
| @ -137,6 +147,9 @@ joinable!(comments -> posts (post_id)); | ||||
| joinable!(comments -> users (author_id)); | ||||
| joinable!(likes -> posts (post_id)); | ||||
| joinable!(likes -> users (user_id)); | ||||
| joinable!(mentions -> comments (comment_id)); | ||||
| joinable!(mentions -> posts (post_id)); | ||||
| joinable!(mentions -> users (mentioned_id)); | ||||
| joinable!(notifications -> users (user_id)); | ||||
| joinable!(post_authors -> posts (post_id)); | ||||
| joinable!(post_authors -> users (author_id)); | ||||
| @ -152,6 +165,7 @@ allow_tables_to_appear_in_same_query!( | ||||
|     follows, | ||||
|     instances, | ||||
|     likes, | ||||
|     mentions, | ||||
|     notifications, | ||||
|     post_authors, | ||||
|     posts, | ||||
|  | ||||
| @ -12,7 +12,7 @@ use db_conn::DbConn; | ||||
| use models::instance::*; | ||||
| use models::users::*; | ||||
| 
 | ||||
| type PgPool = Pool<ConnectionManager<PgConnection>>; | ||||
| pub type PgPool = Pool<ConnectionManager<PgConnection>>; | ||||
| 
 | ||||
| /// Initializes a database pool.
 | ||||
| fn init_pool() -> Option<PgPool> { | ||||
|  | ||||
							
								
								
									
										50
									
								
								src/utils.rs
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								src/utils.rs
									
									
									
									
									
								
							| @ -1,5 +1,6 @@ | ||||
| use gettextrs::gettext; | ||||
| use heck::CamelCase; | ||||
| use pulldown_cmark::{Event, Parser, Options, Tag, html}; | ||||
| use rocket::{ | ||||
|     http::uri::Uri, | ||||
|     response::{Redirect, Flash} | ||||
| @ -18,3 +19,52 @@ pub fn make_actor_id(name: String) -> String { | ||||
| pub fn requires_login(message: &str, url: Uri) -> Flash<Redirect> { | ||||
|     Flash::new(Redirect::to(Uri::new(format!("/login?m={}", gettext(message.to_string())))), "callback", url.as_str()) | ||||
| } | ||||
| 
 | ||||
| /// Returns (HTML, mentions)
 | ||||
| pub fn md_to_html(md: &str) -> (String, Vec<String>) { | ||||
|     let parser = Parser::new_ext(md, Options::all()); | ||||
| 
 | ||||
|     let (parser, mentions): (Vec<Vec<Event>>, Vec<Vec<String>>) = parser.map(|evt| match evt { | ||||
|         Event::Text(txt) => { | ||||
|             let (evts, _, _, _, new_mentions) = txt.chars().fold((vec![], false, String::new(), 0, vec![]), |(mut events, in_mention, text_acc, n, mut mentions), c| { | ||||
|                 if in_mention { | ||||
|                     if (c.is_alphanumeric() || c == '@' || c == '.' || c == '-' || c == '_') && (n < (txt.chars().count() - 1)) { | ||||
|                         (events, in_mention, text_acc + c.to_string().as_ref(), n + 1, mentions) | ||||
|                     } else { | ||||
|                         let mention = text_acc + c.to_string().as_ref(); | ||||
|                         let short_mention = mention.clone(); | ||||
|                         let short_mention = short_mention.splitn(1, '@').nth(0).unwrap_or(""); | ||||
|                         let link = Tag::Link(format!("/@/{}/", mention).into(), short_mention.to_string().into()); | ||||
| 
 | ||||
|                         mentions.push(mention); | ||||
|                         events.push(Event::Start(link.clone())); | ||||
|                         events.push(Event::Text(format!("@{}", short_mention).into())); | ||||
|                         events.push(Event::End(link)); | ||||
| 
 | ||||
|                         (events, false, c.to_string(), n + 1, mentions) | ||||
|                     } | ||||
|                 } else { | ||||
|                     if c == '@' { | ||||
|                         events.push(Event::Text(text_acc.into())); | ||||
|                         (events, true, String::new(), n + 1, mentions) | ||||
|                     } else { | ||||
|                         if n >= (txt.chars().count() - 1) { // Add the text after at the end, even if it is not followed by a mention.
 | ||||
|                             events.push(Event::Text((text_acc.clone() + c.to_string().as_ref()).into())) | ||||
|                         } | ||||
|                         (events, in_mention, text_acc + c.to_string().as_ref(), n + 1, mentions) | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|             (evts, new_mentions) | ||||
|         }, | ||||
|         _ => (vec![evt], vec![]) | ||||
|     }).unzip(); | ||||
|     let parser = parser.into_iter().flatten(); | ||||
|     let mentions = mentions.into_iter().flatten().map(|m| String::from(m.trim())); | ||||
| 
 | ||||
|     // TODO: fetch mentionned profiles in background, if needed
 | ||||
| 
 | ||||
|     let mut buf = String::new(); | ||||
|     html::push_html(&mut buf, parser); | ||||
|     (buf, mentions.collect()) | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user