mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-26 04:11:09 +00:00
user working and tests passing
This commit is contained in:
parent
e0bd8d2791
commit
69d08b824e
12 changed files with 250 additions and 81 deletions
|
@ -29,7 +29,7 @@ create table local_user (
|
||||||
create table instance_follow (
|
create table instance_follow (
|
||||||
id serial primary key,
|
id serial primary key,
|
||||||
instance_id int REFERENCES instance ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
instance_id int REFERENCES instance ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||||
follower_id int REFERENCES instance ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
follower_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||||
pending boolean not null,
|
pending boolean not null,
|
||||||
unique(instance_id, follower_id)
|
unique(instance_id, follower_id)
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use crate::database::instance::{DbInstance, InstanceView};
|
use crate::database::instance::{DbInstance, InstanceView};
|
||||||
|
use crate::database::user::LocalUserView;
|
||||||
use crate::database::MyDataHandle;
|
use crate::database::MyDataHandle;
|
||||||
use crate::error::MyResult;
|
use crate::error::MyResult;
|
||||||
use crate::federation::activities::follow::Follow;
|
use crate::federation::activities::follow::Follow;
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
|
use axum::Extension;
|
||||||
use axum::{Form, Json};
|
use axum::{Form, Json};
|
||||||
use axum_macros::debug_handler;
|
use axum_macros::debug_handler;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -25,14 +27,14 @@ pub struct FollowInstance {
|
||||||
/// updated articles.
|
/// updated articles.
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub(in crate::api) async fn follow_instance(
|
pub(in crate::api) async fn follow_instance(
|
||||||
|
Extension(user): Extension<LocalUserView>,
|
||||||
data: Data<MyDataHandle>,
|
data: Data<MyDataHandle>,
|
||||||
Form(query): Form<FollowInstance>,
|
Form(query): Form<FollowInstance>,
|
||||||
) -> MyResult<()> {
|
) -> MyResult<()> {
|
||||||
let local_instance = DbInstance::read_local_instance(&data.db_connection)?;
|
|
||||||
let target = DbInstance::read(query.id, &data.db_connection)?;
|
let target = DbInstance::read(query.id, &data.db_connection)?;
|
||||||
let pending = !target.local;
|
let pending = !target.local;
|
||||||
DbInstance::follow(local_instance.id, target.id, pending, &data)?;
|
DbInstance::follow(&user.person, &target, pending, &data)?;
|
||||||
let instance = DbInstance::read(query.id, &data.db_connection)?;
|
let instance = DbInstance::read(query.id, &data.db_connection)?;
|
||||||
Follow::send(local_instance, instance, &data).await?;
|
Follow::send(user.person, instance, &data).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::database::schema::{instance, instance_follow};
|
use crate::database::schema::{instance, instance_follow};
|
||||||
|
use crate::database::user::DbPerson;
|
||||||
use crate::database::MyDataHandle;
|
use crate::database::MyDataHandle;
|
||||||
use crate::error::MyResult;
|
use crate::error::MyResult;
|
||||||
use crate::federation::objects::articles_collection::DbArticleCollection;
|
use crate::federation::objects::articles_collection::DbArticleCollection;
|
||||||
|
@ -48,7 +49,7 @@ pub struct DbInstanceForm {
|
||||||
#[diesel(table_name = article, check_for_backend(diesel::pg::Pg))]
|
#[diesel(table_name = article, check_for_backend(diesel::pg::Pg))]
|
||||||
pub struct InstanceView {
|
pub struct InstanceView {
|
||||||
pub instance: DbInstance,
|
pub instance: DbInstance,
|
||||||
pub followers: Vec<DbInstance>,
|
pub followers: Vec<DbPerson>,
|
||||||
pub following: Vec<DbInstance>,
|
pub following: Vec<DbInstance>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,35 +99,36 @@ impl DbInstance {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn follow(
|
pub fn follow(
|
||||||
follower_id_: i32,
|
follower: &DbPerson,
|
||||||
instance_id_: i32,
|
instance: &DbInstance,
|
||||||
pending_: bool,
|
pending_: bool,
|
||||||
data: &Data<MyDataHandle>,
|
data: &Data<MyDataHandle>,
|
||||||
) -> MyResult<()> {
|
) -> MyResult<()> {
|
||||||
debug_assert_ne!(follower_id_, instance_id_);
|
|
||||||
use instance_follow::dsl::{follower_id, instance_id, pending};
|
use instance_follow::dsl::{follower_id, instance_id, pending};
|
||||||
let mut conn = data.db_connection.lock().unwrap();
|
let mut conn = data.db_connection.lock().unwrap();
|
||||||
let form = (
|
let form = (
|
||||||
instance_id.eq(instance_id_),
|
instance_id.eq(instance.id),
|
||||||
follower_id.eq(follower_id_),
|
follower_id.eq(follower.id),
|
||||||
pending.eq(pending_),
|
pending.eq(pending_),
|
||||||
);
|
);
|
||||||
insert_into(instance_follow::table)
|
let rows = insert_into(instance_follow::table)
|
||||||
.values(form)
|
.values(form)
|
||||||
.on_conflict((instance_id, follower_id))
|
.on_conflict((instance_id, follower_id))
|
||||||
.do_update()
|
.do_update()
|
||||||
.set(form)
|
.set(form)
|
||||||
.execute(conn.deref_mut())?;
|
.execute(conn.deref_mut())?;
|
||||||
|
assert_eq!(1, rows);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_followers(id_: i32, conn: &Mutex<PgConnection>) -> MyResult<Vec<Self>> {
|
pub fn read_followers(id_: i32, conn: &Mutex<PgConnection>) -> MyResult<Vec<DbPerson>> {
|
||||||
|
use crate::database::schema::person;
|
||||||
use instance_follow::dsl::{follower_id, instance_id};
|
use instance_follow::dsl::{follower_id, instance_id};
|
||||||
let mut conn = conn.lock().unwrap();
|
let mut conn = conn.lock().unwrap();
|
||||||
Ok(instance_follow::table
|
Ok(instance_follow::table
|
||||||
.inner_join(instance::table.on(follower_id.eq(instance::dsl::id)))
|
.inner_join(person::table.on(follower_id.eq(person::dsl::id)))
|
||||||
.filter(instance_id.eq(id_))
|
.filter(instance_id.eq(id_))
|
||||||
.select(instance::all_columns)
|
.select(person::all_columns)
|
||||||
.get_results(conn.deref_mut())?)
|
.get_results(conn.deref_mut())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use crate::database::instance::DbInstance;
|
use crate::database::instance::DbInstance;
|
||||||
use crate::error::MyResult;
|
use crate::error::MyResult;
|
||||||
|
use crate::federation::send_activity;
|
||||||
use crate::utils::generate_activity_id;
|
use crate::utils::generate_activity_id;
|
||||||
use crate::{database::MyDataHandle, federation::activities::follow::Follow};
|
use crate::{database::MyDataHandle, federation::activities::follow::Follow};
|
||||||
|
use activitypub_federation::traits::Actor;
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data, fetch::object_id::ObjectId, kinds::activity::AcceptType, traits::ActivityHandler,
|
config::Data, fetch::object_id::ObjectId, kinds::activity::AcceptType, traits::ActivityHandler,
|
||||||
};
|
};
|
||||||
|
@ -19,14 +21,28 @@ pub struct Accept {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Accept {
|
impl Accept {
|
||||||
pub fn new(actor: ObjectId<DbInstance>, object: Follow) -> MyResult<Accept> {
|
pub async fn send(
|
||||||
let id = generate_activity_id(actor.inner())?;
|
local_instance: DbInstance,
|
||||||
Ok(Accept {
|
object: Follow,
|
||||||
actor,
|
data: &Data<MyDataHandle>,
|
||||||
|
) -> MyResult<()> {
|
||||||
|
let id = generate_activity_id(local_instance.ap_id.inner())?;
|
||||||
|
let follower = object.actor.dereference(data).await?;
|
||||||
|
let accept = Accept {
|
||||||
|
actor: local_instance.ap_id.clone(),
|
||||||
object,
|
object,
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id,
|
id,
|
||||||
})
|
};
|
||||||
|
dbg!(&accept);
|
||||||
|
send_activity(
|
||||||
|
&local_instance,
|
||||||
|
accept,
|
||||||
|
vec![follower.shared_inbox_or_inbox()],
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,10 +64,11 @@ impl ActivityHandler for Accept {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
|
dbg!(&self);
|
||||||
// add to follows
|
// add to follows
|
||||||
let local_instance = DbInstance::read_local_instance(&data.db_connection)?;
|
let person = self.object.actor.dereference_local(data).await?;
|
||||||
let actor = self.actor.dereference(data).await?;
|
let instance = self.actor.dereference(data).await?;
|
||||||
DbInstance::follow(local_instance.id, actor.id, false, data)?;
|
DbInstance::follow(&person, &instance, false, data)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use crate::database::instance::DbInstance;
|
use crate::database::instance::DbInstance;
|
||||||
|
use crate::database::user::DbPerson;
|
||||||
use crate::error::MyResult;
|
use crate::error::MyResult;
|
||||||
|
use crate::federation::send_activity;
|
||||||
use crate::{database::MyDataHandle, federation::activities::accept::Accept, generate_activity_id};
|
use crate::{database::MyDataHandle, federation::activities::accept::Accept, generate_activity_id};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data,
|
config::Data,
|
||||||
|
@ -13,7 +15,7 @@ use url::Url;
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Follow {
|
pub struct Follow {
|
||||||
pub actor: ObjectId<DbInstance>,
|
pub actor: ObjectId<DbPerson>,
|
||||||
pub object: ObjectId<DbInstance>,
|
pub object: ObjectId<DbInstance>,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
kind: FollowType,
|
kind: FollowType,
|
||||||
|
@ -21,21 +23,16 @@ pub struct Follow {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Follow {
|
impl Follow {
|
||||||
pub async fn send(
|
pub async fn send(actor: DbPerson, to: DbInstance, data: &Data<MyDataHandle>) -> MyResult<()> {
|
||||||
local_instance: DbInstance,
|
let id = generate_activity_id(actor.ap_id.inner())?;
|
||||||
to: DbInstance,
|
|
||||||
data: &Data<MyDataHandle>,
|
|
||||||
) -> MyResult<()> {
|
|
||||||
let id = generate_activity_id(local_instance.ap_id.inner())?;
|
|
||||||
let follow = Follow {
|
let follow = Follow {
|
||||||
actor: local_instance.ap_id.clone(),
|
actor: actor.ap_id.clone(),
|
||||||
object: to.ap_id.clone(),
|
object: to.ap_id.clone(),
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id,
|
id,
|
||||||
};
|
};
|
||||||
local_instance
|
|
||||||
.send(follow, vec![to.shared_inbox_or_inbox()], data)
|
send_activity(&actor, follow, vec![to.shared_inbox_or_inbox()], data).await?;
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,16 +55,14 @@ impl ActivityHandler for Follow {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
|
dbg!(&self);
|
||||||
let actor = self.actor.dereference(data).await?;
|
let actor = self.actor.dereference(data).await?;
|
||||||
let local_instance = DbInstance::read_local_instance(&data.db_connection)?;
|
let local_instance = DbInstance::read_local_instance(&data.db_connection)?;
|
||||||
DbInstance::follow(actor.id, local_instance.id, false, data)?;
|
dbg!(&actor.ap_id, &local_instance.ap_id);
|
||||||
|
DbInstance::follow(&actor, &local_instance, false, data)?;
|
||||||
|
|
||||||
// send back an accept
|
// send back an accept
|
||||||
let follower = self.actor.dereference(data).await?;
|
Accept::send(local_instance, self, data).await?;
|
||||||
let accept = Accept::new(local_instance.ap_id.clone(), self)?;
|
|
||||||
local_instance
|
|
||||||
.send(accept, vec![follower.shared_inbox_or_inbox()], data)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ use activitypub_federation::{
|
||||||
traits::ActivityHandler,
|
traits::ActivityHandler,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::federation::send_activity;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
@ -41,9 +42,13 @@ impl RejectEdit {
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id,
|
id,
|
||||||
};
|
};
|
||||||
local_instance
|
send_activity(
|
||||||
.send(reject, vec![Url::parse(&user_instance.inbox_url)?], data)
|
&local_instance,
|
||||||
.await?;
|
reject,
|
||||||
|
vec![Url::parse(&user_instance.inbox_url)?],
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ use crate::database::instance::DbInstance;
|
||||||
use crate::federation::activities::reject::RejectEdit;
|
use crate::federation::activities::reject::RejectEdit;
|
||||||
use crate::federation::activities::update_local_article::UpdateLocalArticle;
|
use crate::federation::activities::update_local_article::UpdateLocalArticle;
|
||||||
use crate::federation::objects::edit::ApubEdit;
|
use crate::federation::objects::edit::ApubEdit;
|
||||||
|
use crate::federation::send_activity;
|
||||||
use crate::utils::generate_activity_id;
|
use crate::utils::generate_activity_id;
|
||||||
use activitypub_federation::kinds::activity::UpdateType;
|
use activitypub_federation::kinds::activity::UpdateType;
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
|
@ -47,9 +48,13 @@ impl UpdateRemoteArticle {
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id,
|
id,
|
||||||
};
|
};
|
||||||
local_instance
|
send_activity(
|
||||||
.send(update, vec![Url::parse(&article_instance.inbox_url)?], data)
|
&local_instance,
|
||||||
.await?;
|
update,
|
||||||
|
vec![Url::parse(&article_instance.inbox_url)?],
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,34 @@
|
||||||
|
use crate::database::MyDataHandle;
|
||||||
|
use activitypub_federation::activity_sending::SendActivityTask;
|
||||||
|
use activitypub_federation::config::Data;
|
||||||
|
use activitypub_federation::protocol::context::WithContext;
|
||||||
|
use activitypub_federation::traits::{ActivityHandler, Actor};
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use tracing::log::warn;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
pub mod activities;
|
pub mod activities;
|
||||||
pub mod objects;
|
pub mod objects;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
|
|
||||||
|
pub async fn send_activity<Activity, ActorType: Actor>(
|
||||||
|
actor: &ActorType,
|
||||||
|
activity: Activity,
|
||||||
|
recipients: Vec<Url>,
|
||||||
|
data: &Data<MyDataHandle>,
|
||||||
|
) -> Result<(), <Activity as ActivityHandler>::Error>
|
||||||
|
where
|
||||||
|
Activity: ActivityHandler + Serialize + Debug + Send + Sync,
|
||||||
|
<Activity as ActivityHandler>::Error: From<activitypub_federation::error::Error>,
|
||||||
|
{
|
||||||
|
let activity = WithContext::new_default(activity);
|
||||||
|
let sends = SendActivityTask::prepare(&activity, actor, recipients, data).await?;
|
||||||
|
for send in sends {
|
||||||
|
let send = send.sign_and_send(data).await;
|
||||||
|
if let Err(e) = send {
|
||||||
|
warn!("Failed to send activity {:?}: {e}", activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use crate::database::instance::{DbInstance, DbInstanceForm};
|
||||||
use crate::database::MyDataHandle;
|
use crate::database::MyDataHandle;
|
||||||
use crate::error::{Error, MyResult};
|
use crate::error::{Error, MyResult};
|
||||||
use crate::federation::objects::articles_collection::DbArticleCollection;
|
use crate::federation::objects::articles_collection::DbArticleCollection;
|
||||||
|
use crate::federation::send_activity;
|
||||||
use activitypub_federation::activity_sending::SendActivityTask;
|
use activitypub_federation::activity_sending::SendActivityTask;
|
||||||
use activitypub_federation::fetch::collection_id::CollectionId;
|
use activitypub_federation::fetch::collection_id::CollectionId;
|
||||||
use activitypub_federation::kinds::actor::ServiceType;
|
use activitypub_federation::kinds::actor::ServiceType;
|
||||||
|
@ -62,29 +63,7 @@ impl DbInstance {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|i| Url::parse(&i.inbox_url).unwrap()),
|
.map(|i| Url::parse(&i.inbox_url).unwrap()),
|
||||||
);
|
);
|
||||||
self.send(activity, inboxes, data).await?;
|
send_activity(self, activity, inboxes, data).await?;
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: move to user?
|
|
||||||
pub async fn send<Activity>(
|
|
||||||
&self,
|
|
||||||
activity: Activity,
|
|
||||||
recipients: Vec<Url>,
|
|
||||||
data: &Data<MyDataHandle>,
|
|
||||||
) -> Result<(), <Activity as ActivityHandler>::Error>
|
|
||||||
where
|
|
||||||
Activity: ActivityHandler + Serialize + Debug + Send + Sync,
|
|
||||||
<Activity as ActivityHandler>::Error: From<activitypub_federation::error::Error>,
|
|
||||||
{
|
|
||||||
let activity = WithContext::new_default(activity);
|
|
||||||
let sends = SendActivityTask::prepare(&activity, self, recipients, data).await?;
|
|
||||||
for send in sends {
|
|
||||||
let send = send.sign_and_send(data).await;
|
|
||||||
if let Err(e) = send {
|
|
||||||
warn!("Failed to send activity {:?}: {e}", activity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::database::article::DbArticle;
|
use crate::database::article::DbArticle;
|
||||||
use crate::database::instance::DbInstance;
|
use crate::database::instance::DbInstance;
|
||||||
|
use crate::database::user::DbPerson;
|
||||||
use crate::database::MyDataHandle;
|
use crate::database::MyDataHandle;
|
||||||
use crate::error::MyResult;
|
use crate::error::{Error, MyResult};
|
||||||
use crate::federation::activities::accept::Accept;
|
use crate::federation::activities::accept::Accept;
|
||||||
use crate::federation::activities::create_article::CreateArticle;
|
use crate::federation::activities::create_article::CreateArticle;
|
||||||
use crate::federation::activities::follow::Follow;
|
use crate::federation::activities::follow::Follow;
|
||||||
|
@ -12,10 +13,12 @@ use crate::federation::objects::article::ApubArticle;
|
||||||
use crate::federation::objects::articles_collection::{ArticleCollection, DbArticleCollection};
|
use crate::federation::objects::articles_collection::{ArticleCollection, DbArticleCollection};
|
||||||
use crate::federation::objects::edits_collection::{ApubEditCollection, DbEditCollection};
|
use crate::federation::objects::edits_collection::{ApubEditCollection, DbEditCollection};
|
||||||
use crate::federation::objects::instance::ApubInstance;
|
use crate::federation::objects::instance::ApubInstance;
|
||||||
|
use crate::federation::objects::user::ApubUser;
|
||||||
use activitypub_federation::axum::inbox::{receive_activity, ActivityData};
|
use activitypub_federation::axum::inbox::{receive_activity, ActivityData};
|
||||||
use activitypub_federation::axum::json::FederationJson;
|
use activitypub_federation::axum::json::FederationJson;
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use activitypub_federation::protocol::context::WithContext;
|
use activitypub_federation::protocol::context::WithContext;
|
||||||
|
use activitypub_federation::traits::Actor;
|
||||||
use activitypub_federation::traits::Object;
|
use activitypub_federation::traits::Object;
|
||||||
use activitypub_federation::traits::{ActivityHandler, Collection};
|
use activitypub_federation::traits::{ActivityHandler, Collection};
|
||||||
use axum::extract::Path;
|
use axum::extract::Path;
|
||||||
|
@ -23,12 +26,14 @@ use axum::response::IntoResponse;
|
||||||
use axum::routing::{get, post};
|
use axum::routing::{get, post};
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use axum_macros::debug_handler;
|
use axum_macros::debug_handler;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub fn federation_routes() -> Router {
|
pub fn federation_routes() -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(http_get_instance))
|
.route("/", get(http_get_instance))
|
||||||
|
.route("/user/:name", get(http_get_person))
|
||||||
.route("/all_articles", get(http_get_all_articles))
|
.route("/all_articles", get(http_get_all_articles))
|
||||||
.route("/article/:title", get(http_get_article))
|
.route("/article/:title", get(http_get_article))
|
||||||
.route("/article/:title/edits", get(http_get_article_edits))
|
.route("/article/:title/edits", get(http_get_article_edits))
|
||||||
|
@ -44,6 +49,16 @@ async fn http_get_instance(
|
||||||
Ok(FederationJson(WithContext::new_default(json_instance)))
|
Ok(FederationJson(WithContext::new_default(json_instance)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
async fn http_get_person(
|
||||||
|
Path(name): Path<String>,
|
||||||
|
data: Data<MyDataHandle>,
|
||||||
|
) -> MyResult<FederationJson<WithContext<ApubUser>>> {
|
||||||
|
let person = DbPerson::read_local_from_name(&name, &data)?.person;
|
||||||
|
let json_person = person.into_json(&data).await?;
|
||||||
|
Ok(FederationJson(WithContext::new_default(json_person)))
|
||||||
|
}
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
async fn http_get_all_articles(
|
async fn http_get_all_articles(
|
||||||
data: Data<MyDataHandle>,
|
data: Data<MyDataHandle>,
|
||||||
|
@ -91,6 +106,119 @@ pub async fn http_post_inbox(
|
||||||
data: Data<MyDataHandle>,
|
data: Data<MyDataHandle>,
|
||||||
activity_data: ActivityData,
|
activity_data: ActivityData,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
receive_activity::<WithContext<InboxActivities>, DbInstance, MyDataHandle>(activity_data, &data)
|
receive_activity::<WithContext<InboxActivities>, UserOrInstance, MyDataHandle>(
|
||||||
.await
|
activity_data,
|
||||||
|
&data,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum UserOrInstance {
|
||||||
|
User(DbPerson),
|
||||||
|
Instance(DbInstance),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum PersonOrInstance {
|
||||||
|
Person(ApubUser),
|
||||||
|
Instance(ApubInstance),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum PersonOrInstanceType {
|
||||||
|
Person,
|
||||||
|
Group,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl Object for UserOrInstance {
|
||||||
|
type DataType = MyDataHandle;
|
||||||
|
type Kind = PersonOrInstance;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
||||||
|
Some(match self {
|
||||||
|
UserOrInstance::User(p) => p.last_refreshed_at,
|
||||||
|
UserOrInstance::Instance(p) => p.last_refreshed_at,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
async fn read_from_id(
|
||||||
|
object_id: Url,
|
||||||
|
data: &Data<Self::DataType>,
|
||||||
|
) -> Result<Option<Self>, Error> {
|
||||||
|
let person = DbPerson::read_from_id(object_id.clone(), data).await?;
|
||||||
|
Ok(match person {
|
||||||
|
Some(o) => Some(UserOrInstance::User(o)),
|
||||||
|
None => DbInstance::read_from_id(object_id, data)
|
||||||
|
.await?
|
||||||
|
.map(UserOrInstance::Instance),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
async fn delete(self, data: &Data<Self::DataType>) -> Result<(), Error> {
|
||||||
|
match self {
|
||||||
|
UserOrInstance::User(p) => p.delete(data).await,
|
||||||
|
UserOrInstance::Instance(p) => p.delete(data).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn into_json(self, _data: &Data<Self::DataType>) -> Result<Self::Kind, Error> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
async fn verify(
|
||||||
|
apub: &Self::Kind,
|
||||||
|
expected_domain: &Url,
|
||||||
|
data: &Data<Self::DataType>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
match apub {
|
||||||
|
PersonOrInstance::Person(a) => DbPerson::verify(a, expected_domain, data).await,
|
||||||
|
PersonOrInstance::Instance(a) => DbInstance::verify(a, expected_domain, data).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
async fn from_json(apub: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Error> {
|
||||||
|
Ok(match apub {
|
||||||
|
PersonOrInstance::Person(p) => {
|
||||||
|
UserOrInstance::User(DbPerson::from_json(p, data).await?)
|
||||||
|
}
|
||||||
|
PersonOrInstance::Instance(p) => {
|
||||||
|
UserOrInstance::Instance(DbInstance::from_json(p, data).await?)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Actor for UserOrInstance {
|
||||||
|
fn id(&self) -> Url {
|
||||||
|
match self {
|
||||||
|
UserOrInstance::User(u) => u.id(),
|
||||||
|
UserOrInstance::Instance(c) => c.id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn public_key_pem(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
UserOrInstance::User(p) => p.public_key_pem(),
|
||||||
|
UserOrInstance::Instance(p) => p.public_key_pem(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn private_key_pem(&self) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
UserOrInstance::User(p) => p.private_key_pem(),
|
||||||
|
UserOrInstance::Instance(p) => p.private_key_pem(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inbox(&self) -> Url {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -235,13 +235,13 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn follow_instance(api_instance: &str, follow_instance: &str) -> MyResult<()> {
|
pub async fn follow_instance(instance: &FediwikiInstance, follow_instance: &str) -> MyResult<()> {
|
||||||
// fetch beta instance on alpha
|
// fetch beta instance on alpha
|
||||||
let resolve_form = ResolveObject {
|
let resolve_form = ResolveObject {
|
||||||
id: Url::parse(&format!("http://{}", follow_instance))?,
|
id: Url::parse(&format!("http://{}", follow_instance))?,
|
||||||
};
|
};
|
||||||
let instance_resolved: DbInstance =
|
let instance_resolved: DbInstance =
|
||||||
get_query(api_instance, "resolve_instance", Some(resolve_form)).await?;
|
get_query(&instance.hostname, "resolve_instance", Some(resolve_form)).await?;
|
||||||
|
|
||||||
// send follow
|
// send follow
|
||||||
let follow_form = FollowInstance {
|
let follow_form = FollowInstance {
|
||||||
|
@ -249,8 +249,12 @@ pub async fn follow_instance(api_instance: &str, follow_instance: &str) -> MyRes
|
||||||
};
|
};
|
||||||
// cant use post helper because follow doesnt return json
|
// cant use post helper because follow doesnt return json
|
||||||
let res = CLIENT
|
let res = CLIENT
|
||||||
.post(format!("http://{}/api/v1/instance/follow", api_instance))
|
.post(format!(
|
||||||
|
"http://{}/api/v1/instance/follow",
|
||||||
|
instance.hostname
|
||||||
|
))
|
||||||
.form(&follow_form)
|
.form(&follow_form)
|
||||||
|
.bearer_auth(&instance.jwt)
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
if res.status() == StatusCode::OK {
|
if res.status() == StatusCode::OK {
|
||||||
|
|
|
@ -88,7 +88,7 @@ async fn test_follow_instance() -> MyResult<()> {
|
||||||
assert_eq!(0, beta_instance.followers.len());
|
assert_eq!(0, beta_instance.followers.len());
|
||||||
assert_eq!(0, beta_instance.following.len());
|
assert_eq!(0, beta_instance.following.len());
|
||||||
|
|
||||||
follow_instance(&data.alpha.hostname, &data.beta.hostname).await?;
|
follow_instance(&data.alpha, &data.beta.hostname).await?;
|
||||||
|
|
||||||
// check that follow was federated
|
// check that follow was federated
|
||||||
let alpha_instance: InstanceView = get(&data.alpha.hostname, "instance").await?;
|
let alpha_instance: InstanceView = get(&data.alpha.hostname, "instance").await?;
|
||||||
|
@ -102,9 +102,10 @@ async fn test_follow_instance() -> MyResult<()> {
|
||||||
let beta_instance: InstanceView = get(&data.beta.hostname, "instance").await?;
|
let beta_instance: InstanceView = get(&data.beta.hostname, "instance").await?;
|
||||||
assert_eq!(0, beta_instance.following.len());
|
assert_eq!(0, beta_instance.following.len());
|
||||||
assert_eq!(1, beta_instance.followers.len());
|
assert_eq!(1, beta_instance.followers.len());
|
||||||
|
// TODO: compare full ap_id of alpha user, but its not available through api yet
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
alpha_instance.instance.ap_id,
|
alpha_instance.instance.ap_id.inner().domain(),
|
||||||
beta_instance.followers[0].ap_id
|
beta_instance.followers[0].ap_id.inner().domain()
|
||||||
);
|
);
|
||||||
|
|
||||||
data.stop()
|
data.stop()
|
||||||
|
@ -160,7 +161,7 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
||||||
async fn test_edit_local_article() -> MyResult<()> {
|
async fn test_edit_local_article() -> MyResult<()> {
|
||||||
let data = TestData::start().await;
|
let data = TestData::start().await;
|
||||||
|
|
||||||
follow_instance(&data.alpha.hostname, &data.beta.hostname).await?;
|
follow_instance(&data.alpha, &data.beta.hostname).await?;
|
||||||
|
|
||||||
// create new article
|
// create new article
|
||||||
let title = "Manu_Chao".to_string();
|
let title = "Manu_Chao".to_string();
|
||||||
|
@ -203,8 +204,8 @@ async fn test_edit_local_article() -> MyResult<()> {
|
||||||
async fn test_edit_remote_article() -> MyResult<()> {
|
async fn test_edit_remote_article() -> MyResult<()> {
|
||||||
let data = TestData::start().await;
|
let data = TestData::start().await;
|
||||||
|
|
||||||
follow_instance(&data.alpha.hostname, &data.beta.hostname).await?;
|
follow_instance(&data.alpha, &data.beta.hostname).await?;
|
||||||
follow_instance(&data.gamma.hostname, &data.beta.hostname).await?;
|
follow_instance(&data.gamma, &data.beta.hostname).await?;
|
||||||
|
|
||||||
// create new article
|
// create new article
|
||||||
let title = "Manu_Chao".to_string();
|
let title = "Manu_Chao".to_string();
|
||||||
|
@ -309,7 +310,7 @@ async fn test_local_edit_conflict() -> MyResult<()> {
|
||||||
async fn test_federated_edit_conflict() -> MyResult<()> {
|
async fn test_federated_edit_conflict() -> MyResult<()> {
|
||||||
let data = TestData::start().await;
|
let data = TestData::start().await;
|
||||||
|
|
||||||
follow_instance(&data.alpha.hostname, &data.beta.hostname).await?;
|
follow_instance(&data.alpha, &data.beta.hostname).await?;
|
||||||
|
|
||||||
// create new article
|
// create new article
|
||||||
let title = "Manu_Chao".to_string();
|
let title = "Manu_Chao".to_string();
|
||||||
|
|
Loading…
Reference in a new issue