Start refactoring activities

This commit is contained in:
Bat 2018-05-02 21:44:03 +01:00
parent 58fad0d414
commit afe98ab1c3
9 changed files with 153 additions and 71 deletions

View File

@ -1,69 +1,150 @@
use chrono; use chrono;
use diesel::PgConnection; use diesel::PgConnection;
use serde_json; use serde_json;
use std::str::FromStr;
use activity_pub::actor::Actor; use activity_pub::actor::Actor;
use activity_pub::object::Object; use activity_pub::object::Object;
#[derive(Clone)] pub trait Activity: ActivityClone {
pub enum Activity { fn get_id(&self) -> String;
Create(Payload),
Accept(Payload), fn serialize(&self) -> serde_json::Value;
Follow(Payload)
} // 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 { trait ActivityClone {
match self { fn clone_box(&self) -> Box<Activity>;
Activity::Accept(_) => String::from("Accept"), }
Activity::Create(_) => String::from("Create"),
Activity::Follow(_) => String::from("Follow") impl<T> ActivityClone for T
where
T: 'static + Activity + Clone,
{
fn clone_box(&self) -> Box<Activity> {
Box::new(self.clone())
} }
} }
pub fn payload(&self) -> Payload { // We can now implement Clone manually by forwarding to clone_box.
match self { impl Clone for Box<Activity> {
Activity::Accept(p) => p.clone(), fn clone(&self) -> Box<Activity> {
Activity::Create(p) => p.clone(), self.clone_box()
Activity::Follow(p) => p.clone()
}
}
pub fn create<T: Object, U: Actor>(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<A: Actor>(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<A: Actor, B: Actor>(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))))
} }
} }
#[derive(Clone)] #[derive(Clone)]
pub struct Payload { pub struct Accept {
by: serde_json::Value, id: String,
actor: serde_json::Value,
object: serde_json::Value, object: serde_json::Value,
date: chrono::DateTime<chrono::Utc> date: chrono::DateTime<chrono::Utc>
} }
impl Payload { impl Accept {
pub fn new(by: serde_json::Value, obj: serde_json::Value) -> Payload { pub fn new<A: Activity, B: Actor>(who: &B, what: &A, conn: &PgConnection) -> Accept {
Payload { Accept {
by: by, id: "TODO".to_string(),
object: obj, actor: serde_json::Value::String(who.compute_id(conn)),
object: serde_json::Value::String(what.get_id()),
date: chrono::Utc::now() 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<chrono::Utc>
}
impl Create {
pub fn new<A: Actor, B: Object>(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<chrono::Utc>
}
impl Follow {
pub fn new<A: Actor, B: Actor>(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()
})
}
}

View File

@ -67,7 +67,7 @@ pub trait Actor: Sized {
)) ))
} }
fn send_to_inbox(&self, conn: &PgConnection, act: Activity) { fn send_to_inbox<A: Activity>(&self, conn: &PgConnection, act: A) {
let res = Client::new() let res = Client::new()
.post(&self.compute_inbox(conn)[..]) .post(&self.compute_inbox(conn)[..])
.body(act.serialize().to_string()) .body(act.serialize().to_string())

View File

@ -1,7 +1,7 @@
use diesel::PgConnection; use diesel::PgConnection;
use serde_json; use serde_json;
use activity_pub::activity::Activity; use activity_pub::activity;
use activity_pub::actor::Actor; use activity_pub::actor::Actor;
use models::blogs::Blog; use models::blogs::Blog;
use models::follows::{Follow, NewFollow}; use models::follows::{Follow, NewFollow};
@ -29,13 +29,13 @@ pub trait Inbox: Actor + Sized {
} }
}, },
"Follow" => { "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(); 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()) { 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 => { None => {
let blog = Blog::from_url(conn, follow_id.clone()).unwrap(); let blog = Blog::from_url(conn, follow_act.get_target_id()).unwrap();
self.accept_follow(conn, &from, &blog, follow_id, from.id, blog.id) self.accept_follow(conn, &from, &blog, &follow_act, from.id, blog.id)
} }
}; };
@ -45,13 +45,13 @@ pub trait Inbox: Actor + Sized {
} }
} }
fn accept_follow<A: Actor, B: Actor>(&self, conn: &PgConnection, from: &A, target: &B, follow_id: String, from_id: i32, target_id: i32) { fn accept_follow<A: Actor, B: Actor, T: activity::Activity>(&self, conn: &PgConnection, from: &A, target: &B, follow: &T, from_id: i32, target_id: i32) {
Follow::insert(conn, NewFollow { Follow::insert(conn, NewFollow {
follower_id: from_id, follower_id: from_id,
following_id: target_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) from.send_to_inbox(conn, accept)
} }
} }

View File

@ -9,13 +9,13 @@ use activity_pub::activity::Activity;
use activity_pub::actor::Actor; use activity_pub::actor::Actor;
use models::users::User; use models::users::User;
pub struct Outbox { pub struct Outbox<A> where A: Activity + Clone {
id: String, id: String,
items: Vec<Activity> items: Vec<Box<A>>
} }
impl Outbox { impl<A: Activity + Clone + 'static> Outbox<A> {
pub fn new(id: String, items: Vec<Activity>) -> Outbox { pub fn new(id: String, items: Vec<Box<A>>) -> Outbox<A> {
Outbox { Outbox {
id: id, id: id,
items: items items: items
@ -23,24 +23,24 @@ impl Outbox {
} }
fn serialize(&self) -> ActivityPub { fn serialize(&self) -> ActivityPub {
let items = self.items.clone(); let items = self.items.clone().into_iter().map(|i| i.serialize()).collect::<Vec<serde_json::Value>>();
activity_pub(json!({ activity_pub(json!({
"@context": context(), "@context": context(),
"type": "OrderedCollection", "type": "OrderedCollection",
"id": self.id, "id": self.id,
"totalItems": items.len(), "totalItems": items.len(),
"orderedItems": items.into_iter().map(|i| i.serialize()).collect::<Vec<serde_json::Value>>() "orderedItems": items
})) }))
} }
} }
impl<'r> Responder<'r> for Outbox { impl<'r, A: Activity + Clone + 'static> Responder<'r> for Outbox<A> {
fn respond_to(self, request: &Request) -> Result<Response<'r>, Status> { fn respond_to(self, request: &Request) -> Result<Response<'r>, Status> {
self.serialize().respond_to(request) self.serialize().respond_to(request)
} }
} }
pub fn broadcast(conn: &PgConnection, act: Activity, to: Vec<User>) { pub fn broadcast<A: Activity + Clone>(conn: &PgConnection, act: A, to: Vec<User>) {
for user in to { for user in to {
user.send_to_inbox(conn, act.clone()); // TODO: run it in Sidekiq or something like that user.send_to_inbox(conn, act.clone()); // TODO: run it in Sidekiq or something like that
} }

View File

@ -78,11 +78,11 @@ impl Blog {
} }
} }
pub fn outbox(&self, conn: &PgConnection) -> Outbox { pub fn outbox<A: Activity + Clone + 'static>(&self, conn: &PgConnection) -> Outbox<A> {
Outbox::new(self.compute_outbox(conn), self.get_activities(conn)) Outbox::new(self.compute_outbox(conn), self.get_activities(conn))
} }
fn get_activities(&self, _conn: &PgConnection) -> Vec<Activity> { fn get_activities<A: Activity + Clone>(&self, _conn: &PgConnection) -> Vec<Box<A>> {
vec![] vec![]
} }
} }

View File

@ -11,7 +11,7 @@ use serde_json;
use url::Url; use url::Url;
use BASE_URL; use BASE_URL;
use activity_pub::activity::Activity; use activity_pub::activity::{Create, Activity};
use activity_pub::actor::{ActorType, Actor}; use activity_pub::actor::{ActorType, Actor};
use activity_pub::inbox::Inbox; use activity_pub::inbox::Inbox;
use activity_pub::outbox::Outbox; use activity_pub::outbox::Outbox;
@ -184,16 +184,16 @@ impl User {
} }
} }
pub fn outbox(&self, conn: &PgConnection) -> Outbox { pub fn outbox<A: Activity + Clone + 'static>(&self, conn: &PgConnection) -> Outbox<A> {
Outbox::new(self.compute_outbox(conn), self.get_activities(conn)) Outbox::new(self.compute_outbox(conn), self.get_activities(conn))
} }
fn get_activities(&self, conn: &PgConnection) -> Vec<Activity> { fn get_activities<A: Activity>(&self, conn: &PgConnection) -> Vec<Box<A>> {
use schema::posts; use schema::posts;
use schema::post_authors; use schema::post_authors;
let posts_by_self = PostAuthor::belonging_to(self).select(post_authors::post_id); 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(); let posts = posts::table.filter(posts::id.eq(any(posts_by_self))).load::<Post>(conn).unwrap();
posts.into_iter().map(|p| Activity::create(self, p, conn)).collect::<Vec<Activity>>() posts.into_iter().map(|p| Box::new(Create::new(self, &p, conn)) as Box<A>).collect::<Vec<Box<A>>>()
} }
pub fn get_followers(&self, conn: &PgConnection) -> Vec<User> { pub fn get_followers(&self, conn: &PgConnection) -> Vec<User> {

View File

@ -4,6 +4,7 @@ use rocket_contrib::Template;
use std::collections::HashMap; use std::collections::HashMap;
use activity_pub::ActivityPub; use activity_pub::ActivityPub;
use activity_pub::activity::Activity;
use activity_pub::actor::Actor; use activity_pub::actor::Actor;
use activity_pub::outbox::Outbox; use activity_pub::outbox::Outbox;
use db_conn::DbConn; use db_conn::DbConn;
@ -57,7 +58,7 @@ fn create(conn: DbConn, data: Form<NewBlogForm>, user: User) -> Redirect {
} }
#[get("/~/<name>/outbox")] #[get("/~/<name>/outbox")]
fn outbox(name: String, conn: DbConn) -> Outbox { fn outbox<A: Activity + Clone + 'static>(name: String, conn: DbConn) -> Outbox<A> {
let blog = Blog::find_by_actor_id(&*conn, name).unwrap(); let blog = Blog::find_by_actor_id(&*conn, name).unwrap();
blog.outbox(&*conn) blog.outbox(&*conn)
} }

View File

@ -4,7 +4,7 @@ use rocket::response::Redirect;
use rocket_contrib::Template; use rocket_contrib::Template;
use std::collections::HashMap; use std::collections::HashMap;
use activity_pub::activity::Activity; use activity_pub::activity::Create;
use activity_pub::outbox::broadcast; use activity_pub::outbox::broadcast;
use db_conn::DbConn; use db_conn::DbConn;
use models::blogs::*; use models::blogs::*;
@ -55,7 +55,7 @@ fn create(blog_name: String, data: Form<NewPostForm>, user: User, conn: DbConn)
author_id: user.id 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)); broadcast(&*conn, act, user.get_followers(&*conn));
Redirect::to(format!("/~/{}/{}", blog_name, slug).as_str()) Redirect::to(format!("/~/{}/{}", blog_name, slug).as_str())

View File

@ -5,7 +5,7 @@ use serde_json;
use std::collections::HashMap; use std::collections::HashMap;
use activity_pub::ActivityPub; use activity_pub::ActivityPub;
use activity_pub::activity::Activity; use activity_pub::activity;
use activity_pub::actor::Actor; use activity_pub::actor::Actor;
use activity_pub::inbox::Inbox; use activity_pub::inbox::Inbox;
use activity_pub::outbox::Outbox; use activity_pub::outbox::Outbox;
@ -34,7 +34,7 @@ fn follow(name: String, conn: DbConn, user: User) -> Redirect {
follower_id: user.id, follower_id: user.id,
following_id: target.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()) Redirect::to(format!("/@/{}", name).as_ref())
} }