From c779b1c58ade9447cb5a785f5d1702f1a22921e6 Mon Sep 17 00:00:00 2001 From: Bat Date: Wed, 16 May 2018 19:20:44 +0100 Subject: [PATCH 1/4] WIP: use the activitystreams crate --- Cargo.lock | 115 ++++++++++++++++++++++ Cargo.toml | 5 + src/activity_pub/inbox.rs | 190 ++++++++++++++++++++++--------------- src/activity_pub/mod.rs | 71 +++++++++++++- src/activity_pub/outbox.rs | 58 +---------- src/main.rs | 6 ++ src/models/blogs.rs | 14 +-- src/models/instance.rs | 2 +- src/models/users.rs | 25 +++-- src/routes/blogs.rs | 6 +- src/routes/comments.rs | 8 +- src/routes/likes.rs | 12 +-- src/routes/posts.rs | 13 ++- src/routes/user.rs | 17 ++-- 14 files changed, 365 insertions(+), 177 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56ee296c..eab553ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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,17 @@ 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-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 +918,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 +1255,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 +1285,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 +1572,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 +1682,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 +1725,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 +1789,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 +1827,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 +1860,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" diff --git a/Cargo.toml b/Cargo.toml index 71a3234d..4f5ee8f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,10 +3,15 @@ authors = ["Bat' "] name = "plume" version = "0.1.0" [dependencies] +activitystreams = "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 = "*" diff --git a/src/activity_pub/inbox.rs b/src/activity_pub/inbox.rs index 828242c2..25a9200d 100644 --- a/src/activity_pub/inbox.rs +++ b/src/activity_pub/inbox.rs @@ -1,100 +1,138 @@ +use activitystreams_types::{ + actor::Person, + activity::{Create, Follow, Like, Undo}, + object::{Article, Note} +}; +use activitystreams::activity::Activity; 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; +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::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::()?.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::()?.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( + fn accept_follow( &self, - conn: &PgConnection, - from: &A, - target: &B, - follow: &T, - from_id: i32, - target_id: i32 + _conn: &PgConnection, + _from: &A, + _target: &B, + _follow: &T, + _from_id: i32, + _target_id: i32 ) { - Follow::insert(conn, NewFollow { - follower_id: from_id, - following_id: target_id - }); + // TODO + //Follow::insert(conn, 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 accept = activity::Accept::new(target, follow, conn); + //broadcast(conn, from, accept, vec![target.clone()]); } } diff --git a/src/activity_pub/mod.rs b/src/activity_pub/mod.rs index 2d26da35..9ac4c073 100644 --- a/src/activity_pub/mod.rs +++ b/src/activity_pub/mod.rs @@ -1,13 +1,20 @@ -use rocket::http::ContentType; -use rocket::response::Content; +use activitystreams_traits::{Activity, 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,59 @@ 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); + +impl ActivityStream { + pub fn new(t: T) -> ActivityStream { + ActivityStream(t) + } +} + +impl<'r, O: Object> Responder<'r> for ActivityStream { + fn respond_to(self, request: &Request) -> Result, Status> { + serde_json::to_string(&self.0).respond_to(request) + } +} + +pub fn broadcast(conn: &PgConnection, sender: &S, act: A, to: Vec) { + let boxes = to.into_iter() + .map(|u| u.get_shared_inbox_url().unwrap_or(u.get_inbox_url())) + .collect::>() + .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(Serialize, Deserialize)] +pub struct Id { + #[serde(flatten)] + id: String +} + +impl Id { + pub fn new>(id: T) -> Id { + Id { + id: id.into() + } + } +} + +impl Link for Id {} diff --git a/src/activity_pub/outbox.rs b/src/activity_pub/outbox.rs index 8b310625..30d732cc 100644 --- a/src/activity_pub/outbox.rs +++ b/src/activity_pub/outbox.rs @@ -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> -} - -impl Outbox { - pub fn new(id: String, items: Vec>) -> Outbox { - Outbox { - id: id, - items: items - } - } - - fn serialize(&self) -> ActivityPub { - let items = self.items.clone().into_iter().map(|i| i.serialize()).collect::>(); - 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, Status> { - self.serialize().respond_to(request) - } -} - -pub fn broadcast(conn: &PgConnection, sender: &S, act: A, to: Vec) { - let boxes = to.into_iter() - .map(|u| u.get_shared_inbox_url().unwrap_or(u.get_inbox_url())) - .collect::>() - .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) - } - } -} diff --git a/src/main.rs b/src/main.rs index c385664e..c0b18605 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,16 @@ #![feature(plugin, custom_derive, iterator_find_map)] #![plugin(rocket_codegen)] +extern crate activitystreams; +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] diff --git a/src/models/blogs.rs b/src/models/blogs.rs index b258022e..6a931253 100644 --- a/src/models/blogs.rs +++ b/src/models/blogs.rs @@ -1,3 +1,4 @@ +use activitystreams_types::collection::OrderedCollection; use reqwest::Client; use reqwest::header::{Accept, qitem}; use reqwest::mime::Mime; @@ -9,11 +10,9 @@ 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::ActivityStream; use activity_pub::actor::{Actor, ActorType}; -use activity_pub::outbox::Outbox; use activity_pub::sign; use activity_pub::webfinger::*; use models::instance::Instance; @@ -158,11 +157,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 { + 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> { + fn get_activities(&self, _conn: &PgConnection) -> Vec { vec![] } diff --git a/src/models/instance.rs b/src/models/instance.rs index 4025788f..c3967092 100644 --- a/src/models/instance.rs +++ b/src/models/instance.rs @@ -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 } diff --git a/src/models/users.rs b/src/models/users.rs index 511cbc41..1a50c713 100644 --- a/src/models/users.rs +++ b/src/models/users.rs @@ -1,3 +1,7 @@ +use activitystreams_types::{ + activity::Create, + collection::OrderedCollection +}; use bcrypt; use chrono::NaiveDateTime; use diesel::{self, QueryDsl, RunQueryDsl, ExpressionMethods, BelongingToDsl, PgConnection}; @@ -12,15 +16,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::{ap_url, ActivityStream}; use activity_pub::actor::{ActorType, Actor}; use activity_pub::inbox::Inbox; -use activity_pub::outbox::Outbox; use activity_pub::sign::{Signer, gen_keypair}; use activity_pub::webfinger::{Webfinger, resolve}; use db_conn::DbConn; @@ -224,16 +225,22 @@ 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 { + let mut coll = OrderedCollection::default(); // TODO + coll.collection_props.items = serde_json::to_value(self.get_activities(conn)).unwrap(); + ActivityStream::new(coll) } - fn get_activities(&self, conn: &PgConnection) -> Vec> { + fn get_activities(&self, conn: &PgConnection) -> Vec { 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::(conn).unwrap(); - posts.into_iter().map(|p| Arc::new(Create::new(self, &p, conn)) as Arc).collect::>>() + posts.into_iter().map(|_| { + // TODO Create::new(self, &p, conn) + // TODO: add a method to convert Post -> Create + serde_json::to_value(Create::default()).unwrap() + }).collect::>() } pub fn get_fqn(&self, conn: &PgConnection) -> String { @@ -352,7 +359,7 @@ impl Actor for User { 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() { diff --git a/src/routes/blogs.rs b/src/routes/blogs.rs index 16f8912e..db957a01 100644 --- a/src/routes/blogs.rs +++ b/src/routes/blogs.rs @@ -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, user: User) -> Redirect { } #[get("/~//outbox")] -fn outbox(name: String, conn: DbConn) -> Outbox { +fn outbox(name: String, conn: DbConn) -> ActivityStream { let blog = Blog::find_local(&*conn, name).unwrap(); blog.outbox(&*conn) } diff --git a/src/routes/comments.rs b/src/routes/comments.rs index 8a7a8d15..5dbc586a 100644 --- a/src/routes/comments.rs +++ b/src/routes/comments.rs @@ -1,9 +1,9 @@ +use activitystreams_types::activity::Create; 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 +41,8 @@ fn create(blog: String, slug: String, query: CommentQuery, data: Form 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)); + // TODO: let act = Like::new(&user, &post, &*conn); + // TODO: broadcast(&*conn, &user, act, 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)); + // TODO: like.delete(&*conn); + // TODO: broadcast(&*conn, &user, Undo::new(&user, &like, &*conn), user.get_followers(&*conn)); } Redirect::to(format!("/~/{}/{}/", blog, slug).as_ref()) diff --git a/src/routes/posts.rs b/src/routes/posts.rs index ce63d684..44b5e9d9 100644 --- a/src/routes/posts.rs +++ b/src/routes/posts.rs @@ -1,13 +1,12 @@ +use activitystreams_types::activity::Create; use heck::KebabCase; use rocket::request::Form; 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, Id}; use activity_pub::object::Object; -use activity_pub::outbox::broadcast; use db_conn::DbConn; use models::blogs::*; use models::comments::Comment; @@ -86,8 +85,12 @@ fn create(blog_name: String, data: Form, user: User, conn: DbConn) author_id: user.id }); - let act = Create::new(&user, &post, &*conn); - broadcast(&*conn, &user, act, user.get_followers(&*conn)); + // TODO: use Post -> Create conversion + // let act = Create::default(); + // act.object_props.set_id_string(format!("{}/activity", post.compute_id(&*conn))); + // act.set_actor_link(Id::new(user.ap_url)); + // act.set_object_object(); + // broadcast(&*conn, &user, act, user.get_followers(&*conn)); Redirect::to(format!("/~/{}/{}", blog_name, slug).as_str()) } diff --git a/src/routes/user.rs b/src/routes/user.rs index 8e3a93ce..cb562b5b 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -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}; 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,13 @@ fn details(name: String, conn: DbConn, account: Option) -> Template { #[get("/@//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 act = Follow::default(); + // TODO + broadcast(&*conn, &user, act, vec![target]); Redirect::to(format!("/@/{}", name).as_ref()) } @@ -145,7 +150,7 @@ fn create(conn: DbConn, data: Form) -> Redirect { } #[get("/@//outbox")] -fn outbox(name: String, conn: DbConn) -> Outbox { +fn outbox(name: String, conn: DbConn) -> ActivityStream { let user = User::find_local(&*conn, name).unwrap(); user.outbox(&*conn) } From b6bd405f5fbc8dd12c165b99f98356de76a79605 Mon Sep 17 00:00:00 2001 From: Bat Date: Fri, 18 May 2018 09:04:06 +0100 Subject: [PATCH 2/4] Ignore RLS files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 70e3cae7..4cbc3c26 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target **/*.rs.bk +rls From 0e24ccbf29c1a9368ad0a0899daaa54707a2c031 Mon Sep 17 00:00:00 2001 From: Bat Date: Fri, 18 May 2018 09:04:40 +0100 Subject: [PATCH 3/4] Use activitystreams in the Inbox trait --- src/activity_pub/inbox.rs | 46 +++++++++++++++++++++++---------------- src/activity_pub/mod.rs | 8 +++++-- src/models/blogs.rs | 29 ++++++++++++++++++++---- src/models/users.rs | 30 ++++++++++++++++++++----- 4 files changed, 83 insertions(+), 30 deletions(-) diff --git a/src/activity_pub/inbox.rs b/src/activity_pub/inbox.rs index 25a9200d..0a4d6c3f 100644 --- a/src/activity_pub/inbox.rs +++ b/src/activity_pub/inbox.rs @@ -1,18 +1,19 @@ +use activitystreams_traits::Actor; use activitystreams_types::{ actor::Person, - activity::{Create, Follow, Like, Undo}, + activity::{Accept, Create, Follow, Like, Undo}, object::{Article, Note} }; -use activitystreams::activity::Activity; use diesel::PgConnection; use failure::Error; use serde_json; -// use activity_pub::broadcast; +use activity_pub::{broadcast, 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::posts::*; use models::users::User; @@ -63,10 +64,10 @@ pub trait Inbox { 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), + 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) + self.accept_follow(conn, &from, &blog, follow, from.id, blog.id) } }; Ok(()) @@ -117,22 +118,29 @@ pub trait Inbox { } } - fn accept_follow( + fn accept_follow( &self, - _conn: &PgConnection, - _from: &A, - _target: &B, - _follow: &T, - _from_id: i32, - _target_id: i32 + conn: &PgConnection, + from: &A, + target: &B, + follow: Follow, + from_id: i32, + target_id: i32 ) { - // TODO - //Follow::insert(conn, NewFollow { - // follower_id: from_id, - // following_id: target_id - //}); + 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();//new(target, follow, conn); + accept.set_actor_link(from.into()).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; +} diff --git a/src/activity_pub/mod.rs b/src/activity_pub/mod.rs index 9ac4c073..864a5f5a 100644 --- a/src/activity_pub/mod.rs +++ b/src/activity_pub/mod.rs @@ -1,4 +1,4 @@ -use activitystreams_traits::{Activity, Object, Link}; +use activitystreams_traits::{Activity, Actor, Object, Link}; use array_tool::vec::Uniq; use diesel::PgConnection; use reqwest::Client; @@ -76,7 +76,7 @@ impl<'r, O: Object> Responder<'r> for ActivityStream { } } -pub fn broadcast(conn: &PgConnection, sender: &S, act: A, to: Vec) { +pub fn broadcast(conn: &PgConnection, sender: &S, act: A, to: Vec) { let boxes = to.into_iter() .map(|u| u.get_shared_inbox_url().unwrap_or(u.get_inbox_url())) .collect::>() @@ -116,4 +116,8 @@ impl Id { } } +pub trait IntoId { + fn into(&self) -> Id; +} + impl Link for Id {} diff --git a/src/models/blogs.rs b/src/models/blogs.rs index 6a931253..66851f75 100644 --- a/src/models/blogs.rs +++ b/src/models/blogs.rs @@ -1,3 +1,4 @@ +use activitystreams_traits::{Actor, Object}; use activitystreams_types::collection::OrderedCollection; use reqwest::Client; use reqwest::header::{Accept, qitem}; @@ -11,15 +12,16 @@ use openssl::pkey::{PKey, Private}; use openssl::rsa::Rsa; use openssl::sign::Signer; -use activity_pub::ActivityStream; -use activity_pub::actor::{Actor, ActorType}; +use activity_pub::{ActivityStream, Id}; +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, @@ -173,7 +175,26 @@ impl Blog { } } -impl Actor for Blog { +impl Into for Blog { + fn into(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 { + None + } +} + +impl APActor for Blog { fn get_box_prefix() -> &'static str { "~" } diff --git a/src/models/users.rs b/src/models/users.rs index 1a50c713..e39dcb14 100644 --- a/src/models/users.rs +++ b/src/models/users.rs @@ -1,3 +1,4 @@ +use activitystreams_traits::{Actor, Object}; use activitystreams_types::{ activity::Create, collection::OrderedCollection @@ -19,9 +20,9 @@ use serde_json; use url::Url; use BASE_URL; -use activity_pub::{ap_url, ActivityStream}; -use activity_pub::actor::{ActorType, Actor}; -use activity_pub::inbox::Inbox; +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; @@ -35,7 +36,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, @@ -292,7 +293,7 @@ impl<'a, 'r> FromRequest<'a, 'r> for User { } } -impl Actor for User { +impl APActor for User { fn get_box_prefix() -> &'static str { "@" } @@ -357,6 +358,25 @@ impl Actor for User { } } +impl IntoId for User { + fn into(&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 { + self.shared_inbox_url.clone() + } +} + impl Inbox for User { fn received(&self, conn: &PgConnection, act: serde_json::Value) { self.save(conn, act.clone()).unwrap(); From 4a86af6fc107bae886742b23a36daff62be90c46 Mon Sep 17 00:00:00 2001 From: Bat Date: Fri, 18 May 2018 23:04:30 +0100 Subject: [PATCH 4/4] Resolve activitystream TODOs --- Cargo.lock | 1 + Cargo.toml | 1 + src/activity_pub/inbox.rs | 8 +++--- src/activity_pub/mod.rs | 4 +-- src/main.rs | 2 ++ src/models/blogs.rs | 6 ++-- src/models/comments.rs | 37 +++++++++++++++++++++++- src/models/likes.rs | 21 +++++++++++++- src/models/posts.rs | 40 +++++++++++++++++++++++++- src/models/users.rs | 59 ++++++++++++++++++++++++++++++++------- src/routes/comments.rs | 5 ++-- src/routes/likes.rs | 9 +++--- src/routes/posts.rs | 13 +++------ src/routes/user.rs | 8 ++++-- 14 files changed, 172 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eab553ad..3c603262 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -869,6 +869,7 @@ 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)", diff --git a/Cargo.toml b/Cargo.toml index 4f5ee8f7..d5b416c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ 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" diff --git a/src/activity_pub/inbox.rs b/src/activity_pub/inbox.rs index 0a4d6c3f..55f22dae 100644 --- a/src/activity_pub/inbox.rs +++ b/src/activity_pub/inbox.rs @@ -8,7 +8,7 @@ use diesel::PgConnection; use failure::Error; use serde_json; -use activity_pub::{broadcast, IntoId}; +use activity_pub::{broadcast, Id, IntoId}; use activity_pub::actor::Actor as APActor; use activity_pub::sign::*; use models::blogs::Blog; @@ -118,7 +118,7 @@ pub trait Inbox { } } - fn accept_follow( + fn accept_follow( &self, conn: &PgConnection, from: &A, @@ -132,8 +132,8 @@ pub trait Inbox { following_id: target_id }); - let mut accept = Accept::default();//new(target, follow, conn); - accept.set_actor_link(from.into()).unwrap(); + let mut accept = Accept::default(); + accept.set_actor_link::(from.clone().into_id()).unwrap(); accept.set_object_object(follow).unwrap(); broadcast(conn, &*from, accept, vec![target.clone()]); } diff --git a/src/activity_pub/mod.rs b/src/activity_pub/mod.rs index 864a5f5a..f129041e 100644 --- a/src/activity_pub/mod.rs +++ b/src/activity_pub/mod.rs @@ -102,7 +102,7 @@ pub fn broadcast Id; + fn into_id(self) -> Id; } impl Link for Id {} diff --git a/src/main.rs b/src/main.rs index c0b18605..4d17a369 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,8 @@ #![plugin(rocket_codegen)] extern crate activitystreams; +#[macro_use] +extern crate activitystreams_derive; extern crate activitystreams_traits; extern crate activitystreams_types; extern crate array_tool; diff --git a/src/models/blogs.rs b/src/models/blogs.rs index 66851f75..f246dda8 100644 --- a/src/models/blogs.rs +++ b/src/models/blogs.rs @@ -12,7 +12,7 @@ use openssl::pkey::{PKey, Private}; use openssl::rsa::Rsa; use openssl::sign::Signer; -use activity_pub::{ActivityStream, Id}; +use activity_pub::{ActivityStream, Id, IntoId}; use activity_pub::actor::{Actor as APActor, ActorType}; use activity_pub::inbox::WithInbox; use activity_pub::sign; @@ -175,8 +175,8 @@ impl Blog { } } -impl Into for Blog { - fn into(self) -> Id { +impl IntoId for Blog { + fn into_id(self) -> Id { Id::new(self.ap_url) } } diff --git a/src/models/comments.rs b/src/models/comments.rs index 43f38e1c..14a73bbf 100644 --- a/src/models/comments.rs +++ b/src/models/comments.rs @@ -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::>(); + 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::::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 { diff --git a/src/models/likes.rs b/src/models/likes.rs index 1cf3bef5..31de4f0d 100644 --- a/src/models/likes.rs +++ b/src/models/likes.rs @@ -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 } } diff --git a/src/models/posts.rs b/src/models/posts.rs index 87f8eeac..743f7ea6 100644 --- a/src/models/posts.rs +++ b/src/models/posts.rs @@ -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::>()).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::::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::::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 { diff --git a/src/models/users.rs b/src/models/users.rs index e39dcb14..b4614454 100644 --- a/src/models/users.rs +++ b/src/models/users.rs @@ -1,7 +1,9 @@ -use activitystreams_traits::{Actor, Object}; +use activitystreams_traits::{Actor, Object, Link}; use activitystreams_types::{ - activity::Create, - collection::OrderedCollection + actor::Person, + collection::OrderedCollection, + object::properties::ObjectProperties, + CustomObject }; use bcrypt; use chrono::NaiveDateTime; @@ -227,8 +229,11 @@ impl User { } pub fn outbox(&self, conn: &PgConnection) -> ActivityStream { - let mut coll = OrderedCollection::default(); // TODO - coll.collection_props.items = serde_json::to_value(self.get_activities(conn)).unwrap(); + 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) } @@ -237,10 +242,8 @@ impl User { 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::(conn).unwrap(); - posts.into_iter().map(|_| { - // TODO Create::new(self, &p, conn) - // TODO: add a method to convert Post -> Create - serde_json::to_value(Create::default()).unwrap() + posts.into_iter().map(|p| { + serde_json::to_value(p.create_activity(conn)).unwrap() }).collect::>() } @@ -278,6 +281,42 @@ impl User { pub fn get_keypair(&self) -> PKey { 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 { + 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, + + #[activitystreams(ab(Object, Link))] + outbox: Option, + + #[activitystreams(ab(Object, Link))] + preferred_username: Option, + + #[activitystreams(ab(Object))] + endpoints: Option } impl<'a, 'r> FromRequest<'a, 'r> for User { @@ -359,7 +398,7 @@ impl APActor for User { } impl IntoId for User { - fn into(&self) -> Id { + fn into_id(self) -> Id { Id::new(self.ap_url.clone()) } } diff --git a/src/routes/comments.rs b/src/routes/comments.rs index 5dbc586a..4561b731 100644 --- a/src/routes/comments.rs +++ b/src/routes/comments.rs @@ -1,4 +1,3 @@ -use activitystreams_types::activity::Create; use rocket::request::Form; use rocket::response::Redirect; use rocket_contrib::Template; @@ -41,8 +40,8 @@ fn create(blog: String, slug: String, query: CommentQuery, data: Form Redirect { ap_url: "".to_string() }); like.update_ap_url(&*conn); - // TODO: let act = Like::new(&user, &post, &*conn); - // TODO: 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(); - // TODO: like.delete(&*conn); - // TODO: 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()) diff --git a/src/routes/posts.rs b/src/routes/posts.rs index 44b5e9d9..3732dcbc 100644 --- a/src/routes/posts.rs +++ b/src/routes/posts.rs @@ -1,11 +1,10 @@ -use activitystreams_types::activity::Create; use heck::KebabCase; use rocket::request::Form; use rocket::response::Redirect; use rocket_contrib::Template; use serde_json; -use activity_pub::{broadcast, context, activity_pub, ActivityPub, Id}; +use activity_pub::{broadcast, context, activity_pub, ActivityPub}; use activity_pub::object::Object; use db_conn::DbConn; use models::blogs::*; @@ -38,7 +37,7 @@ fn details(blog: String, slug: String, conn: DbConn, user: Option) -> Temp #[get("/~/<_blog>/", 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); @@ -85,12 +84,8 @@ fn create(blog_name: String, data: Form, user: User, conn: DbConn) author_id: user.id }); - // TODO: use Post -> Create conversion - // let act = Create::default(); - // act.object_props.set_id_string(format!("{}/activity", post.compute_id(&*conn))); - // act.set_actor_link(Id::new(user.ap_url)); - // act.set_object_object(); - // broadcast(&*conn, &user, act, user.get_followers(&*conn)); + let act = post.create_activity(&*conn); + broadcast(&*conn, &user, act, user.get_followers(&*conn)); Redirect::to(format!("/~/{}/{}", blog_name, slug).as_str()) } diff --git a/src/routes/user.rs b/src/routes/user.rs index cb562b5b..0cb8be9a 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -7,7 +7,7 @@ use rocket::response::Redirect; use rocket_contrib::Template; use serde_json; -use activity_pub::{activity_pub, ActivityPub, ActivityStream, context, broadcast}; +use activity_pub::{activity_pub, ActivityPub, ActivityStream, context, broadcast, Id, IntoId}; use activity_pub::actor::Actor; use activity_pub::inbox::Inbox; use db_conn::DbConn; @@ -56,8 +56,10 @@ fn follow(name: String, conn: DbConn, user: User) -> Redirect { follower_id: user.id, following_id: target.id }); - let act = Follow::default(); - // TODO + let mut act = Follow::default(); + act.set_actor_link::(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()) }