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 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)
}
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 trait Activity: ActivityClone {
fn get_id(&self) -> String;
fn serialize(&self) -> serde_json::Value;
// fn deserialize(serde_json::Value) -> Self;
}
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<Activity>;
}
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 {
match self {
Activity::Accept(p) => p.clone(),
Activity::Create(p) => p.clone(),
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))))
// We can now implement Clone manually by forwarding to clone_box.
impl Clone for Box<Activity> {
fn clone(&self) -> Box<Activity> {
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<chrono::Utc>
}
impl Payload {
pub fn new(by: serde_json::Value, obj: serde_json::Value) -> Payload {
Payload {
by: by,
object: obj,
impl Accept {
pub fn new<A: Activity, B: Actor>(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<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()
.post(&self.compute_inbox(conn)[..])
.body(act.serialize().to_string())

View file

@ -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<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 {
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)
}
}

View file

@ -9,13 +9,13 @@ use activity_pub::activity::Activity;
use activity_pub::actor::Actor;
use models::users::User;
pub struct Outbox {
pub struct Outbox<A> where A: Activity + Clone {
id: String,
items: Vec<Activity>
items: Vec<Box<A>>
}
impl Outbox {
pub fn new(id: String, items: Vec<Activity>) -> Outbox {
impl<A: Activity + Clone + 'static> Outbox<A> {
pub fn new(id: String, items: Vec<Box<A>>) -> Outbox<A> {
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::<Vec<serde_json::Value>>();
activity_pub(json!({
"@context": context(),
"type": "OrderedCollection",
"id": self.id,
"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> {
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 {
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))
}
fn get_activities(&self, _conn: &PgConnection) -> Vec<Activity> {
fn get_activities<A: Activity + Clone>(&self, _conn: &PgConnection) -> Vec<Box<A>> {
vec![]
}
}

View file

@ -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<A: Activity + Clone + 'static>(&self, conn: &PgConnection) -> Outbox<A> {
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::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| 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> {

View file

@ -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<NewBlogForm>, user: User) -> Redirect {
}
#[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();
blog.outbox(&*conn)
}

View file

@ -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<NewPostForm>, 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())

View file

@ -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())
}