Accept follow requests
This commit is contained in:
parent
2f1f6a0295
commit
9a4f60cfe3
|
@ -816,6 +816,7 @@ dependencies = [
|
|||
"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)",
|
||||
"url 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -5,7 +5,6 @@ version = "0.1.0"
|
|||
[dependencies]
|
||||
base64 = "0.9.1"
|
||||
bcrypt = "0.2"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
dotenv = "*"
|
||||
heck = "0.3.0"
|
||||
hex = "0.3"
|
||||
|
@ -16,6 +15,11 @@ rocket_codegen = "*"
|
|||
serde = "*"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
url = "1.7"
|
||||
|
||||
[dependencies.chrono]
|
||||
features = ["serde"]
|
||||
version = "0.4"
|
||||
|
||||
[dependencies.diesel]
|
||||
features = ["postgres", "r2d2", "chrono"]
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
ALTER TABLE blogs DROP COLUMN ap_url;
|
||||
ALTER TABLE users DROP COLUMN ap_url;
|
|
@ -0,0 +1,3 @@
|
|||
-- Your SQL goes here
|
||||
ALTER TABLE blogs ADD COLUMN ap_url TEXT NOT NULL default '';
|
||||
ALTER TABLE users ADD COLUMN ap_url TEXT NOT NULL default '';
|
|
@ -7,35 +7,52 @@ use activity_pub::object::Object;
|
|||
|
||||
#[derive(Clone)]
|
||||
pub enum Activity {
|
||||
Create(CreatePayload)
|
||||
Create(Payload),
|
||||
Accept(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 fn get_type(&self) -> String {
|
||||
match self {
|
||||
Activity::Create(data) => json!({
|
||||
"type": "Create",
|
||||
"actor": data.by,
|
||||
"object": data.object,
|
||||
"published": data.date.to_rfc3339()
|
||||
})
|
||||
Activity::Accept(_) => String::from("Accept"),
|
||||
Activity::Create(_) => String::from("Create")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn payload(&self) -> Payload {
|
||||
match self {
|
||||
Activity::Accept(p) => p.clone(),
|
||||
Activity::Create(p) => p.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create<T: Object, U: Actor>(by: &U, obj: T, conn: &PgConnection) -> Activity {
|
||||
Activity::Create(CreatePayload::new(serde_json::Value::String(by.compute_id(conn)), obj.serialize(conn)))
|
||||
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)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CreatePayload {
|
||||
pub struct Payload {
|
||||
by: serde_json::Value,
|
||||
object: serde_json::Value,
|
||||
date: chrono::DateTime<chrono::Utc>
|
||||
}
|
||||
|
||||
impl CreatePayload {
|
||||
pub fn new(by: serde_json::Value, obj: serde_json::Value) -> CreatePayload {
|
||||
CreatePayload {
|
||||
impl Payload {
|
||||
pub fn new(by: serde_json::Value, obj: serde_json::Value) -> Payload {
|
||||
Payload {
|
||||
by: by,
|
||||
object: obj,
|
||||
date: chrono::Utc::now()
|
||||
|
|
|
@ -19,7 +19,7 @@ impl ToString for ActorType {
|
|||
}
|
||||
}
|
||||
|
||||
pub trait Actor {
|
||||
pub trait Actor: Sized {
|
||||
fn get_box_prefix() -> &'static str;
|
||||
|
||||
fn get_actor_id(&self) -> String;
|
||||
|
@ -76,4 +76,6 @@ pub trait Actor {
|
|||
Err(_) => println!("Error while sending to inbox")
|
||||
}
|
||||
}
|
||||
|
||||
fn from_url(conn: &PgConnection, url: String) -> Option<Self>;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
use diesel::PgConnection;
|
||||
use diesel::associations::Identifiable;
|
||||
use serde_json;
|
||||
|
||||
use activity_pub::activity::Activity;
|
||||
use activity_pub::actor::Actor;
|
||||
use models::blogs::Blog;
|
||||
use models::follows::{Follow, NewFollow};
|
||||
use models::posts::{Post, NewPost};
|
||||
use models::users::User;
|
||||
|
||||
pub trait Inbox {
|
||||
pub trait Inbox: Actor + Sized {
|
||||
fn received(&self, conn: &PgConnection, act: serde_json::Value);
|
||||
|
||||
fn save(&self, conn: &PgConnection, act: serde_json::Value) {
|
||||
|
@ -23,7 +29,30 @@ pub trait Inbox {
|
|||
x => println!("Received a new {}, but didn't saved it", x)
|
||||
}
|
||||
},
|
||||
"Follow" => {
|
||||
let follow_id = act["object"].as_str().unwrap().to_string();
|
||||
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),
|
||||
None => {
|
||||
let blog = Blog::from_url(conn, follow_id.clone()).unwrap();
|
||||
self.accept_follow(conn, &from, &blog, follow_id, from.id, blog.id)
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: notification
|
||||
}
|
||||
x => println!("Received unknow activity type: {}", x)
|
||||
}
|
||||
}
|
||||
|
||||
fn accept_follow<A: Actor, B: Actor>(&self, conn: &PgConnection, from: &A, target: &B, follow_id: String, 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);
|
||||
from.send_to_inbox(conn, accept)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ extern crate serde;
|
|||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
extern crate url;
|
||||
|
||||
use diesel::pg::PgConnection;
|
||||
use diesel::r2d2::{ConnectionManager, Pool};
|
||||
|
|
|
@ -18,7 +18,8 @@ pub struct Blog {
|
|||
pub outbox_url: String,
|
||||
pub inbox_url: String,
|
||||
pub instance_id: i32,
|
||||
pub creation_date: NaiveDateTime
|
||||
pub creation_date: NaiveDateTime,
|
||||
pub ap_url: String
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
|
@ -29,7 +30,8 @@ pub struct NewBlog {
|
|||
pub summary: String,
|
||||
pub outbox_url: String,
|
||||
pub inbox_url: String,
|
||||
pub instance_id: i32
|
||||
pub instance_id: i32,
|
||||
pub ap_url: String
|
||||
}
|
||||
|
||||
impl Blog {
|
||||
|
@ -52,7 +54,7 @@ impl Blog {
|
|||
blogs::table.filter(blogs::actor_id.eq(username))
|
||||
.limit(1)
|
||||
.load::<Blog>(conn)
|
||||
.expect("Error loading blog by email")
|
||||
.expect("Error loading blog by actor_id")
|
||||
.into_iter().nth(0)
|
||||
}
|
||||
|
||||
|
@ -68,6 +70,12 @@ impl Blog {
|
|||
.set(blogs::inbox_url.eq(self.compute_inbox(conn)))
|
||||
.get_result::<Blog>(conn).expect("Couldn't update inbox URL");
|
||||
}
|
||||
|
||||
if self.ap_url.len() == 0 {
|
||||
diesel::update(self)
|
||||
.set(blogs::ap_url.eq(self.compute_id(conn)))
|
||||
.get_result::<Blog>(conn).expect("Couldn't update AP URL");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn outbox(&self, conn: &PgConnection) -> Outbox {
|
||||
|
@ -95,6 +103,14 @@ impl Actor for Blog {
|
|||
fn get_actor_type () -> ActorType {
|
||||
ActorType::Blog
|
||||
}
|
||||
|
||||
fn from_url(conn: &PgConnection, url: String) -> Option<Blog> {
|
||||
blogs::table.filter(blogs::ap_url.eq(url))
|
||||
.limit(1)
|
||||
.load::<Blog>(conn)
|
||||
.expect("Error loading blog from url")
|
||||
.into_iter().nth(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Webfinger for Blog {
|
||||
|
@ -137,7 +153,8 @@ impl NewBlog {
|
|||
summary: summary,
|
||||
outbox_url: String::from(""),
|
||||
inbox_url: String::from(""),
|
||||
instance_id: instance_id
|
||||
instance_id: instance_id,
|
||||
ap_url: String::from("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ use reqwest::mime::Mime;
|
|||
use rocket::request::{self, FromRequest, Request};
|
||||
use rocket::outcome::IntoOutcome;
|
||||
use serde_json;
|
||||
use url::Url;
|
||||
|
||||
use activity_pub::activity::Activity;
|
||||
use activity_pub::actor::{ActorType, Actor};
|
||||
|
@ -35,7 +36,8 @@ pub struct User {
|
|||
pub email: Option<String>,
|
||||
pub hashed_password: Option<String>,
|
||||
pub instance_id: i32,
|
||||
pub creation_date: NaiveDateTime
|
||||
pub creation_date: NaiveDateTime,
|
||||
pub ap_url: String
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
|
@ -49,7 +51,8 @@ pub struct NewUser {
|
|||
pub summary: String,
|
||||
pub email: Option<String>,
|
||||
pub hashed_password: Option<String>,
|
||||
pub instance_id: i32
|
||||
pub instance_id: i32,
|
||||
pub ap_url: String
|
||||
}
|
||||
|
||||
impl User {
|
||||
|
@ -83,7 +86,7 @@ impl User {
|
|||
.filter(users::instance_id.eq(instance_id))
|
||||
.limit(1)
|
||||
.load::<User>(conn)
|
||||
.expect("Error loading user by email")
|
||||
.expect("Error loading user by name")
|
||||
.into_iter().nth(0)
|
||||
}
|
||||
|
||||
|
@ -109,19 +112,7 @@ impl User {
|
|||
|
||||
fn fetch_from_webfinger(conn: &PgConnection, acct: String) -> Option<User> {
|
||||
match resolve(acct.clone()) {
|
||||
Ok(url) => {
|
||||
let req = Client::new()
|
||||
.get(&url[..])
|
||||
.header(Accept(vec![qitem("application/activity+json".parse::<Mime>().unwrap())]))
|
||||
.send();
|
||||
match req {
|
||||
Ok(mut res) => {
|
||||
let json: serde_json::Value = serde_json::from_str(&res.text().unwrap()).unwrap();
|
||||
Some(User::from_activity(conn, json, acct.split("@").last().unwrap().to_string()))
|
||||
},
|
||||
Err(_) => None
|
||||
}
|
||||
},
|
||||
Ok(url) => User::fetch_from_url(conn, url),
|
||||
Err(details) => {
|
||||
println!("{}", details);
|
||||
None
|
||||
|
@ -129,6 +120,20 @@ impl User {
|
|||
}
|
||||
}
|
||||
|
||||
fn fetch_from_url(conn: &PgConnection, url: String) -> Option<User> {
|
||||
let req = Client::new()
|
||||
.get(&url[..])
|
||||
.header(Accept(vec![qitem("application/activity+json".parse::<Mime>().unwrap())]))
|
||||
.send();
|
||||
match req {
|
||||
Ok(mut res) => {
|
||||
let json: serde_json::Value = serde_json::from_str(&res.text().unwrap()).unwrap();
|
||||
Some(User::from_activity(conn, json, Url::parse(url.as_ref()).unwrap().host_str().unwrap().to_string()))
|
||||
},
|
||||
Err(_) => None
|
||||
}
|
||||
}
|
||||
|
||||
fn from_activity(conn: &PgConnection, acct: serde_json::Value, inst: String) -> User {
|
||||
let instance = match Instance::get_by_domain(conn, inst.clone()) {
|
||||
Some(instance) => instance,
|
||||
|
@ -145,7 +150,8 @@ impl User {
|
|||
summary: acct["summary"].as_str().unwrap().to_string(),
|
||||
email: None,
|
||||
hashed_password: None,
|
||||
instance_id: instance.id
|
||||
instance_id: instance.id,
|
||||
ap_url: acct["id"].as_str().unwrap().to_string()
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -167,7 +173,13 @@ impl User {
|
|||
if self.inbox_url.len() == 0 {
|
||||
diesel::update(self)
|
||||
.set(users::inbox_url.eq(self.compute_inbox(conn)))
|
||||
.get_result::<User>(conn).expect("Couldn't update outbox URL");
|
||||
.get_result::<User>(conn).expect("Couldn't update inbox URL");
|
||||
}
|
||||
|
||||
if self.ap_url.len() == 0 {
|
||||
diesel::update(self)
|
||||
.set(users::ap_url.eq(self.compute_id(conn)))
|
||||
.get_result::<User>(conn).expect("Couldn't update AP URL");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,6 +237,26 @@ impl Actor for User {
|
|||
fn get_actor_type() -> ActorType {
|
||||
ActorType::Person
|
||||
}
|
||||
|
||||
fn from_url(conn: &PgConnection, url: String) -> Option<User> {
|
||||
let in_db = users::table.filter(users::ap_url.eq(url.clone()))
|
||||
.limit(1)
|
||||
.load::<User>(conn)
|
||||
.expect("Error loading user by AP url")
|
||||
.into_iter().nth(0);
|
||||
match in_db {
|
||||
Some(u) => Some(u),
|
||||
None => {
|
||||
// The requested user was not in the DB
|
||||
// We try to fetch it if it is remote
|
||||
if Url::parse(url.as_ref()).unwrap().host_str().unwrap() != Instance::get_local(conn).unwrap().public_domain {
|
||||
Some(User::fetch_from_url(conn, url).unwrap())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inbox for User {
|
||||
|
@ -281,7 +313,8 @@ impl NewUser {
|
|||
summary: summary,
|
||||
email: Some(email),
|
||||
hashed_password: Some(password),
|
||||
instance_id: instance_id
|
||||
instance_id: instance_id,
|
||||
ap_url: String::from("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ table! {
|
|||
inbox_url -> Varchar,
|
||||
instance_id -> Int4,
|
||||
creation_date -> Timestamp,
|
||||
ap_url -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,6 +75,7 @@ table! {
|
|||
hashed_password -> Nullable<Text>,
|
||||
instance_id -> Int4,
|
||||
creation_date -> Timestamp,
|
||||
ap_url -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue