diff --git a/src/activity_pub/activity.rs b/src/activity_pub/activity.rs index 6ffb0540..6e0b47c0 100644 --- a/src/activity_pub/activity.rs +++ b/src/activity_pub/activity.rs @@ -1,69 +1,150 @@ use chrono; use diesel::PgConnection; use serde_json; +use std::str::FromStr; use activity_pub::actor::Actor; use activity_pub::object::Object; -#[derive(Clone)] -pub enum Activity { - Create(Payload), - Accept(Payload), - Follow(Payload) +pub trait Activity: ActivityClone { + fn get_id(&self) -> String; + + fn serialize(&self) -> serde_json::Value; + + // fn deserialize(serde_json::Value) -> Self; } -impl Activity { - pub fn serialize(&self) -> serde_json::Value { - json!({ - "type": self.get_type(), - "actor": self.payload().by, - "object": self.payload().object, - "published": self.payload().date.to_rfc3339() - }) - } - pub fn get_type(&self) -> String { - match self { - Activity::Accept(_) => String::from("Accept"), - Activity::Create(_) => String::from("Create"), - Activity::Follow(_) => String::from("Follow") - } - } +trait ActivityClone { + fn clone_box(&self) -> Box; +} - pub fn payload(&self) -> Payload { - match self { - Activity::Accept(p) => p.clone(), - Activity::Create(p) => p.clone(), - Activity::Follow(p) => p.clone() - } +impl ActivityClone for T +where + T: 'static + Activity + Clone, +{ + fn clone_box(&self) -> Box { + Box::new(self.clone()) } +} - pub fn create(by: &U, obj: T, conn: &PgConnection) -> Activity { - Activity::Create(Payload::new(serde_json::Value::String(by.compute_id(conn)), obj.serialize(conn))) - } - - pub fn accept(by: &A, what: String, conn: &PgConnection) -> Activity { - Activity::Accept(Payload::new(serde_json::Value::String(by.compute_id(conn)), serde_json::Value::String(what))) - } - - pub fn follow(by: &A, obj: &B, conn: &PgConnection) -> Activity { - Activity::Follow(Payload::new(serde_json::Value::String(by.compute_id(conn)), serde_json::Value::String(obj.compute_id(conn)))) +// We can now implement Clone manually by forwarding to clone_box. +impl Clone for Box { + fn clone(&self) -> Box { + self.clone_box() } } #[derive(Clone)] -pub struct Payload { - by: serde_json::Value, +pub struct Accept { + id: String, + actor: serde_json::Value, object: serde_json::Value, date: chrono::DateTime } -impl Payload { - pub fn new(by: serde_json::Value, obj: serde_json::Value) -> Payload { - Payload { - by: by, - object: obj, +impl Accept { + pub fn new(who: &B, what: &A, conn: &PgConnection) -> Accept { + Accept { + id: "TODO".to_string(), + actor: serde_json::Value::String(who.compute_id(conn)), + object: serde_json::Value::String(what.get_id()), date: chrono::Utc::now() } } } +impl Activity for Accept { + fn get_id(&self) -> String { + self.id.clone() + } + + fn serialize(&self) -> serde_json::Value { + json!({ + "type": "Accept", + "actor": self.actor, + "object": self.object, + "published": self.date.to_rfc3339() + }) + } +} + +#[derive(Clone)] +pub struct Create { + id: String, + actor: serde_json::Value, + object: serde_json::Value, + date: chrono::DateTime +} + +impl Create { + pub fn new(actor: &A, obj: &B, conn: &PgConnection) -> Create { + Create { + id: "TODO".to_string(), + actor: serde_json::Value::String(actor.compute_id(conn)), + object: obj.serialize(conn), + date: chrono::Utc::now() + } + } +} + +impl Activity for Create { + fn get_id(&self) -> String { + self.id.clone() + } + + fn serialize(&self) -> serde_json::Value { + json!({ + "type": "Create", + "actor": self.actor, + "object": self.object, + "published": self.date.to_rfc3339() + }) + } +} + +#[derive(Clone)] +pub struct Follow { + id: String, + actor: serde_json::Value, + object: serde_json::Value, + date: chrono::DateTime +} + +impl Follow { + pub fn new(follower: &A, following: &B, conn: &PgConnection) -> Follow { + Follow { + id: "TODO".to_string(), + actor: serde_json::Value::String(follower.compute_id(conn)), + object: serde_json::Value::String(following.compute_id(conn)), + date: chrono::Utc::now() + } + } + + pub fn deserialize(json: serde_json::Value) -> Follow { + Follow { + id: json["id"].as_str().unwrap().to_string(), + actor: json["actor"].clone(), + object: json["object"].clone(), + date: chrono::DateTime::from_str(json["published"].as_str().unwrap()).unwrap() + } + } + + pub fn get_target_id(&self) -> String { + self.object.as_str().unwrap().to_string() + } +} + +impl Activity for Follow { + fn get_id(&self) -> String { + self.id.clone() + } + + fn serialize(&self) -> serde_json::Value { + json!({ + "type": "Follow", + "actor": self.actor, + "object": self.object, + "published": self.date.to_rfc3339() + }) + } +} diff --git a/src/activity_pub/actor.rs b/src/activity_pub/actor.rs index 192d190d..75e05c9f 100644 --- a/src/activity_pub/actor.rs +++ b/src/activity_pub/actor.rs @@ -67,7 +67,7 @@ pub trait Actor: Sized { )) } - fn send_to_inbox(&self, conn: &PgConnection, act: Activity) { + fn send_to_inbox(&self, conn: &PgConnection, act: A) { let res = Client::new() .post(&self.compute_inbox(conn)[..]) .body(act.serialize().to_string()) diff --git a/src/activity_pub/inbox.rs b/src/activity_pub/inbox.rs index fd125be5..e1971372 100644 --- a/src/activity_pub/inbox.rs +++ b/src/activity_pub/inbox.rs @@ -1,7 +1,7 @@ use diesel::PgConnection; use serde_json; -use activity_pub::activity::Activity; +use activity_pub::activity; use activity_pub::actor::Actor; use models::blogs::Blog; use models::follows::{Follow, NewFollow}; @@ -29,13 +29,13 @@ pub trait Inbox: Actor + Sized { } }, "Follow" => { - let follow_id = act["object"].as_str().unwrap().to_string(); + 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_id, from.id, u.id), + Some(u) => self.accept_follow(conn, &from, &u, &follow_act, from.id, u.id), None => { - let blog = Blog::from_url(conn, follow_id.clone()).unwrap(); - self.accept_follow(conn, &from, &blog, follow_id, from.id, blog.id) + let blog = Blog::from_url(conn, follow_act.get_target_id()).unwrap(); + self.accept_follow(conn, &from, &blog, &follow_act, from.id, blog.id) } }; @@ -45,13 +45,13 @@ pub trait Inbox: Actor + Sized { } } - fn accept_follow(&self, conn: &PgConnection, from: &A, target: &B, follow_id: String, from_id: i32, target_id: i32) { + fn accept_follow(&self, 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 }); - let accept = Activity::accept(target, follow_id, conn); + let accept = activity::Accept::new(target, follow, conn); from.send_to_inbox(conn, accept) } } diff --git a/src/activity_pub/outbox.rs b/src/activity_pub/outbox.rs index 82558683..9aa891d8 100644 --- a/src/activity_pub/outbox.rs +++ b/src/activity_pub/outbox.rs @@ -9,13 +9,13 @@ use activity_pub::activity::Activity; use activity_pub::actor::Actor; use models::users::User; -pub struct Outbox { +pub struct Outbox where A: Activity + Clone { id: String, - items: Vec + items: Vec> } -impl Outbox { - pub fn new(id: String, items: Vec) -> Outbox { +impl Outbox { + pub fn new(id: String, items: Vec>) -> Outbox { Outbox { id: id, items: items @@ -23,24 +23,24 @@ impl Outbox { } fn serialize(&self) -> ActivityPub { - let items = self.items.clone(); + 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.into_iter().map(|i| i.serialize()).collect::>() + "orderedItems": items })) } } -impl<'r> Responder<'r> for Outbox { +impl<'r, A: Activity + Clone + 'static> Responder<'r> for Outbox { fn respond_to(self, request: &Request) -> Result, Status> { self.serialize().respond_to(request) } } -pub fn broadcast(conn: &PgConnection, act: Activity, to: Vec) { +pub fn broadcast(conn: &PgConnection, act: A, to: Vec) { for user in to { user.send_to_inbox(conn, act.clone()); // TODO: run it in Sidekiq or something like that } diff --git a/src/models/blogs.rs b/src/models/blogs.rs index 06f57e21..0957c43d 100644 --- a/src/models/blogs.rs +++ b/src/models/blogs.rs @@ -78,11 +78,11 @@ impl Blog { } } - pub fn outbox(&self, conn: &PgConnection) -> Outbox { + pub fn outbox(&self, conn: &PgConnection) -> Outbox { Outbox::new(self.compute_outbox(conn), self.get_activities(conn)) } - fn get_activities(&self, _conn: &PgConnection) -> Vec { + fn get_activities(&self, _conn: &PgConnection) -> Vec> { vec![] } } diff --git a/src/models/users.rs b/src/models/users.rs index 9bfcf7da..8a57ac9b 100644 --- a/src/models/users.rs +++ b/src/models/users.rs @@ -11,7 +11,7 @@ use serde_json; use url::Url; use BASE_URL; -use activity_pub::activity::Activity; +use activity_pub::activity::{Create, Activity}; use activity_pub::actor::{ActorType, Actor}; use activity_pub::inbox::Inbox; use activity_pub::outbox::Outbox; @@ -184,16 +184,16 @@ impl User { } } - pub fn outbox(&self, conn: &PgConnection) -> Outbox { + pub fn outbox(&self, conn: &PgConnection) -> Outbox { Outbox::new(self.compute_outbox(conn), self.get_activities(conn)) } - 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| Activity::create(self, p, conn)).collect::>() + posts.into_iter().map(|p| Box::new(Create::new(self, &p, conn)) as Box).collect::>>() } pub fn get_followers(&self, conn: &PgConnection) -> Vec { diff --git a/src/routes/blogs.rs b/src/routes/blogs.rs index ace2597b..4bc619b7 100644 --- a/src/routes/blogs.rs +++ b/src/routes/blogs.rs @@ -4,6 +4,7 @@ use rocket_contrib::Template; use std::collections::HashMap; use activity_pub::ActivityPub; +use activity_pub::activity::Activity; use activity_pub::actor::Actor; use activity_pub::outbox::Outbox; use db_conn::DbConn; @@ -57,7 +58,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) -> Outbox { let blog = Blog::find_by_actor_id(&*conn, name).unwrap(); blog.outbox(&*conn) } diff --git a/src/routes/posts.rs b/src/routes/posts.rs index 6c571c82..9c2ad4d8 100644 --- a/src/routes/posts.rs +++ b/src/routes/posts.rs @@ -4,7 +4,7 @@ use rocket::response::Redirect; use rocket_contrib::Template; use std::collections::HashMap; -use activity_pub::activity::Activity; +use activity_pub::activity::Create; use activity_pub::outbox::broadcast; use db_conn::DbConn; use models::blogs::*; @@ -55,7 +55,7 @@ fn create(blog_name: String, data: Form, user: User, conn: DbConn) author_id: user.id }); - let act = Activity::create(&user, post, &*conn); + let act = Create::new(&user, &post, &*conn); broadcast(&*conn, 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 79e5de02..08cf2fca 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -5,7 +5,7 @@ use serde_json; use std::collections::HashMap; use activity_pub::ActivityPub; -use activity_pub::activity::Activity; +use activity_pub::activity; use activity_pub::actor::Actor; use activity_pub::inbox::Inbox; use activity_pub::outbox::Outbox; @@ -34,7 +34,7 @@ fn follow(name: String, conn: DbConn, user: User) -> Redirect { follower_id: user.id, following_id: target.id }); - target.send_to_inbox(&*conn, Activity::follow(&user, &target, &*conn)); + target.send_to_inbox(&*conn, activity::Follow::new(&user, &target, &*conn)); Redirect::to(format!("/@/{}", name).as_ref()) }