Merge pull request #27 from Plume-org/activitystreams
Use the Activitystreams crate
This commit is contained in:
		
						commit
						d7552ba369
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -2,3 +2,4 @@ | ||||
| rls | ||||
| /target | ||||
| **/*.rs.bk | ||||
| rls | ||||
|  | ||||
							
								
								
									
										116
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										116
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -1,3 +1,46 @@ | ||||
| [[package]] | ||||
| name = "activitystreams" | ||||
| version = "0.1.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "activitystreams-traits 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "activitystreams-types 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "activitystreams-derive" | ||||
| version = "0.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "proc-macro2 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "activitystreams-traits" | ||||
| version = "0.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "serde 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "serde_json 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "activitystreams-types" | ||||
| version = "0.1.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "activitystreams-derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "activitystreams-traits 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "chrono 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "serde 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "serde_derive 1.0.43 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "serde_json 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "adler32" | ||||
| version = "1.0.2" | ||||
| @ -323,6 +366,25 @@ dependencies = [ | ||||
|  "backtrace 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "failure" | ||||
| version = "0.1.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "backtrace 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "failure_derive" | ||||
| version = "0.1.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "foreign-types" | ||||
| version = "0.3.2" | ||||
| @ -806,12 +868,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| name = "plume" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "activitystreams 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "activitystreams-derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "activitystreams-traits 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "activitystreams-types 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "array_tool 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "base64 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "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)", | ||||
|  "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)", | ||||
|  "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "hyper 0.11.25 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| @ -851,6 +919,11 @@ dependencies = [ | ||||
|  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "quote" | ||||
| version = "0.3.15" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "quote" | ||||
| version = "0.4.2" | ||||
| @ -1183,6 +1256,16 @@ name = "state" | ||||
| version = "0.4.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "syn" | ||||
| version = "0.11.11" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "syn" | ||||
| version = "0.12.15" | ||||
| @ -1203,6 +1286,23 @@ dependencies = [ | ||||
|  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "synom" | ||||
| version = "0.11.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "synstructure" | ||||
| version = "0.6.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| dependencies = [ | ||||
|  "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "syntex_fmt_macros" | ||||
| version = "0.5.0" | ||||
| @ -1473,6 +1573,11 @@ name = "unicode-segmentation" | ||||
| version = "1.2.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "unicode-xid" | ||||
| version = "0.0.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "unicode-xid" | ||||
| version = "0.1.0" | ||||
| @ -1578,6 +1683,10 @@ version = "0.4.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| 
 | ||||
| [metadata] | ||||
| "checksum activitystreams 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "638541e5169c839f6581302c50e38876312389475cd911ecc7c446c7491004cc" | ||||
| "checksum activitystreams-derive 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "48db826c588a009960d74530e7c215e21fae130f585362504dc6b6357e5ce86b" | ||||
| "checksum activitystreams-traits 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "670ef03168e704b0cae242e7a5d8b40506772b339687e01a3496fc4afe2e8542" | ||||
| "checksum activitystreams-types 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aff9aa0c3412fe4da72a1f6e4b1c2e9792bfdf1308b709389192f17aa8e2b3cd" | ||||
| "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 antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" | ||||
| @ -1617,6 +1726,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| "checksum encoding_rs 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "98fd0f24d1fb71a4a6b9330c8ca04cbd4e7cc5d846b54ca74ff376bc7c9f798d" | ||||
| "checksum error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8" | ||||
| "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" | ||||
| "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" | ||||
| "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" | ||||
| "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" | ||||
| @ -1679,6 +1790,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| "checksum pq-sys 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4dfb5e575ef93a1b7b2a381d47ba7c5d4e4f73bff37cee932195de769aad9a54" | ||||
| "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 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" | ||||
| "checksum r2d2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f9078ca6a8a5568ed142083bb2f7dc9295b69d16f867ddcc9849e51b17d8db46" | ||||
| @ -1716,8 +1828,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| "checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013" | ||||
| "checksum smallvec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ee4f357e8cd37bf8822e1b964e96fd39e2cb5a0424f8aaa284ccaccc2162411c" | ||||
| "checksum state 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5562ac59585fe3d9a1ccf6b4e298ce773f5063db80d59f783776b410c1714c2" | ||||
| "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" | ||||
| "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" | ||||
| "checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd" | ||||
| "checksum syntex_fmt_macros 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5386bdc48758d136af85b3880548e1f3a9fad8d7dc2b38bdb48c36a9cdefc0" | ||||
| "checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" | ||||
| "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" | ||||
| @ -1746,6 +1861,7 @@ 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-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 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" | ||||
|  | ||||
| @ -3,10 +3,16 @@ authors = ["Bat' <baptiste@gelez.xyz>"] | ||||
| name = "plume" | ||||
| version = "0.1.0" | ||||
| [dependencies] | ||||
| activitystreams = "0.1" | ||||
| activitystreams-derive = "0.1" | ||||
| activitystreams-traits = "0.1" | ||||
| activitystreams-types = "0.1" | ||||
| array_tool = "1.0" | ||||
| base64 = "0.9" | ||||
| bcrypt = "0.2" | ||||
| dotenv = "*" | ||||
| failure = "0.1" | ||||
| failure_derive = "0.1" | ||||
| heck = "0.3.0" | ||||
| hex = "0.3" | ||||
| hyper = "*" | ||||
|  | ||||
| @ -1,100 +1,146 @@ | ||||
| use activitystreams_traits::Actor; | ||||
| use activitystreams_types::{ | ||||
|     actor::Person, | ||||
|     activity::{Accept, Create, Follow, Like, Undo}, | ||||
|     object::{Article, Note} | ||||
| }; | ||||
| use diesel::PgConnection; | ||||
| use failure::Error; | ||||
| use serde_json; | ||||
| 
 | ||||
| use activity_pub::activity; | ||||
| use activity_pub::actor::Actor; | ||||
| use activity_pub::outbox::broadcast; | ||||
| use activity_pub::{broadcast, Id, IntoId}; | ||||
| use activity_pub::actor::Actor as APActor; | ||||
| use activity_pub::sign::*; | ||||
| use models::blogs::Blog; | ||||
| use models::comments::*; | ||||
| use models::follows::*; | ||||
| use models::likes::*; | ||||
| use models::follows; | ||||
| use models::likes; | ||||
| use models::posts::*; | ||||
| use models::users::User; | ||||
| 
 | ||||
| #[derive(Fail, Debug)] | ||||
| enum InboxError { | ||||
|     #[fail(display = "The `type` property is required, but was not present")] | ||||
|     NoType, | ||||
|     #[fail(display = "Invalid activity type")] | ||||
|     InvalidType, | ||||
|     #[fail(display = "Couldn't undo activity")] | ||||
|     CantUndo | ||||
| } | ||||
| 
 | ||||
| pub trait Inbox { | ||||
|     fn received(&self, conn: &PgConnection, act: serde_json::Value); | ||||
| 
 | ||||
|     fn save(&self, conn: &PgConnection, act: serde_json::Value) { | ||||
|         match act["type"].as_str().unwrap() { | ||||
|             "Create" => { | ||||
|                 match act["object"]["type"].as_str().unwrap() { | ||||
|                     "Article" => { | ||||
|                         Post::insert(conn, NewPost { | ||||
|                             blog_id: 0, // TODO
 | ||||
|                             slug: String::from(""), // TODO
 | ||||
|                             title: String::from(""), // TODO
 | ||||
|                             content: act["object"]["content"].as_str().unwrap().to_string(), | ||||
|                             published: true, | ||||
|                             license: String::from("CC-0"), | ||||
|                             ap_url: act["object"]["url"].as_str().unwrap().to_string() | ||||
|                         }); | ||||
|                     }, | ||||
|                     "Note" => { | ||||
|                         let previous_comment = Comment::find_by_ap_url(conn, act["object"]["inReplyTo"].as_str().unwrap().to_string()); | ||||
|                         Comment::insert(conn, NewComment { | ||||
|                             content: act["object"]["content"].as_str().unwrap().to_string(), | ||||
|                             spoiler_text: act["object"]["summary"].as_str().unwrap_or("").to_string(), | ||||
|                             ap_url: Some(act["object"]["id"].as_str().unwrap().to_string()), | ||||
|                             in_response_to_id: previous_comment.clone().map(|c| c.id), | ||||
|                             post_id: previous_comment | ||||
|                                 .map(|c| c.post_id) | ||||
|                                 .unwrap_or_else(|| Post::find_by_ap_url(conn, act["object"]["inReplyTo"].as_str().unwrap().to_string()).unwrap().id), | ||||
|                             author_id: User::from_url(conn, act["actor"].as_str().unwrap().to_string()).unwrap().id, | ||||
|                             sensitive: act["object"]["sensitive"].as_bool().unwrap_or(false) | ||||
|                         }); | ||||
|                     } | ||||
|                     x => println!("Received a new {}, but didn't saved it", x) | ||||
|                 } | ||||
|             }, | ||||
|             "Follow" => { | ||||
|                 let follow_act = activity::Follow::deserialize(act.clone()); | ||||
|                 let from = User::from_url(conn, act["actor"].as_str().unwrap().to_string()).unwrap(); | ||||
|                 match User::from_url(conn, act["object"].as_str().unwrap().to_string()) { | ||||
|                     Some(u) => self.accept_follow(conn, &from, &u, &follow_act, from.id, u.id), | ||||
|                     None => { | ||||
|                         let blog = Blog::from_url(conn, follow_act.get_target_id()).unwrap(); | ||||
|                         self.accept_follow(conn, &from, &blog, &follow_act, from.id, blog.id) | ||||
|                     } | ||||
|                 }; | ||||
|     fn new_article(&self, conn: &PgConnection, article: Article) -> Result<(), Error> { | ||||
|         Post::insert(conn, NewPost { | ||||
|             blog_id: 0, // TODO
 | ||||
|             slug: String::from(""), // TODO
 | ||||
|             title: article.object_props.name_string().unwrap(), | ||||
|             content: article.object_props.content_string().unwrap(), | ||||
|             published: true, | ||||
|             license: String::from("CC-0"), | ||||
|             ap_url: article.object_props.url_string()? | ||||
|         }); | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     fn new_comment(&self, conn: &PgConnection, note: Note, actor_id: String) -> Result<(), Error> { | ||||
|         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()); | ||||
|         Comment::insert(conn, NewComment { | ||||
|             content: note.object_props.content_string().unwrap(), | ||||
|             spoiler_text: note.object_props.summary_string().unwrap(), | ||||
|             ap_url: note.object_props.id_string().ok(), | ||||
|             in_response_to_id: previous_comment.clone().map(|c| c.id), | ||||
|             post_id: previous_comment | ||||
|                 .map(|c| c.post_id) | ||||
|                 .unwrap_or_else(|| Post::find_by_ap_url(conn, previous_url).unwrap().id), | ||||
|             author_id: User::from_url(conn, actor_id).unwrap().id, | ||||
|             sensitive: false // "sensitive" is not a standard property, we need to think about how to support it with the activitystreams crate
 | ||||
|         }); | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     fn follow(&self, conn: &PgConnection, follow: Follow) -> Result<(), Error> { | ||||
|         let from = User::from_url(conn, follow.actor.as_str().unwrap().to_string()).unwrap(); | ||||
|         match User::from_url(conn, follow.object.as_str().unwrap().to_string()) { | ||||
|             Some(u) => self.accept_follow(conn, &from, &u, follow, from.id, u.id), | ||||
|             None => { | ||||
|                 let blog = Blog::from_url(conn, follow.object.as_str().unwrap().to_string()).unwrap(); | ||||
|                 self.accept_follow(conn, &from, &blog, follow, from.id, blog.id) | ||||
|             } | ||||
|             "Like" => { | ||||
|                 let liker = User::from_url(conn, act["actor"].as_str().unwrap().to_string()); | ||||
|                 let post = Post::find_by_ap_url(conn, act["object"].as_str().unwrap().to_string()); | ||||
|                 Like::insert(conn, NewLike { | ||||
|                     post_id: post.unwrap().id, | ||||
|                     user_id: liker.unwrap().id, | ||||
|                     ap_url: act["id"].as_str().unwrap().to_string() | ||||
|                 }); | ||||
|             }, | ||||
|             "Undo" => { | ||||
|                 match act["object"]["type"].as_str().unwrap() { | ||||
|                     "Like" => { | ||||
|                         let like = Like::find_by_ap_url(conn, act["object"]["id"].as_str().unwrap().to_string()).unwrap(); | ||||
|                         like.delete(conn); | ||||
|         }; | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     fn like(&self, conn: &PgConnection, like: Like) -> Result<(), Error> { | ||||
|         let liker = User::from_url(conn, like.actor.as_str().unwrap().to_string()); | ||||
|         let post = Post::find_by_ap_url(conn, like.object.as_str().unwrap().to_string()); | ||||
|         likes::Like::insert(conn, likes::NewLike { | ||||
|             post_id: post.unwrap().id, | ||||
|             user_id: liker.unwrap().id, | ||||
|             ap_url: like.object_props.id_string()? | ||||
|         }); | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     fn unlike(&self, conn: &PgConnection, undo: Undo) -> Result<(), Error> { | ||||
|         let like = likes::Like::find_by_ap_url(conn, undo.object_object::<Like>()?.object_props.id_string()?).unwrap(); | ||||
|         like.delete(conn); | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     fn save(&self, conn: &PgConnection, act: serde_json::Value) -> Result<(), Error> { | ||||
|         match act["type"].as_str() { | ||||
|             Some(t) => { | ||||
|                 match t { | ||||
|                     "Create" => { | ||||
|                         let act: Create = serde_json::from_value(act.clone())?; | ||||
|                         match act.object["type"].as_str().unwrap() { | ||||
|                             "Article" => self.new_article(conn, act.object_object().unwrap()), | ||||
|                             "Note" => self.new_comment(conn, act.object_object().unwrap(), act.actor_object::<Person>()?.object_props.id_string()?), | ||||
|                             _ => Err(InboxError::InvalidType)? | ||||
|                         } | ||||
|                     }, | ||||
|                     "Follow" => self.follow(conn, serde_json::from_value(act.clone())?), | ||||
|                     "Like" => self.like(conn, serde_json::from_value(act.clone())?), | ||||
|                     "Undo" => { | ||||
|                         let act: Undo = serde_json::from_value(act.clone())?; | ||||
|                         match act.object["type"].as_str().unwrap() { | ||||
|                             "Like" => self.unlike(conn, act), | ||||
|                             _ => Err(InboxError::CantUndo)? | ||||
|                         } | ||||
|                     } | ||||
|                     x => println!("Wanted to Undo a {}, but it is not supported yet", x) | ||||
|                     _ => Err(InboxError::InvalidType)? | ||||
|                 } | ||||
|             }, | ||||
|             x => println!("Received unknow activity type: {}", x) | ||||
|             None => Err(InboxError::NoType)? | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn accept_follow<A: Actor + Signer, B: Actor + Clone, T: activity::Activity>( | ||||
|     fn accept_follow<A: Signer + IntoId + Clone, B: Clone + WithInbox + Actor>( | ||||
|         &self, | ||||
|         conn: &PgConnection, | ||||
|         from: &A, | ||||
|         target: &B, | ||||
|         follow: &T, | ||||
|         follow: Follow, | ||||
|         from_id: i32, | ||||
|         target_id: i32 | ||||
|     ) { | ||||
|         Follow::insert(conn, NewFollow { | ||||
|         follows::Follow::insert(conn, follows::NewFollow { | ||||
|             follower_id: from_id, | ||||
|             following_id: target_id | ||||
|         }); | ||||
| 
 | ||||
|         let accept = activity::Accept::new(target, follow, conn); | ||||
|         broadcast(conn, from, accept, vec![target.clone()]); | ||||
|         let mut accept = Accept::default(); | ||||
|         accept.set_actor_link::<Id>(from.clone().into_id()).unwrap(); | ||||
|         accept.set_object_object(follow).unwrap(); | ||||
|         broadcast(conn, &*from, accept, vec![target.clone()]); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub trait WithInbox { | ||||
|     fn get_inbox_url(&self) -> String; | ||||
| 
 | ||||
|     fn get_shared_inbox_url(&self) -> Option<String>; | ||||
| } | ||||
|  | ||||
| @ -1,13 +1,20 @@ | ||||
| use rocket::http::ContentType; | ||||
| use rocket::response::Content; | ||||
| use activitystreams_traits::{Activity, Actor, Object, Link}; | ||||
| use array_tool::vec::Uniq; | ||||
| use diesel::PgConnection; | ||||
| use reqwest::Client; | ||||
| use rocket::http::{ContentType, Status}; | ||||
| use rocket::response::{Response, Responder, Content}; | ||||
| use rocket::request::Request; | ||||
| use rocket_contrib::Json; | ||||
| use serde_json; | ||||
| 
 | ||||
| pub mod activity; | ||||
| use self::sign::Signable; | ||||
| 
 | ||||
| // pub mod activity;
 | ||||
| pub mod actor; | ||||
| pub mod inbox; | ||||
| pub mod object; | ||||
| pub mod outbox; | ||||
| // pub mod outbox;
 | ||||
| pub mod request; | ||||
| pub mod sign; | ||||
| pub mod webfinger; | ||||
| @ -54,3 +61,63 @@ pub fn context() -> serde_json::Value { | ||||
| pub fn activity_pub(json: serde_json::Value) -> ActivityPub { | ||||
|     Content(ContentType::new("application", "activity+json"), Json(json)) | ||||
| } | ||||
| 
 | ||||
| pub struct ActivityStream<T> (T); | ||||
| 
 | ||||
| impl<T> ActivityStream<T> { | ||||
|     pub fn new(t: T) -> ActivityStream<T> { | ||||
|         ActivityStream(t) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'r, O: Object> Responder<'r> for ActivityStream<O> { | ||||
|     fn respond_to(self, request: &Request) -> Result<Response<'r>, Status> { | ||||
|         serde_json::to_string(&self.0).respond_to(request) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn broadcast<A: Activity + Clone, S: sign::Signer, T: inbox::WithInbox + Actor>(conn: &PgConnection, sender: &S, act: A, to: Vec<T>) { | ||||
|     let boxes = to.into_iter() | ||||
|         .map(|u| u.get_shared_inbox_url().unwrap_or(u.get_inbox_url())) | ||||
|         .collect::<Vec<String>>() | ||||
|         .unique(); | ||||
|     for inbox in boxes { | ||||
|         // TODO: run it in Sidekiq or something like that
 | ||||
|         
 | ||||
|         let mut act = serde_json::to_value(act.clone()).unwrap(); | ||||
|         act["@context"] = context(); | ||||
|         let signed = act.sign(sender, conn); | ||||
| 
 | ||||
|         let res = Client::new() | ||||
|             .post(&inbox[..]) | ||||
|             .headers(request::headers()) | ||||
|             .header(request::signature(sender, request::headers(), conn)) | ||||
|             .header(request::digest(signed.to_string())) | ||||
|             .body(signed.to_string()) | ||||
|             .send(); | ||||
|         match res { | ||||
|             Ok(mut r) => println!("Successfully sent activity to inbox ({})\n\n{:?}", inbox, r.text().unwrap()), | ||||
|             Err(e) => println!("Error while sending to inbox ({:?})", e) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Serialize, Deserialize)] | ||||
| pub struct Id { | ||||
|     #[serde(flatten)] | ||||
|     id: String | ||||
| } | ||||
| 
 | ||||
| impl Id { | ||||
|     pub fn new<T: Into<String>>(id: T) -> Id { | ||||
|         Id { | ||||
|             id: id.into() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub trait IntoId { | ||||
|     fn into_id(self) -> Id; | ||||
| } | ||||
| 
 | ||||
| impl Link for Id {} | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| use activitystreams_traits::{Activity, Object}; | ||||
| use array_tool::vec::Uniq; | ||||
| use diesel::PgConnection; | ||||
| use reqwest::Client; | ||||
| @ -8,64 +9,7 @@ use serde_json; | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use activity_pub::{activity_pub, ActivityPub, context}; | ||||
| use activity_pub::activity::Activity; | ||||
| use activity_pub::actor::Actor; | ||||
| use activity_pub::request; | ||||
| use activity_pub::sign::*; | ||||
| 
 | ||||
| pub struct Outbox { | ||||
|     id: String, | ||||
|     items: Vec<Arc<Activity>> | ||||
| } | ||||
| 
 | ||||
| impl Outbox { | ||||
|     pub fn new(id: String, items: Vec<Arc<Activity>>) -> Outbox { | ||||
|         Outbox { | ||||
|             id: id, | ||||
|             items: items | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn serialize(&self) -> ActivityPub { | ||||
|         let items = self.items.clone().into_iter().map(|i| i.serialize()).collect::<Vec<serde_json::Value>>(); | ||||
|         activity_pub(json!({ | ||||
|             "@context": context(), | ||||
|             "type": "OrderedCollection", | ||||
|             "id": self.id, | ||||
|             "totalItems": items.len(), | ||||
|             "orderedItems": items | ||||
|         })) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'r> Responder<'r> for Outbox { | ||||
|     fn respond_to(self, request: &Request) -> Result<Response<'r>, Status> { | ||||
|         self.serialize().respond_to(request) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn broadcast<A: Activity + Clone, S: Actor + Signer, T: Actor>(conn: &PgConnection, sender: &S, act: A, to: Vec<T>) { | ||||
|     let boxes = to.into_iter() | ||||
|         .map(|u| u.get_shared_inbox_url().unwrap_or(u.get_inbox_url())) | ||||
|         .collect::<Vec<String>>() | ||||
|         .unique(); | ||||
|     for inbox in boxes { | ||||
|         // TODO: run it in Sidekiq or something like that        
 | ||||
|         
 | ||||
|         let mut act = act.serialize(); | ||||
|         act["@context"] = context(); | ||||
|         let signed = act.sign(sender, conn); | ||||
|         
 | ||||
|         let res = Client::new() | ||||
|             .post(&inbox[..]) | ||||
|             .headers(request::headers()) | ||||
|             .header(request::signature(sender, request::headers(), conn)) | ||||
|             .header(request::digest(signed.to_string())) | ||||
|             .body(signed.to_string()) | ||||
|             .send(); | ||||
|         match res { | ||||
|             Ok(mut r) => println!("Successfully sent activity to inbox ({})\n\n{:?}", inbox, r.text().unwrap()), | ||||
|             Err(e) => println!("Error while sending to inbox ({:?})", e) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,10 +1,18 @@ | ||||
| #![feature(plugin, custom_derive, iterator_find_map)] | ||||
| #![plugin(rocket_codegen)] | ||||
| 
 | ||||
| extern crate activitystreams; | ||||
| #[macro_use] | ||||
| extern crate activitystreams_derive; | ||||
| extern crate activitystreams_traits; | ||||
| extern crate activitystreams_types; | ||||
| extern crate array_tool; | ||||
| extern crate base64; | ||||
| extern crate bcrypt; | ||||
| extern crate chrono; | ||||
| extern crate failure; | ||||
| #[macro_use] | ||||
| extern crate failure_derive; | ||||
| extern crate heck; | ||||
| extern crate hex; | ||||
| #[macro_use] | ||||
|  | ||||
| @ -1,3 +1,5 @@ | ||||
| use activitystreams_traits::{Actor, Object}; | ||||
| use activitystreams_types::collection::OrderedCollection; | ||||
| use reqwest::Client; | ||||
| use reqwest::header::{Accept, qitem}; | ||||
| use reqwest::mime::Mime; | ||||
| @ -9,18 +11,17 @@ use openssl::hash::MessageDigest; | ||||
| use openssl::pkey::{PKey, Private}; | ||||
| use openssl::rsa::Rsa; | ||||
| use openssl::sign::Signer; | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use activity_pub::activity::Activity; | ||||
| use activity_pub::actor::{Actor, ActorType}; | ||||
| use activity_pub::outbox::Outbox; | ||||
| use activity_pub::{ActivityStream, Id, IntoId}; | ||||
| use activity_pub::actor::{Actor as APActor, ActorType}; | ||||
| use activity_pub::inbox::WithInbox; | ||||
| use activity_pub::sign; | ||||
| use activity_pub::webfinger::*; | ||||
| use models::instance::Instance; | ||||
| use schema::blogs; | ||||
| 
 | ||||
| 
 | ||||
| #[derive(Queryable, Identifiable, Serialize, Clone)] | ||||
| #[derive(Queryable, Identifiable, Serialize, Deserialize, Clone)] | ||||
| pub struct Blog { | ||||
|     pub id: i32, | ||||
|     pub actor_id: String, | ||||
| @ -158,11 +159,14 @@ impl Blog { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn outbox(&self, conn: &PgConnection) -> Outbox { | ||||
|         Outbox::new(self.compute_outbox(conn), self.get_activities(conn)) | ||||
|     pub fn outbox(&self, conn: &PgConnection) -> ActivityStream<OrderedCollection> { | ||||
|         let mut coll = OrderedCollection::default(); | ||||
|         coll.collection_props.items = serde_json::to_value(self.get_activities(conn)).unwrap(); | ||||
|         coll.collection_props.set_total_items_u64(self.get_activities(conn).len() as u64).unwrap(); | ||||
|         ActivityStream::new(coll) | ||||
|     } | ||||
| 
 | ||||
|     fn get_activities(&self, _conn: &PgConnection) -> Vec<Arc<Activity>> { | ||||
|     fn get_activities(&self, _conn: &PgConnection) -> Vec<serde_json::Value> { | ||||
|         vec![] | ||||
|     } | ||||
| 
 | ||||
| @ -171,7 +175,26 @@ impl Blog { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Actor for Blog { | ||||
| impl IntoId for Blog { | ||||
|     fn into_id(self) -> Id { | ||||
|         Id::new(self.ap_url) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Object for Blog {} | ||||
| impl Actor for Blog {} | ||||
| 
 | ||||
| impl WithInbox for Blog { | ||||
|     fn get_inbox_url(&self) -> String { | ||||
|         self.inbox_url.clone() | ||||
|     } | ||||
| 
 | ||||
|     fn get_shared_inbox_url(&self) -> Option<String> { | ||||
|         None | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl APActor for Blog { | ||||
|     fn get_box_prefix() -> &'static str { | ||||
|         "~" | ||||
|     } | ||||
|  | ||||
| @ -1,8 +1,12 @@ | ||||
| use activitystreams_types::{ | ||||
|     activity::Create, | ||||
|     object::{Note, properties::ObjectProperties} | ||||
| }; | ||||
| use chrono; | ||||
| use diesel::{self, PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods}; | ||||
| use serde_json; | ||||
| 
 | ||||
| use activity_pub::{ap_url, PUBLIC_VISIBILTY}; | ||||
| use activity_pub::{ap_url, IntoId, PUBLIC_VISIBILTY}; | ||||
| use activity_pub::actor::Actor; | ||||
| use activity_pub::object::Object; | ||||
| use models::posts::Post; | ||||
| @ -71,6 +75,37 @@ impl Comment { | ||||
|     pub fn get_post(&self, conn: &PgConnection) -> Post { | ||||
|         Post::get(conn, self.post_id).unwrap()        
 | ||||
|     } | ||||
| 
 | ||||
|     pub fn into_activity(&self, conn: &PgConnection) -> Note { | ||||
|         let mut to = self.get_author(conn).get_followers(conn).into_iter().map(|f| f.ap_url).collect::<Vec<String>>(); | ||||
|         to.append(&mut self.get_post(conn).get_receivers_urls(conn)); | ||||
|         to.push(PUBLIC_VISIBILTY.to_string()); | ||||
| 
 | ||||
|         let mut comment = Note::default(); | ||||
|         comment.object_props = ObjectProperties { | ||||
|             id: Some(serde_json::to_value(self.ap_url.clone()).unwrap()), | ||||
|             summary: Some(serde_json::to_value(self.spoiler_text.clone()).unwrap()), | ||||
|             content: Some(serde_json::to_value(self.content.clone()).unwrap()), | ||||
|             in_reply_to: Some(serde_json::to_value(self.in_response_to_id.map_or_else(|| self.get_post(conn).ap_url, |id| { | ||||
|                 let comm = Comment::get(conn, id).unwrap(); | ||||
|                 comm.ap_url.clone().unwrap_or(comm.compute_id(conn)) | ||||
|             })).unwrap()), | ||||
|             published: Some(serde_json::to_value(self.creation_date).unwrap()), | ||||
|             attributed_to: Some(serde_json::to_value(self.get_author(conn).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() | ||||
|         }; | ||||
|         comment | ||||
|     } | ||||
| 
 | ||||
|     pub fn create_activity(&self, conn: &PgConnection) -> Create { | ||||
|         let mut act = Create::default(); | ||||
|         act.set_actor_link(self.get_author(conn).into_id()).unwrap(); | ||||
|         act.set_object_object(self.into_activity(conn)).unwrap(); | ||||
|         act.object_props.set_id_string(format!("{}/activity", self.ap_url.clone().unwrap())).unwrap(); | ||||
|         act | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Object for Comment { | ||||
|  | ||||
| @ -86,7 +86,7 @@ impl Instance { | ||||
| 
 | ||||
| impl Inbox for Instance { | ||||
|     fn received(&self, conn: &PgConnection, act: serde_json::Value) { | ||||
|         self.save(conn, act.clone()); | ||||
|         self.save(conn, act.clone()).unwrap(); | ||||
| 
 | ||||
|         // TODO: add to stream, or whatever needs to be done
 | ||||
|     } | ||||
|  | ||||
| @ -1,7 +1,9 @@ | ||||
| use activitystreams_types::activity; | ||||
| use chrono; | ||||
| use diesel::{self, PgConnection, QueryDsl, RunQueryDsl, ExpressionMethods}; | ||||
| use serde_json; | ||||
| 
 | ||||
| use activity_pub::IntoId; | ||||
| use activity_pub::actor::Actor; | ||||
| use activity_pub::object::Object; | ||||
| use models::posts::Post; | ||||
| @ -66,8 +68,25 @@ impl Like { | ||||
|             .into_iter().nth(0) | ||||
|     } | ||||
| 
 | ||||
|     pub fn delete(&self, conn: &PgConnection) { | ||||
|     pub fn delete(&self, conn: &PgConnection) -> activity::Undo { | ||||
|         diesel::delete(self).execute(conn).unwrap(); | ||||
| 
 | ||||
|         let mut act = activity::Undo::default(); | ||||
|         act.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).unwrap(); | ||||
|         act.set_object_object(self.into_activity(conn)).unwrap(); | ||||
|         act | ||||
|     } | ||||
| 
 | ||||
|     pub fn into_activity(&self, conn: &PgConnection) -> activity::Like { | ||||
|         let mut act = activity::Like::default(); | ||||
|         act.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).unwrap(); | ||||
|         act.set_object_link(Post::get(conn, self.post_id).unwrap().into_id()).unwrap(); | ||||
|         act.object_props.set_id_string(format!("{}/like/{}", | ||||
|             User::get(conn, self.user_id).unwrap().ap_url, | ||||
|             Post::get(conn, self.post_id).unwrap().ap_url | ||||
|         )).unwrap(); | ||||
| 
 | ||||
|         act | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,10 +1,14 @@ | ||||
| use activitystreams_types::{ | ||||
|     activity::Create, | ||||
|     object::{Article, properties::ObjectProperties} | ||||
| }; | ||||
| use chrono::NaiveDateTime; | ||||
| use diesel::{self, PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods, BelongingToDsl}; | ||||
| use diesel::dsl::any; | ||||
| use serde_json; | ||||
| 
 | ||||
| use BASE_URL; | ||||
| use activity_pub::{PUBLIC_VISIBILTY, ap_url}; | ||||
| use activity_pub::{PUBLIC_VISIBILTY, ap_url, Id, IntoId}; | ||||
| use activity_pub::actor::Actor; | ||||
| use activity_pub::object::Object; | ||||
| use models::blogs::Blog; | ||||
| @ -137,6 +141,40 @@ impl Post { | ||||
|         }); | ||||
|         to | ||||
|     } | ||||
| 
 | ||||
|     pub fn into_activity(&self, conn: &PgConnection) -> Article { | ||||
|         let mut to = self.get_receivers_urls(conn); | ||||
|         to.push(PUBLIC_VISIBILTY.to_string()); | ||||
| 
 | ||||
|         let mut article = Article::default(); | ||||
|         article.object_props = ObjectProperties { | ||||
|             name: Some(serde_json::to_value(self.title.clone()).unwrap()), | ||||
|             id: Some(serde_json::to_value(self.ap_url.clone()).unwrap()), | ||||
|             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()), | ||||
|             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()                
 | ||||
|         }; | ||||
|         article | ||||
|     } | ||||
| 
 | ||||
|     pub fn create_activity(&self, conn: &PgConnection) -> Create { | ||||
|         let mut act = Create::default(); | ||||
|         act.object_props.set_id_string(format!("{}/activity", self.ap_url)).unwrap(); | ||||
|         act.set_actor_link(Id::new(self.get_authors(conn)[0].clone().ap_url)).unwrap(); | ||||
|         act.set_object_object(self.into_activity(conn)).unwrap(); | ||||
|         act | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl IntoId for Post { | ||||
|     fn into_id(self) -> Id { | ||||
|         Id::new(self.ap_url.clone()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Object for Post { | ||||
|  | ||||
| @ -1,3 +1,10 @@ | ||||
| use activitystreams_traits::{Actor, Object, Link}; | ||||
| use activitystreams_types::{ | ||||
|     actor::Person, | ||||
|     collection::OrderedCollection, | ||||
|     object::properties::ObjectProperties, | ||||
|     CustomObject | ||||
| }; | ||||
| use bcrypt; | ||||
| use chrono::NaiveDateTime; | ||||
| use diesel::{self, QueryDsl, RunQueryDsl, ExpressionMethods, BelongingToDsl, PgConnection}; | ||||
| @ -12,15 +19,12 @@ use reqwest::mime::Mime; | ||||
| use rocket::request::{self, FromRequest, Request}; | ||||
| use rocket::outcome::IntoOutcome; | ||||
| use serde_json; | ||||
| use std::sync::Arc; | ||||
| use url::Url; | ||||
| 
 | ||||
| use BASE_URL; | ||||
| use activity_pub::ap_url; | ||||
| use activity_pub::activity::{Create, Activity}; | ||||
| use activity_pub::actor::{ActorType, Actor}; | ||||
| use activity_pub::inbox::Inbox; | ||||
| use activity_pub::outbox::Outbox; | ||||
| use activity_pub::{ap_url, ActivityStream, Id, IntoId}; | ||||
| use activity_pub::actor::{ActorType, Actor as APActor}; | ||||
| use activity_pub::inbox::{Inbox, WithInbox}; | ||||
| use activity_pub::sign::{Signer, gen_keypair}; | ||||
| use activity_pub::webfinger::{Webfinger, resolve}; | ||||
| use db_conn::DbConn; | ||||
| @ -34,7 +38,7 @@ use schema::users; | ||||
| 
 | ||||
| pub const AUTH_COOKIE: &'static str = "user_id"; | ||||
| 
 | ||||
| #[derive(Queryable, Identifiable, Serialize, Clone)] | ||||
| #[derive(Queryable, Identifiable, Serialize, Deserialize, Clone)] | ||||
| pub struct User { | ||||
|     pub id: i32, | ||||
|     pub username: String, | ||||
| @ -224,16 +228,23 @@ impl User { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn outbox(&self, conn: &PgConnection) -> Outbox { | ||||
|         Outbox::new(self.compute_outbox(conn), self.get_activities(conn)) | ||||
|     pub fn outbox(&self, conn: &PgConnection) -> ActivityStream<OrderedCollection> { | ||||
|         let acts = self.get_activities(conn); | ||||
|         let n_acts = acts.len(); | ||||
|         let mut coll = OrderedCollection::default(); | ||||
|         coll.collection_props.items = serde_json::to_value(acts).unwrap(); | ||||
|         coll.collection_props.set_total_items_u64(n_acts as u64).unwrap(); | ||||
|         ActivityStream::new(coll) | ||||
|     } | ||||
| 
 | ||||
|     fn get_activities(&self, conn: &PgConnection) -> Vec<Arc<Activity>> { | ||||
|     fn get_activities(&self, conn: &PgConnection) -> Vec<serde_json::Value> { | ||||
|         use schema::posts; | ||||
|         use schema::post_authors; | ||||
|         let posts_by_self = PostAuthor::belonging_to(self).select(post_authors::post_id); | ||||
|         let posts = posts::table.filter(posts::id.eq(any(posts_by_self))).load::<Post>(conn).unwrap(); | ||||
|         posts.into_iter().map(|p| Arc::new(Create::new(self, &p, conn)) as Arc<Activity>).collect::<Vec<Arc<Activity>>>() | ||||
|         posts.into_iter().map(|p| { | ||||
|             serde_json::to_value(p.create_activity(conn)).unwrap() | ||||
|         }).collect::<Vec<serde_json::Value>>() | ||||
|     } | ||||
| 
 | ||||
|     pub fn get_fqn(&self, conn: &PgConnection) -> String { | ||||
| @ -270,6 +281,42 @@ impl User { | ||||
|     pub fn get_keypair(&self) -> PKey<Private> { | ||||
|         PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.clone().unwrap().as_ref()).unwrap()).unwrap() | ||||
|     } | ||||
| 
 | ||||
|     pub fn into_activity(&self, conn: &PgConnection) -> CustomObject<ApProps, Person> { | ||||
|         let mut actor = Person::default(); | ||||
|         actor.object_props = ObjectProperties { | ||||
|             id: Some(serde_json::to_value(self.compute_id(conn)).unwrap()), | ||||
|             name: Some(serde_json::to_value(self.get_display_name()).unwrap()), | ||||
|             summary: Some(serde_json::to_value(self.get_summary()).unwrap()), | ||||
|             url: Some(serde_json::to_value(self.compute_id(conn)).unwrap()), | ||||
|             ..ObjectProperties::default() | ||||
|         }; | ||||
| 
 | ||||
|         CustomObject::new(actor, ApProps { | ||||
|             inbox: Some(serde_json::to_value(self.compute_inbox(conn)).unwrap()), | ||||
|             outbox: Some(serde_json::to_value(self.compute_outbox(conn)).unwrap()), | ||||
|             preferred_username: Some(serde_json::to_value(self.get_actor_id()).unwrap()), | ||||
|             endpoints: Some(json!({ | ||||
|                 "sharedInbox": ap_url(format!("{}/inbox", BASE_URL.as_str())) | ||||
|             })) | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Serialize, Deserialize, Default, Properties)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| pub struct ApProps { | ||||
|     #[activitystreams(ab(Object, Link))] | ||||
|     inbox: Option<serde_json::Value>, | ||||
| 
 | ||||
|     #[activitystreams(ab(Object, Link))] | ||||
|     outbox: Option<serde_json::Value>, | ||||
| 
 | ||||
|     #[activitystreams(ab(Object, Link))] | ||||
|     preferred_username: Option<serde_json::Value>, | ||||
| 
 | ||||
|     #[activitystreams(ab(Object))] | ||||
|     endpoints: Option<serde_json::Value> | ||||
| } | ||||
| 
 | ||||
| impl<'a, 'r> FromRequest<'a, 'r> for User { | ||||
| @ -285,7 +332,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for User { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Actor for User { | ||||
| impl APActor for User { | ||||
|     fn get_box_prefix() -> &'static str { | ||||
|         "@" | ||||
|     } | ||||
| @ -350,9 +397,28 @@ impl Actor for User { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl IntoId for User { | ||||
|     fn into_id(self) -> Id { | ||||
|         Id::new(self.ap_url.clone()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Object for User {} | ||||
| impl Actor for User {} | ||||
| 
 | ||||
| impl WithInbox for User { | ||||
|     fn get_inbox_url(&self) -> String { | ||||
|         self.inbox_url.clone() | ||||
|     } | ||||
| 
 | ||||
|     fn get_shared_inbox_url(&self) -> Option<String> { | ||||
|        self.shared_inbox_url.clone() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Inbox for User { | ||||
|     fn received(&self, conn: &PgConnection, act: serde_json::Value) { | ||||
|         self.save(conn, act.clone()); | ||||
|         self.save(conn, act.clone()).unwrap(); | ||||
| 
 | ||||
|         // Notifications
 | ||||
|         match act["type"].as_str().unwrap() { | ||||
|  | ||||
| @ -1,11 +1,11 @@ | ||||
| use activitystreams_types::collection::OrderedCollection; | ||||
| use rocket::request::Form; | ||||
| use rocket::response::Redirect; | ||||
| use rocket_contrib::Template; | ||||
| use serde_json; | ||||
| 
 | ||||
| use activity_pub::ActivityPub; | ||||
| use activity_pub::{ActivityStream, ActivityPub}; | ||||
| use activity_pub::actor::Actor; | ||||
| use activity_pub::outbox::Outbox; | ||||
| use db_conn::DbConn; | ||||
| use models::blog_authors::*; | ||||
| use models::blogs::*; | ||||
| @ -78,7 +78,7 @@ fn create(conn: DbConn, data: Form<NewBlogForm>, user: User) -> Redirect { | ||||
| } | ||||
| 
 | ||||
| #[get("/~/<name>/outbox")] | ||||
| fn outbox(name: String, conn: DbConn) -> Outbox { | ||||
| fn outbox(name: String, conn: DbConn) -> ActivityStream<OrderedCollection> { | ||||
|     let blog = Blog::find_local(&*conn, name).unwrap(); | ||||
|     blog.outbox(&*conn) | ||||
| } | ||||
|  | ||||
| @ -2,8 +2,7 @@ use rocket::request::Form; | ||||
| use rocket::response::Redirect; | ||||
| use rocket_contrib::Template; | ||||
| 
 | ||||
| use activity_pub::activity::Create; | ||||
| use activity_pub::outbox::broadcast; | ||||
| use activity_pub::broadcast; | ||||
| use db_conn::DbConn; | ||||
| use models::comments::*; | ||||
| use models::posts::Post; | ||||
| @ -41,8 +40,8 @@ fn create(blog: String, slug: String, query: CommentQuery, data: Form<NewComment | ||||
|         sensitive: false, | ||||
|         spoiler_text: "".to_string() | ||||
|     }); | ||||
|     let act = Create::new(&user, &comment, &*conn); | ||||
|     broadcast(&*conn, &user, act, user.get_followers(&*conn)); | ||||
| 
 | ||||
|     broadcast(&*conn, &user, comment.create_activity(&*conn), user.get_followers(&*conn)); | ||||
| 
 | ||||
|     Redirect::to(format!("/~/{}/{}/#comment-{}", blog, slug, comment.id).as_ref()) | ||||
| } | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| use rocket::response::Redirect; | ||||
| 
 | ||||
| use activity_pub::activity::{Like, Undo}; | ||||
| use activity_pub::outbox::broadcast; | ||||
| use activity_pub::broadcast; | ||||
| use db_conn::DbConn; | ||||
| use models::likes; | ||||
| use models::posts::Post; | ||||
| @ -18,12 +17,12 @@ fn create(blog: String, slug: String, user: User, conn: DbConn) -> Redirect { | ||||
|                 ap_url: "".to_string() | ||||
|         }); | ||||
|         like.update_ap_url(&*conn); | ||||
|         let act = Like::new(&user, &post, &*conn); | ||||
|         broadcast(&*conn, &user, act, user.get_followers(&*conn)); | ||||
| 
 | ||||
|         broadcast(&*conn, &user, like.into_activity(&*conn), user.get_followers(&*conn)); | ||||
|     } else { | ||||
|         let like = likes::Like::find_by_user_on_post(&*conn, &user, &post).unwrap(); | ||||
|         like.delete(&*conn); | ||||
|         broadcast(&*conn, &user, Undo::new(&user, &like, &*conn), user.get_followers(&*conn)); | ||||
|         let delete_act = like.delete(&*conn); | ||||
|         broadcast(&*conn, &user, delete_act, user.get_followers(&*conn)); | ||||
|     } | ||||
| 
 | ||||
|     Redirect::to(format!("/~/{}/{}/", blog, slug).as_ref()) | ||||
|  | ||||
| @ -4,10 +4,8 @@ use rocket::response::Redirect; | ||||
| use rocket_contrib::Template; | ||||
| use serde_json; | ||||
| 
 | ||||
| use activity_pub::{context, activity_pub, ActivityPub}; | ||||
| use activity_pub::activity::Create; | ||||
| use activity_pub::{broadcast, context, activity_pub, ActivityPub}; | ||||
| use activity_pub::object::Object; | ||||
| use activity_pub::outbox::broadcast; | ||||
| use db_conn::DbConn; | ||||
| use models::blogs::*; | ||||
| use models::comments::Comment; | ||||
| @ -39,7 +37,7 @@ fn details(blog: String, slug: String, conn: DbConn, user: Option<User>) -> Temp | ||||
| 
 | ||||
| #[get("/~/<_blog>/<slug>", rank = 3, format = "application/activity+json")] | ||||
| fn activity_details(_blog: String, slug: String, conn: DbConn) -> ActivityPub { | ||||
|     // TODO: posts in different blogs may have the same slug
 | ||||
|     // FIXME: posts in different blogs may have the same slug
 | ||||
|     let post = Post::find_by_slug(&*conn, slug).unwrap(); | ||||
| 
 | ||||
|     let mut act = post.serialize(&*conn); | ||||
| @ -86,7 +84,7 @@ fn create(blog_name: String, data: Form<NewPostForm>, user: User, conn: DbConn) | ||||
|         author_id: user.id | ||||
|     }); | ||||
| 
 | ||||
|     let act = Create::new(&user, &post, &*conn); | ||||
|     let act = post.create_activity(&*conn); | ||||
|     broadcast(&*conn, &user, act, user.get_followers(&*conn)); | ||||
| 
 | ||||
|     Redirect::to(format!("/~/{}/{}", blog_name, slug).as_str()) | ||||
|  | ||||
| @ -1,14 +1,17 @@ | ||||
| use activitystreams_types::{ | ||||
|     activity::Follow, | ||||
|     collection::OrderedCollection | ||||
| }; | ||||
| use rocket::request::Form; | ||||
| use rocket::response::Redirect; | ||||
| use rocket_contrib::Template; | ||||
| use serde_json; | ||||
| 
 | ||||
| use activity_pub::{activity, activity_pub, ActivityPub, context}; | ||||
| use activity_pub::{activity_pub, ActivityPub, ActivityStream, context, broadcast, Id, IntoId}; | ||||
| use activity_pub::actor::Actor; | ||||
| use activity_pub::inbox::Inbox; | ||||
| use activity_pub::outbox::{broadcast, Outbox}; | ||||
| use db_conn::DbConn; | ||||
| use models::follows::*; | ||||
| use models::follows; | ||||
| use models::instance::Instance; | ||||
| use models::posts::Post; | ||||
| use models::users::*; | ||||
| @ -49,11 +52,15 @@ fn details(name: String, conn: DbConn, account: Option<User>) -> Template { | ||||
| #[get("/@/<name>/follow")] | ||||
| fn follow(name: String, conn: DbConn, user: User) -> Redirect { | ||||
|     let target = User::find_by_fqn(&*conn, name.clone()).unwrap(); | ||||
|     Follow::insert(&*conn, NewFollow { | ||||
|     follows::Follow::insert(&*conn, follows::NewFollow { | ||||
|         follower_id: user.id, | ||||
|         following_id: target.id | ||||
|     }); | ||||
|     broadcast(&*conn, &user, activity::Follow::new(&user, &target, &*conn), vec![target]); | ||||
|     let mut act = Follow::default(); | ||||
|     act.set_actor_link::<Id>(user.clone().into_id()).unwrap(); | ||||
|     act.set_object_object(user.into_activity(&*conn)).unwrap(); | ||||
|     act.object_props.set_id_string(format!("{}/follow/{}", user.ap_url, target.ap_url)).unwrap(); | ||||
|     broadcast(&*conn, &user, act, vec![target]); | ||||
|     Redirect::to(format!("/@/{}", name).as_ref()) | ||||
| } | ||||
| 
 | ||||
| @ -152,7 +159,7 @@ fn create(conn: DbConn, data: Form<NewUserForm>) -> Result<Redirect, String> { | ||||
| } | ||||
| 
 | ||||
| #[get("/@/<name>/outbox")] | ||||
| fn outbox(name: String, conn: DbConn) -> Outbox { | ||||
| fn outbox(name: String, conn: DbConn) -> ActivityStream<OrderedCollection> { | ||||
|     let user = User::find_local(&*conn, name).unwrap(); | ||||
|     user.outbox(&*conn) | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user