Get inbox working properly

pull/722/head
Felix 2020-04-13 15:06:41 +02:00
parent fac1cc7e1d
commit fdaf0b3364
9 changed files with 164 additions and 49 deletions

View File

@ -28,6 +28,7 @@ services:
- LEMMY_FEDERATION__TLS_ENABLED=false - LEMMY_FEDERATION__TLS_ENABLED=false
- LEMMY_PORT=8540 - LEMMY_PORT=8540
- RUST_BACKTRACE=1 - RUST_BACKTRACE=1
- RUST_LOG=actix_web=debug
restart: always restart: always
depends_on: depends_on:
- postgres_alpha - postgres_alpha
@ -58,6 +59,7 @@ services:
- LEMMY_FEDERATION__TLS_ENABLED=false - LEMMY_FEDERATION__TLS_ENABLED=false
- LEMMY_PORT=8550 - LEMMY_PORT=8550
- RUST_BACKTRACE=1 - RUST_BACKTRACE=1
- RUST_LOG=actix_web=debug
restart: always restart: always
depends_on: depends_on:
- postgres_beta - postgres_beta

View File

@ -1,9 +1,11 @@
#!/bin/bash #!/bin/bash
set -e set -e
pushd ../../ui/ || exit if [ "$1" = "-yarn" ]; then
yarn build pushd ../../ui/ || exit
popd || exit yarn build
popd || exit
fi
pushd ../../server/ || exit pushd ../../server/ || exit
cargo build cargo build

View File

@ -1,9 +1,9 @@
use super::*; use super::*;
use crate::apub::activities::post_create; use crate::apub::activities::{post_create, post_update};
use diesel::PgConnection; use diesel::PgConnection;
use std::str::FromStr; use std::str::FromStr;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Debug)]
pub struct CreatePost { pub struct CreatePost {
name: String, name: String,
url: Option<String>, url: Option<String>,
@ -150,12 +150,12 @@ impl Perform<PostResponse> for Oper<CreatePost> {
} }
}; };
match Post::update_ap_id(&conn, inserted_post.id) { let updated_post = match Post::update_ap_id(&conn, inserted_post.id) {
Ok(post) => post, Ok(post) => post,
Err(_e) => return Err(APIError::err("couldnt_create_post").into()), Err(_e) => return Err(APIError::err("couldnt_create_post").into()),
}; };
post_create(&inserted_post, &user, conn)?; post_create(&updated_post, &user, conn)?;
// They like their own post by default // They like their own post by default
let like_form = PostLikeForm { let like_form = PostLikeForm {
@ -369,7 +369,8 @@ impl Perform<PostResponse> for Oper<EditPost> {
} }
// Check for a site ban // Check for a site ban
if UserView::read(&conn, user_id)?.banned { let user = User_::read(&conn, user_id)?;
if user.banned {
return Err(APIError::err("site_ban").into()); return Err(APIError::err("site_ban").into());
} }
@ -400,7 +401,7 @@ impl Perform<PostResponse> for Oper<EditPost> {
published: None, published: None,
}; };
let _updated_post = match Post::update(&conn, data.edit_id, &post_form) { let updated_post = match Post::update(&conn, data.edit_id, &post_form) {
Ok(post) => post, Ok(post) => post,
Err(e) => { Err(e) => {
let err_type = if e.to_string() == "value too long for type character varying(200)" { let err_type = if e.to_string() == "value too long for type character varying(200)" {
@ -442,6 +443,8 @@ impl Perform<PostResponse> for Oper<EditPost> {
ModStickyPost::create(&conn, &form)?; ModStickyPost::create(&conn, &form)?;
} }
post_update(&updated_post, &user, conn)?;
let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?; let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?;
Ok(PostResponse { post: post_view }) Ok(PostResponse { post: post_view })

View File

@ -3,30 +3,36 @@ use crate::db::community::Community;
use crate::db::post::Post; use crate::db::post::Post;
use crate::db::user::User_; use crate::db::user::User_;
use crate::db::Crud; use crate::db::Crud;
use activitystreams::activity::Create; use activitystreams::activity::{Create, Update};
use activitystreams::context; use activitystreams::object::properties::ObjectProperties;
use activitystreams::{context, public};
use diesel::PgConnection; use diesel::PgConnection;
use failure::Error; use failure::Error;
use failure::_core::fmt::Debug;
use isahc::prelude::*; use isahc::prelude::*;
use serde::Serialize;
pub fn post_create(post: &Post, creator: &User_, conn: &PgConnection) -> Result<(), Error> { fn populate_object_props(
let page = post.as_page(conn)?; props: &mut ObjectProperties,
let community = Community::read(conn, post.community_id)?; addressed_to: &str,
let mut create = Create::new(); object_id: &str,
create.object_props.set_context_xsd_any_uri(context())?; ) -> Result<(), Error> {
create props
.object_props .set_context_xsd_any_uri(context())?
// TODO: seems like the create activity needs its own id (and be fetchable there) // TODO: the activity needs a seperate id from the object
.set_id(page.object_props.get_id().unwrap().to_string())? .set_id(object_id)?
// TODO: should to/cc go on the Create, or on the Post? or on both? // TODO: should to/cc go on the Create, or on the Post? or on both?
// TODO: handle privacy on the receiving side (at least ignore anything thats not public) // TODO: handle privacy on the receiving side (at least ignore anything thats not public)
.set_to_xsd_any_uri("https://www.w3.org/ns/activitystreams#Public")? .set_to_xsd_any_uri(public())?
.set_cc_xsd_any_uri(format!("{}/followers", community.actor_id))?; .set_cc_xsd_any_uri(addressed_to)?;
create Ok(())
.create_props }
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?;
create.create_props.set_object_base_box(page)?; fn send_activity<A>(activity: &A) -> Result<(), Error>
let json = serde_json::to_string(&create)?; where
A: Serialize + Debug,
{
let json = serde_json::to_string(&activity)?;
for i in get_following_instances() { for i in get_following_instances() {
// TODO: need to send this to the inbox of following users // TODO: need to send this to the inbox of following users
let inbox = format!( let inbox = format!(
@ -42,3 +48,37 @@ pub fn post_create(post: &Post, creator: &User_, conn: &PgConnection) -> Result<
} }
Ok(()) Ok(())
} }
pub fn post_create(post: &Post, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let page = post.as_page(conn)?;
let community = Community::read(conn, post.community_id)?;
let mut create = Create::new();
populate_object_props(
&mut create.object_props,
&community.get_followers_url(),
&post.ap_id,
)?;
create
.create_props
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(page)?;
send_activity(&create)?;
Ok(())
}
pub fn post_update(post: &Post, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let page = post.as_page(conn)?;
let community = Community::read(conn, post.community_id)?;
let mut update = Update::new();
populate_object_props(
&mut update.object_props,
&community.get_followers_url(),
&post.ap_id,
)?;
update
.update_props
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(page)?;
send_activity(&update)?;
Ok(())
}

View File

@ -79,9 +79,9 @@ impl Community {
actor_props actor_props
.set_preferred_username(self.title.to_owned())? .set_preferred_username(self.title.to_owned())?
.set_inbox(format!("{}/inbox", &self.actor_id))? .set_inbox(self.get_inbox_url())?
.set_outbox(format!("{}/outbox", &self.actor_id))? .set_outbox(self.get_outbox_url())?
.set_followers(format!("{}/followers", &self.actor_id))?; .set_followers(self.get_followers_url())?;
let public_key = PublicKey { let public_key = PublicKey {
id: format!("{}#main-key", self.actor_id), id: format!("{}#main-key", self.actor_id),
@ -91,6 +91,16 @@ impl Community {
Ok(group.extend(actor_props).extend(public_key.to_ext())) Ok(group.extend(actor_props).extend(public_key.to_ext()))
} }
pub fn get_followers_url(&self) -> String {
format!("{}/followers", &self.actor_id)
}
pub fn get_inbox_url(&self) -> String {
format!("{}/inbox", &self.actor_id)
}
pub fn get_outbox_url(&self) -> String {
format!("{}/outbox", &self.actor_id)
}
} }
impl CommunityForm { impl CommunityForm {

View File

@ -78,8 +78,7 @@ fn fetch_remote_community_posts(
community: &Community, community: &Community,
conn: &PgConnection, conn: &PgConnection,
) -> Result<Vec<Post>, Error> { ) -> Result<Vec<Post>, Error> {
// TODO: need to add outbox field to Community let outbox_url = Url::parse(&community.get_outbox_url())?;
let outbox_url = Url::parse(&format!("{}/outbox", community.actor_id))?;
let outbox = fetch_remote_object::<OrderedCollection>(&outbox_url)?; let outbox = fetch_remote_object::<OrderedCollection>(&outbox_url)?;
let items = outbox.collection_props.get_many_items_base_boxes(); let items = outbox.collection_props.get_many_items_base_boxes();

View File

@ -1,27 +1,88 @@
use crate::db::post::{Post, PostForm}; use crate::db::post::{Post, PostForm};
use crate::db::Crud; use crate::db::Crud;
use activitystreams::activity::Create;
use activitystreams::object::Page; use activitystreams::object::Page;
use activitystreams::{
object::{Object, ObjectBox},
primitives::XsdAnyUri,
Base, BaseBox, PropRefs,
};
use actix_web::{web, HttpResponse}; use actix_web::{web, HttpResponse};
use diesel::r2d2::{ConnectionManager, Pool}; use diesel::r2d2::{ConnectionManager, Pool};
use diesel::PgConnection; use diesel::PgConnection;
use failure::Error; use failure::Error;
use std::collections::HashMap;
// TODO: need a proper actor that has this inbox // TODO: need a proper actor that has this inbox
pub async fn create_inbox( pub async fn inbox(
create: web::Json<Create>, input: web::Json<AcceptedObjects>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>, db: web::Data<Pool<ConnectionManager<PgConnection>>>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let page = create let input = input.into_inner();
.create_props let conn = &db.get().unwrap();
.get_object_base_box() match input.kind {
.unwrap() ValidTypes::Create => handle_create(&input, conn),
.to_owned() ValidTypes::Update => handle_update(&input, conn),
.to_concrete::<Page>()?; }
let post = PostForm::from_page(&page, &db.get().unwrap())?; }
Post::create(&db.get().unwrap(), &post)?;
fn handle_create(create: &AcceptedObjects, conn: &PgConnection) -> Result<HttpResponse, Error> {
let page = create.object.to_owned().to_concrete::<Page>()?;
let post = PostForm::from_page(&page, conn)?;
Post::create(conn, &post)?;
// TODO: send the new post out via websocket // TODO: send the new post out via websocket
dbg!(&post);
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }
fn handle_update(update: &AcceptedObjects, conn: &PgConnection) -> Result<HttpResponse, Error> {
let page = update.object.to_owned().to_concrete::<Page>()?;
let post = PostForm::from_page(&page, conn)?;
let id = Post::read_from_apub_id(conn, &post.ap_id)?.id;
Post::update(conn, id, &post)?;
// TODO: send the new post out via websocket
Ok(HttpResponse::Ok().finish())
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AcceptedObjects {
pub id: XsdAnyUri,
#[serde(rename = "type")]
pub kind: ValidTypes,
pub actor: XsdAnyUri,
pub object: BaseBox,
#[serde(flatten)]
ext: HashMap<String, serde_json::Value>,
}
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "PascalCase")]
pub enum ValidTypes {
Create,
Update,
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[serde(untagged)]
#[serde(rename_all = "camelCase")]
pub enum ValidObjects {
Id(XsdAnyUri),
Object(AnyExistingObject),
}
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, PropRefs)]
#[serde(rename_all = "camelCase")]
#[prop_refs(Object)]
pub struct AnyExistingObject {
pub id: XsdAnyUri,
#[serde(rename = "type")]
pub kind: String,
#[serde(flatten)]
ext: HashMap<String, serde_json::Value>,
}

View File

@ -41,7 +41,7 @@ impl Post {
// Not needed when the Post is embedded in a collection (like for community outbox) // Not needed when the Post is embedded in a collection (like for community outbox)
.set_context_xsd_any_uri(context())? .set_context_xsd_any_uri(context())?
.set_id(base_url)? .set_id(base_url)?
// Use summary field to be consistent with mastodon content warning. // Use summary field to be consistent with mastodon content warning.
// https://mastodon.xyz/@Louisa/103987265222901387.json // https://mastodon.xyz/@Louisa/103987265222901387.json
.set_summary_xsd_string(self.name.to_owned())? .set_summary_xsd_string(self.name.to_owned())?
.set_published(convert_datetime(self.published))? .set_published(convert_datetime(self.published))?

View File

@ -11,10 +11,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
web::get().to(apub::community::get_apub_community_list), web::get().to(apub::community::get_apub_community_list),
) )
// TODO: this needs to be moved to the actors (eg /federation/u/{}/inbox) // TODO: this needs to be moved to the actors (eg /federation/u/{}/inbox)
.route( .route("/federation/inbox", web::post().to(apub::inbox::inbox))
"/federation/inbox", .route("/federation/inbox", web::post().to(apub::inbox::inbox))
web::post().to(apub::inbox::create_inbox),
)
.route( .route(
"/federation/c/{community_name}", "/federation/c/{community_name}",
web::get().to(apub::community::get_apub_community_http), web::get().to(apub::community::get_apub_community_http),