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 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_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)",
|
"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]]
|
[[package]]
|
||||||
|
|
|
@ -5,7 +5,6 @@ version = "0.1.0"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.9.1"
|
base64 = "0.9.1"
|
||||||
bcrypt = "0.2"
|
bcrypt = "0.2"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
|
||||||
dotenv = "*"
|
dotenv = "*"
|
||||||
heck = "0.3.0"
|
heck = "0.3.0"
|
||||||
hex = "0.3"
|
hex = "0.3"
|
||||||
|
@ -16,6 +15,11 @@ rocket_codegen = "*"
|
||||||
serde = "*"
|
serde = "*"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
url = "1.7"
|
||||||
|
|
||||||
|
[dependencies.chrono]
|
||||||
|
features = ["serde"]
|
||||||
|
version = "0.4"
|
||||||
|
|
||||||
[dependencies.diesel]
|
[dependencies.diesel]
|
||||||
features = ["postgres", "r2d2", "chrono"]
|
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)]
|
#[derive(Clone)]
|
||||||
pub enum Activity {
|
pub enum Activity {
|
||||||
Create(CreatePayload)
|
Create(Payload),
|
||||||
|
Accept(Payload)
|
||||||
}
|
}
|
||||||
impl Activity {
|
impl Activity {
|
||||||
pub fn serialize(&self) -> serde_json::Value {
|
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 {
|
match self {
|
||||||
Activity::Create(data) => json!({
|
Activity::Accept(_) => String::from("Accept"),
|
||||||
"type": "Create",
|
Activity::Create(_) => String::from("Create")
|
||||||
"actor": data.by,
|
}
|
||||||
"object": data.object,
|
}
|
||||||
"published": data.date.to_rfc3339()
|
|
||||||
})
|
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 {
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct CreatePayload {
|
pub struct Payload {
|
||||||
by: serde_json::Value,
|
by: serde_json::Value,
|
||||||
object: serde_json::Value,
|
object: serde_json::Value,
|
||||||
date: chrono::DateTime<chrono::Utc>
|
date: chrono::DateTime<chrono::Utc>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreatePayload {
|
impl Payload {
|
||||||
pub fn new(by: serde_json::Value, obj: serde_json::Value) -> CreatePayload {
|
pub fn new(by: serde_json::Value, obj: serde_json::Value) -> Payload {
|
||||||
CreatePayload {
|
Payload {
|
||||||
by: by,
|
by: by,
|
||||||
object: obj,
|
object: obj,
|
||||||
date: chrono::Utc::now()
|
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_box_prefix() -> &'static str;
|
||||||
|
|
||||||
fn get_actor_id(&self) -> String;
|
fn get_actor_id(&self) -> String;
|
||||||
|
@ -76,4 +76,6 @@ pub trait Actor {
|
||||||
Err(_) => println!("Error while sending to inbox")
|
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::PgConnection;
|
||||||
|
use diesel::associations::Identifiable;
|
||||||
use serde_json;
|
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::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 received(&self, conn: &PgConnection, act: serde_json::Value);
|
||||||
|
|
||||||
fn save(&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)
|
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)
|
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;
|
extern crate serde_derive;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
extern crate url;
|
||||||
|
|
||||||
use diesel::pg::PgConnection;
|
use diesel::pg::PgConnection;
|
||||||
use diesel::r2d2::{ConnectionManager, Pool};
|
use diesel::r2d2::{ConnectionManager, Pool};
|
||||||
|
|
|
@ -18,7 +18,8 @@ pub struct Blog {
|
||||||
pub outbox_url: String,
|
pub outbox_url: String,
|
||||||
pub inbox_url: String,
|
pub inbox_url: String,
|
||||||
pub instance_id: i32,
|
pub instance_id: i32,
|
||||||
pub creation_date: NaiveDateTime
|
pub creation_date: NaiveDateTime,
|
||||||
|
pub ap_url: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Insertable)]
|
#[derive(Insertable)]
|
||||||
|
@ -29,7 +30,8 @@ pub struct NewBlog {
|
||||||
pub summary: String,
|
pub summary: String,
|
||||||
pub outbox_url: String,
|
pub outbox_url: String,
|
||||||
pub inbox_url: String,
|
pub inbox_url: String,
|
||||||
pub instance_id: i32
|
pub instance_id: i32,
|
||||||
|
pub ap_url: String
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Blog {
|
impl Blog {
|
||||||
|
@ -52,7 +54,7 @@ impl Blog {
|
||||||
blogs::table.filter(blogs::actor_id.eq(username))
|
blogs::table.filter(blogs::actor_id.eq(username))
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.load::<Blog>(conn)
|
.load::<Blog>(conn)
|
||||||
.expect("Error loading blog by email")
|
.expect("Error loading blog by actor_id")
|
||||||
.into_iter().nth(0)
|
.into_iter().nth(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +70,12 @@ impl Blog {
|
||||||
.set(blogs::inbox_url.eq(self.compute_inbox(conn)))
|
.set(blogs::inbox_url.eq(self.compute_inbox(conn)))
|
||||||
.get_result::<Blog>(conn).expect("Couldn't update inbox URL");
|
.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 {
|
pub fn outbox(&self, conn: &PgConnection) -> Outbox {
|
||||||
|
@ -95,6 +103,14 @@ impl Actor for Blog {
|
||||||
fn get_actor_type () -> ActorType {
|
fn get_actor_type () -> ActorType {
|
||||||
ActorType::Blog
|
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 {
|
impl Webfinger for Blog {
|
||||||
|
@ -137,7 +153,8 @@ impl NewBlog {
|
||||||
summary: summary,
|
summary: summary,
|
||||||
outbox_url: String::from(""),
|
outbox_url: String::from(""),
|
||||||
inbox_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::request::{self, FromRequest, Request};
|
||||||
use rocket::outcome::IntoOutcome;
|
use rocket::outcome::IntoOutcome;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use activity_pub::activity::Activity;
|
use activity_pub::activity::Activity;
|
||||||
use activity_pub::actor::{ActorType, Actor};
|
use activity_pub::actor::{ActorType, Actor};
|
||||||
|
@ -35,7 +36,8 @@ pub struct User {
|
||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
pub hashed_password: Option<String>,
|
pub hashed_password: Option<String>,
|
||||||
pub instance_id: i32,
|
pub instance_id: i32,
|
||||||
pub creation_date: NaiveDateTime
|
pub creation_date: NaiveDateTime,
|
||||||
|
pub ap_url: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Insertable)]
|
#[derive(Insertable)]
|
||||||
|
@ -49,7 +51,8 @@ pub struct NewUser {
|
||||||
pub summary: String,
|
pub summary: String,
|
||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
pub hashed_password: Option<String>,
|
pub hashed_password: Option<String>,
|
||||||
pub instance_id: i32
|
pub instance_id: i32,
|
||||||
|
pub ap_url: String
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
|
@ -83,7 +86,7 @@ impl User {
|
||||||
.filter(users::instance_id.eq(instance_id))
|
.filter(users::instance_id.eq(instance_id))
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.load::<User>(conn)
|
.load::<User>(conn)
|
||||||
.expect("Error loading user by email")
|
.expect("Error loading user by name")
|
||||||
.into_iter().nth(0)
|
.into_iter().nth(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,19 +112,7 @@ impl User {
|
||||||
|
|
||||||
fn fetch_from_webfinger(conn: &PgConnection, acct: String) -> Option<User> {
|
fn fetch_from_webfinger(conn: &PgConnection, acct: String) -> Option<User> {
|
||||||
match resolve(acct.clone()) {
|
match resolve(acct.clone()) {
|
||||||
Ok(url) => {
|
Ok(url) => User::fetch_from_url(conn, 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
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(details) => {
|
Err(details) => {
|
||||||
println!("{}", details);
|
println!("{}", details);
|
||||||
None
|
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 {
|
fn from_activity(conn: &PgConnection, acct: serde_json::Value, inst: String) -> User {
|
||||||
let instance = match Instance::get_by_domain(conn, inst.clone()) {
|
let instance = match Instance::get_by_domain(conn, inst.clone()) {
|
||||||
Some(instance) => instance,
|
Some(instance) => instance,
|
||||||
|
@ -145,7 +150,8 @@ impl User {
|
||||||
summary: acct["summary"].as_str().unwrap().to_string(),
|
summary: acct["summary"].as_str().unwrap().to_string(),
|
||||||
email: None,
|
email: None,
|
||||||
hashed_password: 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 {
|
if self.inbox_url.len() == 0 {
|
||||||
diesel::update(self)
|
diesel::update(self)
|
||||||
.set(users::inbox_url.eq(self.compute_inbox(conn)))
|
.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 {
|
fn get_actor_type() -> ActorType {
|
||||||
ActorType::Person
|
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 {
|
impl Inbox for User {
|
||||||
|
@ -281,7 +313,8 @@ impl NewUser {
|
||||||
summary: summary,
|
summary: summary,
|
||||||
email: Some(email),
|
email: Some(email),
|
||||||
hashed_password: Some(password),
|
hashed_password: Some(password),
|
||||||
instance_id: instance_id
|
instance_id: instance_id,
|
||||||
|
ap_url: String::from("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ table! {
|
||||||
inbox_url -> Varchar,
|
inbox_url -> Varchar,
|
||||||
instance_id -> Int4,
|
instance_id -> Int4,
|
||||||
creation_date -> Timestamp,
|
creation_date -> Timestamp,
|
||||||
|
ap_url -> Text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +75,7 @@ table! {
|
||||||
hashed_password -> Nullable<Text>,
|
hashed_password -> Nullable<Text>,
|
||||||
instance_id -> Int4,
|
instance_id -> Int4,
|
||||||
creation_date -> Timestamp,
|
creation_date -> Timestamp,
|
||||||
|
ap_url -> Text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue