Start refactoring activities
This commit is contained in:
parent
58fad0d414
commit
afe98ab1c3
|
@ -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<Activity>;
|
||||
}
|
||||
|
||||
pub fn payload(&self) -> Payload {
|
||||
match self {
|
||||
Activity::Accept(p) => p.clone(),
|
||||
Activity::Create(p) => p.clone(),
|
||||
Activity::Follow(p) => p.clone()
|
||||
}
|
||||
impl<T> ActivityClone for T
|
||||
where
|
||||
T: 'static + Activity + Clone,
|
||||
{
|
||||
fn clone_box(&self) -> Box<Activity> {
|
||||
Box::new(self.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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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![]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue